import type { Language } from "linguist-languages"; import * as linguistLangs from "linguist-languages"; import { bundledLanguages, bundledLanguagesInfo, createHighlighter, type Highlighter, } from "shiki"; import { INLINE_MAX_BYTES } from "../config.ts"; import { MAX_FILE_CACHE } from "../constants.ts"; let highlighter: Highlighter | null = null; let extToLangId: Map | null = null; let filenameToLangId: Map | null = null; export async function highlightStartup(): Promise { console.log( "[highlight] initializing highlighter and language detection maps...", ); // Index linguist languages by lowercase name and aliases for precise lookup. // Many shiki languages share the same tmScope (e.g. source.js has 31 entries), // so scope-based matching is unreliable — name/alias matching is used instead. const linguistByName = new Map(); for (const lang of Object.values( linguistLangs as Record, )) { linguistByName.set(lang.name.toLowerCase(), lang); for (const alias of lang.aliases ?? []) { if (!linguistByName.has(alias.toLowerCase())) { linguistByName.set(alias.toLowerCase(), lang); } } } const extMap = new Map(); const fnMap = new Map(); for (const { id, aliases } of bundledLanguagesInfo) { // Match shiki lang → linguist language by ID then aliases (no scope fallback) const linguistLang = linguistByName.get(id) ?? aliases?.reduce( (found, a) => found ?? linguistByName.get(a.toLowerCase()), undefined, ); if (!linguistLang) continue; for (const ext of linguistLang.extensions ?? []) { if (!extMap.has(ext)) extMap.set(ext, id); } for (const fn of linguistLang.filenames ?? []) { if (!fnMap.has(fn)) fnMap.set(fn, id); } } extToLangId = extMap; filenameToLangId = fnMap; highlighter = await createHighlighter({ themes: ["github-light", "github-dark"], langs: Object.keys(bundledLanguages), }); console.log( `[highlight] ready: ${extMap.size} extensions, ${fnMap.size} filenames`, ); } export function getHighlighter(): Highlighter { if (!highlighter) throw new Error("highlightStartup() has not been called"); return highlighter; } // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional binary content detection const BINARY_RE = /[\x00-\x08\x0e-\x1f]/; export function hasBinaryContent(buf: Buffer): boolean { const sample = buf.subarray(0, 8000); return BINARY_RE.test(sample.toString("binary")); } export function detectLang(filename: string): string { if (!extToLangId || !filenameToLangId) return "text"; const base = filename.split("/").pop() ?? filename; const ext = base.includes(".") ? base.slice(base.lastIndexOf(".")) : ""; return ( filenameToLangId.get(base) ?? (ext ? extToLangId.get(ext) : undefined) ?? "text" ); } export type FileView = | { type: "inline"; html: string; lines: number } | { type: "download"; size: number } | { type: "binary"; size: number }; const fileCache = new Map(); export async function serveFile( content: Buffer, filename: string, cacheKey: string, ): Promise { const cached = fileCache.get(cacheKey); if (cached) return cached; const isBinary = hasBinaryContent(content); if (isBinary) { return { type: "binary", size: content.length }; } if (content.length > INLINE_MAX_BYTES) { return { type: "download", size: content.length }; } const text = content.toString("utf-8"); const lines = text.split("\n").length; let view: FileView; try { const h = getHighlighter(); const lang = detectLang(filename); const html = h.codeToHtml(text, { lang, themes: { light: "github-light", dark: "github-dark" }, }); view = { type: "inline", html, lines }; } catch { // fallback: plain pre const escaped = text .replace(/&/g, "&") .replace(//g, ">"); view = { type: "inline", html: `
${escaped}
`, lines, }; } if (cacheKey) { if (fileCache.size >= MAX_FILE_CACHE) fileCache.delete(fileCache.keys().next().value!); fileCache.set(cacheKey, view); } return view; }