86 | ${!this.hasAttribute('linenumbers') ? html`` : html`
87 |
88 |
1
89 | ${(this.code.match(/\r?\n/g) || []).map((_, i) => html`
90 |
${i + 2}
91 | `)}
92 |
93 | `}
94 |
95 |
100 |
101 |
${
102 | IS_PRISM ? Prism.tokenize(this.code, this.grammar).map(htmlize) : html`${this.code}`
103 | }
104 |
105 | `;
106 | }
107 |
108 | setCode(code) {
109 | this.code = code;
110 | this.updateTextarea();
111 | }
112 |
113 | getCode() {
114 | return this.code;
115 | }
116 |
117 | createRenderRoot() {
118 | return this.shadowDom ? super.createRenderRoot() : this;
119 | }
120 |
121 | setCursor(pos) {
122 | this.elTextarea.setSelectionRange(pos, pos);
123 | }
124 |
125 | setSelect(from, to) {
126 | this.elTextarea.setSelectionRange(from, to);
127 | }
128 |
129 | getCurrentLineIndent() {
130 | const selStart = this.elTextarea.selectionStart;
131 | const selEnd = this.elTextarea.selectionEnd;
132 |
133 | const indentStart = this.code.lastIndexOf('\n', selStart - 1) + 1;
134 | const spaces = (() => {
135 | let pos = indentStart;
136 | while (this.code[pos] === ' ' && pos < selEnd) pos++;
137 | return pos - indentStart;
138 | })();
139 | return ' '.repeat(spaces);
140 | }
141 |
142 | updateTextarea() {
143 | if (!this.elTextarea) return;
144 | this.elTextarea.value = this.code;
145 |
146 | this.dispatchEvent(new CustomEvent('update', { detail: this.code }));
147 | }
148 |
149 | insertCode(pos, text, placeCursor = true) {
150 | this.code =
151 | this.code.substring(0, pos) + text + this.code.substring(pos)
152 | this.updateTextarea();
153 | if (placeCursor) this.setCursor(pos + text.length);
154 | }
155 |
156 | replaceCode(posFrom, posTo, text = '', placeCursor = true) {
157 | this.code =
158 | this.code.substring(0, posFrom) + text + this.code.substring(posTo);
159 | this.updateTextarea();
160 | if (placeCursor) this.setCursor(posFrom + text.length);
161 | }
162 |
163 | handleKeys(e) {
164 | switch (e.code) {
165 | case 'Tab': this.handleTabs(e); break;
166 | case 'Enter': this.handleNewLine(e); break;
167 | case 'Backspace': this.handleBackspace(e); break;
168 | default:
169 | if (this.opening.includes(e.key))
170 | this.handleAutoClose(e);
171 | else if (this.closing.includes(e.key))
172 | this.handleAutoSkip(e);
173 | }
174 | }
175 |
176 | handleInput({ target }) {
177 | this.code = target.value;
178 | this.dispatchEvent(new CustomEvent('update', { detail: this.code }));
179 | }
180 |
181 | handleTabs(e) {
182 | e.preventDefault();
183 | const selStart = this.elTextarea.selectionStart;
184 | const selEnd = this.elTextarea.selectionEnd;
185 |
186 | if (selStart !== selEnd) { //multiline indent
187 | const selLineStart = Math.max(0, this.code.lastIndexOf('\n', selStart - 1));
188 | const selLineEnd = Math.max(this.code.indexOf('\n', selEnd), selEnd);
189 |
190 | let linesInChunk = 0;
191 | let codeChunk = this.code.substring(selLineStart, selLineEnd);
192 | let lenShift = this.indent.length;
193 | if (selLineStart === 0) codeChunk = '\n' + codeChunk;
194 |
195 | if (e.shiftKey) { //Unindent
196 | lenShift = -lenShift;
197 | linesInChunk = (codeChunk.match(new RegExp('\n' + this.indent, 'g')) || []).length;
198 | codeChunk = codeChunk.replaceAll('\n' + this.indent, '\n');
199 | }
200 | else { //Indent
201 | linesInChunk = (codeChunk.match(/\n/g) || []).length;
202 | codeChunk = codeChunk.replaceAll('\n', '\n' + this.indent);
203 | }
204 |
205 | if (selLineStart === 0) codeChunk = codeChunk.replace(/^\n/, '');
206 | this.replaceCode(selLineStart, selLineEnd, codeChunk, false);
207 |
208 | const newStart = Math.max(selLineStart + 1, selStart + lenShift);
209 | const newEnd = selEnd + linesInChunk * lenShift;
210 | this.setSelect(newStart, newEnd);
211 | }
212 | else {
213 | this.insertCode(selStart, this.indent, true);
214 | }
215 | }
216 |
217 | handleBackspace(e) {
218 | const selStart = this.elTextarea.selectionStart;
219 | const selEnd = this.elTextarea.selectionEnd;
220 | if (e.ctrlKey || selStart !== selEnd) return;
221 |
222 | e.preventDefault();
223 |
224 | //remove brackets pairs
225 | const prevSymbol = this.code[selStart - 1];
226 | const curSymbol = this.code[selStart];
227 | const isInPairs = this.opening.includes(prevSymbol) && this.closing.includes(curSymbol);
228 | const isPair = this.closing[this.opening.indexOf(prevSymbol)] === curSymbol;
229 |
230 | if (isInPairs && isPair) {
231 | this.replaceCode(selStart - 1, selStart + 1);
232 | }
233 | else { //remove indent
234 | const chunkStart = selStart - this.indent.length;
235 | const chunkEnd = selStart;
236 | const chunk = this.code.substring(chunkStart, chunkEnd);
237 |
238 | if (chunk === this.indent) this.replaceCode(chunkStart, chunkEnd);
239 | else this.replaceCode(selStart - 1, selStart);
240 | }
241 | }
242 |
243 | handleAutoClose(e) {
244 | const selStart = this.elTextarea.selectionStart;
245 | const selEnd = this.elTextarea.selectionEnd;
246 | if (this.code[selStart] === '\'' || this.code[selStart] === '"') {
247 | return this.handleAutoSkip(e);
248 | }
249 | e.preventDefault();
250 |
251 | if (selStart === selEnd) {
252 | const opening = e.key;
253 | const closing = this.closing[this.opening.indexOf(opening)];
254 |
255 | if (opening === '{'
256 | && (this.code[selStart] === '\n' || this.code.length === selStart)
257 | ) {
258 | const lineShift = '\n' + this.getCurrentLineIndent();
259 | this.insertCode(selStart, opening + lineShift + this.indent + lineShift + closing);
260 | this.setCursor(selStart + lineShift.length + this.indent.length + 1);
261 | }
262 | else {
263 | this.insertCode(selStart, opening + closing);
264 | this.setCursor(selStart + 1);
265 | }
266 | }
267 | }
268 |
269 | handleAutoSkip(e) {
270 | const selStart = this.elTextarea.selectionStart;
271 |
272 | if (this.code[selStart] === e.key) {
273 | e.preventDefault();
274 | this.setCursor(selStart + 1);
275 | }
276 | }
277 |
278 | handleNewLine(e) {
279 | e.preventDefault();
280 | this.insertCode(this.elTextarea.selectionStart, '\n' + this.getCurrentLineIndent());
281 | if (this.elTextarea.selectionStart === this.code.length) {
282 | this.elContainer.scrollTop = this.elContainer.scrollHeight;
283 | }
284 | }
285 | }
286 |
287 | customElements.define('lit-code', LitCode);
288 |
--------------------------------------------------------------------------------