import { bundledLanguages, codeToHtml, type BundledLanguage, type ThemeRegistration } from "shiki"; const SPECIAL_NAMES: Record = { dockerfile: "docker", makefile: "make", gemfile: "ruby", rakefile: "ruby", ".gitignore": "shellscript", ".env": "dotenv", }; export function languageFor(filename: string): BundledLanguage | null { const lower = filename.toLowerCase(); const candidate = SPECIAL_NAMES[lower] ?? (lower.includes(".") ? lower.split(".").pop()! : ""); return candidate in bundledLanguages ? (candidate as BundledLanguage) : null; } const MAX_HIGHLIGHT_BYTES = 512 * 1024; const syn = (token: string) => `var(--mu-syn-${token})`; /** * MetalUI's code tints (the code card's: keywords, types, members, strings, numbers, comments, * punctuation) as a Shiki theme. The colours are MetalUI's `--mu-syn-*` variables, which follow the * colorway, so one theme serves bone and graphite (and matches the editor's, see editor-theme.ts). */ const metalSyntax: ThemeRegistration = { name: "metalui", type: "light", colors: { "editor.background": "transparent", "editor.foreground": syn("text") }, tokenColors: [ { settings: { foreground: syn("text") } }, { scope: ["comment", "punctuation.definition.comment", "string.comment", "markup.quote"], settings: { foreground: syn("comment") } }, { scope: [ "keyword", "storage", "storage.type", "storage.modifier", "keyword.operator.new", "keyword.operator.expression", "keyword.operator.logical.python", "variable.language", "entity.name.tag", "markup.heading", "markup.heading entity.name", "punctuation.definition.heading", ], settings: { foreground: syn("keyword") }, }, { scope: [ "entity.name.type", "entity.name.class", "entity.name.namespace", "entity.other.inherited-class", "support.type", "support.class", "storage.type.primitive", "storage.type.built-in", "entity.name.type.module", "entity.other.attribute-name.class.css", "entity.name.tag.css", ], settings: { foreground: syn("type") }, }, { scope: [ "entity.name.function", "support.function", "meta.function-call entity.name.function", "variable.other.property", "variable.other.object.property", "support.variable.property", "meta.object-literal.key", "entity.other.attribute-name", "support.type.property-name", "meta.property-name", "markup.underline.link", "string.other.link", ], settings: { foreground: syn("member") }, }, { scope: ["string", "string.template", "string.regexp", "punctuation.definition.string", "markup.inline.raw", "markup.fenced_code", "markup.raw"], settings: { foreground: syn("string") }, }, { scope: ["constant.numeric", "constant.language", "constant.character", "constant.other", "support.constant", "variable.other.constant", "keyword.other.unit"], settings: { foreground: syn("number") }, }, { scope: [ "punctuation", "keyword.operator", "meta.brace", "punctuation.definition.tag", "punctuation.separator", "punctuation.terminator", "punctuation.accessor", "meta.embedded punctuation.section", ], settings: { foreground: syn("punct") }, }, { scope: ["punctuation.definition.template-expression", "punctuation.section.embedded"], settings: { foreground: syn("keyword") } }, { scope: ["markup.inserted"], settings: { foreground: syn("string") } }, { scope: ["markup.deleted"], settings: { foreground: "var(--mu-red)" } }, ], }; export async function CodeView({ code, filename }: { code: string; filename: string }) { const lang = languageFor(filename); // Very large files are shown without highlighting to keep rendering fast. const html = await codeToHtml(code.replace(/\n$/, ""), { lang: lang && code.length <= MAX_HIGHLIGHT_BYTES ? lang : "text", // Both slots get the same theme: its colours are colorway variables, and the app's graphite rule // (globals.css) reads Shiki's dark slot. themes: { light: metalSyntax, dark: { ...metalSyntax, name: "metalui-dark", type: "dark" } }, defaultColor: "light", }); return
; }