Skip to content

Commit dc40446

Browse files
committed
Make go-to-line plugin fit conventions of other plugins
1 parent 35e6398 commit dc40446

File tree

4 files changed

+194
-143
lines changed

4 files changed

+194
-143
lines changed

code-input.d.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,23 @@ export namespace plugins {
110110
constructor(delayMs: number);
111111
}
112112

113+
/**
114+
* Add basic Go-To-Line (ctrl-G by default) functionality to the code editor.
115+
* Files: go-to-line.js / go-to-line.css
116+
*/
117+
class GoToLine extends Plugin {
118+
/**
119+
* Create a go-to-line command plugin to pass into a template
120+
* @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)`.
121+
*/
122+
constructor(useCtrlG: boolean);
123+
/**
124+
* Show a search-like dialog prompting line number.
125+
* @param {codeInput.CodeInput} codeInput the `<code-input>` element.
126+
*/
127+
showPrompt(codeInput: CodeInput): void;
128+
}
129+
113130
/**
114131
* Adds indentation using the `Tab` key, and auto-indents after a newline, as well as making it
115132
* possible to indent/unindent multiple lines using Tab/Shift+Tab
Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
@keyframes roll-in {
1+
@keyframes code-input_go-to_roll-in {
22
0% {opacity: 0; transform: translateY(-34px);}
33
100% {opacity: 1; transform: translateY(0px);}
44
}
55

6-
@keyframes roll-out {
6+
@keyframes code-input_go-to_roll-out {
77
0% {opacity: 1; transform: translateY(0px);}
88
100% {opacity: 0; transform: translateY(-34px);}
99
}
1010

11-
.goto-dialog {
11+
.code-input_go-to_dialog {
1212
position: absolute;
1313
top: 0; right: 14px;
1414
height: 28px;
@@ -18,45 +18,53 @@
1818
background-color: white;
1919
border-radius: 6px;
2020
box-shadow: 0 .2em 1em .2em rgba(0, 0, 0, 0.16);
21-
animation: roll-in .2s;
21+
animation: code-input_go-to_roll-in .2s;
2222
z-index: 10;
2323
}
2424

25-
.goto-dialog.bye {
26-
animation: roll-out .2s;
25+
.code-input_go-to_dialog.bye {
26+
animation: code-input_go-to_roll-out .2s;
2727
}
2828

29-
.goto-dialog input {
29+
.code-input_go-to_dialog input::placeholder {
30+
font-size: 80%;
31+
}
32+
33+
.code-input_go-to_dialog input {
3034
position: relative;
31-
width: 60px; height: 32px; top: -3px;
35+
width: 240px; height: 32px; top: -3px;
3236
font-size: large;
3337
color: #000000aa;
3438
border: 0;
3539
}
3640

37-
.goto-dialog input.error {
41+
.code-input_go-to_dialog input.error {
3842
color: #ff0000aa;
3943
}
4044

41-
.goto-dialog input:focus {
45+
.code-input_go-to_dialog input:focus {
4246
outline: none;
4347
}
4448

45-
.goto-dialog span {
49+
.code-input_go-to_dialog span {
4650
display: inline-block;
47-
width: 24px; line-height: 24px;
48-
font-family: system-ui; font-size: 22px; font-weight: 500;
51+
width: 24px;
52+
line-height: 24px;
53+
font-family: system-ui;
54+
font-size: 22px;
55+
font-weight: 500;
4956
text-align: center;
5057
border-radius: 50%;
5158
color: black;
5259
opacity: 0.6;
60+
vertical-align: top;
5361
}
5462

55-
.goto-dialog span:before {
63+
.code-input_go-to_dialog span:before {
5664
content: "\00d7";
5765
}
5866

59-
.goto-dialog span:hover {
67+
.code-input_go-to_dialog span:hover {
6068
opacity: .8;
6169
background-color: #00000018;
6270
}

plugins/go-to-line.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
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+
7+
/**
8+
* Create a go-to-line command plugin to pass into a template
9+
* @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)`.
10+
*/
11+
constructor(useCtrlG) {
12+
super([]); // No observed attributes
13+
}
14+
15+
/* Add keystroke events */
16+
afterElementsAdded(codeInput) {
17+
const textarea = codeInput.textareaElement;
18+
textarea.addEventListener('keydown', (event) => { this.checkCtrlG(codeInput, event); });
19+
}
20+
21+
blockSearch(dialog, event) {
22+
if (event.ctrlKey && event.key == 'g') {
23+
return event.preventDefault();
24+
}
25+
}
26+
27+
checkPrompt(dialog, event) {
28+
// Line number(:column number)
29+
const lines = dialog.textarea.value.split('\n');
30+
const maxLineNo = lines.length;
31+
const lineNo = Number(dialog.input.value.split(':')[0]);
32+
let columnNo = 0; // Means go to start of indented line
33+
let maxColumnNo = 1;
34+
const querySplitByColons = dialog.input.value.split(':');
35+
if(querySplitByColons.length > 2) return dialog.input.classList.add('error');
36+
37+
if(querySplitByColons.length >= 2) {
38+
columnNo = Number(querySplitByColons[1]);
39+
maxColumnNo = lines[lineNo-1].length;
40+
}
41+
42+
if (event.key == 'Escape') return this.cancelPrompt(dialog, event);
43+
44+
if (dialog.input.value) {
45+
if (!/^[0-9:]*$/.test(dialog.input.value) || lineNo < 1 || columnNo < 0 || lineNo > maxLineNo || columnNo > maxColumnNo) {
46+
return dialog.input.classList.add('error');
47+
} else {
48+
dialog.input.classList.remove('error');
49+
}
50+
}
51+
52+
if (event.key == 'Enter') {
53+
this.goTo(dialog.textarea, lineNo, columnNo);
54+
this.cancelPrompt(dialog, event);
55+
}
56+
}
57+
58+
cancelPrompt(dialog, event) {
59+
let delay;
60+
event.preventDefault();
61+
dialog.textarea.focus();
62+
63+
// Remove dialog after animation
64+
dialog.classList.add('bye');
65+
66+
if (dialog.computedStyleMap) {
67+
delay = 1000 * dialog.computedStyleMap().get('animation').toString().split('s')[0];
68+
} else {
69+
delay = 1000 * document.defaultView.getComputedStyle(dialog, null).getPropertyValue('animation').split('s')[0];
70+
}
71+
72+
setTimeout(() => { dialog.codeInput.removeChild(dialog); }, .9 * delay);
73+
}
74+
75+
/**
76+
* Show a search-like dialog prompting line number.
77+
* @param {codeInput.CodeInput} codeInput the `<code-input>` element.
78+
*/
79+
showPrompt(codeInput) {
80+
const textarea = codeInput.textareaElement;
81+
82+
const dialog = document.createElement('div');
83+
const input = document.createElement('input');
84+
const cancel = document.createElement('span');
85+
86+
dialog.appendChild(input);
87+
dialog.appendChild(cancel);
88+
89+
dialog.className = 'code-input_go-to_dialog';
90+
input.spellcheck = false;
91+
input.placeholder = "Line:Column / Line no. then Enter";
92+
dialog.codeInput = codeInput;
93+
dialog.textarea = textarea;
94+
dialog.input = input;
95+
96+
input.addEventListener('keydown', (event) => { this.blockSearch(dialog, event); });
97+
input.addEventListener('keyup', (event) => { this.checkPrompt(dialog, event); });
98+
cancel.addEventListener('click', (event) => { this.cancelPrompt(dialog, event); });
99+
100+
codeInput.appendChild(dialog);
101+
102+
input.focus();
103+
}
104+
105+
/* Set the cursor on the first non-space char of textarea's nth line; and scroll it into view */
106+
goTo(textarea, lineNo, columnNo = 0) {
107+
let fontSize;
108+
let lineHeight;
109+
let scrollAmount;
110+
let topPadding;
111+
let cursorPos = -1;
112+
let lines = textarea.value.split('\n');
113+
114+
if (lineNo > 0 && lineNo <= lines.length) {
115+
if (textarea.computedStyleMap) {
116+
fontSize = textarea.computedStyleMap().get('font-size').value;
117+
lineHeight = fontSize * textarea.computedStyleMap().get('line-height').value;
118+
} else {
119+
fontSize = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('font-size').split('px')[0];
120+
lineHeight = document.defaultView.getComputedStyle(textarea, null).getPropertyValue('line-height').split('px')[0];
121+
}
122+
123+
// scroll amount and initial top padding (3 lines above, if possible)
124+
scrollAmount = (lineNo > 3 ? lineNo - 3 : 1) * lineHeight;
125+
topPadding = (lineHeight - fontSize) / 2;
126+
127+
if (lineNo > 1) {
128+
// cursor positon just after n - 1 full lines
129+
cursorPos = lines.slice(0, lineNo - 1).join('\n').length;
130+
}
131+
132+
// scan first non-space char in nth line
133+
if (columnNo == 0) {
134+
do cursorPos++; while (textarea.value[cursorPos] != '\n' && /\s/.test(textarea.value[cursorPos]));
135+
} else {
136+
cursorPos += 1 + columnNo - 1;
137+
}
138+
139+
textarea.scrollTop = scrollAmount - topPadding;
140+
textarea.setSelectionRange(cursorPos, cursorPos);
141+
textarea.click();
142+
}
143+
}
144+
145+
/* Event handlers */
146+
checkCtrlG(codeInput, event) {
147+
const textarea = codeInput.textareaElement;
148+
if (event.ctrlKey && event.key == 'g') {
149+
event.preventDefault();
150+
151+
this.showPrompt(codeInput);
152+
}
153+
}
154+
}

plugins/goto.js

Lines changed: 0 additions & 128 deletions
This file was deleted.

0 commit comments

Comments
 (0)