I have a contenteditable div. I am trying to implement "on-the-fly syntax highlighting". Here's what I have so far:
const codeElement = document.getElementById('code');
// func to apply highlighting
function highlight() {
const controlRegex = /\b(function|const|let)\b/g;
const stringRegex = /["'].*["']/g;
const functionCallRegex = /[a-zA-Z_\d]*\([^)]*\)/g;
const constantRegex = /.*[a-zA-Z_\d]+\./g;
codeElement.innerHTML = codeElement.textContent.replace(stringRegex,
'<span style="color: #0d0; font-weight: normal;">$&</span>')
.replace(controlRegex, '<span style="color: pink;">$&</span>')
.replace(functionCallRegex, '<span style="color: #0bb; font-weight: bold;">$&</span>')
.replace(constantRegex, '<span style="color: yellow;">$&</span>');
}
codeElement.addEventListener('input', highlight);
highlight();
It works well, except for one problem. Whenever I press a key, it moves the cursor to the start of the div again.
I tried using the combination of window.getSelection
and document.createRange
to move the cursor to the end of the div whenever a key is pressed, but that raises issues when editing something that's not the last character.
I fixed it by keeping a div
that had the highlighting function, and overlayed on top of it a textarea
with color: transparent
and caret-color: white
used for input.
const inputElement = document.getElementById("input");
const codeElement = document.getElementById("code");
function onInput(evt) {
const cInput = evt.target.value;
// func to apply highlighting
const controlRegex = /\b(function|const|let)\b/g;
const stringRegex = /["'].*["']/g;
const functionCallRegex = /[a-zA-Z_\d]*\([^)]*\)/g;
const constantRegex = /.*[a-zA-Z_\d]+\./g;
codeElement.innerHTML = cInput.replace(stringRegex,
'<span style="color: #0d0; font-weight: normal;">$&</span>')
.replace(controlRegex, '<span style="color: pink;">$&</span>')
.replace(functionCallRegex, '<span style="color: #0bb; font-weight: bold;">$&</span>')
.replace(constantRegex, '<span style="color: yellow;">$&</span>');
}
inputElement.addEventListener('input', onInput);
inputElement.focus();
pre#code,
#input {
overflow-wrap: break-word;
text-wrap: wrap;
font-size: 1rem;
font-family: monospace;
font-weight: lighter;
position: fixed;
top: -50px;
left: -10px;
right: -10px;
bottom: -50px;
;
color: white;
background-color: rgb(20, 20, 20);
padding: 60px 20px;
}
#input {
color: transparent;
caret-color: white;
background: transparent;
}
pre#code {
padding-top: calc(60px - 1em);
}
<pre id="code"></pre>
<textarea id="input" spellcheck="false"></textarea>