@webcoder49/code-input 2.5.1 → 2.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +9 -126
  3. package/code-input.css +70 -33
  4. package/code-input.d.ts +135 -59
  5. package/code-input.js +201 -110
  6. package/code-input.min.css +1 -1
  7. package/code-input.min.js +12 -1
  8. package/docs/LICENSE +3 -0
  9. package/docs/LICENSE.CC-BY-SA-4.0 +116 -0
  10. package/docs/LICENSE.CC0-1.0 +30 -0
  11. package/docs/README.md +5 -0
  12. package/docs/_index.md +308 -0
  13. package/docs/i18n/_index.md +52 -0
  14. package/docs/interface/_index.md +3 -0
  15. package/docs/interface/css/_index.md +12 -0
  16. package/docs/interface/forms/_index.md +17 -0
  17. package/docs/interface/js/_index.md +11 -0
  18. package/docs/modules-and-frameworks/_index.md +3 -0
  19. package/docs/modules-and-frameworks/custom/_index.md +9 -0
  20. package/docs/modules-and-frameworks/hljs/_index.md +13 -0
  21. package/docs/modules-and-frameworks/hljs/esm/_index.md +71 -0
  22. package/docs/modules-and-frameworks/hljs/nuxt/_index.md +250 -0
  23. package/docs/modules-and-frameworks/hljs/nuxt/nuxt-demo-screenshot.png +0 -0
  24. package/docs/modules-and-frameworks/hljs/vue/_index.md +233 -0
  25. package/docs/modules-and-frameworks/hljs/vue/vue-demo-screenshot.png +0 -0
  26. package/docs/modules-and-frameworks/prism/_index.md +14 -0
  27. package/docs/plugins/_index.md +676 -0
  28. package/docs/plugins/new/_index.md +52 -0
  29. package/docs/theory/_index.md +9 -0
  30. package/esm/README.md +23 -0
  31. package/esm/code-input.d.mts +154 -0
  32. package/esm/code-input.mjs +997 -0
  33. package/esm/plugins/auto-close-brackets.d.mts +15 -0
  34. package/esm/plugins/auto-close-brackets.mjs +84 -0
  35. package/esm/plugins/autocomplete.d.mts +14 -0
  36. package/esm/plugins/autocomplete.mjs +93 -0
  37. package/esm/plugins/autodetect.d.mts +11 -0
  38. package/esm/plugins/autodetect.mjs +35 -0
  39. package/esm/plugins/find-and-replace.d.mts +43 -0
  40. package/esm/plugins/find-and-replace.mjs +777 -0
  41. package/esm/plugins/go-to-line.d.mts +29 -0
  42. package/esm/plugins/go-to-line.mjs +217 -0
  43. package/esm/plugins/indent.d.mts +22 -0
  44. package/esm/plugins/indent.mjs +359 -0
  45. package/esm/plugins/select-token-callbacks.d.mts +51 -0
  46. package/esm/plugins/select-token-callbacks.mjs +296 -0
  47. package/esm/plugins/special-chars.d.mts +25 -0
  48. package/esm/plugins/special-chars.mjs +207 -0
  49. package/esm/plugins/test.d.mts +16 -0
  50. package/esm/plugins/test.mjs +56 -0
  51. package/esm/templates/hljs.d.mts +16 -0
  52. package/esm/templates/hljs.mjs +28 -0
  53. package/esm/templates/prism.d.mts +16 -0
  54. package/esm/templates/prism.mjs +25 -0
  55. package/package.json +83 -7
  56. package/plugins/README.md +2 -0
  57. package/plugins/auto-close-brackets.js +2 -0
  58. package/plugins/auto-close-brackets.min.js +1 -1
  59. package/plugins/autocomplete.js +6 -6
  60. package/plugins/autocomplete.min.js +1 -1
  61. package/plugins/autodetect.js +4 -2
  62. package/plugins/autodetect.min.js +1 -1
  63. package/plugins/find-and-replace.css +0 -4
  64. package/plugins/find-and-replace.js +28 -8
  65. package/plugins/find-and-replace.min.css +1 -1
  66. package/plugins/find-and-replace.min.js +1 -1
  67. package/plugins/go-to-line.css +10 -5
  68. package/plugins/go-to-line.js +39 -6
  69. package/plugins/go-to-line.min.css +1 -1
  70. package/plugins/go-to-line.min.js +1 -1
  71. package/plugins/indent.js +4 -2
  72. package/plugins/indent.min.js +1 -1
  73. package/plugins/prism-line-numbers.css +14 -5
  74. package/plugins/prism-line-numbers.min.css +1 -1
  75. package/plugins/select-token-callbacks.js +3 -1
  76. package/plugins/select-token-callbacks.min.js +1 -1
  77. package/plugins/special-chars.css +13 -1
  78. package/plugins/special-chars.js +14 -4
  79. package/plugins/special-chars.min.css +1 -1
  80. package/plugins/special-chars.min.js +1 -1
  81. package/plugins/test.js +22 -7
  82. package/plugins/test.min.js +1 -1
  83. package/.github/workflows/minify.yml +0 -22
  84. package/.github/workflows/npm-publish.yml +0 -21
  85. package/CODE_OF_CONDUCT.md +0 -130
  86. package/CONTRIBUTING.md +0 -35
  87. package/tests/hljs.html +0 -55
  88. package/tests/i18n.html +0 -197
  89. package/tests/prism-match-braces-compatibility.js +0 -215
  90. package/tests/prism-match-braces-compatibility.min.js +0 -1
  91. package/tests/prism.html +0 -54
  92. package/tests/tester.js +0 -600
  93. package/tests/tester.min.js +0 -21
