@webcoder49/code-input 2.0.3 → 2.2.1

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.
@@ -0,0 +1 @@
1
+ codeInput.plugins.FindAndReplace=class extends codeInput.Plugin{useCtrlF=!1;useCtrlH=!1;constructor(a=!0,b=!0){super([]),this.useCtrlF=a,this.useCtrlH=b}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlF&&b.addEventListener("keydown",b=>{this.checkCtrlF(a,b)}),this.useCtrlH&&b.addEventListener("keydown",b=>{this.checkCtrlH(a,b)})}afterHighlight(a){a.pluginData.findAndReplace==null||a.pluginData.findAndReplace.dialog==null||a.pluginData.findAndReplace.dialog.classList.contains("code-input_find-and-replace_hidden-dialog")||(a.pluginData.findAndReplace.dialog.findMatchState.rehighlightMatches(),this.updateMatchDescription(a.pluginData.findAndReplace.dialog),0==a.pluginData.findAndReplace.dialog.findMatchState.numMatches&&a.pluginData.findAndReplace.dialog.findInput.classList.add("code-input_find-and-replace_error"))}text2RegExp(a,b,c){return new RegExp(c?a:a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),b?"g":"gi")}updateMatchDescription(a){a.matchDescription.textContent=0==a.findInput.value.length?"Search for matches in your code.":0>=a.findMatchState.numMatches?"No matches.":1==a.findMatchState.numMatches?"1 match found.":`${a.findMatchState.focusedMatchID+1} of ${a.findMatchState.numMatches} matches.`}updateFindMatches(a){let b=a.findInput.value;setTimeout(()=>{if(b==a.findInput.value){if(a.findMatchState.clearMatches(),0<b.length){try{a.findMatchState.updateMatches(this.text2RegExp(a.findInput.value,a.findCaseSensitiveCheckbox.checked,a.findRegExpCheckbox.checked))}catch(b){if(b instanceof SyntaxError){a.findInput.classList.add("code-input_find-and-replace_error");let c=b.message.split(": ");return void(a.matchDescription.textContent="Error: "+c[c.length-1])}throw b}0<a.findMatchState.numMatches?a.findInput.classList.remove("code-input_find-and-replace_error"):a.findInput.classList.add("code-input_find-and-replace_error")}this.updateMatchDescription(a)}},100)}checkFindPrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.nextMatch(),this.updateMatchDescription(a))}checkReplacePrompt(a,b,c){"Enter"==c.key&&(a.findMatchState.replaceOnce(a.replaceInput.value),this.updateMatchDescription(a))}cancelPrompt(a,b,c){c.preventDefault(),a.textarea.focus(),0<a.findMatchState.numMatches?(b.textareaElement.selectionStart=a.findMatchState.matchStartIndexes[a.findMatchState.focusedMatchID],b.textareaElement.selectionEnd=a.findMatchState.matchEndIndexes[a.findMatchState.focusedMatchID]):(b.textareaElement.selectionStart=a.selectionStart,b.textareaElement.selectionEnd=a.selectionEnd),a.findMatchState.clearMatches(),a.classList.add("code-input_find-and-replace_hidden-dialog")}showPrompt(a,b){if(a.pluginData.findAndReplace==null||a.pluginData.findAndReplace.dialog==null){const c=a.textareaElement,d=document.createElement("div"),e=document.createElement("input"),f=document.createElement("input"),g=document.createElement("input"),h=document.createElement("code"),i=document.createElement("input"),j=document.createElement("details"),k=document.createElement("summary"),l=document.createElement("div"),m=document.createElement("button"),n=document.createElement("button"),o=document.createElement("button"),p=document.createElement("button"),q=document.createElement("span");l.appendChild(m),l.appendChild(n),l.appendChild(o),l.appendChild(p),l.appendChild(q),d.appendChild(l),d.appendChild(e),d.appendChild(g),d.appendChild(f),d.appendChild(h),j.appendChild(k),j.appendChild(i),d.appendChild(j),d.className="code-input_find-and-replace_dialog",e.spellcheck=!1,e.placeholder="Find",f.setAttribute("type","checkbox"),f.title="Match Case Sensitive",f.classList.add("code-input_find-and-replace_case-sensitive-checkbox"),g.setAttribute("type","checkbox"),g.title="Use JavaScript Regular Expression",g.classList.add("code-input_find-and-replace_reg-exp-checkbox"),h.textContent="Search for matches in your code.",h.classList.add("code-input_find-and-replace_match-description"),k.innerText="Replace",i.spellcheck=!1,i.placeholder="Replace with",m.innerText="\u2193",m.title="Find Next Occurence",n.innerText="\u2191",n.title="Find Previous Occurence",o.className="code-input_find-and-replace_button-hidden",o.innerText="Replace",o.title="Replace This Occurence",p.className="code-input_find-and-replace_button-hidden",p.innerText="Replace All",p.title="Replace All Occurences",m.addEventListener("click",a=>{a.preventDefault(),d.findMatchState.nextMatch(),this.updateMatchDescription(d)}),n.addEventListener("click",()=>{event.preventDefault(),d.findMatchState.previousMatch(),this.updateMatchDescription(d)}),o.addEventListener("click",a=>{a.preventDefault(),d.findMatchState.replaceOnce(i.value),o.focus()}),p.addEventListener("click",a=>{a.preventDefault(),d.findMatchState.replaceAll(i.value),p.focus()}),j.addEventListener("toggle",()=>{o.classList.toggle("code-input_find-and-replace_button-hidden"),p.classList.toggle("code-input_find-and-replace_button-hidden")}),d.findMatchState=new codeInput.plugins.FindAndReplace.FindMatchState(a),d.codeInput=a,d.textarea=c,d.findInput=e,d.findCaseSensitiveCheckbox=f,d.findRegExpCheckbox=g,d.matchDescription=h,d.replaceInput=i,d.replaceDropdown=j,this.checkCtrlH&&e.addEventListener("keydown",a=>{a.ctrlKey&&"h"==a.key&&(a.preventDefault(),j.setAttribute("open",!0))}),e.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),i.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),d.addEventListener("keyup",b=>{"Escape"==b.key&&this.cancelPrompt(d,a,b)}),e.addEventListener("keyup",b=>{this.checkFindPrompt(d,a,b)}),e.addEventListener("input",()=>{this.updateFindMatches(d)}),f.addEventListener("click",()=>{this.updateFindMatches(d)}),g.addEventListener("click",()=>{this.updateFindMatches(d)}),i.addEventListener("keyup",b=>{this.checkReplacePrompt(d,a,b),i.focus()}),q.addEventListener("click",b=>{this.cancelPrompt(d,a,b)}),a.dialogContainerElement.appendChild(d),a.pluginData.findAndReplace={dialog:d},e.focus(),b&&j.setAttribute("open",!0),d.selectionStart=a.textareaElement.selectionStart,d.selectionEnd=a.textareaElement.selectionEnd}else a.pluginData.findAndReplace.dialog.classList.remove("code-input_find-and-replace_hidden-dialog"),a.pluginData.findAndReplace.dialog.findInput.focus(),b?a.pluginData.findAndReplace.dialog.replaceDropdown.setAttribute("open",!0):a.pluginData.findAndReplace.dialog.replaceDropdown.removeAttribute("open"),this.updateFindMatches(a.pluginData.findAndReplace.dialog),a.pluginData.findAndReplace.dialog.selectionStart=a.textareaElement.selectionStart,a.pluginData.findAndReplace.dialog.selectionEnd=a.textareaElement.selectionEnd}checkCtrlF(a,b){b.ctrlKey&&"f"==b.key&&(b.preventDefault(),this.showPrompt(a,!1))}checkCtrlH(a,b){b.ctrlKey&&"h"==b.key&&(b.preventDefault(),this.showPrompt(a,!0))}};const CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE=500;codeInput.plugins.FindAndReplace.FindMatchState=class{codeInput=null;lastValue=null;lastSearchRegexp=null;numMatches=0;focusedMatchID=0;matchStartIndexes=[];matchEndIndexes=[];focusedMatchStartIndex=0;matchBlocksHighlighted=[];constructor(a){this.focusedMatchStartIndex=a.textareaElement.selectionStart,this.codeInput=a}clearMatches(){this.numMatches=0,this.matchStartIndexes=[],this.matchEndIndexes=[];let a=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_temporary-span");for(let b=0;b<a.length;b++)a[b].parentElement.replaceChild(new Text(a[b].textContent),a[b]);let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match");for(let a=0;a<b.length;a++)b[a].removeAttribute("data-code-input_find-and-replace_match-id"),b[a].classList.remove("code-input_find-and-replace_find-match"),b[a].classList.remove("code-input_find-and-replace_find-match-focused")}updateMatches(a){var b=Math.floor;this.lastSearchRegexp=a,this.lastValue=this.codeInput.value;let c,d=0;this.matchStartIndexes=[],this.matchEndIndexes=[],this.matchBlocksHighlighted=[];let e=b(this.focusedMatchID/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);for(let b=0;b<e;b++)this.matchBlocksHighlighted.push(!1);for(this.matchBlocksHighlighted.push(!0);null!==(c=a.exec(this.codeInput.value));){let a=c[0];if(0==a.length)throw SyntaxError("Causes an infinite loop");let e=b(d/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);this.matchBlocksHighlighted.length<e&&this.matchBlocksHighlighted.push(!1),this.matchBlocksHighlighted[e]&&this.highlightMatch(d,this.codeInput.codeElement,c.index,c.index+a.length),this.matchStartIndexes.push(c.index),this.matchEndIndexes.push(c.index+a.length),d++}this.numMatches=d,0<this.numMatches&&this.focusMatch()}rehighlightMatches(){this.updateMatches(this.lastSearchRegexp),this.focusMatch()}replaceOnce(a){0<this.numMatches&&a!=this.codeInput.value.substring(0,this.matchStartIndexes[this.focusedMatchID],this.matchEndIndexes[this.focusedMatchID])&&(this.focusedMatchStartIndex+=a.length,this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[this.focusedMatchID],this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[this.focusedMatchID],document.execCommand("insertText",!1,a))}replaceAll(a){const b=a.length;let c=0;for(let d=0;d<this.numMatches;d++)this.codeInput.textareaElement.focus(),this.codeInput.textareaElement.selectionStart=this.matchStartIndexes[d]+c,this.codeInput.textareaElement.selectionEnd=this.matchEndIndexes[d]+c,c+=b-(this.matchEndIndexes[d]-this.matchStartIndexes[d]),document.execCommand("insertText",!1,a)}nextMatch(){this.focusMatch((this.focusedMatchID+1)%this.numMatches)}previousMatch(){this.focusMatch((this.focusedMatchID+this.numMatches-1)%this.numMatches)}focusMatch(a=void 0){if(a===void 0){for(a=0;a<this.matchStartIndexes.length&&this.matchStartIndexes[a]<this.focusedMatchStartIndex;)a++;a>=this.matchStartIndexes.length&&(a=0)}this.focusedMatchStartIndex=this.matchStartIndexes[a],this.focusedMatchID=a;let b=this.codeInput.codeElement.querySelectorAll(".code-input_find-and-replace_find-match-focused");for(let c=0;c<b.length;c++)b[c].classList.remove("code-input_find-and-replace_find-match-focused");let c=Math.floor(a/CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE);if(!this.matchBlocksHighlighted[c]){this.matchBlocksHighlighted[c]=!0;for(let a=CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*c;a<CODE_INPUT_FIND_AND_REPLACE_MATCH_BLOCK_SIZE*(c+1);a++)this.highlightMatch(a,this.codeInput.codeElement,this.matchStartIndexes[a],this.matchEndIndexes[a])}let d=this.codeInput.codeElement.querySelectorAll(`.code-input_find-and-replace_find-match[data-code-input_find-and-replace_match-id="${a}"]`);for(let b=0;b<d.length;b++)d[b].classList.add("code-input_find-and-replace_find-match-focused");0<d.length&&this.codeInput.scrollTo(d[0].offsetLeft-this.codeInput.offsetWidth/2,d[0].offsetTop-this.codeInput.offsetHeight/2)}highlightMatch(a,b,c,d){for(let e=0;e<b.childNodes.length;e++){let f=b.childNodes[e],g=f.textContent,h=!1;if(3==f.nodeType){if(e+1<b.childNodes.length&&3==b.childNodes[e+1].nodeType){b.childNodes[e+1].textContent=f.textContent+b.childNodes[e+1].textContent,b.removeChild(f),e--;continue}h=!0;let a=document.createElement("span");a.textContent=g,a.classList.add("code-input_find-and-replace_temporary-span"),b.replaceChild(a,f),f=a}if(0>=c){if(g.length>=d){if(h){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_find-match"),b.setAttribute("data-code-input_find-and-replace_match-id",a),b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,d),"\n"==b.textContent[0]&&b.classList.add("code-input_find-and-replace_start-newline");let c=g.substring(d);return f.textContent=c,f.insertAdjacentElement("beforebegin",b),void e++}return void this.highlightMatch(a,f,0,d)}f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline")}else if(g.length>c){if(!h)this.highlightMatch(a,f,c,d);else if(g.length>d){let b=document.createElement("span");b.classList.add("code-input_find-and-replace_temporary-span"),b.textContent=g.substring(0,c);let h=g.substring(c,d);f.textContent=h,f.classList.add("code-input_find-and-replace_find-match"),f.setAttribute("data-code-input_find-and-replace_match-id",a),"\n"==f.textContent[0]&&f.classList.add("code-input_find-and-replace_start-newline");let i=document.createElement("span");i.classList.add("code-input_find-and-replace_temporary-span"),i.textContent=g.substring(d),f.insertAdjacentElement("beforebegin",b),f.insertAdjacentElement("afterend",i),e++}else{let b=g.substring(0,c);f.textContent=b;let d=document.createElement("span");d.classList.add("code-input_find-and-replace_find-match"),d.setAttribute("data-code-input_find-and-replace_match-id",a),d.classList.add("code-input_find-and-replace_temporary-span"),d.textContent=g.substring(c),"\n"==d.textContent[0]&&d.classList.add("code-input_find-and-replace_start-newline"),f.insertAdjacentElement("afterend",d),e++}if(g.length>d)return}c-=g.length,d-=g.length}}};
@@ -0,0 +1,77 @@
1
+ @keyframes code-input_go-to-line_roll-in {
2
+ 0% {opacity: 0; transform: translateY(-34px);}
3
+ 100% {opacity: 1; transform: translateY(0px);}
4
+ }
5
+
6
+ @keyframes code-input_go-to-line_roll-out {
7
+ 0% {opacity: 1; transform: translateY(0px);}
8
+ 100% {opacity: 0; transform: translateY(-34px);}
9
+ }
10
+
11
+ .code-input_go-to-line_dialog {
12
+ position: absolute;
13
+ top: 0; right: 14px;
14
+ height: 28px;
15
+ padding: 6px;
16
+ padding-top: 8px;
17
+ border: solid 1px #00000044;
18
+ background-color: white;
19
+ border-radius: 6px;
20
+ box-shadow: 0 .2em 1em .2em rgba(0, 0, 0, 0.16);
21
+ }
22
+
23
+ .code-input_go-to-line_dialog:not(.code-input_go-to-line_hidden-dialog) {
24
+ animation: code-input_go-to-line_roll-in .2s;
25
+ opacity: 1;
26
+ pointer-events: all;
27
+ }
28
+
29
+ .code-input_go-to-line_dialog.code-input_go-to-line_hidden-dialog {
30
+ animation: code-input_go-to-line_roll-out .2s;
31
+ opacity: 0;
32
+ pointer-events: none;
33
+ }
34
+
35
+ .code-input_go-to-line_dialog input::placeholder {
36
+ font-size: 80%;
37
+ }
38
+
39
+ .code-input_go-to-line_dialog input {
40
+ position: relative;
41
+ width: 240px; height: 32px; top: -3px;
42
+ font-size: large;
43
+ color: #000000aa;
44
+ border: 0;
45
+ }
46
+
47
+ .code-input_go-to-line_dialog input.code-input_go-to-line_error {
48
+ color: #ff0000aa;
49
+ }
50
+
51
+ .code-input_go-to-line_dialog input:focus {
52
+ outline: none;
53
+ }
54
+
55
+ /* Cancel icon */
56
+ .code-input_go-to-line_dialog span {
57
+ display: inline-block;
58
+ width: 24px;
59
+ line-height: 24px;
60
+ font-family: system-ui;
61
+ font-size: 22px;
62
+ font-weight: 500;
63
+ text-align: center;
64
+ border-radius: 50%;
65
+ color: black;
66
+ opacity: 0.6;
67
+ vertical-align: top;
68
+ }
69
+
70
+ .code-input_go-to-line_dialog span:before {
71
+ content: "\00d7";
72
+ }
73
+
74
+ .code-input_go-to-line_dialog span:hover {
75
+ opacity: .8;
76
+ background-color: #00000018;
77
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Add basic Go-To-Line (Ctrl+G by default) functionality to the code editor.
3
+ * Files: go-to-line.js / go-to-line.css
4
+ */
5
+ codeInput.plugins.GoToLine = class extends codeInput.Plugin {
6
+ useCtrlG = false;
7
+
8
+ /**
9
+ * Create a go-to-line command plugin to pass into a template
10
+ * @param {boolean} useCtrlG Should Ctrl+G be overriden for go-to-line functionality? If not, you can trigger it yourself using (instance of this plugin)`.showPrompt(code-input element)`.
11
+ */
12
+ constructor(useCtrlG = true) {
13
+ super([]); // No observed attributes
14
+ this.useCtrlG = useCtrlG;
15
+ }
16
+
17
+ /* Add keystroke events */
18
+ afterElementsAdded(codeInput) {
19
+ const textarea = codeInput.textareaElement;
20
+ if(this.useCtrlG) {
21
+ textarea.addEventListener('keydown', (event) => { this.checkCtrlG(codeInput, event); });
22
+ }
23
+ }
24
+
25
+ /* Called with a dialog box keyup event to check the validity of the line number entered and submit the dialog if Enter is pressed */
26
+ checkPrompt(dialog, event) {
27
+ // Line number(:column number)
28
+ const lines = dialog.textarea.value.split('\n');
29
+ const maxLineNo = lines.length;
30
+ const lineNo = Number(dialog.input.value.split(':')[0]);
31
+ let columnNo = 0; // Means go to start of indented line
32
+ let maxColumnNo = 1;
33
+ const querySplitByColons = dialog.input.value.split(':');
34
+ if(querySplitByColons.length > 2) return dialog.input.classList.add('code-input_go-to-line_error');
35
+
36
+ if (event.key == 'Escape') return this.cancelPrompt(dialog, event);
37
+
38
+ if (dialog.input.value) {
39
+ if (!/^[0-9:]*$/.test(dialog.input.value) || lineNo < 1 || lineNo > maxLineNo) {
40
+ return dialog.input.classList.add('code-input_go-to-line_error');
41
+ } else {
42
+ // Check if line:column
43
+ if(querySplitByColons.length >= 2) {
44
+ columnNo = Number(querySplitByColons[1]);
45
+ maxColumnNo = lines[lineNo-1].length;
46
+ }
47
+ if(columnNo < 0 || columnNo > maxColumnNo) {
48
+ return dialog.input.classList.add('code-input_go-to-line_error');
49
+ } else {
50
+ dialog.input.classList.remove('code-input_go-to-line_error');
51
+ }
52
+ }
53
+ }
54
+
55
+ if (event.key == 'Enter') {
56
+ this.goTo(dialog.textarea, lineNo, columnNo);
57
+ this.cancelPrompt(dialog, event);
58
+ }
59
+ }
60
+
61
+ /* Called with a dialog box keyup event to close and clear the dialog box */
62
+ cancelPrompt(dialog, event) {
63
+ event.preventDefault();
64
+ dialog.textarea.focus();
65
+
66
+ // Remove dialog after animation
67
+ dialog.classList.add('code-input_go-to-line_hidden-dialog');
68
+ dialog.input.value = "";
69
+ }
70
+
71
+ /**
72
+ * Show a search-like dialog prompting line number.
73
+ * @param {codeInput.CodeInput} codeInput the `<code-input>` element.
74
+ */
75
+ showPrompt(codeInput) {
76
+ if(codeInput.pluginData.goToLine == undefined || codeInput.pluginData.goToLine.dialog == undefined) {
77
+ const textarea = codeInput.textareaElement;
78
+
79
+ const dialog = document.createElement('div');
80
+ const input = document.createElement('input');
81
+ const cancel = document.createElement('span');
82
+
83
+ dialog.appendChild(input);
84
+ dialog.appendChild(cancel);
85
+
86
+ dialog.className = 'code-input_go-to-line_dialog';
87
+ input.spellcheck = false;
88
+ input.placeholder = "Line:Column / Line no. then Enter";
89
+ dialog.codeInput = codeInput;
90
+ dialog.textarea = textarea;
91
+ dialog.input = input;
92
+
93
+ input.addEventListener('keypress', (event) => {
94
+ /* Stop enter from submitting form */
95
+ if (event.key == 'Enter') event.preventDefault();
96
+ });
97
+
98
+ input.addEventListener('keyup', (event) => { return this.checkPrompt(dialog, event); });
99
+ cancel.addEventListener('click', (event) => { this.cancelPrompt(dialog, event); });
100
+
101
+ codeInput.dialogContainerElement.appendChild(dialog);
102
+ codeInput.pluginData.goToLine = {dialog: dialog};
103
+ input.focus();
104
+ } else {
105
+ codeInput.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog");
106
+ codeInput.pluginData.goToLine.dialog.input.focus();
107
+ }
108
+ }
109
+
110
+ /* 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 */
111
+ goTo(textarea, lineNo, columnNo = 0) {
112
+ let fontSize;
113
+ let lineHeight;
114
+ let scrollAmount;
115
+ let topPadding;
116
+ let cursorPos = -1;
117
+ let lines = textarea.value.split('\n');
118
+
119
+ if (lineNo > 0 && lineNo <= lines.length) {
120
+ if (textarea.computedStyleMap) {
121
+ fontSize = textarea.computedStyleMap().get('font-size').value;
122
+ lineHeight = fontSize * textarea.computedStyleMap().get('line-height').value;
123
+ } else {
124
+ fontSize = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('font-size').split('px')[0];
125
+ lineHeight = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('line-height').split('px')[0];
126
+ }
127
+
128
+ // scroll amount and initial top padding (3 lines above, if possible)
129
+ scrollAmount = (lineNo > 3 ? lineNo - 3 : 1) * lineHeight;
130
+ topPadding = (lineHeight - fontSize) / 2;
131
+
132
+ if (lineNo > 1) {
133
+ // cursor positon just after n - 1 full lines
134
+ cursorPos = lines.slice(0, lineNo - 1).join('\n').length;
135
+ }
136
+
137
+ // scan first non-space char in nth line
138
+ if (columnNo == 0) {
139
+ do cursorPos++; while (textarea.value[cursorPos] != '\n' && /\s/.test(textarea.value[cursorPos]));
140
+ } else {
141
+ cursorPos += 1 + columnNo - 1;
142
+ }
143
+
144
+ textarea.scrollTop = scrollAmount - topPadding;
145
+ textarea.setSelectionRange(cursorPos, cursorPos);
146
+ textarea.click();
147
+ }
148
+ }
149
+
150
+ /* Event handler for keydown event that makes Ctrl+G open go to line dialog */
151
+ checkCtrlG(codeInput, event) {
152
+ if (event.ctrlKey && event.key == 'g') {
153
+ event.preventDefault();
154
+ this.showPrompt(codeInput);
155
+ }
156
+ }
157
+ }
@@ -0,0 +1 @@
1
+ @keyframes code-input_go-to-line_roll-in{0%{opacity:0;transform:translateY(-34px)}100%{opacity:1;transform:translateY(0)}}@keyframes code-input_go-to-line_roll-out{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(-34px)}}.code-input_go-to-line_dialog{position:absolute;top:0;right:14px;height:28px;padding:6px;padding-top:8px;border:solid 1px #00000044;background-color:#fff;border-radius:6px;box-shadow:0 .2em 1em .2em rgba(0,0,0,.16)}.code-input_go-to-line_dialog:not(.code-input_go-to-line_hidden-dialog){animation:code-input_go-to-line_roll-in .2s;opacity:1;pointer-events:all}.code-input_go-to-line_dialog.code-input_go-to-line_hidden-dialog{animation:code-input_go-to-line_roll-out .2s;opacity:0;pointer-events:none}.code-input_go-to-line_dialog input::placeholder{font-size:80%}.code-input_go-to-line_dialog input{position:relative;width:240px;height:32px;top:-3px;font-size:large;color:#000000aa;border:0}.code-input_go-to-line_dialog input.code-input_go-to-line_error{color:#ff0000aa}.code-input_go-to-line_dialog input:focus{outline:0}.code-input_go-to-line_dialog span{display:inline-block;width:24px;line-height:24px;font-family:system-ui;font-size:22px;font-weight:500;text-align:center;border-radius:50%;color:#000;opacity:.6;vertical-align:top}.code-input_go-to-line_dialog span:before{content:"\00d7"}.code-input_go-to-line_dialog span:hover{opacity:.8;background-color:#00000018}
@@ -0,0 +1 @@
1
+ codeInput.plugins.GoToLine=class extends codeInput.Plugin{useCtrlG=!1;constructor(a=!0){super([]),this.useCtrlG=a}afterElementsAdded(a){const b=a.textareaElement;this.useCtrlG&&b.addEventListener("keydown",b=>{this.checkCtrlG(a,b)})}checkPrompt(a,b){const c=a.textarea.value.split("\n"),d=c.length,e=+a.input.value.split(":")[0];let f=0,g=1;const h=a.input.value.split(":");if(2<h.length)return a.input.classList.add("code-input_go-to-line_error");if("Escape"==b.key)return this.cancelPrompt(a,b);if(a.input.value){if(!/^[0-9:]*$/.test(a.input.value)||1>e||e>d)return a.input.classList.add("code-input_go-to-line_error");if(2<=h.length&&(f=+h[1],g=c[e-1].length),0>f||f>g)return a.input.classList.add("code-input_go-to-line_error");a.input.classList.remove("code-input_go-to-line_error")}"Enter"==b.key&&(this.goTo(a.textarea,e,f),this.cancelPrompt(a,b))}cancelPrompt(a,b){b.preventDefault(),a.textarea.focus(),a.classList.add("code-input_go-to-line_hidden-dialog"),a.input.value=""}showPrompt(a){if(a.pluginData.goToLine==null||a.pluginData.goToLine.dialog==null){const b=a.textareaElement,c=document.createElement("div"),d=document.createElement("input"),e=document.createElement("span");c.appendChild(d),c.appendChild(e),c.className="code-input_go-to-line_dialog",d.spellcheck=!1,d.placeholder="Line:Column / Line no. then Enter",c.codeInput=a,c.textarea=b,c.input=d,d.addEventListener("keypress",a=>{"Enter"==a.key&&a.preventDefault()}),d.addEventListener("keyup",a=>this.checkPrompt(c,a)),e.addEventListener("click",a=>{this.cancelPrompt(c,a)}),a.dialogContainerElement.appendChild(c),a.pluginData.goToLine={dialog:c},d.focus()}else a.pluginData.goToLine.dialog.classList.remove("code-input_go-to-line_hidden-dialog"),a.pluginData.goToLine.dialog.input.focus()}goTo(a,b,c=0){let d,e,f,g,h=-1,i=a.value.split("\n");if(0<b&&b<=i.length){if(a.computedStyleMap?(d=a.computedStyleMap().get("font-size").value,e=d*a.computedStyleMap().get("line-height").value):(d=document.defaultView.getComputedStyle(a,null).getPropertyValue("font-size").split("px")[0],e=document.defaultView.getComputedStyle(a,null).getPropertyValue("line-height").split("px")[0]),f=(3<b?b-3:1)*e,g=(e-d)/2,1<b&&(h=i.slice(0,b-1).join("\n").length),0==c)do h++;while("\n"!=a.value[h]&&/\s/.test(a.value[h]));else h+=1+c-1;a.scrollTop=f-g,a.setSelectionRange(h,h),a.click()}}checkCtrlG(a,b){b.ctrlKey&&"g"==b.key&&(b.preventDefault(),this.showPrompt(a))}};
package/plugins/indent.js CHANGED
@@ -1,154 +1,238 @@
1
1
  /**
2
- * Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it
2
+ * Add indentation using the `Tab` key, and auto-indents after a newline, as well as making it
3
3
  * possible to indent/unindent multiple lines using Tab/Shift+Tab
4
4
  * Files: indent.js
5
5
  */
6
6
  codeInput.plugins.Indent = class extends codeInput.Plugin {
7
- constructor() {
7
+
8
+ bracketPairs = {}; // No bracket-auto-indentation used when {}
9
+ indentation = "\t";
10
+ indentationNumChars = 1;
11
+
12
+ /**
13
+ * Create an indentation plugin to pass into a template
14
+ * @param {Boolean} defaultSpaces Should the Tab key enter spaces rather than tabs? Defaults to false.
15
+ * @param {Number} numSpaces How many spaces is each tab character worth? Defaults to 4.
16
+ * @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.
17
+ */
18
+ constructor(defaultSpaces=false, numSpaces=4, bracketPairs={"(": ")", "[": "]", "{": "}"}) {
8
19
  super([]); // No observed attributes
20
+
21
+ this.bracketPairs = bracketPairs;
22
+ if(defaultSpaces) {
23
+ this.indentation = "";
24
+ for(let i = 0; i < numSpaces; i++) {
25
+ this.indentation += " ";
26
+ }
27
+ this.indentationNumChars = numSpaces;
28
+ }
9
29
  }
10
30
 
11
31
  /* Add keystroke events */
12
32
  afterElementsAdded(codeInput) {
13
- let textarea = codeInput.querySelector("textarea");
14
- textarea.addEventListener('keydown', (event) => { this.check_tab(codeInput, event); this.check_enter(codeInput, event); });
33
+ let textarea = codeInput.textareaElement;
34
+ textarea.addEventListener('keydown', (event) => { this.checkTab(codeInput, event); this.checkEnter(codeInput, event); this.checkBackspace(codeInput, event); });
35
+ textarea.addEventListener('beforeinput', (event) => { this.checkCloseBracket(codeInput, event); });
15
36
  }
16
37
 
17
- /* Event handlers */
18
- check_tab(codeInput, event) {
38
+ /* Deal with the Tab key causing indentation, and Tab+Selection indenting / Shift+Tab+Selection unindenting lines */
39
+ checkTab(codeInput, event) {
19
40
  if(event.key != "Tab") {
20
41
  return;
21
42
  }
22
- let input_element = codeInput.querySelector("textarea");
23
- let code = input_element.value;
43
+ let inputElement = codeInput.textareaElement;
24
44
  event.preventDefault(); // stop normal
25
45
 
26
- if(!event.shiftKey && input_element.selectionStart == input_element.selectionEnd) {
27
- // Just place a tab here.
28
- document.execCommand("insertText", false, "\t");
46
+ if(!event.shiftKey && inputElement.selectionStart == inputElement.selectionEnd) {
47
+ // Just place a tab/spaces here.
48
+ document.execCommand("insertText", false, this.indentation);
29
49
 
30
50
  } else {
31
- let lines = input_element.value.split("\n");
32
- let letter_i = 0;
51
+ let lines = inputElement.value.split("\n");
52
+ let letterI = 0;
33
53
 
34
- let selection_start = input_element.selectionStart; // where cursor moves after tab - moving forward by 1 indent
35
- let selection_end = input_element.selectionEnd; // where cursor moves after tab - moving forward by 1 indent
54
+ let selectionStartI = inputElement.selectionStart; // where cursor moves after tab - moving forward by 1 indent
55
+ let selectionEndI = inputElement.selectionEnd; // where cursor moves after tab - moving forward by 1 indent
36
56
 
37
- let number_indents = 0;
38
- let first_line_indents = 0;
39
-
40
- for (let i = 0; i < lines.length; i++) {
41
- // console.log(lines[i], ": start", selection_start, letter_i + lines[i].length + 1, "&& end", selection_end , letter_i + 1)
42
- if((selection_start <= letter_i+lines[i].length && selection_end >= letter_i + 1)
43
- || (selection_start == selection_end && selection_start <= letter_i+lines[i].length+1 && selection_end >= letter_i)) { // + 1 so newlines counted
57
+ for (let i = 0; i < lines.length; i++) {
58
+ if((selectionStartI <= letterI+lines[i].length && selectionEndI >= letterI + 1)
59
+ || (selectionStartI == selectionEndI && selectionStartI <= letterI+lines[i].length+1 && selectionEndI >= letterI)) { // + 1 so newlines counted
44
60
  // Starts before or at last char and ends after or at first char
45
61
  if(event.shiftKey) {
46
- if(lines[i][0] == "\t") {
47
- // Remove first tab
48
- input_element.selectionStart = letter_i;
49
- input_element.selectionEnd = letter_i+1;
62
+ if(lines[i].substring(0, this.indentationNumChars) == this.indentation) {
63
+ // Remove first indent
64
+ inputElement.selectionStart = letterI;
65
+ inputElement.selectionEnd = letterI+this.indentationNumChars;
50
66
  document.execCommand("delete", false, "");
51
67
 
52
68
  // Change selection
53
- if(selection_start > letter_i) { // Indented outside selection
54
- selection_start--;
69
+ if(selectionStartI > letterI) { // Indented outside selection
70
+ selectionStartI = Math.max(selectionStartI - this.indentationNumChars, letterI); // Don't move to before indent
55
71
  }
56
- selection_end--;
57
- letter_i--;
72
+ selectionEndI -= this.indentationNumChars;
73
+ letterI -= this.indentationNumChars;
58
74
  }
59
75
  } else {
60
76
  // Add tab at start
61
- input_element.selectionStart = letter_i;
62
- input_element.selectionEnd = letter_i;
63
- document.execCommand("insertText", false, "\t");
77
+ inputElement.selectionStart = letterI;
78
+ inputElement.selectionEnd = letterI;
79
+ document.execCommand("insertText", false, this.indentation);
64
80
 
65
81
  // Change selection
66
- if(selection_start > letter_i) { // Indented outside selection
67
- selection_start++;
82
+ if(selectionStartI > letterI) { // Indented outside selection
83
+ selectionStartI += this.indentationNumChars;
68
84
  }
69
- selection_end++;
70
- letter_i++;
85
+ selectionEndI += this.indentationNumChars;
86
+ letterI += this.indentationNumChars;
71
87
  }
72
88
  }
73
89
 
74
- letter_i += lines[i].length+1; // newline counted
90
+ letterI += lines[i].length+1; // newline counted
75
91
  }
76
- // input_element.value = lines.join("\n");
77
92
 
78
93
  // move cursor
79
- input_element.selectionStart = selection_start;
80
- input_element.selectionEnd = selection_end;
94
+ inputElement.selectionStart = selectionStartI;
95
+ inputElement.selectionEnd = selectionEndI;
81
96
  }
82
97
 
83
- codeInput.update(input_element.value);
98
+ codeInput.value = inputElement.value;
84
99
  }
85
100
 
86
- check_enter(codeInput, event) {
101
+ /* Deal with new lines retaining indentation */
102
+ checkEnter(codeInput, event) {
87
103
  if(event.key != "Enter") {
88
104
  return;
89
105
  }
90
- event.preventDefault(); // stop normal
106
+ event.preventDefault(); // Stop normal \n only
91
107
 
92
- let input_element = codeInput.querySelector("textarea");
93
- let lines = input_element.value.split("\n");
94
- let letter_i = 0;
95
- let current_line = lines.length - 1;
96
- let new_line = "";
97
- let number_indents = 0;
108
+ let inputElement = codeInput.textareaElement;
109
+ let lines = inputElement.value.split("\n");
110
+ let letterI = 0;
111
+ let currentLineI = lines.length - 1;
112
+ let newLine = "";
113
+ let numberIndents = 0;
98
114
 
99
115
  // find the index of the line our cursor is currently on
100
116
  for (let i = 0; i < lines.length; i++) {
101
- letter_i += lines[i].length + 1;
102
- if(input_element.selectionEnd <= letter_i) {
103
- current_line = i;
117
+ letterI += lines[i].length + 1;
118
+ if(inputElement.selectionEnd <= letterI) {
119
+ currentLineI = i;
104
120
  break;
105
121
  }
106
122
  }
107
123
 
108
124
  // count the number of indents the current line starts with (up to our cursor position in the line)
109
- let cursor_pos_in_line = lines[current_line].length - (letter_i - input_element.selectionEnd) + 1;
110
- for (let i = 0; i < cursor_pos_in_line; i++) {
111
- if (lines[current_line][i] == "\t") {
112
- number_indents++;
125
+ let cursorPosInLine = lines[currentLineI].length - (letterI - inputElement.selectionEnd) + 1;
126
+ for (let i = 0; i < cursorPosInLine; i += this.indentationNumChars) {
127
+ if (lines[currentLineI].substring(i, i+this.indentationNumChars) == this.indentation) {
128
+ numberIndents++;
113
129
  } else {
114
130
  break;
115
131
  }
116
132
  }
117
133
 
118
134
  // determine the text before and after the cursor and chop the current line at the new line break
119
- let text_after_cursor = "";
120
- if (cursor_pos_in_line != lines[current_line].length) {
121
- text_after_cursor = lines[current_line].substring(cursor_pos_in_line);
122
- lines[current_line] = lines[current_line].substring(0, cursor_pos_in_line);
135
+ let textAfterCursor = "";
136
+ if (cursorPosInLine != lines[currentLineI].length) {
137
+ textAfterCursor = lines[currentLineI].substring(cursorPosInLine);
138
+ lines[currentLineI] = lines[currentLineI].substring(0, cursorPosInLine);
139
+ }
140
+
141
+ let bracketThreeLinesTriggered = false;
142
+ let furtherIndentation = "";
143
+ if(this.bracketPairs != null) {
144
+ for(let openingBracket in this.bracketPairs) {
145
+ if(lines[currentLineI][lines[currentLineI].length-1] == openingBracket) {
146
+ let closingBracket = this.bracketPairs[openingBracket];
147
+ if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
148
+ // Create new line and then put textAfterCursor on yet another line:
149
+ // {
150
+ // |CARET|
151
+ // }
152
+ bracketThreeLinesTriggered = true;
153
+ for (let i = 0; i < numberIndents+1; i++) {
154
+ furtherIndentation += this.indentation;
155
+ }
156
+ } else {
157
+ // Just create new line:
158
+ // {
159
+ // |CARET|
160
+ numberIndents++;
161
+ }
162
+ break;
163
+ } else {
164
+ // Check whether brackets cause unindent
165
+ let closingBracket = this.bracketPairs[openingBracket];
166
+ if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
167
+ numberIndents--;
168
+ break;
169
+ }
170
+ }
171
+ }
123
172
  }
124
173
 
125
174
  // insert our indents and any text from the previous line that might have been after the line break
126
- for (let i = 0; i < number_indents; i++) {
127
- new_line += "\t";
175
+ for (let i = 0; i < numberIndents; i++) {
176
+ newLine += this.indentation;
128
177
  }
129
178
 
130
179
  // save the current cursor position
131
- let selection_start = input_element.selectionStart;
132
- let selection_end = input_element.selectionEnd;
180
+ let selectionStartI = inputElement.selectionStart;
133
181
 
134
- document.execCommand("insertText", false, "\n" + new_line); // Write new line, including auto-indentation
182
+ if(bracketThreeLinesTriggered) {
183
+ document.execCommand("insertText", false, "\n" + furtherIndentation); // Write indented line
184
+ numberIndents += 1; // Reflects the new indent
185
+ }
186
+ document.execCommand("insertText", false, "\n" + newLine); // Write new line, including auto-indentation
135
187
 
136
188
  // move cursor to new position
137
- input_element.selectionStart = selection_start + number_indents + 1; // count the indent level and the newline character
138
- input_element.selectionEnd = selection_start + number_indents + 1;
189
+ inputElement.selectionStart = selectionStartI + numberIndents*this.indentationNumChars + 1; // count the indent level and the newline character
190
+ inputElement.selectionEnd = inputElement.selectionStart;
191
+
192
+
193
+ // Scroll down to cursor if necessary
194
+ let paddingTop = Number(getComputedStyle(inputElement).paddingTop.replace("px", ""));
195
+ let lineHeight = Number(getComputedStyle(inputElement).lineHeight.replace("px", ""));
196
+ let inputHeight = Number(getComputedStyle(codeInput).height.replace("px", ""));
197
+ if(currentLineI*lineHeight + lineHeight*2 + paddingTop >= inputElement.scrollTop + inputHeight) { // Cursor too far down
198
+ codeInput.scrollBy(0, Number(getComputedStyle(inputElement).lineHeight.replace("px", "")));
199
+ }
139
200
 
140
- codeInput.update(input_element.value);
201
+ codeInput.value = inputElement.value;
202
+ }
141
203
 
204
+ /* Deal with one 'tab' of spaces-based-indentation being deleted by each backspace, rather than one space */
205
+ checkBackspace(codeInput, event) {
206
+ if(event.key != "Backspace" || this.indentationNumChars == 1) {
207
+ return; // Normal backspace when indentation of 1
208
+ }
142
209
 
143
- // Update scrolls
144
- input_element.scrollLeft = 0;
145
- // Move down 1 line
146
- let lineHeight = Number(getComputedStyle(input_element).lineHeight.split(0, -2));
147
- // console.log(getComputedStyle(input_element).lineHeight);
148
- if(lineHeight == NaN && getComputedStyle(input_element).lineHeight.split(-2) == "px") {
149
- input_element.scrollTop += lineHeight;
150
- } else {
151
- input_element.scrollTop += 20; // px
210
+ let inputElement = codeInput.textareaElement;
211
+
212
+ if(inputElement.selectionStart == inputElement.selectionEnd && codeInput.value.substring(inputElement.selectionStart - this.indentationNumChars, inputElement.selectionStart) == this.indentation) {
213
+ // Indentation before cursor = delete it
214
+ inputElement.selectionStart -= this.indentationNumChars;
215
+ event.preventDefault();
216
+ document.execCommand("delete", false, "");
217
+ }
218
+ }
219
+
220
+ /* Deal with the typing of closing brackets causing a decrease in indentation */
221
+ checkCloseBracket(codeInput, event) {
222
+ if(codeInput.textareaElement.selectionStart != codeInput.textareaElement.selectionEnd) {
223
+ return;
224
+ }
225
+
226
+ for(let openingBracket in this.bracketPairs) {
227
+ let closingBracket = this.bracketPairs[openingBracket];
228
+ if(event.data == closingBracket) {
229
+ // Closing bracket unindents line
230
+ if(codeInput.value.substring(codeInput.textareaElement.selectionStart - this.indentationNumChars, codeInput.textareaElement.selectionStart) == this.indentation) {
231
+ // Indentation before cursor = delete it
232
+ codeInput.textareaElement.selectionStart -= this.indentationNumChars;
233
+ document.execCommand("delete", false, "");
234
+ }
235
+ }
152
236
  }
153
237
  }
154
238
  }
@@ -1 +1 @@
1
- codeInput.plugins.Indent=class extends codeInput.Plugin{constructor(){super([])}afterElementsAdded(a){let b=a.querySelector("textarea");b.addEventListener("keydown",b=>{this.check_tab(a,b),this.check_enter(a,b)})}check_tab(a,b){if("Tab"!=b.key)return;let c=a.querySelector("textarea"),d=c.value;if(b.preventDefault(),!b.shiftKey&&c.selectionStart==c.selectionEnd)document.execCommand("insertText",!1,"\t");else{let a=c.value.split("\n"),d=0,e=c.selectionStart,f=c.selectionEnd;for(let g=0;g<a.length;g++)(e<=d+a[g].length&&f>=d+1||e==f&&e<=d+a[g].length+1&&f>=d)&&(b.shiftKey?"\t"==a[g][0]&&(c.selectionStart=d,c.selectionEnd=d+1,document.execCommand("delete",!1,""),e>d&&e--,f--,d--):(c.selectionStart=d,c.selectionEnd=d,document.execCommand("insertText",!1,"\t"),e>d&&e++,f++,d++)),d+=a[g].length+1;c.selectionStart=e,c.selectionEnd=f}a.update(c.value)}check_enter(a,b){if("Enter"!=b.key)return;b.preventDefault();let c=a.querySelector("textarea"),d=c.value.split("\n"),e=0,f=d.length-1,g="",h=0;for(let g=0;g<d.length;g++)if(e+=d[g].length+1,c.selectionEnd<=e){f=g;break}let j=d[f].length-(e-c.selectionEnd)+1;for(let c=0;c<j&&"\t"==d[f][c];c++)h++;let k="";j!=d[f].length&&(k=d[f].substring(j),d[f]=d[f].substring(0,j));for(let c=0;c<h;c++)g+="\t";let l=c.selectionStart,m=c.selectionEnd;document.execCommand("insertText",!1,"\n"+g),c.selectionStart=l+h+1,c.selectionEnd=l+h+1,a.update(c.value),c.scrollLeft=0;let n=+getComputedStyle(c).lineHeight.split(0,-2);c.scrollTop+=n==NaN&&"px"==getComputedStyle(c).lineHeight.split(-2)?n:20}};
1
+ codeInput.plugins.Indent=class extends codeInput.Plugin{bracketPairs={};indentation="\t";indentationNumChars=1;constructor(a=!1,b=4,c={"(":")","[":"]","{":"}"}){if(super([]),this.bracketPairs=c,a){this.indentation="";for(let a=0;a<b;a++)this.indentation+=" ";this.indentationNumChars=b}}afterElementsAdded(a){let b=a.textareaElement;b.addEventListener("keydown",b=>{this.checkTab(a,b),this.checkEnter(a,b),this.checkBackspace(a,b)}),b.addEventListener("beforeinput",b=>{this.checkCloseBracket(a,b)})}checkTab(a,b){var c=Math.max;if("Tab"==b.key){let d=a.textareaElement;if(b.preventDefault(),!b.shiftKey&&d.selectionStart==d.selectionEnd)document.execCommand("insertText",!1,this.indentation);else{let a=d.value.split("\n"),e=0,f=d.selectionStart,g=d.selectionEnd;for(let h=0;h<a.length;h++)(f<=e+a[h].length&&g>=e+1||f==g&&f<=e+a[h].length+1&&g>=e)&&(b.shiftKey?a[h].substring(0,this.indentationNumChars)==this.indentation&&(d.selectionStart=e,d.selectionEnd=e+this.indentationNumChars,document.execCommand("delete",!1,""),f>e&&(f=c(f-this.indentationNumChars,e)),g-=this.indentationNumChars,e-=this.indentationNumChars):(d.selectionStart=e,d.selectionEnd=e,document.execCommand("insertText",!1,this.indentation),f>e&&(f+=this.indentationNumChars),g+=this.indentationNumChars,e+=this.indentationNumChars)),e+=a[h].length+1;d.selectionStart=f,d.selectionEnd=g}a.value=d.value}}checkEnter(a,b){if("Enter"!=b.key)return;b.preventDefault();let c=a.textareaElement,d=c.value.split("\n"),e=0,f=d.length-1,g="",h=0;for(let g=0;g<d.length;g++)if(e+=d[g].length+1,c.selectionEnd<=e){f=g;break}let j=d[f].length-(e-c.selectionEnd)+1;for(let c=0;c<j&&d[f].substring(c,c+this.indentationNumChars)==this.indentation;c+=this.indentationNumChars)h++;let k="";j!=d[f].length&&(k=d[f].substring(j),d[f]=d[f].substring(0,j));let l=!1,m="";if(null!=this.bracketPairs)for(let a in this.bracketPairs)if(d[f][d[f].length-1]==a){let b=this.bracketPairs[a];if(0<k.length&&k[0]==b){l=!0;for(let a=0;a<h+1;a++)m+=this.indentation}else h++;break}else{let b=this.bracketPairs[a];if(0<k.length&&k[0]==b){h--;break}}for(let c=0;c<h;c++)g+=this.indentation;let n=c.selectionStart;l&&(document.execCommand("insertText",!1,"\n"+m),h+=1),document.execCommand("insertText",!1,"\n"+g),c.selectionStart=n+h*this.indentationNumChars+1,c.selectionEnd=c.selectionStart;let o=+getComputedStyle(c).paddingTop.replace("px",""),p=+getComputedStyle(c).lineHeight.replace("px",""),q=+getComputedStyle(a).height.replace("px","");f*p+2*p+o>=c.scrollTop+q&&a.scrollBy(0,+getComputedStyle(c).lineHeight.replace("px","")),a.value=c.value}checkBackspace(a,b){if("Backspace"==b.key&&1!=this.indentationNumChars){let c=a.textareaElement;c.selectionStart==c.selectionEnd&&a.value.substring(c.selectionStart-this.indentationNumChars,c.selectionStart)==this.indentation&&(c.selectionStart-=this.indentationNumChars,b.preventDefault(),document.execCommand("delete",!1,""))}}checkCloseBracket(a,b){if(a.textareaElement.selectionStart==a.textareaElement.selectionEnd)for(let c in this.bracketPairs){let d=this.bracketPairs[c];b.data==d&&a.value.substring(a.textareaElement.selectionStart-this.indentationNumChars,a.textareaElement.selectionStart)==this.indentation&&(a.textareaElement.selectionStart-=this.indentationNumChars,document.execCommand("delete",!1,""))}}};
@@ -24,10 +24,6 @@
24
24
  --code-input_special-chars_F: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAFCAYAAACAcVaiAAAAAXNSR0IArs4c6QAAAB5JREFUGFdj/P///38GKGAEcRgZGRlBfDAHtwxMGQDZZhP+BnB1kwAAAABJRU5ErkJggg==');
25
25
  }
26
26
 
27
- .code-input_special-char_container { /* pre element */
28
- font-size: 20px;
29
- }
30
-
31
27
  .code-input_special-char {
32
28
  display: inline-block;
33
29
  position: relative;