Skip to content

Commit

Permalink
Merge pull request #66 from andrelsm/andrelsm-goto-plugin
Browse files Browse the repository at this point in the history
GoTo plugin
  • Loading branch information
WebCoder49 committed Dec 12, 2023
2 parents 2f38815 + 4c7c239 commit 6b0b119
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 1 deletion.
17 changes: 17 additions & 0 deletions code-input.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,23 @@ export namespace plugins {
constructor(delayMs: number);
}

/**
* Add basic Go-To-Line (ctrl-G by default) functionality to the code editor.
* Files: go-to-line.js / go-to-line.css
*/
class GoToLine extends Plugin {
/**
* 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: boolean);
/**
* Show a search-like dialog prompting line number.
* @param {codeInput.CodeInput} codeInput the `<code-input>` element.
*/
showPrompt(codeInput: CodeInput): void;
}

/**
* 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
Expand Down
9 changes: 8 additions & 1 deletion plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ Files: [debounce-update.js](./debounce-update.js)

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

### Go To Line
Add a feature to go to a specific line when a line number is given (or column as well, in the format line no:column no) that appears when (optionally) Ctrl+G is pressed or when JavaScript triggers it.

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

Expand All @@ -36,7 +43,7 @@ Files: [indent.js](./indent.js)
[🚀 *CodePen Demo*](https://codepen.io/WebCoder49/pen/WNgdzar)

### Prism Line Numbers
Allows code-input elements to be used with the Prism.js line-numbers plugin, as long as the code-input element or a parent element of it has the CSS class `line-numbers`. [Prism.js Plugin Docs](https://prismjs.com/plugins/line-numbers/)
Allow code-input elements to be used with the Prism.js line-numbers plugin, as long as the code-input element or a parent element of it has the CSS class `line-numbers`. [Prism.js Plugin Docs](https://prismjs.com/plugins/line-numbers/)

Files: [prism-line-numbers.css](./prism-line-numbers.css) (NO JS FILE)

Expand Down
70 changes: 70 additions & 0 deletions plugins/go-to-line.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
@keyframes code-input_go-to_roll-in {
0% {opacity: 0; transform: translateY(-34px);}
100% {opacity: 1; transform: translateY(0px);}
}

@keyframes code-input_go-to_roll-out {
0% {opacity: 1; transform: translateY(0px);}
100% {opacity: 0; transform: translateY(-34px);}
}

.code-input_go-to_dialog {
position: absolute;
top: 0; right: 14px;
height: 28px;
padding: 6px;
padding-top: 8px;
border: solid 1px #00000044;
background-color: white;
border-radius: 6px;
box-shadow: 0 .2em 1em .2em rgba(0, 0, 0, 0.16);
animation: code-input_go-to_roll-in .2s;
z-index: 10;
}

.code-input_go-to_dialog.bye {
animation: code-input_go-to_roll-out .2s;
}

.code-input_go-to_dialog input::placeholder {
font-size: 80%;
}

.code-input_go-to_dialog input {
position: relative;
width: 240px; height: 32px; top: -3px;
font-size: large;
color: #000000aa;
border: 0;
}

.code-input_go-to_dialog input.error {
color: #ff0000aa;
}

.code-input_go-to_dialog input:focus {
outline: none;
}

.code-input_go-to_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: black;
opacity: 0.6;
vertical-align: top;
}

.code-input_go-to_dialog span:before {
content: "\00d7";
}

.code-input_go-to_dialog span:hover {
opacity: .8;
background-color: #00000018;
}
154 changes: 154 additions & 0 deletions plugins/go-to-line.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* Add basic Go-To-Line (Ctrl+G by default) functionality to the code editor.
* Files: go-to-line.js / go-to-line.css
*/
codeInput.plugins.GoToLine = class extends codeInput.Plugin {

/**
* 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) {
super([]); // No observed attributes
}

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

blockSearch(dialog, event) {
if (event.ctrlKey && event.key == 'g') {
return event.preventDefault();
}
}

checkPrompt(dialog, event) {
// Line number(:column number)
const lines = dialog.textarea.value.split('\n');
const maxLineNo = lines.length;
const lineNo = Number(dialog.input.value.split(':')[0]);
let columnNo = 0; // Means go to start of indented line
let maxColumnNo = 1;
const querySplitByColons = dialog.input.value.split(':');
if(querySplitByColons.length > 2) return dialog.input.classList.add('error');

if(querySplitByColons.length >= 2) {
columnNo = Number(querySplitByColons[1]);
maxColumnNo = lines[lineNo-1].length;
}

if (event.key == 'Escape') return this.cancelPrompt(dialog, event);

if (dialog.input.value) {
if (!/^[0-9:]*$/.test(dialog.input.value) || lineNo < 1 || columnNo < 0 || lineNo > maxLineNo || columnNo > maxColumnNo) {
return dialog.input.classList.add('error');
} else {
dialog.input.classList.remove('error');
}
}

if (event.key == 'Enter') {
this.goTo(dialog.textarea, lineNo, columnNo);
this.cancelPrompt(dialog, event);
}
}

cancelPrompt(dialog, event) {
let delay;
event.preventDefault();
dialog.textarea.focus();

// Remove dialog after animation
dialog.classList.add('bye');

if (dialog.computedStyleMap) {
delay = 1000 * dialog.computedStyleMap().get('animation').toString().split('s')[0];
} else {
delay = 1000 * document.defaultView.getComputedStyle(dialog, null).getPropertyValue('animation').split('s')[0];
}

setTimeout(() => { dialog.codeInput.removeChild(dialog); }, .9 * delay);
}

/**
* Show a search-like dialog prompting line number.
* @param {codeInput.CodeInput} codeInput the `<code-input>` element.
*/
showPrompt(codeInput) {
const textarea = codeInput.textareaElement;

const dialog = document.createElement('div');
const input = document.createElement('input');
const cancel = document.createElement('span');

dialog.appendChild(input);
dialog.appendChild(cancel);

dialog.className = 'code-input_go-to_dialog';
input.spellcheck = false;
input.placeholder = "Line:Column / Line no. then Enter";
dialog.codeInput = codeInput;
dialog.textarea = textarea;
dialog.input = input;

input.addEventListener('keydown', (event) => { this.blockSearch(dialog, event); });
input.addEventListener('keyup', (event) => { this.checkPrompt(dialog, event); });
cancel.addEventListener('click', (event) => { this.cancelPrompt(dialog, event); });

codeInput.appendChild(dialog);

input.focus();
}

/* Set the cursor on the first non-space char of textarea's nth line; and scroll it into view */
goTo(textarea, lineNo, columnNo = 0) {
let fontSize;
let lineHeight;
let scrollAmount;
let topPadding;
let cursorPos = -1;
let lines = textarea.value.split('\n');

if (lineNo > 0 && lineNo <= lines.length) {
if (textarea.computedStyleMap) {
fontSize = textarea.computedStyleMap().get('font-size').value;
lineHeight = fontSize * textarea.computedStyleMap().get('line-height').value;
} else {
fontSize = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('font-size').split('px')[0];
lineHeight = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('line-height').split('px')[0];
}

// scroll amount and initial top padding (3 lines above, if possible)
scrollAmount = (lineNo > 3 ? lineNo - 3 : 1) * lineHeight;
topPadding = (lineHeight - fontSize) / 2;

if (lineNo > 1) {
// cursor positon just after n - 1 full lines
cursorPos = lines.slice(0, lineNo - 1).join('\n').length;
}

// scan first non-space char in nth line
if (columnNo == 0) {
do cursorPos++; while (textarea.value[cursorPos] != '\n' && /\s/.test(textarea.value[cursorPos]));
} else {
cursorPos += 1 + columnNo - 1;
}

textarea.scrollTop = scrollAmount - topPadding;
textarea.setSelectionRange(cursorPos, cursorPos);
textarea.click();
}
}

/* Event handlers */
checkCtrlG(codeInput, event) {
const textarea = codeInput.textareaElement;
if (event.ctrlKey && event.key == 'g') {
event.preventDefault();

this.showPrompt(codeInput);
}
}
}

0 comments on commit 6b0b119

Please sign in to comment.