forks/sorcery

git clone https://git.gae.moe/forks/sorcery

Charlotte Somuse a web worker to highlight codeblocks85f1b9e

grace-patches
2.2 KiB61 linesraw
1import type { HighlightReply, HighlightRequest } from "./highlight-worker.ts";
2import { detectLanguage } from "./languages.ts";
3
4let worker: Worker | null = null;
5let nextId = 0;
6const pending = new Map<number, PromiseWithResolvers<string | null>>();
7
8function spawnWorker(): Worker {
9  const spawned = new Worker("/js/highlight-worker.js", { type: "module" });
10  spawned.onmessage = ({ data }: MessageEvent<HighlightReply>) => {
11    const request = pending.get(data.id)!;
12    pending.delete(data.id);
13    if ("error" in data) request.reject(new Error(data.error));
14    else request.resolve(data.html);
15  };
16  // also fires if the script fails to load; respawn on next use rather than hang
17  spawned.onerror = event => {
18    spawned.terminate();
19    worker = null;
20    for (const request of pending.values()) request.reject(new Error(event.message));
21    pending.clear();
22  };
23  return spawned;
24}
25
26function splitHighlighted(nodes: Node[]): Node[][] {
27  const lines: Node[][] = [[]];
28  for (const node of nodes) {
29    let parts: Node[][];
30    if (node instanceof Text) {
31      parts = node.data.split("\n").map(text => text ? [document.createTextNode(text)] : []);
32    } else if (node instanceof Element) {
33      parts = splitHighlighted([...node.childNodes]).map(children => {
34        if (children.length === 0) return [];
35        const clone = node.cloneNode(false) as Element;
36        clone.append(...children);
37        return [clone];
38      });
39    } else {
40      parts = splitHighlighted([...node.childNodes]);
41    }
42    lines.at(-1)!.push(...parts[0]);
43    for (const part of parts.slice(1)) lines.push(part);
44  }
45  return lines;
46}
47
48export async function highlightedLines(path: string, source: string): Promise<Node[][] | null> {
49  const language = detectLanguage(path);
50  if (language === null) return null;
51  worker ??= spawnWorker();
52  const request = Promise.withResolvers<string | null>();
53  const id = nextId++;
54  pending.set(id, request);
55  worker.postMessage({ id, language, source } satisfies HighlightRequest);
56  const html = await request.promise;
57  if (html === null) return null;
58  const template = document.createElement("template");
59  template.innerHTML = html;
60  return splitHighlighted([...template.content.childNodes]);
61}