/// import { ShikiStreamTokenizer } from "@shikijs/stream" import { createMarkdownParser } from "@opencode-ai/ui/context/marked-parser" import { OpenCodeTheme } from "@opencode-ai/ui/context/marked-theme" import { bundledLanguages, createHighlighter, getTokenStyleObject, stringifyTokenStyle, type BundledLanguage, type ThemedToken, } from "shiki" import type { MarkdownToken, MarkdownWorkerRequest, MarkdownWorkerResponse } from "./markdown-worker-protocol" import { createLatestWorkerQueue } from "./markdown-worker-queue" import { project, type Projection } from "./markdown-stream" type Stream = { language: string source: string tokenizer: ShikiStreamTokenizer } const streams = new Map() const projections = new Map() let highlighter: ReturnType | undefined const highlightQueue = createLatestWorkerQueue>({ run: highlight, supersede: (request) => post({ type: "superseded", id: request.id, key: request.key }), dispose: (key) => void streams.delete(key), }) const projectQueue = createLatestWorkerQueue>({ run: runProject, supersede: (request) => post({ type: "superseded", id: request.id, key: request.key }), dispose: (key) => void projections.delete(key), }) const parser = createMarkdownParser(async (code, language) => { const instance = await getHighlighter() const name = language in bundledLanguages ? language : "text" if (!instance.getLoadedLanguages().includes(name)) await instance.loadLanguage(bundledLanguages[name as BundledLanguage]) return instance.codeToHtml(code, { lang: name as BundledLanguage, theme: "OpenCode", tabindex: false }) }) self.onmessage = (event: MessageEvent) => { if (event.data.type === "dispose") { highlightQueue.dispose(event.data.key) projectQueue.dispose(event.data.key) return } if (event.data.type === "parse") { void parse(event.data) return } if (event.data.type === "project") { projectQueue.highlight(event.data) return } highlightQueue.highlight(event.data) } async function parse(request: Extract) { try { post({ type: "parse", id: request.id, html: await parser.parse(request.text) }) } catch (error) { post({ type: "error", id: request.id, message: error instanceof Error ? error.message : String(error) }) } } async function runProject(request: Extract) { try { const projection = project(projections.get(request.key), request.text, request.live) projections.set(request.key, projection) post({ type: "project", id: request.id, key: request.key, projection }) } catch (error) { post({ type: "error", id: request.id, key: request.key, message: error instanceof Error ? error.message : String(error), }) } } async function highlight(request: Extract) { try { const instance = await getHighlighter() const language = request.language in bundledLanguages ? request.language : "text" if (!instance.getLoadedLanguages().includes(language)) await instance.loadLanguage(bundledLanguages[language as BundledLanguage]) if (request.complete) { const result = instance.codeToTokens(request.text, { lang: language as BundledLanguage, theme: "OpenCode" }) streams.delete(request.key) post({ type: "highlight", id: request.id, key: request.key, language, reset: true, stable: result.tokens .flatMap((line, index) => index === result.tokens.length - 1 ? line : [...line, { content: "\n", offset: 0 }], ) .map(token), unstable: [], }) return } const previous = streams.get(request.key) const reset = !previous || previous.language !== language || !request.text.startsWith(previous.source) const stream = reset ? { language, source: "", tokenizer: new ShikiStreamTokenizer({ highlighter: instance, lang: language, theme: "OpenCode" }), } : previous const result = await stream.tokenizer.enqueue(request.text.slice(stream.source.length)) stream.source = request.text streams.set(request.key, stream) post({ type: "highlight", id: request.id, key: request.key, language, reset, stable: result.stable.filter((token) => token.content.length > 0).map(token), unstable: result.unstable.filter((token) => token.content.length > 0).map(token), }) } catch (error) { post({ type: "error", id: request.id, key: request.key, message: error instanceof Error ? error.message : String(error), }) } } function getHighlighter() { return (highlighter ??= createHighlighter({ themes: [OpenCodeTheme], langs: [] })) } function post(response: MarkdownWorkerResponse) { self.postMessage(response) } function token(value: ThemedToken): MarkdownToken { return [value.content, stringifyTokenStyle(value.htmlStyle ?? getTokenStyleObject(value))] }