Skip to content

Commit 7875e46

Browse files
authored
Merge pull request #73 from WebCoder49/brackets-support
Add Auto-Close Brackets plugin and brackets functionality for indenta…
2 parents 200ec12 + a2c4c3d commit 7875e46

File tree

5 files changed

+145
-6
lines changed

5 files changed

+145
-6
lines changed

code-input.d.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ export namespace plugins {
7676
constructor();
7777
}
7878

79+
/**
80+
* Automatically closes pairs of brackets/quotes/other syntaxes in code, but also lets you choose the brackets this
81+
* is activated for.
82+
* Files: auto-close-brackets.js
83+
*/
84+
class AutoCloseBrackets extends Plugin {
85+
/**
86+
* Create an auto-close brackets plugin to pass into a template
87+
* @param {Object} bracketPairs Opening brackets mapped to closing brackets, default and example {"(": ")", "[": "]", "{": "}", '"': '"'}. All brackets must only be one character.
88+
*/
89+
constructor(bracketPairs: Object);
90+
}
91+
7992
/**
8093
* Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
8194
* Files: autocomplete.js / autocomplete.css
@@ -137,8 +150,9 @@ export namespace plugins {
137150
* Create an indentation plugin to pass into a template
138151
* @param {Boolean} defaultSpaces Should the Tab key enter spaces rather than tabs? Defaults to false.
139152
* @param {Number} numSpaces How many spaces is each tab character worth? Defaults to 4.
153+
* @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.
140154
*/
141-
constructor(defaultSpaces?: boolean, numSpaces?: Number);
155+
constructor(defaultSpaces?: boolean, numSpaces?: Number, bracketPairs?: Object);
142156
}
143157

144158
/**

plugins/README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77

88
---
99

10+
### Auto-Close Brackets
11+
Automatically close pairs of brackets/quotes/other syntaxes in code, but also optionally choose the brackets this
12+
is activated for.
13+
14+
Files: [auto-close-brackets.js](./auto-close-brackets.js)
15+
16+
[🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/qBgGGKR)
17+
1018
### Autocomplete
1119
Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
1220

@@ -36,7 +44,7 @@ Files: [go-to-line.js](./go-to-line.js) / [go-to-line.css](./go-to-line.css)
3644
[🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/YzBMOXP)
3745

3846
### Indent
39-
Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it possible to indent/unindent multiple lines using Tab/Shift+Tab. **Supports tab characters and custom numbers of spaces as indentation.**
47+
Add indentation using the `Tab` key, and auto-indents after a newline, as well as making it possible to indent/unindent multiple lines using Tab/Shift+Tab. **Supports tab characters and custom numbers of spaces as indentation, as well as (optionally) brackets typed affecting indentation.**
4048

4149
Files: [indent.js](./indent.js)
4250

plugins/auto-close-brackets.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Automatically close pairs of brackets/quotes/other syntaxes in code, but also optionally choose the brackets this
3+
* is activated for.
4+
* Files: auto-close-brackets.js
5+
*/
6+
codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin {
7+
bracketPairs = [];
8+
bracketsOpenedStack = []; // Each item [closing bracket string, opening bracket location] Innermost at right so can know which brackets should be ignored when retyped
9+
10+
/**
11+
* Create an auto-close brackets plugin to pass into a template
12+
* @param {Object} bracketPairs Opening brackets mapped to closing brackets, default and example {"(": ")", "[": "]", "{": "}", '"': '"'}. All brackets must only be one character.
13+
*/
14+
constructor(bracketPairs={"(": ")", "[": "]", "{": "}", '"': '"'}) {
15+
super([]); // No observed attributes
16+
17+
this.bracketPairs = bracketPairs;
18+
}
19+
20+
/* Add keystroke events */
21+
afterElementsAdded(codeInput) {
22+
let textarea = codeInput.textareaElement;
23+
textarea.addEventListener('keydown', (event) => { this.checkBackspace(codeInput, event) });
24+
textarea.addEventListener('beforeinput', (event) => { this.checkBrackets(codeInput, event); });
25+
26+
}
27+
28+
/* Event handlers */
29+
checkBrackets(codeInput, event) {
30+
if(this.bracketsOpenedStack.length > 0 && event.data == this.bracketsOpenedStack[this.bracketsOpenedStack.length-1][0] && event.data == codeInput.textareaElement.value[codeInput.textareaElement.selectionStart]) {
31+
// "Retype" bracket, i.e. just move caret
32+
codeInput.textareaElement.selectionStart = codeInput.textareaElement.selectionEnd += 1;
33+
this.bracketsOpenedStack.pop();
34+
event.preventDefault();
35+
} else if(event.data in this.bracketPairs) {
36+
// Create bracket pair
37+
let closingBracket = this.bracketPairs[event.data];
38+
this.bracketsOpenedStack.push([closingBracket, codeInput.textareaElement.selectionStart]);
39+
document.execCommand("insertText", false, closingBracket);
40+
codeInput.textareaElement.selectionStart = codeInput.textareaElement.selectionEnd -= 1;
41+
}
42+
}
43+
44+
checkBackspace(codeInput, event) {
45+
if(event.key == "Backspace" && codeInput.textareaElement.selectionStart == codeInput.textareaElement.selectionEnd) {
46+
if(this.bracketsOpenedStack.length > 0 && this.bracketsOpenedStack[this.bracketsOpenedStack.length-1][1]+1 == codeInput.textareaElement.selectionStart && codeInput.textareaElement.value[codeInput.textareaElement.selectionStart] == this.bracketsOpenedStack[this.bracketsOpenedStack.length-1][0]) {
47+
// Delete closing bracket as well
48+
codeInput.textareaElement.selectionEnd = codeInput.textareaElement.selectionStart + 1;
49+
codeInput.textareaElement.selectionStart -= 1;
50+
this.bracketsOpenedStack.pop();
51+
}
52+
}
53+
}
54+
}

