Просмотр исходного кода

Large inputs with long line lengths are now handled better. Issues with toggleLoader fixed.

n1474335 2 лет назад
Родитель
Сommit
251bd86ce5
2 измененных файлов с 102 добавлено и 35 удалено
  1. 34 5
      src/web/waiters/InputWaiter.mjs
  2. 68 30
      src/web/waiters/OutputWaiter.mjs

+ 34 - 5
src/web/waiters/InputWaiter.mjs

@@ -221,12 +221,41 @@ class InputWaiter {
      * @param {string} data
      * @param {string} data
      */
      */
     setInput(data) {
     setInput(data) {
-        this.inputEditorView.dispatch({
-            changes: {
-                from: 0,
-                to: this.inputEditorView.state.doc.length,
-                insert: data
+        const lineLengthThreshold = 131072; // 128KB
+        let wrap = this.app.options.wordWrap;
+        if (data.length > lineLengthThreshold) {
+            const lines = data.split(this.getEOLSeq());
+            const longest = lines.reduce((a, b) =>
+                a > b.length ? a : b.length, 0
+            );
+            if (longest > lineLengthThreshold) {
+                // If we are exceeding the max line length, turn off word wrap
+                wrap = false;
+                this.app.alert("Maximum line length exceeded. Word wrap will be temporarily disabled to improve performance.", 20000);
             }
             }
+        }
+
+        // If turning word wrap off, do it before we populate the editor for performance reasons
+        if (!wrap) this.setWordWrap(wrap);
+
+        // We use setTimeout here to delay the editor dispatch until the next event cycle,
+        // ensuring all async actions have completed before attempting to set the contents
+        // of the editor. This is mainly with the above call to setWordWrap() in mind.
+        setTimeout(() => {
+            // Insert data into editor, overwriting any previous contents
+            this.inputEditorView.dispatch({
+                changes: {
+                    from: 0,
+                    to: this.inputEditorView.state.doc.length,
+                    insert: data
+                }
+            });
+
+            // If turning word wrap on, do it after we populate the editor
+            if (wrap)
+                setTimeout(() => {
+                    this.setWordWrap(wrap);
+                });
         });
         });
     }
     }
 
 

+ 68 - 30
src/web/waiters/OutputWaiter.mjs

@@ -128,6 +128,10 @@ class OutputWaiter {
                 EditorView.updateListener.of(e => {
                 EditorView.updateListener.of(e => {
                     if (e.selectionSet)
                     if (e.selectionSet)
                         this.manager.highlighter.selectionChange("output", e);
                         this.manager.highlighter.selectionChange("output", e);
+                    if (e.docChanged || this.docChanging) {
+                        this.docChanging = false;
+                        this.toggleLoader(false);
+                    }
                 })
                 })
             ]
             ]
         });
         });
@@ -230,6 +234,8 @@ class OutputWaiter {
         if (!force && data === this.currentOutputCache) return;
         if (!force && data === this.currentOutputCache) return;
         this.currentOutputCache = data;
         this.currentOutputCache = data;
 
 
+        this.toggleLoader(true);
+
         // If data is an ArrayBuffer, convert to a string in the correct character encoding
         // If data is an ArrayBuffer, convert to a string in the correct character encoding
         const tabNum = this.manager.tabs.getActiveTab("output");
         const tabNum = this.manager.tabs.getActiveTab("output");
         this.manager.timing.recordTime("outputDecodingStart", tabNum);
         this.manager.timing.recordTime("outputDecodingStart", tabNum);
@@ -245,13 +251,42 @@ class OutputWaiter {
             )
             )
         });
         });
 
 
-        // Insert data into editor
-        this.outputEditorView.dispatch({
-            changes: {
-                from: 0,
-                to: this.outputEditorView.state.doc.length,
-                insert: data
+        // Ensure we're not exceeding the maximum line length
+        let wrap = this.app.options.wordWrap;
+        const lineLengthThreshold = 131072; // 128KB
+        if (data.length > lineLengthThreshold) {
+            const lines = data.split(this.getEOLSeq());
+            const longest = lines.reduce((a, b) =>
+                a > b.length ? a : b.length, 0
+            );
+            if (longest > lineLengthThreshold) {
+                // If we are exceeding the max line length, turn off word wrap
+                wrap = false;
             }
             }
+        }
+
+        // If turning word wrap off, do it before we populate the editor for performance reasons
+        if (!wrap) this.setWordWrap(wrap);
+
+        // We use setTimeout here to delay the editor dispatch until the next event cycle,
+        // ensuring all async actions have completed before attempting to set the contents
+        // of the editor. This is mainly with the above call to setWordWrap() in mind.
+        setTimeout(() => {
+            this.docChanging = true;
+            // Insert data into editor, overwriting any previous contents
+            this.outputEditorView.dispatch({
+                changes: {
+                    from: 0,
+                    to: this.outputEditorView.state.doc.length,
+                    insert: data
+                }
+            });
+
+            // If turning word wrap on, do it after we populate the editor
+            if (wrap)
+                setTimeout(() => {
+                    this.setWordWrap(wrap);
+                });
         });
         });
     }
     }
 
 
