/** Maximum source size eligible for optional asynchronous Shiki enhancement. */ export const MAX_HL_CHARS = 32_000; const MAX_CACHE_ENTRIES = 48; type CodeToAnsi = (code: string, lang: string, theme: string) => Promise; export type ShikiLoader = () => Promise; type Subscriber = { fallback: readonly string[]; invalidate: () => void }; type Pending = { work: Promise; subscribers: Set }; let defaultLoad: Promise | undefined; const loadDefault: ShikiLoader = () => { if (!defaultLoad) { defaultLoad = import("@shikijs/cli") .then((module) => module.codeToANSI as CodeToAnsi) .catch((error) => { defaultLoad = undefined; throw error; }); } return defaultLoad; }; function differs(lines: readonly string[], fallback: readonly string[]): boolean { return lines.length !== fallback.length || lines.some((line, index) => line !== fallback[index]); } /** * Small, viewer-local async cache. The synchronous Pi highlighter remains the * first paint; Shiki is only requested by an expanded card that has metadata. */ export class ShikiHighlightCache { private readonly cache = new Map(); private readonly pending = new Map(); constructor(private readonly loader: ShikiLoader = loadDefault) {} get( code: string, lang: string | undefined, theme: string, fallback: readonly string[], invalidate?: () => void, ): string[] | undefined { if (!lang || code.length > MAX_HL_CHARS) return undefined; const key = `${theme}\0${lang}\0${code}`; const cached = this.cache.get(key); if (cached) { // LRU: a frequently viewed card should not evict itself immediately. this.cache.delete(key); this.cache.set(key, cached); return cached; } const existing = this.pending.get(key); if (existing) { if (invalidate && ![...existing.subscribers].some((item) => item.invalidate === invalidate)) { existing.subscribers.add({ fallback, invalidate }); } return undefined; } const pending: Pending = { work: undefined as never, subscribers: new Set() }; if (invalidate) pending.subscribers.add({ fallback, invalidate }); pending.work = Promise.resolve() .then(() => this.loader()) .then((highlight) => highlight(code, lang, theme)) .then((ansi) => { const lines = ansi.replace(/\r/g, "").replace(/\n$/, "").split("\n"); this.cache.set(key, lines); while (this.cache.size > MAX_CACHE_ENTRIES) { const oldest = this.cache.keys().next().value; if (oldest !== undefined) this.cache.delete(oldest); } for (const subscriber of pending.subscribers) { if (!differs(lines, subscriber.fallback)) continue; try { subscriber.invalidate(); } catch { // A stale card must not prevent another card from being repainted. } } }) .catch(() => { // The synchronous Pi highlighter remains the durable fallback. }) .finally(() => this.pending.delete(key)); this.pending.set(key, pending); return undefined; } clear(): void { this.cache.clear(); } } export const shikiHighlightCache = new ShikiHighlightCache();