diff --git a/web/src/lib/components/repo/pierre.ts b/web/src/lib/components/repo/pierre.ts index 338b966a8..d902b133a 100644 --- a/web/src/lib/components/repo/pierre.ts +++ b/web/src/lib/components/repo/pierre.ts @@ -2,32 +2,24 @@ import { getSharedHighlighter, - registerCustomCSSVariableTheme, type FileDiffOptions, type FileOptions, type VirtualFileMetrics } from "@pierre/diffs"; import { browser } from "$app/environment"; import diffShadowCSS from "$lib/components/repo/pierreDiff.css?raw"; +import { SYNTAX_THEME_NAME } from "$lib/syntax"; import type { WorkerPoolManager } from "@pierre/diffs/worker"; // pierre's two diff layouts, threaded from the ?diff= url param down to the // FileDiff instances export type DiffStyle = NonNullable["diffStyle"]>; -export const PIERRE_THEME_NAME = "tangled"; - -registerCustomCSSVariableTheme(PIERRE_THEME_NAME, { - background: "#ffffff", - foreground: "#4c4f69" -}); - -// pierre skips highlighting lines past 1e3 chars by default. -// lockfiles however do not care... +// pierre stops highlighting after 1000 characters, lockfiles often run longer const PIERRE_TOKENIZE_MAX_LINE_LENGTH = 5000; const PIERRE_BASE_OPTIONS = { - theme: PIERRE_THEME_NAME, + theme: SYNTAX_THEME_NAME, themeType: "system", tokenizeMaxLineLength: PIERRE_TOKENIZE_MAX_LINE_LENGTH } as const; @@ -92,7 +84,7 @@ const createWorkerPool = async (): Promise => { totalASTLRUCacheSize: 24 }, highlighterOptions: { - theme: PIERRE_THEME_NAME, + theme: SYNTAX_THEME_NAME, useTokenTransformer: false, lineDiffType: "word-alt", maxLineDiffLength: 1000, @@ -117,10 +109,8 @@ export const getPierreWorkerPool = (): Promise => return (workerPoolPromise ??= createWorkerPool()); }; -// a renderer with a missing language paints plain text first and swaps in -// the highlight, but only once the shared highlighter is up. before that the -// first render waits on the shiki import and paints nothing, so warm it here -if (browser) void getSharedHighlighter({ themes: [PIERRE_THEME_NAME], langs: [] }); +// without this warmup pierre's first render of a missing language is blank +if (browser) void getSharedHighlighter({ themes: [SYNTAX_THEME_NAME], langs: [] }); export const PIERRE_VALIDATE_ITEM_HEIGHTS = import.meta.env.DEV && browser && new URLSearchParams(location.search).has("diffmetrics"); diff --git a/web/src/lib/markup/highlight.ts b/web/src/lib/markup/highlight.ts index 2107c3bc7..365eb6af8 100644 --- a/web/src/lib/markup/highlight.ts +++ b/web/src/lib/markup/highlight.ts @@ -1,5 +1,4 @@ import type { Action } from "svelte/action"; -import { PIERRE_THEME_NAME } from "$lib/components/repo/pierre"; const BLOCK = /
([\s\S]*?)<\/code><\/pre>/g;
 
@@ -36,21 +35,25 @@ const INNER = /]*>([\s\S]*)<\/code>\s*<\/pre>/;
 
 type Highlighter = Awaited>;
 
-const loadHighlighter = async (langs: string[]): Promise => {
+type Loaded = { highlighter: Highlighter; theme: string };
+
+// importing this lazily keeps shiki out of markdown pages with no code
+const loadHighlighter = async (langs: string[]): Promise => {
 	try {
-		const { getSharedHighlighter } = await import("@pierre/diffs");
-		return await getSharedHighlighter({ themes: [PIERRE_THEME_NAME], langs });
+		const { SYNTAX_THEME_NAME, getSharedHighlighter } = await import("$lib/syntax");
+		const highlighter = await getSharedHighlighter({ themes: [SYNTAX_THEME_NAME], langs });
+		return { highlighter, theme: SYNTAX_THEME_NAME };
 	} catch {
 		return null;
 	}
 };
 
-const highlightInner = (highlighter: Highlighter, code: string, lang: string): string | null => {
+const highlightInner = (loaded: Loaded, code: string, lang: string): string | null => {
 	let full: string;
 	try {
-		full = highlighter.codeToHtml(code, {
+		full = loaded.highlighter.codeToHtml(code, {
 			lang,
-			theme: PIERRE_THEME_NAME,
+			theme: loaded.theme,
 			defaultColor: false,
 			cssVariablePrefix: "--diffs-token-"
 		});
@@ -71,13 +74,13 @@ export const highlightCodeblocks = async (html: string): Promise => {
 	});
 	if (blocks.length === 0) return html;
 
-	const highlighter = await loadHighlighter([...new Set(blocks.map((block) => block.lang))]);
-	if (!highlighter) return html;
+	const loaded = await loadHighlighter([...new Set(blocks.map((block) => block.lang))]);
+	if (!loaded) return html;
 
 	const swapped = new Map();
 	for (const block of blocks) {
 		if (swapped.has(block.raw)) continue;
-		const inner = highlightInner(highlighter, block.code, block.lang);
+		const inner = highlightInner(loaded, block.code, block.lang);
 		if (inner !== null)
 			swapped.set(
 				block.raw,
@@ -121,16 +124,14 @@ export const enhanceCodeblocks: Action = (node) => {
 
 		await idle();
 		if (cancelled || !node.isConnected) return;
-		const highlighter = await loadHighlighter([
-			...new Set(targets.map((target) => target.lang))
-		]);
-		if (!highlighter || cancelled) return;
+		const loaded = await loadHighlighter([...new Set(targets.map((target) => target.lang))]);
+		if (!loaded || cancelled) return;
 
 		for (const target of targets) {
 			if (cancelled || !target.code.isConnected) continue;
 			// a re-render may have highlighted it since
 			if (target.code.querySelector("span") !== null) continue;
-			const inner = highlightInner(highlighter, target.text, target.lang);
+			const inner = highlightInner(loaded, target.text, target.lang);
 			if (inner !== null) target.code.innerHTML = inner;
 		}
 	};
diff --git a/web/src/lib/syntax.ts b/web/src/lib/syntax.ts
new file mode 100644
index 000000000..29cb9e78b
--- /dev/null
+++ b/web/src/lib/syntax.ts
@@ -0,0 +1,12 @@
+import { getSharedHighlighter, registerCustomCSSVariableTheme } from "@pierre/diffs";
+
+// separate from pierre so a markdown page with no code doesn't load the diff renderer
+
+export const SYNTAX_THEME_NAME = "tangled";
+
+registerCustomCSSVariableTheme(SYNTAX_THEME_NAME, {
+	background: "#ffffff",
+	foreground: "#4c4f69"
+});
+
+export { getSharedHighlighter };