| 1 | /** |
| 2 | * The languages a composer fence can be switched to from its header, and how |
| 3 | * a choice is written back into the fence's info string. |
| 4 | * |
| 5 | * Ids are the names the highlighter and the file icons both know. The list |
| 6 | * is curated rather than every grammar the highlighter bundles: a picker is |
| 7 | * for the common case, and any other language can still be typed on the |
| 8 | * fence line, where it shows as its own entry. |
| 9 | */ |
| 10 | export const CODE_BLOCK_LANGUAGES = [ |
| 11 | { id: "", label: "Plain text" }, |
| 12 | { id: "bash", label: "Bash" }, |
| 13 | { id: "c", label: "C" }, |
| 14 | { id: "cpp", label: "C++" }, |
| 15 | { id: "csharp", label: "C#" }, |
| 16 | { id: "css", label: "CSS" }, |
| 17 | { id: "dart", label: "Dart" }, |
| 18 | { id: "diff", label: "Diff" }, |
| 19 | { id: "dockerfile", label: "Dockerfile" }, |
| 20 | { id: "elixir", label: "Elixir" }, |
| 21 | { id: "go", label: "Go" }, |
| 22 | { id: "graphql", label: "GraphQL" }, |
| 23 | { id: "haskell", label: "Haskell" }, |
| 24 | { id: "html", label: "HTML" }, |
| 25 | { id: "java", label: "Java" }, |
| 26 | { id: "javascript", label: "JavaScript" }, |
| 27 | { id: "json", label: "JSON" }, |
| 28 | { id: "jsx", label: "JSX" }, |
| 29 | { id: "kotlin", label: "Kotlin" }, |
| 30 | { id: "lua", label: "Lua" }, |
| 31 | { id: "markdown", label: "Markdown" }, |
| 32 | { id: "php", label: "PHP" }, |
| 33 | { id: "powershell", label: "PowerShell" }, |
| 34 | { id: "python", label: "Python" }, |
| 35 | { id: "r", label: "R" }, |
| 36 | { id: "ruby", label: "Ruby" }, |
| 37 | { id: "rust", label: "Rust" }, |
| 38 | { id: "scala", label: "Scala" }, |
| 39 | { id: "scss", label: "SCSS" }, |
| 40 | { id: "sql", label: "SQL" }, |
| 41 | { id: "svelte", label: "Svelte" }, |
| 42 | { id: "swift", label: "Swift" }, |
| 43 | { id: "toml", label: "TOML" }, |
| 44 | { id: "tsx", label: "TSX" }, |
| 45 | { id: "typescript", label: "TypeScript" }, |
| 46 | { id: "vue", label: "Vue" }, |
| 47 | { id: "xml", label: "XML" }, |
| 48 | { id: "yaml", label: "YAML" }, |
| 49 | { id: "zig", label: "Zig" }, |
| 50 | ] as const; |
| 51 | |
| 52 | export type CodeBlockLanguage = { readonly id: string; readonly label: string }; |
| 53 | |
| 54 | /** Short names people type on the fence line, labelled like the entry they mean. */ |
| 55 | const LANGUAGE_ALIASES: Record<string, string> = { |
| 56 | cs: "csharp", |
| 57 | "c++": "cpp", |
| 58 | js: "javascript", |
| 59 | md: "markdown", |
| 60 | py: "python", |
| 61 | rb: "ruby", |
| 62 | rs: "rust", |
| 63 | sh: "bash", |
| 64 | shell: "bash", |
| 65 | ts: "typescript", |
| 66 | yml: "yaml", |
| 67 | }; |
| 68 | |
| 69 | /** The language is the info string's first word; the rest belongs to the fence. */ |
| 70 | export function languageOfInfoString(info: string): string { |
| 71 | return info.trim().split(/\s+/, 1)[0] ?? ""; |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * Replaces the language and keeps everything around it: the whitespace the |
| 76 | * fence had before it and whatever follows it. Plain text clears it, unless |
| 77 | * something follows: a bare `title=x` would read as the language, so that |
| 78 | * keeps `text` in front. |
| 79 | */ |
| 80 | export function withInfoStringLanguage(info: string, language: string): string { |
| 81 | const leading = /^\s*/.exec(info)![0]; |
| 82 | // The language is the first word, so what follows it is empty or starts |
| 83 | // with whitespace. |
| 84 | const rest = info.slice(leading.length + languageOfInfoString(info).length); |
| 85 | if (language) return `${leading}${language}${rest}`; |
| 86 | return rest.trim() ? `${leading}text${rest}` : ""; |
| 87 | } |
| 88 | |
| 89 | /** The canonical entry a language names, if the picker lists it. */ |
| 90 | export function codeLanguageEntry(language: string): CodeBlockLanguage | null { |
| 91 | const id = language === "text" ? "" : (LANGUAGE_ALIASES[language.toLowerCase()] ?? language); |
| 92 | return CODE_BLOCK_LANGUAGES.find((entry) => entry.id === id.toLowerCase()) ?? null; |
| 93 | } |
| 94 | |
| 95 | export function codeLanguageLabel(language: string): string { |
| 96 | return codeLanguageEntry(language)?.label ?? language; |
| 97 | } |