plugins/go-to-line.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,23 @@
33
* Files: go-to-line.js / go-to-line.css
44
*/
55
codeInput.plugins.GoToLine = class extends codeInput.Plugin {
6+
useCtrlG = false;
67

78
/**
89
* Create a go-to-line command plugin to pass into a template
910
* @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)`.
1011
*/
11-
constructor(useCtrlG) {
12+
constructor(useCtrlG = true) {
1213
super([]); // No observed attributes
14+
this.useCtrlG = useCtrlG;
1315
}
1416

1517
/* Add keystroke events */
1618
afterElementsAdded(codeInput) {
1719
const textarea = codeInput.textareaElement;
18-
textarea.addEventListener('keydown', (event) => { this.checkCtrlG(codeInput, event); });
20+
if(this.useCtrlG) {
21+
textarea.addEventListener('keydown', (event) => { this.checkCtrlG(codeInput, event); });
22+
}
1923
}
2024

2125
blockSearch(dialog, event) {

plugins/indent.js

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
/**
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
33
* possible to indent/unindent multiple lines using Tab/Shift+Tab
44
* Files: indent.js
55
*/
66
codeInput.plugins.Indent = class extends codeInput.Plugin {
77

88
numSpaces;
9+
bracketPairs = null; // No bracket-auto-indentation used
910
indentation = "\t";
1011
indentationNumChars = 1;
1112

1213
/**
1314
* Create an indentation plugin to pass into a template
1415
* @param {Boolean} defaultSpaces Should the Tab key enter spaces rather than tabs? Defaults to false.
1516
* @param {Number} numSpaces How many spaces is each tab character worth? Defaults to 4.
17+
* @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.
1618
*/
17-
constructor(defaultSpaces=false, numSpaces=4) {
19+
constructor(defaultSpaces=false, numSpaces=4, bracketPairs={"(": ")", "[": "]", "{": "}"}) {
1820
super([]); // No observed attributes
1921

2022
this.numSpaces = numSpaces;
23+
this.bracketPairs = bracketPairs;
2124
if(defaultSpaces) {
2225
this.indentation = "";
2326
for(let i = 0; i < numSpaces; i++) {
@@ -31,6 +34,7 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
3134
afterElementsAdded(codeInput) {
3235
let textarea = codeInput.textareaElement;
3336
textarea.addEventListener('keydown', (event) => { this.checkTab(codeInput, event); this.checkEnter(codeInput, event); this.checkBackspace(codeInput, event); });
37+
textarea.addEventListener('beforeinput', (event) => { this.checkCloseBracket(codeInput, event); });
3438
}
3539

3640
/* Event handlers */
@@ -135,6 +139,39 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
135139
lines[currentLineI] = lines[currentLineI].substring(0, cursorPosInLine);
136140
}
137141

142+
let bracketThreeLinesTriggered = false;
143+
let furtherIndentation = "";
144+
if(this.bracketPairs != null) {
145+
for(let openingBracket in this.bracketPairs) {
146+
if(lines[currentLineI][lines[currentLineI].length-1] == openingBracket) {
147+
let closingBracket = this.bracketPairs[openingBracket];
148+
if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
149+
// Create new line and then put textAfterCursor on yet another line:
150+
// {
151+
// |CARET|
152+
// }
153+
bracketThreeLinesTriggered = true;
154+
for (let i = 0; i < numberIndents+1; i++) {
155+
furtherIndentation += this.indentation;
156+
}
157+
} else {
158+
// Just create new line:
159+
// {
160+
// |CARET|
161+
numberIndents++;
162+
}
163+
break;
164+
} else {
165+
// Check whether brackets cause unindent
166+
let closingBracket = this.bracketPairs[openingBracket];
167+
if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
168+
numberIndents--;
169+
break;
170+
}
171+
}
172+
}
173+
}
174+
138175
// insert our indents and any text from the previous line that might have been after the line break
139176
for (let i = 0; i < numberIndents; i++) {
140177
newLine += this.indentation;
@@ -143,6 +180,10 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
143180
// save the current cursor position
144181
let selectionStartI = inputElement.selectionStart;
145182

183+
if(bracketThreeLinesTriggered) {
184+
document.execCommand("insertText", false, "\n" + furtherIndentation); // Write indented line
185+
numberIndents += 1; // Reflects the new indent
186+
}
146187
document.execCommand("insertText", false, "\n" + newLine); // Write new line, including auto-indentation
147188

148189
// move cursor to new position
@@ -175,4 +216,22 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
175216
document.execCommand("delete", false, "");
176217
}
177218
}
219+
220+
checkCloseBracket(codeInput, event) {
221+
if(codeInput.textareaElement.selectionStart != codeInput.textareaElement.selectionEnd) {
222+
return;
223+
}
224+
225+
for(let openingBracket in this.bracketPairs) {
226+
let closingBracket = this.bracketPairs[openingBracket];
227+
if(event.data == closingBracket) {
228+
// Closing bracket unindents line
229+
if(codeInput.value.substring(codeInput.textareaElement.selectionStart - this.indentationNumChars, codeInput.textareaElement.selectionStart) == this.indentation) {
230+
// Indentation before cursor = delete it
231+
codeInput.textareaElement.selectionStart -= this.indentationNumChars;
232+
document.execCommand("delete", false, "");
233+
}
234+
}
235+
}
236+
}
178237
}

0 commit comments

Comments
 (0)