Reputation: 183
I am trying to make a code editor using HTML:
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta http-equiv='X-UA-Compatible' content='IE=Edge'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='stylesheet' href='style.css'>
</head>
<body>
<pre id='editor'><code contenteditable='true'></code></pre>
<script type='module'>
import { highlight } from './highlighter.js';
import { Caret } from './caret.js';
(() =>
{
const editor = document.querySelector('#editor code');
const caret = new Caret(editor);
highlight(editor);
editor.addEventListener('input', e =>
{
highlight(editor);
e.preventDefault();
});
editor.addEventListener('keydown', e =>
{
const TAB = 9;
const ENTER = 13;
switch (e.keyCode)
{
// ...
}
});
})();
</script>
</body>
</html>
highlighter.js:
import { Caret } from './caret.js';
export function highlight(editor)
{
// ...
const NORM = '#E6E6FA';
// ...
const Highlighter = {
source: editor.innerText,
start: 0,
curr: 0,
// ...
fin()
{
return this.curr >= this.source.length;
},
advance()
{
return this.source[this.curr++];
},
// ...
scan()
{
let result = '';
this.start = this.curr;
if (this.fin())
{
return null;
}
const char = this.advance();
let color = NORM;
switch (char)
{
// ...
}
return {
color,
text: this.source.substring(this.start, this.curr),
};
},
};
let result = '';
const caret = new Caret(editor);
const save = caret.getPos();
for (;;)
{
const lexeme = Highlighter.scan();
if (lexeme === null)
{
break;
}
const chars = lexeme.text.split('').map(
x => `<span style='color: ${lexeme.color};'>${x}</span>`);
result += chars.join('');
}
editor.innerHTML = result;
caret.setPos(save);
};
Basically, it takes the text content of the user's code, scans it to generate lexemes with color data, splits those lexemes into characters that are put into <span> tags with the colors, then appends those <span>s to a string that the innerHTML of the editor is updated to, and finally the user's cursor is put back to its proper position; this is done on input. There's one problem, though: if the user types too fast, their inputed text can be doubled. I've tried to remedy this with other types of event listeners, and tried simply using setInterval, but it hasn't worked well at all.
Upvotes: 1
Views: 51
Reputation: 2711
From what you've described
if the user types too fast
and seeing that the computationally-expensive highlight()
function is called on every change to the input
element
editor.addEventListener('input', e => {
highlight(editor); // <--
e.preventDefault();
});
I'd suggest debouncing that call to highlight. Here's a good explainer of debouncing.
// Vanilla debounce: https://gist.github.com/peduarte/7ee475dd0fae1940f857582ecbb9dc5f
function debounce(func, wait = 100) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
// ...
// adjust delay to find a balance between responsiveness and performance
const delay = 500;
const runHighlight = () => highlight(editor);
const debouncedHighlight = debounce(runHighlight, delay);
editor.addEventListener('input', e => {
e.preventDefault();
debouncedHighlight();
});
Upvotes: 1