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 ;
}