127 lines
4.2 KiB
JavaScript
127 lines
4.2 KiB
JavaScript
|
|
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 };
|