127 lines
4.2 KiB
JavaScript
Raw Normal View History

2026-05-31 13:21:13 +02:00
import { INITIAL, EncodedTokenMetadata } from '@shikijs/vscode-textmate';
function textmateThemeToMonacoTheme(theme) {
let rules = "rules" in theme ? theme.rules : undefined;
if (!rules) {
rules = [];
const themeSettings = theme.settings || theme.tokenColors;
for (const { scope, settings: { foreground, background, fontStyle } } of themeSettings) {
const scopes = Array.isArray(scope) ? scope : [scope];
for (const s of scopes) {
if (s && (foreground || background || fontStyle)) {
rules.push({
token: s,
foreground: normalizeColor(foreground),
background: normalizeColor(background),
fontStyle
});
}
}
}
}
const colors = Object.fromEntries(
Object.entries(theme.colors || {}).map(([key, value]) => [key, `#${normalizeColor(value)}`])
);
return {
base: theme.type === "light" ? "vs" : "vs-dark",
inherit: false,
colors,
rules
};
}
function shikiToMonaco(highlighter, monaco, options = {}) {
const themeMap = /* @__PURE__ */ new Map();
const themeIds = highlighter.getLoadedThemes();
for (const themeId of themeIds) {
const tmTheme = highlighter.getTheme(themeId);
const monacoTheme = textmateThemeToMonacoTheme(tmTheme);
themeMap.set(themeId, monacoTheme);
monaco.editor.defineTheme(themeId, monacoTheme);
}
const colorMap = [];
const colorToScopeMap = /* @__PURE__ */ new Map();
const _setTheme = monaco.editor.setTheme.bind(monaco.editor);
monaco.editor.setTheme = (themeName) => {
const ret = highlighter.setTheme(themeName);
const theme = themeMap.get(themeName);
colorMap.length = ret.colorMap.length;
ret.colorMap.forEach((color, i) => {
colorMap[i] = color;
});
colorToScopeMap.clear();
theme?.rules.forEach((rule) => {
const c = normalizeColor(rule.foreground);
if (c && !colorToScopeMap.has(c))
colorToScopeMap.set(c, rule.token);
});
_setTheme(themeName);
};
monaco.editor.setTheme(themeIds[0]);
function findScopeByColor(color) {
return colorToScopeMap.get(color);
}
const {
tokenizeMaxLineLength = 2e4,
tokenizeTimeLimit = 500
} = options;
const monacoLanguageIds = new Set(monaco.languages.getLanguages().map((l) => l.id));
for (const lang of highlighter.getLoadedLanguages()) {
if (monacoLanguageIds.has(lang)) {
monaco.languages.setTokensProvider(lang, {
getInitialState() {
return new TokenizerState(INITIAL);
},
tokenize(line, state) {
if (line.length >= tokenizeMaxLineLength) {
return {
endState: state,
tokens: [{ startIndex: 0, scopes: "" }]
};
}
const grammar = highlighter.getLanguage(lang);
const result = grammar.tokenizeLine2(line, state.ruleStack, tokenizeTimeLimit);
if (result.stoppedEarly)
console.warn(`Time limit reached when tokenizing line: ${line.substring(0, 100)}`);
const tokensLength = result.tokens.length / 2;
const tokens = [];
for (let j = 0; j < tokensLength; j++) {
const startIndex = result.tokens[2 * j];
const metadata = result.tokens[2 * j + 1];
const color = normalizeColor(colorMap[EncodedTokenMetadata.getForeground(metadata)] || "");
const scope = findScopeByColor(color) || "";
tokens.push({ startIndex, scopes: scope });
}
return { endState: new TokenizerState(result.ruleStack), tokens };
}
});
}
}
}
class TokenizerState {
constructor(_ruleStack) {
this._ruleStack = _ruleStack;
}
get ruleStack() {
return this._ruleStack;
}
clone() {
return new TokenizerState(this._ruleStack);
}
equals(other) {
if (!other || !(other instanceof TokenizerState) || other !== this || other._ruleStack !== this._ruleStack) {
return false;
}
return true;
}
}
function normalizeColor(color) {
if (!color)
return color;
color = (color.charCodeAt(0) === 35 ? color.slice(1) : color).toLowerCase();
if (color.length === 3 || color.length === 4)
color = color.split("").map((c) => c + c).join("");
return color;
}
export { shikiToMonaco, textmateThemeToMonacoTheme };