@@ -263,8 +298,9 @@ class OutputWaiter {
         this.htmlOutput.html = html;
         this.htmlOutput.html = html;
         this.htmlOutput.changed = true;
         this.htmlOutput.changed = true;
         // This clears the text output, but also fires a View update which
         // This clears the text output, but also fires a View update which
-        // triggers the htmlWidget to render the HTML.
-        await this.setOutput("");
+        // triggers the htmlWidget to render the HTML. We set the force flag
+        // to ensure the loader gets removed and HTML is rendered.
+        await this.setOutput("", true);
 
 
         // Turn off drawSelection
         // Turn off drawSelection
         this.outputEditorView.dispatch({
         this.outputEditorView.dispatch({
@@ -542,7 +578,6 @@ class OutputWaiter {
                 // otherwise don't do anything
                 // otherwise don't do anything
                 document.querySelector("#output-loader .loading-msg").textContent = output.statusMessage;
                 document.querySelector("#output-loader .loading-msg").textContent = output.statusMessage;
             } else if (output.status === "error") {
             } else if (output.status === "error") {
-                this.toggleLoader(false);
                 this.clearHTMLOutput();
                 this.clearHTMLOutput();
 
 
                 if (output.error) {
                 if (output.error) {
@@ -556,7 +591,6 @@ class OutputWaiter {
                 if (output.data === null) {
                 if (output.data === null) {
                     this.clearHTMLOutput();
                     this.clearHTMLOutput();
                     await this.setOutput("");
                     await this.setOutput("");
-                    this.toggleLoader(false);
                     return;
                     return;
                 }
                 }
 
 
@@ -571,7 +605,6 @@ class OutputWaiter {
                         await this.setOutput(output.data.result);
                         await this.setOutput(output.data.result);
                         break;
                         break;
                 }
                 }
-                this.toggleLoader(false);
                 this.manager.timing.recordTime("complete", inputNum);
                 this.manager.timing.recordTime("complete", inputNum);
 
 
                 // Trigger an update so that the status bar recalculates timings
                 // Trigger an update so that the status bar recalculates timings
@@ -665,35 +698,40 @@ class OutputWaiter {
      * @param {boolean} value - If true, show the loader
      * @param {boolean} value - If true, show the loader
      */
      */
     toggleLoader(value) {
     toggleLoader(value) {
-        clearTimeout(this.appendBombeTimeout);
-        clearTimeout(this.outputLoaderTimeout);
-
         const outputLoader = document.getElementById("output-loader"),
         const outputLoader = document.getElementById("output-loader"),
             animation = document.getElementById("output-loader-animation");
             animation = document.getElementById("output-loader-animation");
 
 
         if (value) {
         if (value) {
             this.manager.controls.hideStaleIndicator();
             this.manager.controls.hideStaleIndicator();
+            // Don't add the bombe if it's already there or scheduled to be loaded
+            if (animation.children.length === 0 && !this.appendBombeTimeout) {
+                // Start a timer to add the Bombe to the DOM just before we make it
+                // visible so that there is no stuttering
+                this.appendBombeTimeout = setTimeout(function() {
+                    this.appendBombeTimeout = null;
+                    animation.appendChild(this.bombeEl);
+                }.bind(this), 150);
+            }
 
 
-            // Don't add the bombe if it's already there!
-            if (animation.children.length > 0) return;
-
-            // Start a timer to add the Bombe to the DOM just before we make it
-            // visible so that there is no stuttering
-            this.appendBombeTimeout = setTimeout(function() {
-                animation.appendChild(this.bombeEl);
-            }.bind(this), 150);
+            if (outputLoader.style.visibility !== "visible" && !this.outputLoaderTimeout) {
+                // Show the loading screen
+                this.outputLoaderTimeout = setTimeout(function() {
+                    this.outputLoaderTimeout = null;
+                    outputLoader.style.visibility = "visible";
+                    outputLoader.style.opacity = 1;
+                }, 200);
+            }
+        } else if (outputLoader.style.visibility !== "hidden" || this.appendBombeTimeout || this.outputLoaderTimeout) {
+            clearTimeout(this.appendBombeTimeout);
+            clearTimeout(this.outputLoaderTimeout);
+            this.appendBombeTimeout = null;
+            this.outputLoaderTimeout = null;
 
 
-            // Show the loading screen
-            this.outputLoaderTimeout = setTimeout(function() {
-                outputLoader.style.visibility = "visible";
-                outputLoader.style.opacity = 1;
-            }, 200);
-        } else {
             // Remove the Bombe from the DOM to save resources
             // Remove the Bombe from the DOM to save resources
             this.outputLoaderTimeout = setTimeout(function () {
             this.outputLoaderTimeout = setTimeout(function () {
-                try {
+                this.outputLoaderTimeout = null;
+                if (animation.children.length > 0)
                     animation.removeChild(this.bombeEl);
                     animation.removeChild(this.bombeEl);
-                } catch (err) {}
             }.bind(this), 500);
             }.bind(this), 500);
             outputLoader.style.opacity = 0;
             outputLoader.style.opacity = 0;
             outputLoader.style.visibility = "hidden";
             outputLoader.style.visibility = "hidden";