@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.
- package/CODE_OF_CONDUCT.md +130 -0
- package/CONTRIBUTING.md +35 -0
- package/README.md +19 -10
- package/code-input.css +51 -23
- package/code-input.d.ts +70 -4
- package/code-input.js +128 -180
- package/code-input.min.css +1 -1
- package/code-input.min.js +1 -1
- package/package.json +1 -1
- package/plugins/README.md +28 -7
- package/plugins/auto-close-brackets.js +61 -0
- package/plugins/auto-close-brackets.min.js +1 -0
- package/plugins/autocomplete.js +20 -13
- package/plugins/autocomplete.min.js +1 -1
- package/plugins/autodetect.js +8 -8
- package/plugins/autodetect.min.js +1 -1
- package/plugins/find-and-replace.css +145 -0
- package/plugins/find-and-replace.js +652 -0
- package/plugins/find-and-replace.min.css +1 -0
- package/plugins/find-and-replace.min.js +1 -0
- package/plugins/go-to-line.css +77 -0
- package/plugins/go-to-line.js +157 -0
- package/plugins/go-to-line.min.css +1 -0
- package/plugins/go-to-line.min.js +1 -0
- package/plugins/indent.js +162 -78
- package/plugins/indent.min.js +1 -1
- package/plugins/special-chars.css +0 -4
- package/plugins/special-chars.js +60 -88
- package/plugins/special-chars.min.css +1 -1
- package/plugins/special-chars.min.js +1 -1
- package/plugins/test.js +1 -2
- package/plugins/test.min.js +1 -1
- package/tests/hljs.html +54 -0
- package/tests/prism.html +55 -0
- package/tests/tester.js +529 -0
- package/tests/tester.min.js +18 -0
- package/plugins/debounce-update.js +0 -41
- package/plugins/debounce-update.min.js +0 -1
|
@@ -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
|
-
*
|
|
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
|
-
|
|
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.
|
|
14
|
-
textarea.addEventListener('keydown', (event) => { this.
|
|
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
|
-
/*
|
|
18
|
-
|
|
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
|
|
23
|
-
let code = input_element.value;
|
|
43
|
+
let inputElement = codeInput.textareaElement;
|
|
24
44
|
event.preventDefault(); // stop normal
|
|
25
45
|
|
|
26
|
-
if(!event.shiftKey &&
|
|
27
|
-
// Just place a tab here.
|
|
28
|
-
document.execCommand("insertText", false,
|
|
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 =
|
|
32
|
-
let
|
|
51
|
+
let lines = inputElement.value.split("\n");
|
|
52
|
+
let letterI = 0;
|
|
33
53
|
|
|
34
|
-
let
|
|
35
|
-
let
|
|
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
|
|
38
|
-
|
|
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]
|
|
47
|
-
// Remove first
|
|
48
|
-
|
|
49
|
-
|
|
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(
|
|
54
|
-
|
|
69
|
+
if(selectionStartI > letterI) { // Indented outside selection
|
|
70
|
+
selectionStartI = Math.max(selectionStartI - this.indentationNumChars, letterI); // Don't move to before indent
|
|
55
71
|
}
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
selectionEndI -= this.indentationNumChars;
|
|
73
|
+
letterI -= this.indentationNumChars;
|
|
58
74
|
}
|
|
59
75
|
} else {
|
|
60
76
|
// Add tab at start
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
document.execCommand("insertText", false,
|
|
77
|
+
inputElement.selectionStart = letterI;
|
|
78
|
+
inputElement.selectionEnd = letterI;
|
|
79
|
+
document.execCommand("insertText", false, this.indentation);
|
|
64
80
|
|
|
65
81
|
// Change selection
|
|
66
|
-
if(
|
|
67
|
-
|
|
82
|
+
if(selectionStartI > letterI) { // Indented outside selection
|
|
83
|
+
selectionStartI += this.indentationNumChars;
|
|
68
84
|
}
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
selectionEndI += this.indentationNumChars;
|
|
86
|
+
letterI += this.indentationNumChars;
|
|
71
87
|
}
|
|
72
88
|
}
|
|
73
89
|
|
|
74
|
-
|
|
90
|
+
letterI += lines[i].length+1; // newline counted
|
|
75
91
|
}
|
|
76
|
-
// input_element.value = lines.join("\n");
|
|
77
92
|
|
|
78
93
|
// move cursor
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
inputElement.selectionStart = selectionStartI;
|
|
95
|
+
inputElement.selectionEnd = selectionEndI;
|
|
81
96
|
}
|
|
82
97
|
|
|
83
|
-
codeInput.
|
|
98
|
+
codeInput.value = inputElement.value;
|
|
84
99
|
}
|
|
85
100
|
|
|
86
|
-
|
|
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(); //
|
|
106
|
+
event.preventDefault(); // Stop normal \n only
|
|
91
107
|
|
|
92
|
-
let
|
|
93
|
-
let lines =
|
|
94
|
-
let
|
|
95
|
-
let
|
|
96
|
-
let
|
|
97
|
-
let
|
|
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
|
-
|
|
102
|
-
if(
|
|
103
|
-
|
|
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
|
|
110
|
-
for (let i = 0; i <
|
|
111
|
-
if (lines[
|
|
112
|
-
|
|
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
|
|
120
|
-
if (
|
|
121
|
-
|
|
122
|
-
lines[
|
|
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 <
|
|
127
|
-
|
|
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
|
|
132
|
-
let selection_end = input_element.selectionEnd;
|
|
180
|
+
let selectionStartI = inputElement.selectionStart;
|
|
133
181
|
|
|
134
|
-
|
|
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
|
-
|
|
138
|
-
|
|
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.
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
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
|
}
|
package/plugins/indent.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
codeInput.plugins.Indent=class extends codeInput.Plugin{constructor(){super([])}afterElementsAdded(a){let b=a.
|
|
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;
|