Syntax coloring with css' mix-blend-mode and tree-sitter

HTML

<!--
This is a proof-of-concept of syntax highlighting of a <textarea> with
css' mix-blend-mode. A bunch of colored blocks are created
on top of the textbox, and are "blended" into the textarea with `mix-blend-mode`.
The code parsing is done with tree sitter.
-->
<textarea id="editor" spellcheck="false">
// This is an actual textarea
// with js syntax highlighting
function foobar(x) {
  return x + 4; // A comment
}
const text = "Some text"
console.log(foobar(2));
4 = b; // Error
</textarea>
<div id="blocks"></div>
<br>
<label><input id="debug" type="checkbox"></input>debug</label>

CSS

body {
  margin: 0;
  background: grey;
}

#blocks,
textarea {
  font-family: monospace;
  font-size: 24px;
}

#blocks {
  position: absolute;
  top: 20px;
  left: 20px;
  pointer-events: none;
}

textarea {
  margin: 20px;
  white-space: pre;
  height: 400px;
  width: 400px;
  resize: none;
  padding: 0;
  border-width: 0;
}

#blocks > div {
  position: absolute;
  mix-blend-mode: lighten;
  top: calc(var(--y) * 1lh);
  height: 1lh;
  left: calc(var(--x) * 1ch);
  width: calc(var(--width) * 1ch);
  background-color: var(--hi-color);
}

#blocks.debug > div {
  mix-blend-mode: unset;
  opacity: 0.3;
}

#blocks.debug > div {
  mix-blend-mode: unset;
  opacity: 0.5;
}

#blocks > .comment {
  --hi-color: #909090;
}
#blocks > .ERROR {
  --hi-color: #ff0000;
}
#blocks > .bare_key {
  --hi-color: #00f;
}
#blocks > .string {
  --hi-color: #ffbb00;
}
#blocks > .number {
  --hi-color: rgb(23, 162, 39);
}

#blocks > .function,
#blocks > .return,
#blocks > .const {
  --hi-color: rgb(23, 179, 184);
}

JavaScript

import {
  Parser,
  Language,
} from "https://cdn.jsdelivr.net/npm/[email protected]/tree-sitter.js";

await Parser.init();
const parser = new Parser();

const JS = await Language.load(
  "https://cdn.jsdelivr.net/npm/[email protected]/tree-sitter-javascript.wasm",
);

parser.setLanguage(JS);

let blocks = document.querySelector("#blocks");
let textarea = document.querySelector("textarea");

function CreateBlock(cursor, lengthes) {
  for (let y = cursor.startPosition.row; y <= cursor.endPosition.row; y++) {
    let x;
    if (y == cursor.startPosition.row) {
      x = cursor.startPosition.column;
    } else {
      x = 0;
    }
    let width;
    if (y == cursor.endPosition.row) {
      width = cursor.endPosition.column - x;
    } else {
      width = lengthes[y] - x;
    }
    let div = document.createElement("div");
    div.style = `--x:${x};--y:${y};--width:${width}`;
    div.className = cursor.nodeType;
    blocks.appendChild(div);
  }
}

function Parse(text) {
  let lengthes = text.split("\n").map((line) => line.length);
  let cols = Math.max(...lengthes);
  let rows = lengthes.length;
  textarea.style.minWidth = `${cols + 1}ch`; // room for cursor at end of line
  textarea.style.minHeight = `${rows}lh`;
  textarea.style.width = `${cols}ch`;
  textarea.style.height = `${rows}lh`;
  blocks.style.width = `${cols}ch`;
  blocks.style.height = `${rows}lh`;

  const tree = parser.parse(text);
  if (!tree) {
    return;
  }

  blocks.innerHTML = "";

  const cursor = tree.walk();

  function visit() {
    const supported = new Set(["ERROR", "string", "comment", "integer", "number", "function", "return", "const"]);
    if (supported.has(cursor.nodeType)) {
      CreateBlock(cursor, lengthes);
    } else {
      if (cursor.gotoFirstChild()) {
        visit();
        cursor.gotoParent();
      }
    }
    while (cursor.gotoNextSibling()) {
      visit();
    }
  }

 ...