diff --git a/plugins/auto-close-brackets.js b/plugins/auto-close-brackets.js index a30a813..d8f7473 100644 --- a/plugins/auto-close-brackets.js +++ b/plugins/auto-close-brackets.js @@ -19,13 +19,17 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin { /* Add keystroke events */ afterElementsAdded(codeInput) { - codeInput.textareaElement.addEventListener('keydown', (event) => { this.checkBackspace(codeInput, event) }); - codeInput.textareaElement.addEventListener('beforeinput', (event) => { this.checkBrackets(codeInput, event); }); + codeInput.pluginData.autoCloseBrackets = { automatedKeypresses: false}; + codeInput.textareaElement.addEventListener('keydown', (event) => { this.checkBackspace(codeInput, event); }); + codeInput.textareaElement.addEventListener('beforeinput', (event) => { this.checkClosingBracket(codeInput, event); }); + codeInput.textareaElement.addEventListener('input', (event) => { this.checkOpeningBracket(codeInput, event); }); } - /* Deal with the automatic creation of closing bracket when opening brackets are typed, and the ability to "retype" a closing - bracket where one has already been placed. */ - checkBrackets(codeInput, event) { + /* Deal with the ability to "retype" a closing bracket where one has already + been placed. Runs before input so newly typing a closing bracket can be + prevented.*/ + checkClosingBracket(codeInput, event) { + if(codeInput.pluginData.autoCloseBrackets.automatedKeypresses) return; if(event.data == codeInput.textareaElement.value[codeInput.textareaElement.selectionStart]) { // Check if a closing bracket is typed for(let openingBracket in this.bracketPairs) { @@ -37,11 +41,22 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin { break; } } - } else if(event.data in this.bracketPairs) { + } + } + + /* Deal with the automatic creation of closing bracket when opening brackets are typed. Runs after input for consistency between browsers. */ + checkOpeningBracket(codeInput, event) { + if(codeInput.pluginData.autoCloseBrackets.automatedKeypresses) return; + if(event.data in this.bracketPairs) { // Opening bracket typed; Create bracket pair let closingBracket = this.bracketPairs[event.data]; // Insert the closing bracket + // automatedKeypresses property to prevent keypresses being captured + // by this plugin during automated input as some browsers + // (e.g. GNOME Web) do. + codeInput.pluginData.autoCloseBrackets.automatedKeypresses = true; document.execCommand("insertText", false, closingBracket); + codeInput.pluginData.autoCloseBrackets.automatedKeypresses = false; // Move caret before the inserted closing bracket codeInput.textareaElement.selectionStart = codeInput.textareaElement.selectionEnd -= 1; } @@ -49,6 +64,7 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin { /* Deal with cases where a backspace deleting an opening bracket deletes the closing bracket straight after it as well */ checkBackspace(codeInput, event) { + if(codeInput.pluginData.autoCloseBrackets.automatedKeypresses) return; if(event.key == "Backspace" && codeInput.textareaElement.selectionStart == codeInput.textareaElement.selectionEnd) { let closingBracket = this.bracketPairs[codeInput.textareaElement.value[codeInput.textareaElement.selectionStart-1]]; if(closingBracket != undefined && codeInput.textareaElement.value[codeInput.textareaElement.selectionStart] == closingBracket) { @@ -58,4 +74,4 @@ codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin { } } } -} \ No newline at end of file +} diff --git a/plugins/indent.js b/plugins/indent.js index 7ddc2fe..2e67ac6 100644 --- a/plugins/indent.js +++ b/plugins/indent.js @@ -81,11 +81,12 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { let indentationWidthPx = testIndentationWidthSpan.offsetWidth; codeInput.removeChild(testIndentationWidthPre); - codeInput.pluginData.indent = {indentationWidthPx: indentationWidthPx}; + codeInput.pluginData.indent = {automatedKeypresses: false, indentationWidthPx: indentationWidthPx}; } /* Deal with the Tab key causing indentation, and Tab+Selection indenting / Shift+Tab+Selection unindenting lines, and the mechanism through which Tab can be used to switch focus instead (accessibility). */ checkTab(codeInput, event) { + if(codeInput.pluginData.indent.automatedKeypresses) return; if(!this.tabIndentationEnabled) return; if(this.escTabToChangeFocus) { // Accessibility - allow Tab for keyboard navigation when Esc pressed right before it. @@ -116,7 +117,12 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { if(!event.shiftKey && inputElement.selectionStart == inputElement.selectionEnd) { // Just place a tab/spaces here. + // automatedKeypresses property to prevent keypresses being captured + // by this plugin during automated input as some browsers + // (e.g. GNOME Web) do. + codeInput.pluginData.indent.automatedKeypresses = true; document.execCommand("insertText", false, this.indentation); + codeInput.pluginData.indent.automatedKeypresses = false; } else { let lines = inputElement.value.split("\n"); @@ -147,7 +153,12 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { // Add tab at start inputElement.selectionStart = letterI; inputElement.selectionEnd = letterI; + // automatedKeypresses property to prevent keypresses being captured + // by this plugin during automated input as some browsers + // (e.g. GNOME Web) do. + codeInput.pluginData.indent.f = true; document.execCommand("insertText", false, this.indentation); + codeInput.pluginData.indent.automatedKeypresses = false; // Change selection if(selectionStartI > letterI) { // Indented outside selection @@ -191,6 +202,7 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { /* Deal with new lines retaining indentation */ checkEnter(codeInput, event) { + if(codeInput.pluginData.indent.automatedKeypresses) return; if(event.key != "Enter") { return; } @@ -263,6 +275,10 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { } // insert our indents and any text from the previous line that might have been after the line break + // negative indents shouldn't exist and would only break future calculations. + if(numberIndents < 0) { + numberIndents = 0; + } for (let i = 0; i < numberIndents; i++) { newLine += this.indentation; } @@ -270,11 +286,16 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { // save the current cursor position let selectionStartI = inputElement.selectionStart; + // automatedKeypresses property to prevent keypresses being captured + // by this plugin during automated input as some browsers + // (e.g. GNOME Web) do. + codeInput.pluginData.indent.automatedKeypresses = true; if(bracketThreeLinesTriggered) { document.execCommand("insertText", false, "\n" + furtherIndentation); // Write indented line numberIndents += 1; // Reflects the new indent } document.execCommand("insertText", false, "\n" + newLine); // Write new line, including auto-indentation + codeInput.pluginData.indent.automatedKeypresses = false; // move cursor to new position inputElement.selectionStart = selectionStartI + numberIndents*this.indentationNumChars + 1; // count the indent level and the newline character @@ -294,6 +315,7 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { /* Deal with one 'tab' of spaces-based-indentation being deleted by each backspace, rather than one space */ checkBackspace(codeInput, event) { + if(codeInput.pluginData.indent.automatedKeypresses) return; if(event.key != "Backspace" || this.indentationNumChars == 1) { return; // Normal backspace when indentation of 1 } @@ -321,7 +343,8 @@ codeInput.plugins.Indent = class extends codeInput.Plugin { if(codeInput.value.substring(codeInput.textareaElement.selectionStart - this.indentationNumChars, codeInput.textareaElement.selectionStart) == this.indentation) { // Indentation before cursor = delete it codeInput.textareaElement.selectionStart -= this.indentationNumChars; - document.execCommand("delete", false, ""); + // document.execCommand("delete", false, ""); + // event.preventDefault(); } } } diff --git a/tests/tester.js b/tests/tester.js index 5b0899e..a408ce3 100644 --- a/tests/tester.js +++ b/tests/tester.js @@ -162,12 +162,17 @@ function startLoad(codeInputElem, isHLJS) { } /* Make input events work and be trusted in the inputElement - thanks for this SO answer: https://stackoverflow.com/a/49519772/21785620 */ -function allowInputEvents(inputElement) { +function allowInputEvents(inputElement, codeInputElement=undefined) { inputElement.addEventListener('input', function(e){ if(!e.isTrusted){ e.preventDefault(); // Manually trigger + // Prevent auto-close-brackets plugin recapturing the event + // Needed because this interception is hacky. + // TODO: Potentially plugin-agnostic way, probably automatedKeypresses var in core, won't be needed much but may be helpful extra feature. + if(codeInputElement !== undefined) codeInputElement.pluginData.autoCloseBrackets.automatedKeypresses = true; document.execCommand("insertText", false, e.data); + if(codeInputElement !== undefined) codeInputElement.pluginData.autoCloseBrackets.automatedKeypresses = false; } }, false); } @@ -175,9 +180,9 @@ function allowInputEvents(inputElement) { /* Start the tests using the textarea inside the code-input element and whether highlight.js is being used (as the Autodetect plugin only works with highlight.js, for example) */ async function startTests(textarea, isHLJS) { textarea.focus(); - allowInputEvents(textarea); codeInputElement = textarea.parentElement; + allowInputEvents(textarea, codeInputElement); /*--- Tests for core functionality ---*/ @@ -438,7 +443,7 @@ console.log("I've got another line!", 2 < 3, "should be true."); findInput.focus(); allowInputEvents(findInput); addText(findInput, "hello"); - await waitAsync(150); // Wait for highlighting so matches update + await waitAsync(200); // Wait for highlighting so matches update replaceInput.value = "hi"; replaceAllButton.click(); @@ -525,8 +530,10 @@ console.log("I've got another line!", 2 < 3, "should be true."); backspace(textarea); testAddingText("Indent-AutoCloseBrackets", textarea, function(textarea) { - addText(textarea, `function printTriples(max) {\nfor(let i = 0; i < max-2; i++) {\nfor(let j = 0; j < max-1; j++) {\nfor(let k = 0; k < max; k++) {\nconsole.log(i,j,k);\n}\n//Hmmm...`, true); - }, 'function printTriples(max) {\n for(let i = 0; i < max-2; i++) {\n for(let j = 0; j < max-1; j++) {\n for(let k = 0; k < max; k++) {\n console.log(i,j,k);\n }\n //Hmmm...\n }\n }\n }\n}', 189, 189); + addText(textarea, `function printTriples(max) {\nfor(let i = 0; i < max-2; i++) {\nfor(let j = 0; j < max-1; j++) {\nfor(let k = 0; k < max; k++) {\nconsole.log(i,j,k);\n}\n//Hmmm...\n}//Test auto-unindent\n{`, true); + move(textarea, 1); // Move after created closing bracket + backspace(textarea); // Remove created closing bracket + }, 'function printTriples(max) {\n for(let i = 0; i < max-2; i++) {\n for(let j = 0; j < max-1; j++) {\n for(let k = 0; k < max; k++) {\n console.log(i,j,k);\n }\n //Hmmm...\n }//Test auto-unindent\n {\n }\n }\n }\n}', 221, 211); // SelectTokenCallbacks if(isHLJS) { @@ -590,4 +597,4 @@ console.log("I've got another line!", 2 < 3, "should be true."); document.querySelector("h2").style.backgroundColor = "lightgreen"; document.querySelector("h2").textContent = "All Tests have Passed."; } -} \ No newline at end of file +}