From 5e55d6e4b24e80bd275e3a283fdd5e999f4a2638 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 5 Jan 2021 15:20:27 +0100 Subject: [PATCH] Convert Prism/Helper#splitIntoLines into a generator This allow for a much cleaner control flow and also reduces the number of useless intermediate objects (e.g. the DocumentFragment). --- .../files/js/WoltLabSuite/Core/Bbcode/Code.js | 17 ++++---- .../js/WoltLabSuite/Core/Prism/Helper.js | 42 ++++++++----------- .../files/ts/WoltLabSuite/Core/Bbcode/Code.ts | 19 +++++---- .../ts/WoltLabSuite/Core/Prism/Helper.ts | 27 +++++------- 4 files changed, 50 insertions(+), 55 deletions(-) diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Bbcode/Code.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Bbcode/Code.js index e6f5598c62..63ebb5ceed 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Bbcode/Code.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Bbcode/Code.js @@ -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"); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Prism/Helper.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Prism/Helper.js index b929d81224..de9a334d8c 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Prism/Helper.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Prism/Helper.js @@ -1,39 +1,33 @@ +/** + * Provide helper functions for prism processing. + * + * @author Tim Duesterhus + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @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 - * @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; }); diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Bbcode/Code.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Bbcode/Code.ts index d1857d460d..b6b25bd92a 100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Bbcode/Code.ts +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Bbcode/Code.ts @@ -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); } } diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Prism/Helper.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Prism/Helper.ts index dee334bd1c..83d71ace2a 100644 --- a/wcfsetup/install/files/ts/WoltLabSuite/Core/Prism/Helper.ts +++ b/wcfsetup/install/files/ts/WoltLabSuite/Core/Prism/Helper.ts @@ -6,32 +6,27 @@ * @license GNU Lesser General Public License * @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 { 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; } -- 2.20.1