Skip to content

Commit feb3ea7

Browse files
LetZicoclaude
andauthored
fix(web): stop highlighter freezes and worker spin by using the Oniguruma WASM engine (pingdotgg#8360)
Co-authored-by: Claude Fable 5 <[email protected]>
1 parent ea71a19 commit feb3ea7

7 files changed

Lines changed: 23 additions & 2 deletions

File tree

‎apps/web/src/components/DiffPanel.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
resolveDiffThemeName,
3838
resolveFileDiffPath,
3939
} from "../lib/diffRendering";
40+
import { PREFERRED_HIGHLIGHTER } from "../lib/syntaxHighlighting";
4041
import { areAllDiffFilesCollapsed, toggleAllDiffFiles } from "../lib/diffCollapse";
4142
import { useTurnDiffSummaries } from "../hooks/useTurnDiffSummaries";
4243
import { useWorkspaceMutationRefresh } from "../hooks/useWorkspaceMutationRefresh";
@@ -948,6 +949,7 @@ export default function DiffPanel({
948949
lineDiffType: "none",
949950
overflow: wordWrap ? "wrap" : "scroll",
950951
theme: resolveDiffThemeName(resolvedTheme),
952+
preferredHighlighter: PREFERRED_HIGHLIGHTER,
951953
themeType: resolvedTheme as DiffThemeType,
952954
stickyHeaders: true,
953955
...(loadDiffFiles ? { loadDiffFiles } : {}),

‎apps/web/src/components/DiffWorkerPoolProvider.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as Schema from "effect/Schema";
44
import { useEffect, useMemo, type ReactNode } from "react";
55
import { useTheme } from "../hooks/useTheme";
66
import { resolveDiffThemeName, type DiffThemeName } from "../lib/diffRendering";
7+
import { PREFERRED_HIGHLIGHTER } from "../lib/syntaxHighlighting";
78

89
export class DiffWorkerError extends Schema.TaggedErrorClass<DiffWorkerError>()("DiffWorkerError", {
910
operation: Schema.Literals(["create-worker", "get-render-options", "set-render-options"]),
@@ -73,6 +74,7 @@ export function DiffWorkerPoolProvider({ children }: { children?: ReactNode }) {
7374
}}
7475
highlighterOptions={{
7576
theme: diffThemeName,
77+
preferredHighlighter: PREFERRED_HIGHLIGHTER,
7678
tokenizeMaxLineLength: 1_000,
7779
useTokenTransformer: true,
7880
}}

‎apps/web/src/components/chat/MessagesTimeline.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
resolveDiffThemeName,
6060
resolveFileDiffPath,
6161
} from "../../lib/diffRendering";
62+
import { PREFERRED_HIGHLIGHTER } from "../../lib/syntaxHighlighting";
6263
import ChatMarkdown, { ChatMarkdownAssetImage } from "../ChatMarkdown";
6364
import {
6465
BotIcon,
@@ -2060,6 +2061,7 @@ function UserMessageReviewCommentCard({ comment }: { comment: ReviewCommentConte
20602061
collapsed: false,
20612062
diffStyle: "unified",
20622063
theme: resolveDiffThemeName(ctx.resolvedTheme),
2064+
preferredHighlighter: PREFERRED_HIGHLIGHTER,
20632065
}}
20642066
/>
20652067
))}