@@ -0,0 +1,29 @@
1
+ // NOTICE: This code is @generated from code outside the esm directory. Please do not edit it to contribute!
2
+
3
+ import { Plugin, CodeInput } from "../code-input.d.mts";
4
+ /**
5
+ * Add basic Go-To-Line (ctrl-G by default) functionality to the code editor.
6
+ * Files: go-to-line.js / go-to-line.css
7
+ */
8
+ export default class GoToLine extends Plugin {
9
+ /**
10
+ * Create a go-to-line command plugin to pass into a template
11
+ * @param {boolean} useCtrlG Should Ctrl+G be overriden for go-to-line functionality? Either way, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element)`.
12
+ * @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the English text.
13
+ */
14
+ constructor(useCtrlG?: boolean,
15
+ instructionTranslations?: {
16
+ closeDialog?: string;
17
+ input?: string;
18
+ guidanceFormat?: string;
19
+ guidanceLineRange?: (current:Number, max: Number) => string;
20
+ guidanceColumnRange?: (line: Number, current: Number, max: Number) => string;
21
+ guidanceValidLine?: (line: Number) => string;
22
+ guidanceValidColumn?: (line: Number, column: Number) => string;
23
+ });
24
+ /**
25
+ * Show a search-like dialog prompting line number.
26
+ * @param {CodeInput} codeInput the `<code-input>` element.
27
+ */
28
+ showPrompt(codeInput: CodeInput): void;
29
+ }
@@ -0,0 +1,217 @@
1
+ // NOTICE: This code is @generated from code outside the esm directory. Please do not edit it to contribute!
2
+
3
+ import { Plugin } from "../code-input.mjs";
4
+ const plugins = {};
5
+ /**
6
+ * Add basic Go-To-Line (Ctrl+G by default) functionality to the code editor.
7
+ * Files: go-to-line.js / go-to-line.css
8
+ */
9
+ "use strict";
10
+
11
+ plugins.GoToLine = class extends Plugin {
12
+ useCtrlG = false;
13
+
14
+ instructions = {
15
+ closeDialog: "Close Dialog and Return to Editor",
16
+ input: "Line:Column / Line no. then Enter",
17
+ guidanceFormat: "Wrong format. Enter a line number (e.g. 1) or a line number then colon then column number (e.g. 1:3).",
18
+ guidanceLineRange: (current, max) => `Line number (currently ${current}) should be between 1 and ${max} inclusive.`,
19
+ guidanceColumnRange: (line, current, max) => `On line ${line}, column number (currently ${current}) should be between 1 and ${max} inclusive.`,
20
+ guidanceValidLine: (line) => `Press Enter to go to line ${line}.`,
21
+ guidanceValidColumn: (line, column) => `Press Enter to go to line ${line}, column ${column}.`,
22
+ };
23
+
24
+ /**
25
+ * Create a go-to-line command plugin to pass into a template
26
+ * @param {boolean} useCtrlG Should Ctrl+G be overriden for go-to-line functionality? Either way, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element)`.
27
+ * @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the available keys and English text.
28
+ */
29
+ constructor(useCtrlG = true, instructionTranslations = {}) {
30
+ super([]); // No observed attributes
31
+ this.useCtrlG = useCtrlG;
32
+ this.addTranslations(this.instructions, instructionTranslations);
33
+ }
34
+
35
+ /* Add keystroke events */
36
+ afterElementsAdded(codeInput) {
37
+ const textarea = codeInput.textareaElement;
38
+ if(this.useCtrlG) {
39
+ textarea.addEventListener('keydown', (event) => { this.checkCtrlG(codeInput, event); });
40
+ }
41
+ }
42
+
43
+ /* Called with a dialog box keyup event to check the validity of the line number entered and submit the dialog if Enter is pressed */
44
+ checkPrompt(dialog, event) {
45
+ if (event.key == 'Escape') return this.cancelPrompt(dialog, event);
46
+
47
+ // Line number(:column number)
48
+ const lines = dialog.textarea.value.split('\n');
49
+ const maxLineNo = lines.length;
50
+ const lineNo = Number(dialog.input.value.split(':')[0]);
51
+ let columnNo = 0; // Means go to start of indented line
52
+ let maxColumnNo = 1;
53
+ const querySplitByColons = dialog.input.value.split(':');
54
+
55
+ // Invalid format
56
+ if(querySplitByColons.length > 2 || !/^[0-9:]*$/.test(dialog.input.value)) {
57
+ dialog.guidance.textContent = this.instructions.guidanceFormat;
58
+ return dialog.input.classList.add('code-input_go-to-line_error');
59
+ }
60
+
61
+ // Number(s) present
62
+ if (dialog.input.value) {
63
+ if (lineNo < 1 || lineNo > maxLineNo) {
64
+ // Out-of-range line number
65
+ dialog.guidance.textContent = this.instructions.guidanceLineRange(lineNo, maxLineNo);
66
+ return dialog.input.classList.add('code-input_go-to-line_error');
67
+ } else {
68
+ // Check if line:column - if so calculate column number
69
+ if(querySplitByColons.length >= 2) {
70
+ columnNo = Number(querySplitByColons[1]);
71
+ maxColumnNo = lines[lineNo-1].length+1; // column 1 always works since at start of line
72
+ }
73
+ if(columnNo < 0 || columnNo > maxColumnNo) {
74
+ dialog.guidance.textContent = this.instructions.guidanceColumnRange(lineNo, columnNo, maxColumnNo);
75
+ return dialog.input.classList.add('code-input_go-to-line_error');
76
+ } else {
77
+ if(columnNo === 0) {
78
+ // No column specified, or 0 which for backwards compatibility acts
79
+ // like none selected
80
+ dialog.guidance.textContent = this.instructions.guidanceValidLine(lineNo);
81
+ } else {
82
+ dialog.guidance.textContent = this.instructions.guidanceValidColumn(lineNo, columnNo);
83
+ }
84
+ dialog.input.classList.remove('code-input_go-to-line_error');
85
+ }
86
+ }
87
+ } else {
88
+ // No value
89
+ dialog.guidance.textContent = "";
90
+ }
91
+
92
+ if (event.key == 'Enter') {
93
+ this.goTo(dialog.textarea, lineNo, columnNo);
94
+ this.cancelPrompt(dialog, event);
95
+ }
96
+ }
97
+
98
+ /* Called with a dialog box keyup event to close and clear the dialog box */
99
+ cancelPrompt(dialog, event) {
100
+ event.preventDefault();
101
+ dialog.codeInput.handleEventsFromTextarea = false;
102
+ dialog.textarea.focus();
103
+ dialog.codeInput.handleEventsFromTextarea = true;
104
+ dialog.setAttribute("inert", true); // Hide from keyboard navigation when closed.
105
+ dialog.setAttribute("tabindex", -1); // Hide from keyboard navigation when closed.
106
+ dialog.setAttribute("aria-hidden", true); // Hide from screen reader when closed.
107
+
108
+ // Remove dialog after animation
109
+ dialog.classList.add('code-input_go-to-line_hidden-dialog');
110
+ dialog.input.value = "";
111
+ }
112
+
113
+ /**
114
+ * Show a search-like dialog prompting line number.
115
+ * @param {codeInput.CodeInput} codeInput the `<code-input>` element.
116
+ */
117
+ showPrompt(codeInput) {
118
+ if(codeInput.pluginData.goToLine == undefined || codeInput.pluginData.goToLine.dialog == undefined) {
119
+ const textarea = codeInput.textareaElement;
120
+
121
+ const dialog = document.createElement('div');
122
+
123
+ const input = document.createElement('input');
124
+
125
+ // TODO: Make a button element (semantic HTML for accessibility) in next major version
126
+ const cancel = document.createElement('span');
127
+ cancel.setAttribute("role", "button");
128
+ cancel.setAttribute("aria-label", this.instructions.closeDialog);
129
+ cancel.setAttribute("tabindex", 0); // Visible to keyboard navigation
130
+ cancel.setAttribute("title", this.instructions.closeDialog);
131
+
132
+ const guidance = document.createElement('p');
133
+ guidance.setAttribute("aria-live", "assertive"); // Screen reader must read the status message.
134
+ guidance.textContent = "";
135
+
136
+ dialog.appendChild(input);
137
+ dialog.appendChild(cancel);
138
+ dialog.appendChild(guidance);
139
+
140
+ dialog.className = 'code-input_go-to-line_dialog';
141
+ input.spellcheck = false;
142
+ input.placeholder = this.instructions.input;
143
+ dialog.codeInput = codeInput;
144
+ dialog.textarea = textarea;
145
+ dialog.input = input;
146
+ dialog.guidance = guidance;
147
+
148
+ input.addEventListener('keypress', (event) => {
149
+ /* Stop enter from submitting form */
150
+ if (event.key == 'Enter') event.preventDefault();
151
+ });
152
+
153
+ input.addEventListener('keyup', (event) => { return this.checkPrompt(dialog, event); });
154
+ cancel.addEventListener('click', (event) => { this.cancelPrompt(dialog, event); });
155
+ cancel.addEventListener('keypress', (event) => { if (event.key == "Space" || event.key == "Enter") this.cancelPrompt(dialog, event); });
156
+
157
+ codeInput.dialogContainerElement.appendChild(dialog);
158
+ codeInput.pluginData.goToLine = {dialog: dialog};
159
+ input.focus();
160
+ } else {
161
+ codeInput.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog");
162
+ codeInput.pluginData.goToLine.dialog.removeAttribute("inert"); // Show to keyboard navigation when open.
163
+ codeInput.pluginData.goToLine.dialog.setAttribute("tabindex", 0); // Show to keyboard navigation when open.
164
+ codeInput.pluginData.goToLine.dialog.removeAttribute("aria-hidden"); // Show to screen reader when open.
165
+ codeInput.pluginData.goToLine.dialog.input.focus();
166
+ }
167
+ }
168
+
169
+ /* Set the cursor on the first non-space char of textarea's nth line, or to the columnNo-numbered character in the line if it's not 0; and scroll it into view */
170
+ goTo(textarea, lineNo, columnNo = 0) {
171
+ let fontSize;
172
+ let lineHeight;
173
+ let scrollAmount;
174
+ let topPadding;
175
+ let cursorPos = -1;
176
+ let lines = textarea.value.split('\n');
177
+
178
+ if (lineNo > 0 && lineNo <= lines.length) {
179
+ if (textarea.computedStyleMap) {
180
+ fontSize = textarea.computedStyleMap().get('font-size').value;
181
+ lineHeight = fontSize * textarea.computedStyleMap().get('line-height').value;
182
+ } else {
183
+ fontSize = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('font-size').split('px')[0];
184
+ lineHeight = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('line-height').split('px')[0];
185
+ }
186
+
187
+ // scroll amount and initial top padding (3 lines above, if possible)
188
+ scrollAmount = (lineNo > 3 ? lineNo - 3 : 1) * lineHeight;
189
+ topPadding = (lineHeight - fontSize) / 2;
190
+
191
+ if (lineNo > 1) {
192
+ // cursor positon just after n - 1 full lines
193
+ cursorPos = lines.slice(0, lineNo - 1).join('\n').length;
194
+ }
195
+
196
+ // scan first non-space char in nth line
197
+ if (columnNo == 0) {
198
+ do cursorPos++; while (textarea.value[cursorPos] != '\n' && /\s/.test(textarea.value[cursorPos]));
199
+ } else {
200
+ cursorPos += 1 + columnNo - 1;
201
+ }
202
+
203
+ textarea.scrollTop = scrollAmount - topPadding;
204
+ textarea.setSelectionRange(cursorPos, cursorPos);
205
+ textarea.click();
206
+ }
207
+ }
208
+
209
+ /* Event handler for keydown event that makes Ctrl+G open go to line dialog */
210
+ checkCtrlG(codeInput, event) {
211
+ if (event.ctrlKey && event.key == 'g') {
212
+ event.preventDefault();
213
+ this.showPrompt(codeInput);
214
+ }
215
+ }
216
+ }
217
+ export default plugins.GoToLine;
@@ -0,0 +1,22 @@
1
+ // NOTICE: This code is @generated from code outside the esm directory. Please do not edit it to contribute!
2
+
3
+ import { Plugin, CodeInput } from "../code-input.d.mts";
4
+ /**
5
+ * Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it
6
+ * possible to indent/unindent multiple lines using Tab/Shift+Tab
7
+ * Files: indent.js
8
+ */
9
+ export default class Indent extends Plugin {
10
+ /**
11
+ * Create an indentation plugin to pass into a template
12
+ * @param {boolean} defaultSpaces Should the Tab key enter spaces rather than tabs? Defaults to false.
13
+ * @param {Number} numSpaces How many spaces is each tab character worth? Defaults to 4.
14
+ * @param {Object} bracketPairs Opening brackets mapped to closing brackets, default and example {"(": ")", "[": "]", "{": "}"}. All brackets must only be one character, and this can be left as null to remove bracket-based indentation behaviour.
15
+ * @param {boolean} escTabToChangeFocus Whether pressing the Escape key before (Shift+)Tab should make this keypress focus on a different element (Tab's default behaviour). You should always either enable this or use this plugin's disableTabIndentation and enableTabIndentation methods linked to other keyboard shortcuts, for accessibility.
16
+ * @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the English text.
17
+ */
18
+ constructor(defaultSpaces?: boolean, numSpaces?: Number, bracketPairs?: Object, escTabToChangeFocus?: boolean, instructionTwranslations?: {
19
+ tabForIndentation?: string;
20
+ tabForNavigation?: string;
21
+ });
22
+ }
@@ -0,0 +1,359 @@
1
+ // NOTICE: This code is @generated from code outside the esm directory. Please do not edit it to contribute!
2
+
3
+ import { Plugin } from "../code-input.mjs";
4
+ const plugins = {};
5
+ /**
6
+ * Add indentation using the `Tab` key, and auto-indents after a newline, as well as making it
7
+ * possible to indent/unindent multiple lines using Tab/Shift+Tab
8
+ * Files: indent.js
9
+ */
10
+ "use strict";
11
+
12
+ plugins.Indent = class extends Plugin {
13
+
14
+ bracketPairs = {}; // No bracket-auto-indentation used when {}
15
+ indentation = "\t";
16
+ indentationNumChars = 1;
17
+ tabIndentationEnabled = true; // Can be disabled for accessibility reasons to allow keyboard navigation
18
+ escTabToChangeFocus = true;
19
+ escJustPressed = false; // Becomes true when Escape key is pressed and false when another key is pressed
20
+
21
+ instructions = {
22
+ tabForIndentation: "Tab and Shift-Tab currently for indentation. Press Esc to enable keyboard navigation.",
23
+ tabForNavigation: "Tab and Shift-Tab currently for keyboard navigation. Type to return to indentation.",
24
+ };
25
+
26
+ /**
27
+ * Create an indentation plugin to pass into a template
28
+ * @param {boolean} defaultSpaces Should the Tab key enter spaces rather than tabs? Defaults to false.
29
+ * @param {Number} numSpaces How many spaces is each tab character worth? Defaults to 4.
30
+ * @param {Object} bracketPairs Opening brackets mapped to closing brackets, default and example {"(": ")", "[": "]", "{": "}"}. All brackets must only be one character, and this can be left as null to remove bracket-based indentation behaviour.
31
+ * @param {boolean} escTabToChangeFocus Whether pressing the Escape key before Tab and Shift-Tab should make this keypress focus on a different element (Tab's default behaviour). You should always either enable this or use this plugin's disableTabIndentation and enableTabIndentation methods linked to other keyboard shortcuts, for accessibility.
32
+ * @param {Object} instructionTranslations: user interface string keys mapped to translated versions for localisation. Look at the go-to-line.js source code for the available keys and English text.
33
+ */
34
+ constructor(defaultSpaces=false, numSpaces=4, bracketPairs={"(": ")", "[": "]", "{": "}"}, escTabToChangeFocus=true, instructionTranslations = {}) {
35
+ super([]); // No observed attributes
36
+
37
+ this.bracketPairs = bracketPairs;
38
+ if(defaultSpaces) {
39
+ this.indentation = "";
40
+ for(let i = 0; i < numSpaces; i++) {
41
+ this.indentation += " ";
42
+ }
43
+ this.indentationNumChars = numSpaces;
44
+ }
45
+
46
+ this.escTabToChangeFocus = true;
47
+
48
+ this.addTranslations(this.instructions, instructionTranslations);
49
+ }
50
+
51
+ /**
52
+ * Make the Tab key
53
+ */
54
+ disableTabIndentation() {
55
+ this.tabIndentationEnabled = false;
56
+ }
57
+
58
+ enableTabIndentation() {
59
+ this.tabIndentationEnabled = true;
60
+ }
61
+
62
+ /* Add keystroke events, and get the width of the indentation in pixels. */
63
+ afterElementsAdded(codeInput) {
64
+
65
+ let textarea = codeInput.textareaElement;
66
+ textarea.addEventListener('focus', (event) => { if(this.escTabToChangeFocus) codeInput.setKeyboardNavInstructions(this.instructions.tabForIndentation, true); })
67
+ textarea.addEventListener('keydown', (event) => { this.checkTab(codeInput, event); this.checkEnter(codeInput, event); this.checkBackspace(codeInput, event); });
68
+ textarea.addEventListener('beforeinput', (event) => { this.checkCloseBracket(codeInput, event); });
69
+
70
+ // Get the width of the indentation in pixels
71
+ let testIndentationWidthPre = document.createElement("pre");
72
+ testIndentationWidthPre.setAttribute("aria-hidden", "true"); // Hide for screen readers
73
+ let testIndentationWidthSpan = document.createElement("span");
74
+ if(codeInput.templateObject.preElementStyled) {
75
+ testIndentationWidthPre.appendChild(testIndentationWidthSpan);
76
+ testIndentationWidthPre.classList.add("code-input_autocomplete_test-indentation-width");
77
+ codeInput.appendChild(testIndentationWidthPre); // Styled like first pre, but first pre found to update
78
+ } else {
79
+ let testIndentationWidthCode = document.createElement("code");
80
+ testIndentationWidthCode.appendChild(testIndentationWidthSpan);
81
+ testIndentationWidthCode.classList.add("code-input_autocomplete_test-indentation-width");
82
+ testIndentationWidthPre.appendChild(testIndentationWidthCode);
83
+ codeInput.appendChild(testIndentationWidthPre); // Styled like first pre, but first pre found to update
84
+ }
85
+
86
+ testIndentationWidthSpan.innerHTML = codeInput.escapeHtml(this.indentation);
87
+ let indentationWidthPx = testIndentationWidthSpan.offsetWidth;
88
+ codeInput.removeChild(testIndentationWidthPre);
89
+
90
+ codeInput.pluginData.indent = {automatedKeypresses: false, indentationWidthPx: indentationWidthPx};
91
+ }
92
+
93
+ /* 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). */
94
+ checkTab(codeInput, event) {
95
+ if(codeInput.pluginData.indent.automatedKeypresses) return;
96
+ if(!this.tabIndentationEnabled) return;
97
+ if(this.escTabToChangeFocus) {
98
+ // Accessibility - allow Tab for keyboard navigation when Esc pressed right before it.
99
+ if(event.key == "Escape") {
100
+ this.escJustPressed = true;
101
+ codeInput.setKeyboardNavInstructions(this.instructions.tabForNavigation, false);
102
+ return;
103
+ } else if(event.key != "Tab") {
104
+ if(event.key == "Shift") {
105
+ return; // Shift+Tab after Esc should still be keyboard navigation
106
+ }
107
+ codeInput.setKeyboardNavInstructions(this.instructions.tabForIndentation, false);
108
+ this.escJustPressed = false;
109
+ return;
110
+ }
111
+
112
+ if(!this.enableTabIndentation || this.escJustPressed) {
113
+ codeInput.setKeyboardNavInstructions("", false);
114
+ this.escJustPressed = false;
115
+ return;
116
+ }
117
+ } else if(event.key != "Tab") {
118
+ return;
119
+ }
120
+
121
+ let inputElement = codeInput.textareaElement;
122
+ event.preventDefault(); // stop normal
123
+
124
+ if(!event.shiftKey && inputElement.selectionStart == inputElement.selectionEnd) {
125
+ // Just place a tab/spaces here.
126
+ // automatedKeypresses property to prevent keypresses being captured
127
+ // by this plugin during automated input as some browsers
128
+ // (e.g. GNOME Web) do.
129
+ codeInput.pluginData.indent.automatedKeypresses = true;
130
+ document.execCommand("insertText", false, this.indentation);
131
+ codeInput.pluginData.indent.automatedKeypresses = false;
132
+
133
+ } else {
134
+ let lines = inputElement.value.split("\n");
135
+ let letterI = 0;
136
+
137
+ let selectionStartI = inputElement.selectionStart; // where cursor moves after tab - moving forward by 1 indent
138
+ let selectionEndI = inputElement.selectionEnd; // where cursor moves after tab - moving forward by 1 indent
139
+
140
+ for (let i = 0; i < lines.length; i++) {
141
+ if((selectionStartI <= letterI+lines[i].length && selectionEndI >= letterI + 1)
142
+ || (selectionStartI == selectionEndI && selectionStartI <= letterI+lines[i].length+1 && selectionEndI >= letterI)) { // + 1 so newlines counted
143
+ // Starts before or at last char and ends after or at first char
144
+ if(event.shiftKey) {
145
+ if(lines[i].substring(0, this.indentationNumChars) == this.indentation) {
146
+ // Remove first indent
147
+ inputElement.selectionStart = letterI;
148
+ inputElement.selectionEnd = letterI+this.indentationNumChars;
149
+ document.execCommand("delete", false, "");
150
+
151
+ // Change selection
152
+ if(selectionStartI > letterI) { // Indented outside selection
153
+ selectionStartI = Math.max(selectionStartI - this.indentationNumChars, letterI); // Don't move to before indent
154
+ }
155
+ selectionEndI -= this.indentationNumChars;
156
+ letterI -= this.indentationNumChars;
157
+ }
158
+ } else {
159
+ // Add tab at start
160
+ inputElement.selectionStart = letterI;
161
+ inputElement.selectionEnd = letterI;
162
+ // automatedKeypresses property to prevent keypresses being captured
163
+ // by this plugin during automated input as some browsers
164
+ // (e.g. GNOME Web) do.
165
+ codeInput.pluginData.indent.f = true;
166
+ document.execCommand("insertText", false, this.indentation);
167
+ codeInput.pluginData.indent.automatedKeypresses = false;
168
+
169
+ // Change selection
170
+ if(selectionStartI > letterI) { // Indented outside selection
171
+ selectionStartI += this.indentationNumChars;
172
+ }
173
+ selectionEndI += this.indentationNumChars;
174
+ letterI += this.indentationNumChars;
175
+ }
176
+ }
177
+
178
+ letterI += lines[i].length+1; // newline counted
179
+ }
180
+
181
+ // move cursor
182
+ inputElement.selectionStart = selectionStartI;
183
+ inputElement.selectionEnd = selectionEndI;
184
+
185
+ // move scroll position to follow code
186
+ const textDirection = getComputedStyle(codeInput).direction;
187
+ if(textDirection == "rtl") {
188
+ if(event.shiftKey) {
189
+ // Scroll right
190
+ codeInput.scrollBy(codeInput.pluginData.indent.indentationWidthPx, 0);
191
+ } else {
192
+ // Scroll left
193
+ codeInput.scrollBy(-codeInput.pluginData.indent.indentationWidthPx, 0);
194
+ }
195
+ } else {
196
+ if(event.shiftKey) {
197
+ // Scroll left
198
+ codeInput.scrollBy(-codeInput.pluginData.indent.indentationWidthPx, 0);
199
+ } else {
200
+ // Scroll right
201
+ codeInput.scrollBy(codeInput.pluginData.indent.indentationWidthPx, 0);
202
+ }
203
+ }
204
+ }
205
+
206
+ codeInput.value = inputElement.value;
207
+ }
208
+
209
+ /* Deal with new lines retaining indentation */
210
+ checkEnter(codeInput, event) {
211
+ if(codeInput.pluginData.indent.automatedKeypresses) return;
212
+ if(event.key != "Enter") {
213
+ return;
214
+ }
215
+ event.preventDefault(); // Stop normal \n only
216
+
217
+ let inputElement = codeInput.textareaElement;
218
+ let lines = inputElement.value.split("\n");
219
+ let letterI = 0;
220
+ let currentLineI = lines.length - 1;
221
+ let newLine = "";
222
+ let numberIndents = 0;
223
+
224
+ // find the index of the line our cursor is currently on
225
+ for (let i = 0; i < lines.length; i++) {
226
+ letterI += lines[i].length + 1;
227
+ if(inputElement.selectionEnd < letterI) {
228
+ currentLineI = i;
229
+ break;
230
+ }
231
+ }
232
+
233
+ // count the number of indents the current line starts with (up to our cursor position in the line)
234
+ let cursorPosInLine = lines[currentLineI].length - (letterI - inputElement.selectionEnd) + 1;
235
+ for (let i = 0; i < cursorPosInLine; i += this.indentationNumChars) {
236
+ if (lines[currentLineI].substring(i, i+this.indentationNumChars) == this.indentation) {
237
+ numberIndents++;
238
+ } else {
239
+ break;
240
+ }
241
+ }
242
+
243
+ // determine the text before and after the cursor and chop the current line at the new line break
244
+ let textAfterCursor = "";
245
+ if (cursorPosInLine != lines[currentLineI].length) {
246
+ textAfterCursor = lines[currentLineI].substring(cursorPosInLine);
247
+ lines[currentLineI] = lines[currentLineI].substring(0, cursorPosInLine);
248
+ }
249
+
250
+ let bracketThreeLinesTriggered = false;
251
+ let furtherIndentation = "";
252
+ if(this.bracketPairs != null) {
253
+ for(let openingBracket in this.bracketPairs) {
254
+ if(lines[currentLineI][lines[currentLineI].length-1] == openingBracket) {
255
+ let closingBracket = this.bracketPairs[openingBracket];
256
+ if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
257
+ // Create new line and then put textAfterCursor on yet another line:
258
+ // {
259
+ // |CARET|
260
+ // }
261
+ bracketThreeLinesTriggered = true;
262
+ for (let i = 0; i < numberIndents+1; i++) {
263
+ furtherIndentation += this.indentation;
264
+ }
265
+ } else {
266
+ // Just create new line:
267
+ // {
268
+ // |CARET|
269
+ numberIndents++;
270
+ }
271
+ break;
272
+ } else {
273
+ // Check whether brackets cause unindent
274
+ let closingBracket = this.bracketPairs[openingBracket];
275
+ if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
276
+ numberIndents--;
277
+ break;
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ // insert our indents and any text from the previous line that might have been after the line break
284
+ // negative indents shouldn't exist and would only break future calculations.
285
+ if(numberIndents < 0) {
286
+ numberIndents = 0;
287
+ }
288
+ for (let i = 0; i < numberIndents; i++) {
289
+ newLine += this.indentation;
290
+ }
291
+
292
+ // save the current cursor position
293
+ let selectionStartI = inputElement.selectionStart;
294
+
295
+ // automatedKeypresses property to prevent keypresses being captured
296
+ // by this plugin during automated input as some browsers
297
+ // (e.g. GNOME Web) do.
298
+ codeInput.pluginData.indent.automatedKeypresses = true;
299
+ if(bracketThreeLinesTriggered) {
300
+ document.execCommand("insertText", false, "\n" + furtherIndentation); // Write indented line
301
+ numberIndents += 1; // Reflects the new indent
302
+ }
303
+ document.execCommand("insertText", false, "\n" + newLine); // Write new line, including auto-indentation
304
+ codeInput.pluginData.indent.automatedKeypresses = false;
305
+
306
+ // move cursor to new position
307
+ inputElement.selectionStart = selectionStartI + numberIndents*this.indentationNumChars + 1; // count the indent level and the newline character
308
+ inputElement.selectionEnd = inputElement.selectionStart;
309
+
310
+
311
+ // Scroll down to cursor if necessary
312
+ let paddingTop = Number(getComputedStyle(inputElement).paddingTop.replace("px", ""));
313
+ let lineHeight = Number(getComputedStyle(inputElement).lineHeight.replace("px", ""));
314
+ let inputHeight = Number(getComputedStyle(codeInput).height.replace("px", ""));
315
+ if(currentLineI*lineHeight + lineHeight*2 + paddingTop >= inputElement.scrollTop + inputHeight) { // Cursor too far down
316
+ codeInput.scrollBy(0, Number(getComputedStyle(inputElement).lineHeight.replace("px", "")));
317
+ }
318
+
319
+ codeInput.value = inputElement.value;
320
+ }
321
+
322
+ /* Deal with one 'tab' of spaces-based-indentation being deleted by each backspace, rather than one space */
323
+ checkBackspace(codeInput, event) {
324
+ if(codeInput.pluginData.indent.automatedKeypresses) return;
325
+ if(event.key != "Backspace" || this.indentationNumChars == 1) {
326
+ return; // Normal backspace when indentation of 1
327
+ }
328
+
329
+ let inputElement = codeInput.textareaElement;
330
+
331
+ if(inputElement.selectionStart == inputElement.selectionEnd && codeInput.value.substring(inputElement.selectionStart - this.indentationNumChars, inputElement.selectionStart) == this.indentation) {
332
+ // Indentation before cursor = delete it
333
+ inputElement.selectionStart -= this.indentationNumChars;
334
+ event.preventDefault();
335
+ document.execCommand("delete", false, "");
336
+ }
337
+ }
338
+
339
+ /* Deal with the typing of closing brackets causing a decrease in indentation */
340
+ checkCloseBracket(codeInput, event) {
341
+ if(codeInput.textareaElement.selectionStart != codeInput.textareaElement.selectionEnd) {
342
+ return;
343
+ }
344
+
345
+ for(let openingBracket in this.bracketPairs) {
346
+ let closingBracket = this.bracketPairs[openingBracket];
347
+ if(event.data == closingBracket) {
348
+ // Closing bracket unindents line
349
+ if(codeInput.value.substring(codeInput.textareaElement.selectionStart - this.indentationNumChars, codeInput.textareaElement.selectionStart) == this.indentation) {
350
+ // Indentation before cursor = delete it
351
+ codeInput.textareaElement.selectionStart -= this.indentationNumChars;
352
+ // document.execCommand("delete", false, "");
353
+ // event.preventDefault();
354
+ }
355
+ }
356
+ }
357
+ }
358
+ }
359
+ export default plugins.Indent;