Convert Prism/Helper#splitIntoLines into a generator
authorTim Düsterhus <duesterhus@woltlab.com>
Tue, 5 Jan 2021 14:20:27 +0000 (15:20 +0100)
committerTim Düsterhus <duesterhus@woltlab.com>
Tue, 5 Jan 2021 15:56:38 +0000 (16:56 +0100)
This allow for a much cleaner control flow and also reduces the number of
useless intermediate objects (e.g. the DocumentFragment).

wcfsetup/install/files/js/WoltLabSuite/Core/Bbcode/Code.js
wcfsetup/install/files/js/WoltLabSuite/Core/Prism/Helper.js
wcfsetup/install/files/ts/WoltLabSuite/Core/Bbcode/Code.ts
wcfsetup/install/files/ts/WoltLabSuite/Core/Prism/Helper.ts

index e6f5598c62413e881d015491da04f863cd98cbaa..63ebb5ceed0644dbfd8efc34b174804b0567ce3a 100644 (file)
@@ -66,7 +66,9 @@ define(["require", "exports", "tslib", "../Language", "../Clipboard", "../Ui/Not
                 throw new Error(`Unknown language '${this.language}'`);
             }
             this.container.classList.add("highlighting");
+            // Step 1) Load the requested grammar.
             await new Promise((resolve_1, reject_1) => { require(["prism/components/prism-" + prism_meta_1.default[this.language].file], resolve_1, reject_1); }).then(tslib_1.__importStar);
+            // Step 2) Perform the highlighting into a temporary element.
             await waitForIdle();
             const grammar = Prism_1.default.languages[this.language];
             if (!grammar) {
@@ -74,18 +76,19 @@ define(["require", "exports", "tslib", "../Language", "../Clipboard", "../Ui/Not
             }
             const container = document.createElement("div");
             container.innerHTML = Prism_1.default.highlight(this.codeContainer.textContent, grammar, this.language);
+            // Step 3) Insert the highlighted lines into the page.
+            // This is performed in small chunks to prevent the UI thread from being blocked for complex
+            // highlight results.
             await waitForIdle();
-            const highlighted = PrismHelper.splitIntoLines(container);
-            const highlightedLines = highlighted.querySelectorAll("[data-number]");
             const originalLines = this.codeContainer.querySelectorAll(".codeBoxLine > span");
-            if (highlightedLines.length !== originalLines.length) {
-                throw new Error("Unreachable");
-            }
-            for (let chunkStart = 0, max = highlightedLines.length; chunkStart < max; chunkStart += CHUNK_SIZE) {
+            const highlightedLines = PrismHelper.splitIntoLines(container);
+            for (let chunkStart = 0, max = originalLines.length; chunkStart < max; chunkStart += CHUNK_SIZE) {
                 await waitForIdle();
                 const chunkEnd = Math.min(chunkStart + CHUNK_SIZE, max);
                 for (let offset = chunkStart; offset < chunkEnd; offset++) {
-                    originalLines[offset].parentNode.replaceChild(highlightedLines[offset], originalLines[offset]);
+                    const toReplace = originalLines[offset];
+                    const replacement = highlightedLines.next().value;
+                    toReplace.parentNode.replaceChild(replacement, toReplace);
                 }
             }
             this.container.classList.remove("highlighting");
index b929d81224e6b9b922a5d79345f9d38ea95fc0b5..de9a334d8cf94f31ec65d5435f4a297dcf86daa4 100644 (file)
@@ -1,39 +1,33 @@
+/**
+ * Provide helper functions for prism processing.
+ *
+ * @author     Tim Duesterhus
+ * @copyright  2001-2021 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLabSuite/Core/Prism/Helper
+ */
 define(["require", "exports"], function (require, exports) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.splitIntoLines = void 0;
-    /**
-     * Provide helper functions for prism processing.
-     *
-     * @author Tim Duesterhus
-     * @copyright      2001-2021 WoltLab GmbH
-     * @license        GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
-     * @module WoltLabSuite/Core/Prism/Helper
-     */
-    function splitIntoLines(container) {
-        const frag = document.createDocumentFragment();
-        let lineNo = 1;
-        const newLine = () => {
-            const line = document.createElement("span");
-            line.dataset.number = lineNo.toString();
-            lineNo++;
-            frag.appendChild(line);
-            return line;
-        };
+    function* splitIntoLines(container) {
         const it = document.createNodeIterator(container, NodeFilter.SHOW_TEXT, {
             acceptNode() {
                 return NodeFilter.FILTER_ACCEPT;
             },
         });
-        let line = newLine();
+        let line = document.createElement("span");
         let node;
         while ((node = it.nextNode())) {
             const text = node;
-            text.data.split(/\r?\n/).forEach((codeLine, index) => {
+            const lines = text.data.split(/\r?\n/);
+            for (let i = 0, max = lines.length; i < max; i++) {
+                const codeLine = lines[i];
                 // We are behind a newline, insert \n and create new container.
-                if (index >= 1) {
+                if (i >= 1) {
                     line.appendChild(document.createTextNode("\n"));
-                    line = newLine();
+                    yield line;
+                    line = document.createElement("span");
                 }
                 let current = document.createTextNode(codeLine);
                 // Copy hierarchy (to preserve CSS classes).
@@ -45,9 +39,9 @@ define(["require", "exports"], function (require, exports) {
                     parent = parent.parentNode;
                 }
                 line.appendChild(current);
-            });
+            }
         }
-        return frag;
+        yield line;
     }
     exports.splitIntoLines = splitIntoLines;
 });
index d1857d460df8b95ebdd954935e07976877987970..b6b25bd92a55f82489411b5f600d0932fdf768af 100644 (file)
@@ -83,8 +83,10 @@ class Code {
 
     this.container.classList.add("highlighting");
 
+    // Step 1) Load the requested grammar.
     await import("prism/components/prism-" + PrismMeta[this.language].file);
 
+    // Step 2) Perform the highlighting into a temporary element.
     await waitForIdle();
 
     const grammar = Prism.languages[this.language];
@@ -95,22 +97,23 @@ class Code {
     const container = document.createElement("div");
     container.innerHTML = Prism.highlight(this.codeContainer.textContent!, grammar, this.language);
 
+    // Step 3) Insert the highlighted lines into the page.
+    // This is performed in small chunks to prevent the UI thread from being blocked for complex
+    // highlight results.
     await waitForIdle();
 
-    const highlighted = PrismHelper.splitIntoLines(container);
-    const highlightedLines = highlighted.querySelectorAll("[data-number]");
     const originalLines = this.codeContainer.querySelectorAll(".codeBoxLine > span");
+    const highlightedLines = PrismHelper.splitIntoLines(container);
 
-    if (highlightedLines.length !== originalLines.length) {
-      throw new Error("Unreachable");
-    }
-
-    for (let chunkStart = 0, max = highlightedLines.length; chunkStart < max; chunkStart += CHUNK_SIZE) {
+    for (let chunkStart = 0, max = originalLines.length; chunkStart < max; chunkStart += CHUNK_SIZE) {
       await waitForIdle();
+
       const chunkEnd = Math.min(chunkStart + CHUNK_SIZE, max);
 
       for (let offset = chunkStart; offset < chunkEnd; offset++) {
-        originalLines[offset]!.parentNode!.replaceChild(highlightedLines[offset], originalLines[offset]);
+        const toReplace = originalLines[offset]!;
+        const replacement = highlightedLines.next().value as Element;
+        toReplace.parentNode!.replaceChild(replacement, toReplace);
       }
     }
 
index dee334bd1c6cbb3407af7038030bf128c27335cf..83d71ace2ac2aefe5dae8f694eb09c356101aefe 100644 (file)
@@ -6,32 +6,27 @@
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Prism/Helper
  */
-export function splitIntoLines(container: Node): DocumentFragment {
-  const frag = document.createDocumentFragment();
-  let lineNo = 1;
-  const newLine = () => {
-    const line = document.createElement("span");
-    line.dataset.number = lineNo.toString();
-    lineNo++;
-    frag.appendChild(line);
-    return line;
-  };
 
+export function* splitIntoLines(container: Node): Generator<Element, void> {
   const it = document.createNodeIterator(container, NodeFilter.SHOW_TEXT, {
     acceptNode() {
       return NodeFilter.FILTER_ACCEPT;
     },
   });
 
-  let line = newLine();
+  let line = document.createElement("span");
   let node;
   while ((node = it.nextNode())) {
     const text = node as Text;
-    text.data.split(/\r?\n/).forEach((codeLine, index) => {
+    const lines = text.data.split(/\r?\n/);
+
+    for (let i = 0, max = lines.length; i < max; i++) {
+      const codeLine = lines[i];
       // We are behind a newline, insert \n and create new container.
-      if (index >= 1) {
+      if (i >= 1) {
         line.appendChild(document.createTextNode("\n"));
-        line = newLine();
+        yield line;
+        line = document.createElement("span");
       }
 
       let current: Node = document.createTextNode(codeLine);
@@ -44,7 +39,7 @@ export function splitIntoLines(container: Node): DocumentFragment {
         parent = parent.parentNode;
       }
       line.appendChild(current);
-    });
+    }
   }
-  return frag;
+  yield line;
 }