Skip to content

Commit

Permalink
Merge pull request #73 from WebCoder49/brackets-support
Browse files Browse the repository at this point in the history
Add Auto-Close Brackets plugin and brackets functionality for indenta…
  • Loading branch information
WebCoder49 committed Dec 15, 2023
2 parents 200ec12 + a2c4c3d commit 7875e46
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 6 deletions.
16 changes: 15 additions & 1 deletion code-input.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,19 @@ export namespace plugins {
constructor();
}

/**
* Automatically closes pairs of brackets/quotes/other syntaxes in code, but also lets you choose the brackets this
* is activated for.
* Files: auto-close-brackets.js
*/
class AutoCloseBrackets extends Plugin {
/**
* Create an auto-close brackets plugin to pass into a template
* @param {Object} bracketPairs Opening brackets mapped to closing brackets, default and example {"(": ")", "[": "]", "{": "}", '"': '"'}. All brackets must only be one character.
*/
constructor(bracketPairs: Object);
}

/**
* Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.
* Files: autocomplete.js / autocomplete.css
Expand Down Expand Up @@ -137,8 +150,9 @@ export namespace plugins {
* Create an indentation plugin to pass into a template
* @param {Boolean} defaultSpaces Should the Tab key enter spaces rather than tabs? Defaults to false.
* @param {Number} numSpaces How many spaces is each tab character worth? Defaults to 4.
* @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.
*/
constructor(defaultSpaces?: boolean, numSpaces?: Number);
constructor(defaultSpaces?: boolean, numSpaces?: Number, bracketPairs?: Object);
}