‎apps/web/src/components/files/FilePreviewPanel.tsx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { useTheme } from "~/hooks/useTheme";
3131
import { getLocalStorageItem, setLocalStorageItem, useLocalStorage } from "~/hooks/useLocalStorage";
3232
import { useWorkspaceMutationRefresh } from "~/hooks/useWorkspaceMutationRefresh";
3333
import { DIFF_SURFACE_THEME_UNSAFE_CSS, resolveDiffThemeName } from "~/lib/diffRendering";
34+
import { PREFERRED_HIGHLIGHTER } from "~/lib/syntaxHighlighting";
3435
import { cn } from "~/lib/utils";
3536
import { isPreviewSupportedInRuntime } from "~/previewStateStore";
3637
import { resolvePathLinkTarget } from "~/terminal-links";
@@ -755,6 +756,7 @@ function EditableFileSurface({
755756
onLineSelectionEnd: handleLineSelectionEnd,
756757
overflow: wordWrap ? "wrap" : "scroll",
757758
theme: resolveDiffThemeName(resolvedTheme),
759+
preferredHighlighter: PREFERRED_HIGHLIGHTER,
758760
themeType: resolvedTheme,
759761
unsafeCSS: FILE_LINK_REVEAL_UNSAFE_CSS,
760762
onPostRender: handlePostRender,
@@ -1149,6 +1151,7 @@ export default function FilePreviewPanel({
11491151
disableFileHeader: true,
11501152
overflow: wordWrap ? "wrap" : "scroll",
11511153
theme: resolveDiffThemeName(resolvedTheme),
1154+
preferredHighlighter: PREFERRED_HIGHLIGHTER,
11521155
themeType: resolvedTheme,
11531156
unsafeCSS: FILE_LINK_REVEAL_UNSAFE_CSS,
11541157
onPostRender: onFilePostRender,

‎apps/web/src/components/pullRequest/PullRequestCodeTab.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
resolveFileDiffPreviousPath,
4343
type RenderablePatch,
4444
} from "~/lib/diffRendering";
45+
import { PREFERRED_HIGHLIGHTER } from "~/lib/syntaxHighlighting";
4546
import { cn } from "~/lib/utils";
4647
import { createPullRequestDiffFileContentsLoader } from "~/lib/diffFileContents";
4748
import {
@@ -743,6 +744,7 @@ export function PullRequestCodeTab({
743744
lineDiffType: "none" as const,
744745
overflow: wordWrap ? ("wrap" as const) : ("scroll" as const),
745746
theme: resolveDiffThemeName(resolvedTheme),
747+
preferredHighlighter: PREFERRED_HIGHLIGHTER,
746748
themeType: resolvedTheme,
747749
stickyHeaders: true,
748750
loadDiffFiles,

‎apps/web/src/components/settings/SettingsFontPreviews.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { terminalThemeFromApp } from "../ThreadTerminalDrawer";
55
import { useTheme } from "../../hooks/useTheme";
66
import { DISCONNECTED_COMPOSER_PLACEHOLDER } from "../../composerPlaceholder";
77
import { resolveDiffThemeName, type DiffThemeName } from "../../lib/diffRendering";
8+
import { PREFERRED_HIGHLIGHTER } from "../../lib/syntaxHighlighting";
89
import { GhosttyTerminalSurface } from "~/terminal/ghostty/surface";
910

1011
// The font previews are the real surfaces, not lookalikes: the composer's
@@ -79,7 +80,7 @@ function loadDiffPreviewHtml(theme: DiffThemeName): Promise<readonly string[]> {
7980
if (promise === undefined) {
8081
promise = preloadPatchFile({
8182
patch: DIFF_PREVIEW_PATCH,
82-
options: { diffStyle: "unified", theme },
83+
options: { diffStyle: "unified", theme, preferredHighlighter: PREFERRED_HIGHLIGHTER },
8384
}).then((results) => results.map((result) => result.prerenderedHTML));
8485
diffPreviewHtmlByTheme.set(theme, promise);
8586
}

‎apps/web/src/lib/syntaxHighlighting.ts‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
import {
22
getSharedHighlighter,
33
type DiffsHighlighter,
4+
type HighlighterTypes,
45
type SupportedLanguages,
56
} from "@pierre/diffs";
67

78
import { resolveDiffThemeName } from "./diffRendering";
89

10+
/**
11+
* Always highlight with the Oniguruma WASM engine — the JS regex engine can
12+
* backtrack catastrophically and hang the tokenizing thread. The shared
13+
* highlighter is a first-caller-wins singleton, so every creation site must
14+
* pass this value.
15+
*/
16+
export const PREFERRED_HIGHLIGHTER: HighlighterTypes = "shiki-wasm";
17+
918
const highlighterPromiseCache = new Map<string, Promise<DiffsHighlighter>>();
1019

1120
export function getSyntaxHighlighterPromise(language: string): Promise<DiffsHighlighter> {
@@ -15,7 +24,7 @@ export function getSyntaxHighlighterPromise(language: string): Promise<DiffsHigh
1524
const promise = getSharedHighlighter({
1625
themes: [resolveDiffThemeName("dark"), resolveDiffThemeName("light")],
1726
langs: [language as SupportedLanguages],
18-
preferredHighlighter: "shiki-js",
27+
preferredHighlighter: PREFERRED_HIGHLIGHTER,
1928
}).catch((error) => {
2029
if (language === "text") {
2130
highlighterPromiseCache.delete(language);

0 commit comments

Comments
 (0)