var editor = document.querySelector('#code__space');
const keywords = ['if', 'else', 'for'];
let timeoutId;
editor.addEventListener('input', function() {
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
var text = editor.textContent;
for (let keyword of keywords) {
const pattern = new RegExp('\\b' + keyword + '\\b', 'g');
const replacement = '<span class="highlight">' + keyword + '</span>';
text = text.replace(pattern, replacement);
}
const sel = window.getSelection();
const node = sel.focusNode;
const offset = sel.focusOffset;
const pos = getCursorPosition(editor, node, offset, { pos: 0, done: false });
if (offset === 0) pos.pos += 0.5;
editor.innerHTML = text;
// restore the position
sel.removeAllRanges();
const range = setCursorPosition(editor, document.createRange(), {
pos: pos.pos,
done: false,
});
range.collapse(true);
sel.addRange(range);
}, 1000);
});
// get the cursor position from .editor start
function getCursorPosition(parent, node, offset, stat) {
if (stat.done) return stat;
let currentNode = null;
if (parent.childNodes.length == 0) {
stat.pos += parent.textContent.length;
} else {
for (let i = 0; i < parent.childNodes.length && !stat.done; i++) {
currentNode = parent.childNodes[i];
if (currentNode === node) {
stat.pos += offset;
stat.done = true;
return stat;
} else getCursorPosition(currentNode, node, offset, stat);
}
}
return stat;
}
//find the child node and relative position and set it on range
function setCursorPosition(parent, range, stat) {
if (stat.done) return range;
if (parent.childNodes.length == 0) {
if (parent.textContent.length >= stat.pos) {
range.setStart(parent, stat.pos);
stat.done = true;
} else {
stat.pos = stat.pos - parent.textContent.length;
}
} else {
for (let i = 0; i < parent.childNodes.length && !stat.done; i++) {
currentNode = parent.childNodes[i];
setCursorPosition(currentNode, range, stat);
}
}
return range;
}
.highlight {
color: #bf0808;
background: #b6b6b9;
font-weight: bold;
}
#code__space {
border: 1px solid #ccc;
min-height: 100px;
padding: 10px;
direction: ltr;
}
<div name="code-space" id="code__space" contenteditable="true" style="border: 1px solid blue; min-height: 2em"></div>