Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions web/src/highlight/highlighter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,20 @@ it("colours a window inside a multiline comment like the complete syntax stream"
expect(tokenize.range!(lines, 100, 110)).toEqual(complete.slice(100, 110));
expect(tokenize.range!(lines, 178, 184)).toEqual(complete.slice(178, 184));
});

it("colours an anonymous Go struct literal without locking up the page", async () => {
// Shiki's JavaScript regex engine backtracked forever on this shape, and a
// regex running on the main thread cannot be interrupted: the review froze
// the moment the window holding it scrolled near.
await load("go");
const tokenize = tokenizerFor("go")!;
const lines = [
"func handler(w http.ResponseWriter) {",
"\trequire.NoError(t, json.NewEncoder(w).Encode(struct {",
'\t\tItems []contracts.Fact `json:"items"`',
'\t}{Items: []contracts.Fact{{Candidate: contracts.Candidate{Reference: reference, ChatID: "conversation", BusinessObjectID: "appointment", SourceFlow: "reminder"}, Eligible: true, Revision: "rev"}}}))',
"}",
];

expect(tokenize(lines.join("\n"))).toHaveLength(5);
}, 2_000);
16 changes: 9 additions & 7 deletions web/src/highlight/highlighter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ import {
type GrammarState,
type HighlighterCore,
} from "shiki/core";
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
import { createOnigurumaEngine } from "shiki/engine/oniguruma";
import { bundledLanguages } from "shiki/langs";
import { dark, light } from "./theme";
import type { Token, Tokenize } from "./tokens";

/** Shiki, loaded a grammar at a time.
*
* The regex engine in JavaScript rather than the WebAssembly one: it costs a
* few exotic grammars and saves half a megabyte that every reader would carry
* to read one language. Every grammar Shiki ships is reachable, and each is its
* own file — the page starts with none of them and fetches the one the file in
* front of you needs. */
* The WebAssembly regex engine, not the JavaScript one. The JavaScript engine
* saved half a megabyte, but its translation of the Go grammar backtracked
* forever on an anonymous struct literal — and a regex on the main thread
* cannot be interrupted, so the whole review froze. The WASM is fetched once,
* with the first grammar. Every grammar Shiki ships is reachable, and each is
* its own file — the page starts with none of them and fetches the one the
* file in front of you needs. */

let core: Promise<HighlighterCore> | null = null;
let built: HighlighterCore | null = null;
Expand All @@ -31,7 +33,7 @@ function highlighter(): Promise<HighlighterCore> {
core ??= createHighlighterCore({
langs: [],
themes: [light, dark],
engine: createJavaScriptRegexEngine(),
engine: createOnigurumaEngine(import("shiki/wasm")),
}).then((made) => (built = made));
return core;
}
Expand Down
Loading