|
| 1 | +import { createHighlighterCore } from "shiki/core"; |
| 2 | +import { createJavaScriptRegexEngine } from "shiki/engine/javascript"; |
| 3 | +import bash from "shiki/langs/bash.mjs"; |
| 4 | +import css from "shiki/langs/css.mjs"; |
| 5 | +import diff from "shiki/langs/diff.mjs"; |
| 6 | +import dockerfile from "shiki/langs/dockerfile.mjs"; |
| 7 | +import html from "shiki/langs/html.mjs"; |
| 8 | +import ini from "shiki/langs/ini.mjs"; |
| 9 | +import java from "shiki/langs/java.mjs"; |
| 10 | +import javascript from "shiki/langs/javascript.mjs"; |
| 11 | +import json from "shiki/langs/json.mjs"; |
| 12 | +import jsx from "shiki/langs/jsx.mjs"; |
| 13 | +import markdown from "shiki/langs/markdown.mjs"; |
| 14 | +import powershell from "shiki/langs/powershell.mjs"; |
| 15 | +import python from "shiki/langs/python.mjs"; |
| 16 | +import sql from "shiki/langs/sql.mjs"; |
| 17 | +import tsx from "shiki/langs/tsx.mjs"; |
| 18 | +import typescript from "shiki/langs/typescript.mjs"; |
| 19 | +import vue from "shiki/langs/vue.mjs"; |
| 20 | +import xml from "shiki/langs/xml.mjs"; |
| 21 | +import yaml from "shiki/langs/yaml.mjs"; |
| 22 | +import githubDark from "shiki/themes/github-dark.mjs"; |
| 23 | +import githubLight from "shiki/themes/github-light.mjs"; |
| 24 | +import vitesseDark from "shiki/themes/vitesse-dark.mjs"; |
| 25 | +import vitesseLight from "shiki/themes/vitesse-light.mjs"; |
| 26 | + |
| 27 | +export const LIMITED_SHIKI_LANGUAGES = [ |
| 28 | + ...bash, |
| 29 | + ...css, |
| 30 | + ...diff, |
| 31 | + ...dockerfile, |
| 32 | + ...html, |
| 33 | + ...ini, |
| 34 | + ...java, |
| 35 | + ...javascript, |
| 36 | + ...json, |
| 37 | + ...jsx, |
| 38 | + ...markdown, |
| 39 | + ...powershell, |
| 40 | + ...python, |
| 41 | + ...sql, |
| 42 | + ...tsx, |
| 43 | + ...typescript, |
| 44 | + ...vue, |
| 45 | + ...xml, |
| 46 | + ...yaml, |
| 47 | +]; |
| 48 | + |
| 49 | +const THEME_BY_NAME = { |
| 50 | + "github-dark": githubDark, |
| 51 | + "github-light": githubLight, |
| 52 | + "vitesse-dark": vitesseDark, |
| 53 | + "vitesse-light": vitesseLight, |
| 54 | +}; |
| 55 | + |
| 56 | +const BUILT_IN_LANGUAGES = ["text", "plaintext", "plain"]; |
| 57 | + |
| 58 | +export const LIMITED_SHIKI_LANGUAGE_ALIASES = { |
| 59 | + bat: "powershell", |
| 60 | + cjs: "javascript", |
| 61 | + console: "bash", |
| 62 | + cts: "typescript", |
| 63 | + docker: "dockerfile", |
| 64 | + htm: "html", |
| 65 | + js: "javascript", |
| 66 | + md: "markdown", |
| 67 | + mjs: "javascript", |
| 68 | + mts: "typescript", |
| 69 | + plain: "text", |
| 70 | + plaintext: "text", |
| 71 | + ps1: "powershell", |
| 72 | + pwsh: "powershell", |
| 73 | + py: "python", |
| 74 | + shell: "bash", |
| 75 | + shellscript: "bash", |
| 76 | + sh: "bash", |
| 77 | + svg: "xml", |
| 78 | + text: "text", |
| 79 | + ts: "typescript", |
| 80 | + txt: "text", |
| 81 | + xhtml: "html", |
| 82 | + yml: "yaml", |
| 83 | + zsh: "bash", |
| 84 | +}; |
| 85 | + |
| 86 | +export const LIMITED_SHIKI_SUPPORTED_LANGUAGES = new Set([ |
| 87 | + ...BUILT_IN_LANGUAGES, |
| 88 | + ...LIMITED_SHIKI_LANGUAGES.flatMap((language) => [ |
| 89 | + language.name, |
| 90 | + ...(language.aliases || []), |
| 91 | + ]), |
| 92 | +]); |
| 93 | + |
| 94 | +function getThemeName(theme) { |
| 95 | + return typeof theme === "string" ? theme : theme?.name; |
| 96 | +} |
| 97 | + |
| 98 | +function resolveTheme(theme) { |
| 99 | + if (!theme) return null; |
| 100 | + if (typeof theme !== "string") return theme; |
| 101 | + return THEME_BY_NAME[theme] || null; |
| 102 | +} |
| 103 | + |
| 104 | +function uniqueThemes(themes) { |
| 105 | + const seen = new Set(); |
| 106 | + const result = []; |
| 107 | + |
| 108 | + for (const theme of themes) { |
| 109 | + const resolved = resolveTheme(theme); |
| 110 | + const name = getThemeName(resolved); |
| 111 | + if (!resolved || !name || seen.has(name)) continue; |
| 112 | + seen.add(name); |
| 113 | + result.push(resolved); |
| 114 | + } |
| 115 | + |
| 116 | + return result; |
| 117 | +} |
| 118 | + |
| 119 | +export function normalizeLimitedShikiLanguage(language) { |
| 120 | + const normalized = String(language || "text") |
| 121 | + .trim() |
| 122 | + .split(/\s+/, 1)[0] |
| 123 | + .toLowerCase(); |
| 124 | + |
| 125 | + if (!normalized) return "text"; |
| 126 | + if (normalized in LIMITED_SHIKI_LANGUAGE_ALIASES) { |
| 127 | + return LIMITED_SHIKI_LANGUAGE_ALIASES[normalized]; |
| 128 | + } |
| 129 | + |
| 130 | + return LIMITED_SHIKI_SUPPORTED_LANGUAGES.has(normalized) ? normalized : "text"; |
| 131 | +} |
| 132 | + |
| 133 | +function normalizeCodeOptions(options) { |
| 134 | + if (!options || typeof options !== "object") return options; |
| 135 | + return { |
| 136 | + ...options, |
| 137 | + lang: normalizeLimitedShikiLanguage(options.lang), |
| 138 | + }; |
| 139 | +} |
| 140 | + |
| 141 | +function wrapLimitedHighlighter(highlighter) { |
| 142 | + const codeToHtml = highlighter.codeToHtml.bind(highlighter); |
| 143 | + const codeToTokens = highlighter.codeToTokens.bind(highlighter); |
| 144 | + const codeToHast = highlighter.codeToHast.bind(highlighter); |
| 145 | + const getLanguage = highlighter.getLanguage.bind(highlighter); |
| 146 | + const getLoadedLanguages = highlighter.getLoadedLanguages.bind(highlighter); |
| 147 | + const loadThemeSync = highlighter.loadThemeSync?.bind(highlighter); |
| 148 | + const loadTheme = highlighter.loadTheme?.bind(highlighter); |
| 149 | + |
| 150 | + return { |
| 151 | + ...highlighter, |
| 152 | + codeToHast(code, options) { |
| 153 | + return codeToHast(code, normalizeCodeOptions(options)); |
| 154 | + }, |
| 155 | + codeToHtml(code, options) { |
| 156 | + return codeToHtml(code, normalizeCodeOptions(options)); |
| 157 | + }, |
| 158 | + codeToTokens(code, options) { |
| 159 | + return codeToTokens(code, normalizeCodeOptions(options)); |
| 160 | + }, |
| 161 | + getLanguage(language) { |
| 162 | + return getLanguage(normalizeLimitedShikiLanguage(language)); |
| 163 | + }, |
| 164 | + getLoadedLanguages() { |
| 165 | + return [...new Set([...getLoadedLanguages(), ...BUILT_IN_LANGUAGES])]; |
| 166 | + }, |
| 167 | + loadLanguage() { |
| 168 | + return Promise.resolve(); |
| 169 | + }, |
| 170 | + loadLanguageSync() {}, |
| 171 | + async loadTheme(...themes) { |
| 172 | + const resolved = uniqueThemes(themes.flat()); |
| 173 | + if (resolved.length && loadTheme) await loadTheme(...resolved); |
| 174 | + }, |
| 175 | + loadThemeSync(...themes) { |
| 176 | + const resolved = uniqueThemes(themes.flat()); |
| 177 | + if (resolved.length && loadThemeSync) loadThemeSync(...resolved); |
| 178 | + }, |
| 179 | + }; |
| 180 | +} |
| 181 | + |
| 182 | +export async function createHighlighter(options = {}) { |
| 183 | + const themes = uniqueThemes([ |
| 184 | + ...(Array.isArray(options.themes) ? options.themes : []), |
| 185 | + ...Object.values(THEME_BY_NAME), |
| 186 | + ]); |
| 187 | + |
| 188 | + const highlighter = await createHighlighterCore({ |
| 189 | + ...options, |
| 190 | + engine: options.engine || createJavaScriptRegexEngine(), |
| 191 | + langs: LIMITED_SHIKI_LANGUAGES, |
| 192 | + themes, |
| 193 | + }); |
| 194 | + |
| 195 | + return wrapLimitedHighlighter(highlighter); |
| 196 | +} |
0 commit comments