/**
Expand Down
10 changes: 9 additions & 1 deletion plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@

---

### Auto-Close Brackets
Automatically close pairs of brackets/quotes/other syntaxes in code, but also optionally choose the brackets this
is activated for.

Files: [auto-close-brackets.js](./auto-close-brackets.js)

[🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/qBgGGKR)

### Autocomplete
Display a popup under the caret using the text in the code-input element. This works well with autocomplete suggestions.

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

### Indent
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.**
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.**

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

Expand Down
54 changes: 54 additions & 0 deletions plugins/auto-close-brackets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Automatically close pairs of brackets/quotes/other syntaxes in code, but also optionally choose the brackets this
* is activated for.
* Files: auto-close-brackets.js
*/
codeInput.plugins.AutoCloseBrackets = class extends codeInput.Plugin {
bracketPairs = [];
bracketsOpenedStack = []; // Each item [closing bracket string, opening bracket location] Innermost at right so can know which brackets should be ignored when retyped

/**
* Create an auto-close brackets plugin to pass into a template
* @param {Object} bracketPairs Opening brackets mapped to closing brackets, default and example {"(": ")", "[": "]", "{": "}", '"': '"'}. All brackets must only be one character.
*/
constructor(bracketPairs={"(": ")", "[": "]", "{": "}", '"': '"'}) {
super([]); // No observed attributes

this.bracketPairs = bracketPairs;
}

/* Add keystroke events */
afterElementsAdded(codeInput) {
let textarea = codeInput.textareaElement;
textarea.addEventListener('keydown', (event) => { this.checkBackspace(codeInput, event) });
textarea.addEventListener('beforeinput', (event) => { this.checkBrackets(codeInput, event); });

}

/* Event handlers */
checkBrackets(codeInput, event) {
if(this.bracketsOpenedStack.length > 0 && event.data == this.bracketsOpenedStack[this.bracketsOpenedStack.length-1][0] && event.data == codeInput.textareaElement.value[codeInput.textareaElement.selectionStart]) {
// "Retype" bracket, i.e. just move caret
codeInput.textareaElement.selectionStart = codeInput.textareaElement.selectionEnd += 1;
this.bracketsOpenedStack.pop();
event.preventDefault();
} else if(event.data in this.bracketPairs) {
// Create bracket pair
let closingBracket = this.bracketPairs[event.data];
this.bracketsOpenedStack.push([closingBracket, codeInput.textareaElement.selectionStart]);
document.execCommand("insertText", false, closingBracket);
codeInput.textareaElement.selectionStart = codeInput.textareaElement.selectionEnd -= 1;
}
}

checkBackspace(codeInput, event) {
if(event.key == "Backspace" && codeInput.textareaElement.selectionStart == codeInput.textareaElement.selectionEnd) {
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]) {
// Delete closing bracket as well
codeInput.textareaElement.selectionEnd = codeInput.textareaElement.selectionStart + 1;
codeInput.textareaElement.selectionStart -= 1;
this.bracketsOpenedStack.pop();
}
}
}
}
8 changes: 6 additions & 2 deletions plugins/go-to-line.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
* Files: go-to-line.js / go-to-line.css
*/
codeInput.plugins.GoToLine = class extends codeInput.Plugin {
useCtrlG = false;

/**
* Create a go-to-line command plugin to pass into a template
* @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)`.
*/
constructor(useCtrlG) {
constructor(useCtrlG = true) {
super([]); // No observed attributes
this.useCtrlG = useCtrlG;
}

/* Add keystroke events */
afterElementsAdded(codeInput) {
const textarea = codeInput.textareaElement;
textarea.addEventListener('keydown', (event) => { this.checkCtrlG(codeInput, event); });
if(this.useCtrlG) {
textarea.addEventListener('keydown', (event) => { this.checkCtrlG(codeInput, event); });
}
}

blockSearch(dialog, event) {
Expand Down
63 changes: 61 additions & 2 deletions plugins/indent.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
/**
* Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it
* 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
* Files: indent.js
*/
codeInput.plugins.Indent = class extends codeInput.Plugin {

numSpaces;
bracketPairs = null; // No bracket-auto-indentation used
indentation = "\t";
indentationNumChars = 1;

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

this.numSpaces = numSpaces;
this.bracketPairs = bracketPairs;
if(defaultSpaces) {
this.indentation = "";
for(let i = 0; i < numSpaces; i++) {
Expand All @@ -31,6 +34,7 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
afterElementsAdded(codeInput) {
let textarea = codeInput.textareaElement;
textarea.addEventListener('keydown', (event) => { this.checkTab(codeInput, event); this.checkEnter(codeInput, event); this.checkBackspace(codeInput, event); });
textarea.addEventListener('beforeinput', (event) => { this.checkCloseBracket(codeInput, event); });
}

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

let bracketThreeLinesTriggered = false;
let furtherIndentation = "";
if(this.bracketPairs != null) {
for(let openingBracket in this.bracketPairs) {
if(lines[currentLineI][lines[currentLineI].length-1] == openingBracket) {
let closingBracket = this.bracketPairs[openingBracket];
if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
// Create new line and then put textAfterCursor on yet another line:
// {
// |CARET|
// }
bracketThreeLinesTriggered = true;
for (let i = 0; i < numberIndents+1; i++) {
furtherIndentation += this.indentation;
}
} else {
// Just create new line:
// {
// |CARET|
numberIndents++;
}
break;
} else {
// Check whether brackets cause unindent
let closingBracket = this.bracketPairs[openingBracket];
if(textAfterCursor.length > 0 && textAfterCursor[0] == closingBracket) {
numberIndents--;
break;
}
}
}
}

// insert our indents and any text from the previous line that might have been after the line break
for (let i = 0; i < numberIndents; i++) {
newLine += this.indentation;
Expand All @@ -143,6 +180,10 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
// save the current cursor position
let selectionStartI = inputElement.selectionStart;

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

// move cursor to new position
Expand Down Expand Up @@ -175,4 +216,22 @@ codeInput.plugins.Indent = class extends codeInput.Plugin {
document.execCommand("delete", false, "");
}
}

checkCloseBracket(codeInput, event) {
if(codeInput.textareaElement.selectionStart != codeInput.textareaElement.selectionEnd) {
return;
}

for(let openingBracket in this.bracketPairs) {
let closingBracket = this.bracketPairs[openingBracket];
if(event.data == closingBracket) {
// Closing bracket unindents line
if(codeInput.value.substring(codeInput.textareaElement.selectionStart - this.indentationNumChars, codeInput.textareaElement.selectionStart) == this.indentation) {
// Indentation before cursor = delete it
codeInput.textareaElement.selectionStart -= this.indentationNumChars;
document.execCommand("delete", false, "");
}
}
}
}
}

0 comments on commit 7875e46

Please sign in to comment.