2228 lines
71 KiB
JavaScript
2228 lines
71 KiB
JavaScript
import { ShikiError as ShikiError$1 } from '@shikijs/types';
|
|
export * from '@shikijs/types';
|
|
import { createOnigurumaEngine as createOnigurumaEngine$1, loadWasm as loadWasm$1, getDefaultWasmLoader } from '@shikijs/engine-oniguruma';
|
|
import { w as warnDeprecated } from './shared/core.Bn_XU0Iv.mjs';
|
|
export { e as enableDeprecationWarnings } from './shared/core.Bn_XU0Iv.mjs';
|
|
import { FontStyle, INITIAL, EncodedTokenMetadata, Registry as Registry$1, Theme } from '@shikijs/vscode-textmate';
|
|
export { FontStyle, EncodedTokenMetadata as StackElementMetadata } from '@shikijs/vscode-textmate';
|
|
import { toHtml } from 'hast-util-to-html';
|
|
export { toHtml as hastToHtml } from 'hast-util-to-html';
|
|
import { createJavaScriptRegexEngine as createJavaScriptRegexEngine$1, defaultJavaScriptRegexConstructor as defaultJavaScriptRegexConstructor$1 } from '@shikijs/engine-javascript';
|
|
|
|
function createOnigurumaEngine(options) {
|
|
warnDeprecated("import `createOnigurumaEngine` from `@shikijs/engine-oniguruma` or `shiki/engine/oniguruma` instead");
|
|
return createOnigurumaEngine$1(options);
|
|
}
|
|
function createWasmOnigEngine(options) {
|
|
warnDeprecated("import `createOnigurumaEngine` from `@shikijs/engine-oniguruma` or `shiki/engine/oniguruma` instead");
|
|
return createOnigurumaEngine$1(options);
|
|
}
|
|
function loadWasm(options) {
|
|
warnDeprecated("import `loadWasm` from `@shikijs/engine-oniguruma` or `shiki/engine/oniguruma` instead");
|
|
return loadWasm$1(options);
|
|
}
|
|
|
|
function toArray(x) {
|
|
return Array.isArray(x) ? x : [x];
|
|
}
|
|
function splitLines(code, preserveEnding = false) {
|
|
const parts = code.split(/(\r?\n)/g);
|
|
let index = 0;
|
|
const lines = [];
|
|
for (let i = 0; i < parts.length; i += 2) {
|
|
const line = preserveEnding ? parts[i] + (parts[i + 1] || "") : parts[i];
|
|
lines.push([line, index]);
|
|
index += parts[i].length;
|
|
index += parts[i + 1]?.length || 0;
|
|
}
|
|
return lines;
|
|
}
|
|
function isPlainLang(lang) {
|
|
return !lang || ["plaintext", "txt", "text", "plain"].includes(lang);
|
|
}
|
|
function isSpecialLang(lang) {
|
|
return lang === "ansi" || isPlainLang(lang);
|
|
}
|
|
function isNoneTheme(theme) {
|
|
return theme === "none";
|
|
}
|
|
function isSpecialTheme(theme) {
|
|
return isNoneTheme(theme);
|
|
}
|
|
function addClassToHast(node, className) {
|
|
if (!className)
|
|
return node;
|
|
node.properties ||= {};
|
|
node.properties.class ||= [];
|
|
if (typeof node.properties.class === "string")
|
|
node.properties.class = node.properties.class.split(/\s+/g);
|
|
if (!Array.isArray(node.properties.class))
|
|
node.properties.class = [];
|
|
const targets = Array.isArray(className) ? className : className.split(/\s+/g);
|
|
for (const c of targets) {
|
|
if (c && !node.properties.class.includes(c))
|
|
node.properties.class.push(c);
|
|
}
|
|
return node;
|
|
}
|
|
function splitToken(token, offsets) {
|
|
let lastOffset = 0;
|
|
const tokens = [];
|
|
for (const offset of offsets) {
|
|
if (offset > lastOffset) {
|
|
tokens.push({
|
|
...token,
|
|
content: token.content.slice(lastOffset, offset),
|
|
offset: token.offset + lastOffset
|
|
});
|
|
}
|
|
lastOffset = offset;
|
|
}
|
|
if (lastOffset < token.content.length) {
|
|
tokens.push({
|
|
...token,
|
|
content: token.content.slice(lastOffset),
|
|
offset: token.offset + lastOffset
|
|
});
|
|
}
|
|
return tokens;
|
|
}
|
|
function splitTokens(tokens, breakpoints) {
|
|
const sorted = Array.from(breakpoints instanceof Set ? breakpoints : new Set(breakpoints)).sort((a, b) => a - b);
|
|
if (!sorted.length)
|
|
return tokens;
|
|
return tokens.map((line) => {
|
|
return line.flatMap((token) => {
|
|
const breakpointsInToken = sorted.filter((i) => token.offset < i && i < token.offset + token.content.length).map((i) => i - token.offset).sort((a, b) => a - b);
|
|
if (!breakpointsInToken.length)
|
|
return token;
|
|
return splitToken(token, breakpointsInToken);
|
|
});
|
|
});
|
|
}
|
|
async function normalizeGetter(p) {
|
|
return Promise.resolve(typeof p === "function" ? p() : p).then((r) => r.default || r);
|
|
}
|
|
function resolveColorReplacements(theme, options) {
|
|
const replacements = typeof theme === "string" ? {} : { ...theme.colorReplacements };
|
|
const themeName = typeof theme === "string" ? theme : theme.name;
|
|
for (const [key, value] of Object.entries(options?.colorReplacements || {})) {
|
|
if (typeof value === "string")
|
|
replacements[key] = value;
|
|
else if (key === themeName)
|
|
Object.assign(replacements, value);
|
|
}
|
|
return replacements;
|
|
}
|
|
function applyColorReplacements(color, replacements) {
|
|
if (!color)
|
|
return color;
|
|
return replacements?.[color?.toLowerCase()] || color;
|
|
}
|
|
function getTokenStyleObject(token) {
|
|
const styles = {};
|
|
if (token.color)
|
|
styles.color = token.color;
|
|
if (token.bgColor)
|
|
styles["background-color"] = token.bgColor;
|
|
if (token.fontStyle) {
|
|
if (token.fontStyle & FontStyle.Italic)
|
|
styles["font-style"] = "italic";
|
|
if (token.fontStyle & FontStyle.Bold)
|
|
styles["font-weight"] = "bold";
|
|
if (token.fontStyle & FontStyle.Underline)
|
|
styles["text-decoration"] = "underline";
|
|
}
|
|
return styles;
|
|
}
|
|
function stringifyTokenStyle(token) {
|
|
if (typeof token === "string")
|
|
return token;
|
|
return Object.entries(token).map(([key, value]) => `${key}:${value}`).join(";");
|
|
}
|
|
function createPositionConverter(code) {
|
|
const lines = splitLines(code, true).map(([line]) => line);
|
|
function indexToPos(index) {
|
|
if (index === code.length) {
|
|
return {
|
|
line: lines.length - 1,
|
|
character: lines[lines.length - 1].length
|
|
};
|
|
}
|
|
let character = index;
|
|
let line = 0;
|
|
for (const lineText of lines) {
|
|
if (character < lineText.length)
|
|
break;
|
|
character -= lineText.length;
|
|
line++;
|
|
}
|
|
return { line, character };
|
|
}
|
|
function posToIndex(line, character) {
|
|
let index = 0;
|
|
for (let i = 0; i < line; i++)
|
|
index += lines[i].length;
|
|
index += character;
|
|
return index;
|
|
}
|
|
return {
|
|
lines,
|
|
indexToPos,
|
|
posToIndex
|
|
};
|
|
}
|
|
|
|
class ShikiError extends Error {
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = "ShikiError";
|
|
}
|
|
}
|
|
|
|
const _grammarStateMap = /* @__PURE__ */ new WeakMap();
|
|
function setLastGrammarStateToMap(keys, state) {
|
|
_grammarStateMap.set(keys, state);
|
|
}
|
|
function getLastGrammarStateFromMap(keys) {
|
|
return _grammarStateMap.get(keys);
|
|
}
|
|
class GrammarState {
|
|
/**
|
|
* Theme to Stack mapping
|
|
*/
|
|
_stacks = {};
|
|
lang;
|
|
get themes() {
|
|
return Object.keys(this._stacks);
|
|
}
|
|
get theme() {
|
|
return this.themes[0];
|
|
}
|
|
get _stack() {
|
|
return this._stacks[this.theme];
|
|
}
|
|
/**
|
|
* Static method to create a initial grammar state.
|
|
*/
|
|
static initial(lang, themes) {
|
|
return new GrammarState(
|
|
Object.fromEntries(toArray(themes).map((theme) => [theme, INITIAL])),
|
|
lang
|
|
);
|
|
}
|
|
constructor(...args) {
|
|
if (args.length === 2) {
|
|
const [stacksMap, lang] = args;
|
|
this.lang = lang;
|
|
this._stacks = stacksMap;
|
|
} else {
|
|
const [stack, lang, theme] = args;
|
|
this.lang = lang;
|
|
this._stacks = { [theme]: stack };
|
|
}
|
|
}
|
|
/**
|
|
* Get the internal stack object.
|
|
* @internal
|
|
*/
|
|
getInternalStack(theme = this.theme) {
|
|
return this._stacks[theme];
|
|
}
|
|
/**
|
|
* @deprecated use `getScopes` instead
|
|
*/
|
|
get scopes() {
|
|
warnDeprecated("GrammarState.scopes is deprecated, use GrammarState.getScopes() instead");
|
|
return getScopes(this._stacks[this.theme]);
|
|
}
|
|
getScopes(theme = this.theme) {
|
|
return getScopes(this._stacks[theme]);
|
|
}
|
|
toJSON() {
|
|
return {
|
|
lang: this.lang,
|
|
theme: this.theme,
|
|
themes: this.themes,
|
|
scopes: this.scopes
|
|
};
|
|
}
|
|
}
|
|
function getScopes(stack) {
|
|
const scopes = [];
|
|
const visited = /* @__PURE__ */ new Set();
|
|
function pushScope(stack2) {
|
|
if (visited.has(stack2))
|
|
return;
|
|
visited.add(stack2);
|
|
const name = stack2?.nameScopesList?.scopeName;
|
|
if (name)
|
|
scopes.push(name);
|
|
if (stack2.parent)
|
|
pushScope(stack2.parent);
|
|
}
|
|
pushScope(stack);
|
|
return scopes;
|
|
}
|
|
function getGrammarStack(state, theme) {
|
|
if (!(state instanceof GrammarState))
|
|
throw new ShikiError("Invalid grammar state");
|
|
return state.getInternalStack(theme);
|
|
}
|
|
|
|
function transformerDecorations() {
|
|
const map = /* @__PURE__ */ new WeakMap();
|
|
function getContext(shiki) {
|
|
if (!map.has(shiki.meta)) {
|
|
let normalizePosition = function(p) {
|
|
if (typeof p === "number") {
|
|
if (p < 0 || p > shiki.source.length)
|
|
throw new ShikiError(`Invalid decoration offset: ${p}. Code length: ${shiki.source.length}`);
|
|
return {
|
|
...converter.indexToPos(p),
|
|
offset: p
|
|
};
|
|
} else {
|
|
const line = converter.lines[p.line];
|
|
if (line === undefined)
|
|
throw new ShikiError(`Invalid decoration position ${JSON.stringify(p)}. Lines length: ${converter.lines.length}`);
|
|
if (p.character < 0 || p.character > line.length)
|
|
throw new ShikiError(`Invalid decoration position ${JSON.stringify(p)}. Line ${p.line} length: ${line.length}`);
|
|
return {
|
|
...p,
|
|
offset: converter.posToIndex(p.line, p.character)
|
|
};
|
|
}
|
|
};
|
|
const converter = createPositionConverter(shiki.source);
|
|
const decorations = (shiki.options.decorations || []).map((d) => ({
|
|
...d,
|
|
start: normalizePosition(d.start),
|
|
end: normalizePosition(d.end)
|
|
}));
|
|
verifyIntersections(decorations);
|
|
map.set(shiki.meta, {
|
|
decorations,
|
|
converter,
|
|
source: shiki.source
|
|
});
|
|
}
|
|
return map.get(shiki.meta);
|
|
}
|
|
return {
|
|
name: "shiki:decorations",
|
|
tokens(tokens) {
|
|
if (!this.options.decorations?.length)
|
|
return;
|
|
const ctx = getContext(this);
|
|
const breakpoints = ctx.decorations.flatMap((d) => [d.start.offset, d.end.offset]);
|
|
const splitted = splitTokens(tokens, breakpoints);
|
|
return splitted;
|
|
},
|
|
code(codeEl) {
|
|
if (!this.options.decorations?.length)
|
|
return;
|
|
const ctx = getContext(this);
|
|
const lines = Array.from(codeEl.children).filter((i) => i.type === "element" && i.tagName === "span");
|
|
if (lines.length !== ctx.converter.lines.length)
|
|
throw new ShikiError(`Number of lines in code element (${lines.length}) does not match the number of lines in the source (${ctx.converter.lines.length}). Failed to apply decorations.`);
|
|
function applyLineSection(line, start, end, decoration) {
|
|
const lineEl = lines[line];
|
|
let text = "";
|
|
let startIndex = -1;
|
|
let endIndex = -1;
|
|
if (start === 0)
|
|
startIndex = 0;
|
|
if (end === 0)
|
|
endIndex = 0;
|
|
if (end === Number.POSITIVE_INFINITY)
|
|
endIndex = lineEl.children.length;
|
|
if (startIndex === -1 || endIndex === -1) {
|
|
for (let i = 0; i < lineEl.children.length; i++) {
|
|
text += stringify(lineEl.children[i]);
|
|
if (startIndex === -1 && text.length === start)
|
|
startIndex = i + 1;
|
|
if (endIndex === -1 && text.length === end)
|
|
endIndex = i + 1;
|
|
}
|
|
}
|
|
if (startIndex === -1)
|
|
throw new ShikiError(`Failed to find start index for decoration ${JSON.stringify(decoration.start)}`);
|
|
if (endIndex === -1)
|
|
throw new ShikiError(`Failed to find end index for decoration ${JSON.stringify(decoration.end)}`);
|
|
const children = lineEl.children.slice(startIndex, endIndex);
|
|
if (!decoration.alwaysWrap && children.length === lineEl.children.length) {
|
|
applyDecoration(lineEl, decoration, "line");
|
|
} else if (!decoration.alwaysWrap && children.length === 1 && children[0].type === "element") {
|
|
applyDecoration(children[0], decoration, "token");
|
|
} else {
|
|
const wrapper = {
|
|
type: "element",
|
|
tagName: "span",
|
|
properties: {},
|
|
children
|
|
};
|
|
applyDecoration(wrapper, decoration, "wrapper");
|
|
lineEl.children.splice(startIndex, children.length, wrapper);
|
|
}
|
|
}
|
|
function applyLine(line, decoration) {
|
|
lines[line] = applyDecoration(lines[line], decoration, "line");
|
|
}
|
|
function applyDecoration(el, decoration, type) {
|
|
const properties = decoration.properties || {};
|
|
const transform = decoration.transform || ((i) => i);
|
|
el.tagName = decoration.tagName || "span";
|
|
el.properties = {
|
|
...el.properties,
|
|
...properties,
|
|
class: el.properties.class
|
|
};
|
|
if (decoration.properties?.class)
|
|
addClassToHast(el, decoration.properties.class);
|
|
el = transform(el, type) || el;
|
|
return el;
|
|
}
|
|
const lineApplies = [];
|
|
const sorted = ctx.decorations.sort((a, b) => b.start.offset - a.start.offset);
|
|
for (const decoration of sorted) {
|
|
const { start, end } = decoration;
|
|
if (start.line === end.line) {
|
|
applyLineSection(start.line, start.character, end.character, decoration);
|
|
} else if (start.line < end.line) {
|
|
applyLineSection(start.line, start.character, Number.POSITIVE_INFINITY, decoration);
|
|
for (let i = start.line + 1; i < end.line; i++)
|
|
lineApplies.unshift(() => applyLine(i, decoration));
|
|
applyLineSection(end.line, 0, end.character, decoration);
|
|
}
|
|
}
|
|
lineApplies.forEach((i) => i());
|
|
}
|
|
};
|
|
}
|
|
function verifyIntersections(items) {
|
|
for (let i = 0; i < items.length; i++) {
|
|
const foo = items[i];
|
|
if (foo.start.offset > foo.end.offset)
|
|
throw new ShikiError(`Invalid decoration range: ${JSON.stringify(foo.start)} - ${JSON.stringify(foo.end)}`);
|
|
for (let j = i + 1; j < items.length; j++) {
|
|
const bar = items[j];
|
|
const isFooHasBarStart = foo.start.offset < bar.start.offset && bar.start.offset < foo.end.offset;
|
|
const isFooHasBarEnd = foo.start.offset < bar.end.offset && bar.end.offset < foo.end.offset;
|
|
const isBarHasFooStart = bar.start.offset < foo.start.offset && foo.start.offset < bar.end.offset;
|
|
const isBarHasFooEnd = bar.start.offset < foo.end.offset && foo.end.offset < bar.end.offset;
|
|
if (isFooHasBarStart || isFooHasBarEnd || isBarHasFooStart || isBarHasFooEnd) {
|
|
if (isFooHasBarEnd && isFooHasBarEnd)
|
|
continue;
|
|
if (isBarHasFooStart && isBarHasFooEnd)
|
|
continue;
|
|
throw new ShikiError(`Decorations ${JSON.stringify(foo.start)} and ${JSON.stringify(bar.start)} intersect.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function stringify(el) {
|
|
if (el.type === "text")
|
|
return el.value;
|
|
if (el.type === "element")
|
|
return el.children.map(stringify).join("");
|
|
return "";
|
|
}
|
|
|
|
const builtInTransformers = [
|
|
/* @__PURE__ */ transformerDecorations()
|
|
];
|
|
function getTransformers(options) {
|
|
return [
|
|
...options.transformers || [],
|
|
...builtInTransformers
|
|
];
|
|
}
|
|
|
|
// src/colors.ts
|
|
var namedColors = [
|
|
"black",
|
|
"red",
|
|
"green",
|
|
"yellow",
|
|
"blue",
|
|
"magenta",
|
|
"cyan",
|
|
"white",
|
|
"brightBlack",
|
|
"brightRed",
|
|
"brightGreen",
|
|
"brightYellow",
|
|
"brightBlue",
|
|
"brightMagenta",
|
|
"brightCyan",
|
|
"brightWhite"
|
|
];
|
|
|
|
// src/decorations.ts
|
|
var decorations = {
|
|
1: "bold",
|
|
2: "dim",
|
|
3: "italic",
|
|
4: "underline",
|
|
7: "reverse",
|
|
9: "strikethrough"
|
|
};
|
|
|
|
// src/parser.ts
|
|
function findSequence(value, position) {
|
|
const nextEscape = value.indexOf("\x1B[", position);
|
|
if (nextEscape !== -1) {
|
|
const nextClose = value.indexOf("m", nextEscape);
|
|
return {
|
|
sequence: value.substring(nextEscape + 2, nextClose).split(";"),
|
|
startPosition: nextEscape,
|
|
position: nextClose + 1
|
|
};
|
|
}
|
|
return {
|
|
position: value.length
|
|
};
|
|
}
|
|
function parseColor(sequence, index) {
|
|
let offset = 1;
|
|
const colorMode = sequence[index + offset++];
|
|
let color;
|
|
if (colorMode === "2") {
|
|
const rgb = [
|
|
sequence[index + offset++],
|
|
sequence[index + offset++],
|
|
sequence[index + offset]
|
|
].map((x) => Number.parseInt(x));
|
|
if (rgb.length === 3 && !rgb.some((x) => Number.isNaN(x))) {
|
|
color = {
|
|
type: "rgb",
|
|
rgb
|
|
};
|
|
}
|
|
} else if (colorMode === "5") {
|
|
const colorIndex = Number.parseInt(sequence[index + offset]);
|
|
if (!Number.isNaN(colorIndex)) {
|
|
color = { type: "table", index: Number(colorIndex) };
|
|
}
|
|
}
|
|
return [offset, color];
|
|
}
|
|
function parseSequence(sequence) {
|
|
const commands = [];
|
|
for (let i = 0; i < sequence.length; i++) {
|
|
const code = sequence[i];
|
|
const codeInt = Number.parseInt(code);
|
|
if (Number.isNaN(codeInt))
|
|
continue;
|
|
if (codeInt === 0) {
|
|
commands.push({ type: "resetAll" });
|
|
} else if (codeInt <= 9) {
|
|
const decoration = decorations[codeInt];
|
|
if (decoration) {
|
|
commands.push({
|
|
type: "setDecoration",
|
|
value: decorations[codeInt]
|
|
});
|
|
}
|
|
} else if (codeInt <= 29) {
|
|
const decoration = decorations[codeInt - 20];
|
|
if (decoration) {
|
|
commands.push({
|
|
type: "resetDecoration",
|
|
value: decoration
|
|
});
|
|
}
|
|
} else if (codeInt <= 37) {
|
|
commands.push({
|
|
type: "setForegroundColor",
|
|
value: { type: "named", name: namedColors[codeInt - 30] }
|
|
});
|
|
} else if (codeInt === 38) {
|
|
const [offset, color] = parseColor(sequence, i);
|
|
if (color) {
|
|
commands.push({
|
|
type: "setForegroundColor",
|
|
value: color
|
|
});
|
|
}
|
|
i += offset;
|
|
} else if (codeInt === 39) {
|
|
commands.push({
|
|
type: "resetForegroundColor"
|
|
});
|
|
} else if (codeInt <= 47) {
|
|
commands.push({
|
|
type: "setBackgroundColor",
|
|
value: { type: "named", name: namedColors[codeInt - 40] }
|
|
});
|
|
} else if (codeInt === 48) {
|
|
const [offset, color] = parseColor(sequence, i);
|
|
if (color) {
|
|
commands.push({
|
|
type: "setBackgroundColor",
|
|
value: color
|
|
});
|
|
}
|
|
i += offset;
|
|
} else if (codeInt === 49) {
|
|
commands.push({
|
|
type: "resetBackgroundColor"
|
|
});
|
|
} else if (codeInt >= 90 && codeInt <= 97) {
|
|
commands.push({
|
|
type: "setForegroundColor",
|
|
value: { type: "named", name: namedColors[codeInt - 90 + 8] }
|
|
});
|
|
} else if (codeInt >= 100 && codeInt <= 107) {
|
|
commands.push({
|
|
type: "setBackgroundColor",
|
|
value: { type: "named", name: namedColors[codeInt - 100 + 8] }
|
|
});
|
|
}
|
|
}
|
|
return commands;
|
|
}
|
|
function createAnsiSequenceParser() {
|
|
let foreground = null;
|
|
let background = null;
|
|
let decorations2 = /* @__PURE__ */ new Set();
|
|
return {
|
|
parse(value) {
|
|
const tokens = [];
|
|
let position = 0;
|
|
do {
|
|
const findResult = findSequence(value, position);
|
|
const text = findResult.sequence ? value.substring(position, findResult.startPosition) : value.substring(position);
|
|
if (text.length > 0) {
|
|
tokens.push({
|
|
value: text,
|
|
foreground,
|
|
background,
|
|
decorations: new Set(decorations2)
|
|
});
|
|
}
|
|
if (findResult.sequence) {
|
|
const commands = parseSequence(findResult.sequence);
|
|
for (const styleToken of commands) {
|
|
if (styleToken.type === "resetAll") {
|
|
foreground = null;
|
|
background = null;
|
|
decorations2.clear();
|
|
} else if (styleToken.type === "resetForegroundColor") {
|
|
foreground = null;
|
|
} else if (styleToken.type === "resetBackgroundColor") {
|
|
background = null;
|
|
} else if (styleToken.type === "resetDecoration") {
|
|
decorations2.delete(styleToken.value);
|
|
}
|
|
}
|
|
for (const styleToken of commands) {
|
|
if (styleToken.type === "setForegroundColor") {
|
|
foreground = styleToken.value;
|
|
} else if (styleToken.type === "setBackgroundColor") {
|
|
background = styleToken.value;
|
|
} else if (styleToken.type === "setDecoration") {
|
|
decorations2.add(styleToken.value);
|
|
}
|
|
}
|
|
}
|
|
position = findResult.position;
|
|
} while (position < value.length);
|
|
return tokens;
|
|
}
|
|
};
|
|
}
|
|
|
|
// src/palette.ts
|
|
var defaultNamedColorsMap = {
|
|
black: "#000000",
|
|
red: "#bb0000",
|
|
green: "#00bb00",
|
|
yellow: "#bbbb00",
|
|
blue: "#0000bb",
|
|
magenta: "#ff00ff",
|
|
cyan: "#00bbbb",
|
|
white: "#eeeeee",
|
|
brightBlack: "#555555",
|
|
brightRed: "#ff5555",
|
|
brightGreen: "#00ff00",
|
|
brightYellow: "#ffff55",
|
|
brightBlue: "#5555ff",
|
|
brightMagenta: "#ff55ff",
|
|
brightCyan: "#55ffff",
|
|
brightWhite: "#ffffff"
|
|
};
|
|
function createColorPalette(namedColorsMap = defaultNamedColorsMap) {
|
|
function namedColor(name) {
|
|
return namedColorsMap[name];
|
|
}
|
|
function rgbColor(rgb) {
|
|
return `#${rgb.map((x) => Math.max(0, Math.min(x, 255)).toString(16).padStart(2, "0")).join("")}`;
|
|
}
|
|
let colorTable;
|
|
function getColorTable() {
|
|
if (colorTable) {
|
|
return colorTable;
|
|
}
|
|
colorTable = [];
|
|
for (let i = 0; i < namedColors.length; i++) {
|
|
colorTable.push(namedColor(namedColors[i]));
|
|
}
|
|
let levels = [0, 95, 135, 175, 215, 255];
|
|
for (let r = 0; r < 6; r++) {
|
|
for (let g = 0; g < 6; g++) {
|
|
for (let b = 0; b < 6; b++) {
|
|
colorTable.push(rgbColor([levels[r], levels[g], levels[b]]));
|
|
}
|
|
}
|
|
}
|
|
let level = 8;
|
|
for (let i = 0; i < 24; i++, level += 10) {
|
|
colorTable.push(rgbColor([level, level, level]));
|
|
}
|
|
return colorTable;
|
|
}
|
|
function tableColor(index) {
|
|
return getColorTable()[index];
|
|
}
|
|
function value(color) {
|
|
switch (color.type) {
|
|
case "named":
|
|
return namedColor(color.name);
|
|
case "rgb":
|
|
return rgbColor(color.rgb);
|
|
case "table":
|
|
return tableColor(color.index);
|
|
}
|
|
}
|
|
return {
|
|
value
|
|
};
|
|
}
|
|
|
|
function tokenizeAnsiWithTheme(theme, fileContents, options) {
|
|
const colorReplacements = resolveColorReplacements(theme, options);
|
|
const lines = splitLines(fileContents);
|
|
const colorPalette = createColorPalette(
|
|
Object.fromEntries(
|
|
namedColors.map((name) => [
|
|
name,
|
|
theme.colors?.[`terminal.ansi${name[0].toUpperCase()}${name.substring(1)}`]
|
|
])
|
|
)
|
|
);
|
|
const parser = createAnsiSequenceParser();
|
|
return lines.map(
|
|
(line) => parser.parse(line[0]).map((token) => {
|
|
let color;
|
|
let bgColor;
|
|
if (token.decorations.has("reverse")) {
|
|
color = token.background ? colorPalette.value(token.background) : theme.bg;
|
|
bgColor = token.foreground ? colorPalette.value(token.foreground) : theme.fg;
|
|
} else {
|
|
color = token.foreground ? colorPalette.value(token.foreground) : theme.fg;
|
|
bgColor = token.background ? colorPalette.value(token.background) : undefined;
|
|
}
|
|
color = applyColorReplacements(color, colorReplacements);
|
|
bgColor = applyColorReplacements(bgColor, colorReplacements);
|
|
if (token.decorations.has("dim"))
|
|
color = dimColor(color);
|
|
let fontStyle = FontStyle.None;
|
|
if (token.decorations.has("bold"))
|
|
fontStyle |= FontStyle.Bold;
|
|
if (token.decorations.has("italic"))
|
|
fontStyle |= FontStyle.Italic;
|
|
if (token.decorations.has("underline"))
|
|
fontStyle |= FontStyle.Underline;
|
|
return {
|
|
content: token.value,
|
|
offset: line[1],
|
|
// TODO: more accurate offset? might need to fork ansi-sequence-parser
|
|
color,
|
|
bgColor,
|
|
fontStyle
|
|
};
|
|
})
|
|
);
|
|
}
|
|
function dimColor(color) {
|
|
const hexMatch = color.match(/#([0-9a-f]{3})([0-9a-f]{3})?([0-9a-f]{2})?/);
|
|
if (hexMatch) {
|
|
if (hexMatch[3]) {
|
|
const alpha = Math.round(Number.parseInt(hexMatch[3], 16) / 2).toString(16).padStart(2, "0");
|
|
return `#${hexMatch[1]}${hexMatch[2]}${alpha}`;
|
|
} else if (hexMatch[2]) {
|
|
return `#${hexMatch[1]}${hexMatch[2]}80`;
|
|
} else {
|
|
return `#${Array.from(hexMatch[1]).map((x) => `${x}${x}`).join("")}80`;
|
|
}
|
|
}
|
|
const cssVarMatch = color.match(/var\((--[\w-]+-ansi-[\w-]+)\)/);
|
|
if (cssVarMatch)
|
|
return `var(${cssVarMatch[1]}-dim)`;
|
|
return color;
|
|
}
|
|
|
|
function codeToTokensBase(internal, code, options = {}) {
|
|
const {
|
|
lang = "text",
|
|
theme: themeName = internal.getLoadedThemes()[0]
|
|
} = options;
|
|
if (isPlainLang(lang) || isNoneTheme(themeName))
|
|
return splitLines(code).map((line) => [{ content: line[0], offset: line[1] }]);
|
|
const { theme, colorMap } = internal.setTheme(themeName);
|
|
if (lang === "ansi")
|
|
return tokenizeAnsiWithTheme(theme, code, options);
|
|
const _grammar = internal.getLanguage(lang);
|
|
if (options.grammarState) {
|
|
if (options.grammarState.lang !== _grammar.name) {
|
|
throw new ShikiError$1(`Grammar state language "${options.grammarState.lang}" does not match highlight language "${_grammar.name}"`);
|
|
}
|
|
if (!options.grammarState.themes.includes(theme.name)) {
|
|
throw new ShikiError$1(`Grammar state themes "${options.grammarState.themes}" do not contain highlight theme "${theme.name}"`);
|
|
}
|
|
}
|
|
return tokenizeWithTheme(code, _grammar, theme, colorMap, options);
|
|
}
|
|
function getLastGrammarState(...args) {
|
|
if (args.length === 2) {
|
|
return getLastGrammarStateFromMap(args[1]);
|
|
}
|
|
const [internal, code, options = {}] = args;
|
|
const {
|
|
lang = "text",
|
|
theme: themeName = internal.getLoadedThemes()[0]
|
|
} = options;
|
|
if (isPlainLang(lang) || isNoneTheme(themeName))
|
|
throw new ShikiError$1("Plain language does not have grammar state");
|
|
if (lang === "ansi")
|
|
throw new ShikiError$1("ANSI language does not have grammar state");
|
|
const { theme, colorMap } = internal.setTheme(themeName);
|
|
const _grammar = internal.getLanguage(lang);
|
|
return new GrammarState(
|
|
_tokenizeWithTheme(code, _grammar, theme, colorMap, options).stateStack,
|
|
_grammar.name,
|
|
theme.name
|
|
);
|
|
}
|
|
function tokenizeWithTheme(code, grammar, theme, colorMap, options) {
|
|
const result = _tokenizeWithTheme(code, grammar, theme, colorMap, options);
|
|
const grammarState = new GrammarState(
|
|
_tokenizeWithTheme(code, grammar, theme, colorMap, options).stateStack,
|
|
grammar.name,
|
|
theme.name
|
|
);
|
|
setLastGrammarStateToMap(result.tokens, grammarState);
|
|
return result.tokens;
|
|
}
|
|
function _tokenizeWithTheme(code, grammar, theme, colorMap, options) {
|
|
const colorReplacements = resolveColorReplacements(theme, options);
|
|
const {
|
|
tokenizeMaxLineLength = 0,
|
|
tokenizeTimeLimit = 500
|
|
} = options;
|
|
const lines = splitLines(code);
|
|
let stateStack = options.grammarState ? getGrammarStack(options.grammarState, theme.name) ?? INITIAL : options.grammarContextCode != null ? _tokenizeWithTheme(
|
|
options.grammarContextCode,
|
|
grammar,
|
|
theme,
|
|
colorMap,
|
|
{
|
|
...options,
|
|
grammarState: undefined,
|
|
grammarContextCode: undefined
|
|
}
|
|
).stateStack : INITIAL;
|
|
let actual = [];
|
|
const final = [];
|
|
for (let i = 0, len = lines.length; i < len; i++) {
|
|
const [line, lineOffset] = lines[i];
|
|
if (line === "") {
|
|
actual = [];
|
|
final.push([]);
|
|
continue;
|
|
}
|
|
if (tokenizeMaxLineLength > 0 && line.length >= tokenizeMaxLineLength) {
|
|
actual = [];
|
|
final.push([{
|
|
content: line,
|
|
offset: lineOffset,
|
|
color: "",
|
|
fontStyle: 0
|
|
}]);
|
|
continue;
|
|
}
|
|
let resultWithScopes;
|
|
let tokensWithScopes;
|
|
let tokensWithScopesIndex;
|
|
if (options.includeExplanation) {
|
|
resultWithScopes = grammar.tokenizeLine(line, stateStack);
|
|
tokensWithScopes = resultWithScopes.tokens;
|
|
tokensWithScopesIndex = 0;
|
|
}
|
|
const result = grammar.tokenizeLine2(line, stateStack, tokenizeTimeLimit);
|
|
const tokensLength = result.tokens.length / 2;
|
|
for (let j = 0; j < tokensLength; j++) {
|
|
const startIndex = result.tokens[2 * j];
|
|
const nextStartIndex = j + 1 < tokensLength ? result.tokens[2 * j + 2] : line.length;
|
|
if (startIndex === nextStartIndex)
|
|
continue;
|
|
const metadata = result.tokens[2 * j + 1];
|
|
const color = applyColorReplacements(
|
|
colorMap[EncodedTokenMetadata.getForeground(metadata)],
|
|
colorReplacements
|
|
);
|
|
const fontStyle = EncodedTokenMetadata.getFontStyle(metadata);
|
|
const token = {
|
|
content: line.substring(startIndex, nextStartIndex),
|
|
offset: lineOffset + startIndex,
|
|
color,
|
|
fontStyle
|
|
};
|
|
if (options.includeExplanation) {
|
|
const themeSettingsSelectors = [];
|
|
if (options.includeExplanation !== "scopeName") {
|
|
for (const setting of theme.settings) {
|
|
let selectors;
|
|
switch (typeof setting.scope) {
|
|
case "string":
|
|
selectors = setting.scope.split(/,/).map((scope) => scope.trim());
|
|
break;
|
|
case "object":
|
|
selectors = setting.scope;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
themeSettingsSelectors.push({
|
|
settings: setting,
|
|
selectors: selectors.map((selector) => selector.split(/ /))
|
|
});
|
|
}
|
|
}
|
|
token.explanation = [];
|
|
let offset = 0;
|
|
while (startIndex + offset < nextStartIndex) {
|
|
const tokenWithScopes = tokensWithScopes[tokensWithScopesIndex];
|
|
const tokenWithScopesText = line.substring(
|
|
tokenWithScopes.startIndex,
|
|
tokenWithScopes.endIndex
|
|
);
|
|
offset += tokenWithScopesText.length;
|
|
token.explanation.push({
|
|
content: tokenWithScopesText,
|
|
scopes: options.includeExplanation === "scopeName" ? explainThemeScopesNameOnly(
|
|
tokenWithScopes.scopes
|
|
) : explainThemeScopesFull(
|
|
themeSettingsSelectors,
|
|
tokenWithScopes.scopes
|
|
)
|
|
});
|
|
tokensWithScopesIndex += 1;
|
|
}
|
|
}
|
|
actual.push(token);
|
|
}
|
|
final.push(actual);
|
|
actual = [];
|
|
stateStack = result.ruleStack;
|
|
}
|
|
return {
|
|
tokens: final,
|
|
stateStack
|
|
};
|
|
}
|
|
function explainThemeScopesNameOnly(scopes) {
|
|
return scopes.map((scope) => ({ scopeName: scope }));
|
|
}
|
|
function explainThemeScopesFull(themeSelectors, scopes) {
|
|
const result = [];
|
|
for (let i = 0, len = scopes.length; i < len; i++) {
|
|
const scope = scopes[i];
|
|
result[i] = {
|
|
scopeName: scope,
|
|
themeMatches: explainThemeScope(themeSelectors, scope, scopes.slice(0, i))
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
function matchesOne(selector, scope) {
|
|
return selector === scope || scope.substring(0, selector.length) === selector && scope[selector.length] === ".";
|
|
}
|
|
function matches(selectors, scope, parentScopes) {
|
|
if (!matchesOne(selectors[selectors.length - 1], scope))
|
|
return false;
|
|
let selectorParentIndex = selectors.length - 2;
|
|
let parentIndex = parentScopes.length - 1;
|
|
while (selectorParentIndex >= 0 && parentIndex >= 0) {
|
|
if (matchesOne(selectors[selectorParentIndex], parentScopes[parentIndex]))
|
|
selectorParentIndex -= 1;
|
|
parentIndex -= 1;
|
|
}
|
|
if (selectorParentIndex === -1)
|
|
return true;
|
|
return false;
|
|
}
|
|
function explainThemeScope(themeSettingsSelectors, scope, parentScopes) {
|
|
const result = [];
|
|
for (const { selectors, settings } of themeSettingsSelectors) {
|
|
for (const selectorPieces of selectors) {
|
|
if (matches(selectorPieces, scope, parentScopes)) {
|
|
result.push(settings);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function codeToTokensWithThemes(internal, code, options) {
|
|
const themes = Object.entries(options.themes).filter((i) => i[1]).map((i) => ({ color: i[0], theme: i[1] }));
|
|
const themedTokens = themes.map((t) => {
|
|
const tokens2 = codeToTokensBase(internal, code, {
|
|
...options,
|
|
theme: t.theme
|
|
});
|
|
const state = getLastGrammarStateFromMap(tokens2);
|
|
const theme = typeof t.theme === "string" ? t.theme : t.theme.name;
|
|
return {
|
|
tokens: tokens2,
|
|
state,
|
|
theme
|
|
};
|
|
});
|
|
const tokens = syncThemesTokenization(
|
|
...themedTokens.map((i) => i.tokens)
|
|
);
|
|
const mergedTokens = tokens[0].map(
|
|
(line, lineIdx) => line.map((_token, tokenIdx) => {
|
|
const mergedToken = {
|
|
content: _token.content,
|
|
variants: {},
|
|
offset: _token.offset
|
|
};
|
|
if ("includeExplanation" in options && options.includeExplanation) {
|
|
mergedToken.explanation = _token.explanation;
|
|
}
|
|
tokens.forEach((t, themeIdx) => {
|
|
const {
|
|
content: _,
|
|
explanation: __,
|
|
offset: ___,
|
|
...styles
|
|
} = t[lineIdx][tokenIdx];
|
|
mergedToken.variants[themes[themeIdx].color] = styles;
|
|
});
|
|
return mergedToken;
|
|
})
|
|
);
|
|
const mergedGrammarState = themedTokens[0].state ? new GrammarState(
|
|
Object.fromEntries(themedTokens.map((s) => [s.theme, s.state?.getInternalStack(s.theme)])),
|
|
themedTokens[0].state.lang
|
|
) : undefined;
|
|
if (mergedGrammarState)
|
|
setLastGrammarStateToMap(mergedTokens, mergedGrammarState);
|
|
return mergedTokens;
|
|
}
|
|
function syncThemesTokenization(...themes) {
|
|
const outThemes = themes.map(() => []);
|
|
const count = themes.length;
|
|
for (let i = 0; i < themes[0].length; i++) {
|
|
const lines = themes.map((t) => t[i]);
|
|
const outLines = outThemes.map(() => []);
|
|
outThemes.forEach((t, i2) => t.push(outLines[i2]));
|
|
const indexes = lines.map(() => 0);
|
|
const current = lines.map((l) => l[0]);
|
|
while (current.every((t) => t)) {
|
|
const minLength = Math.min(...current.map((t) => t.content.length));
|
|
for (let n = 0; n < count; n++) {
|
|
const token = current[n];
|
|
if (token.content.length === minLength) {
|
|
outLines[n].push(token);
|
|
indexes[n] += 1;
|
|
current[n] = lines[n][indexes[n]];
|
|
} else {
|
|
outLines[n].push({
|
|
...token,
|
|
content: token.content.slice(0, minLength)
|
|
});
|
|
current[n] = {
|
|
...token,
|
|
content: token.content.slice(minLength),
|
|
offset: token.offset + minLength
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return outThemes;
|
|
}
|
|
|
|
function codeToTokens(internal, code, options) {
|
|
let bg;
|
|
let fg;
|
|
let tokens;
|
|
let themeName;
|
|
let rootStyle;
|
|
let grammarState;
|
|
if ("themes" in options) {
|
|
const {
|
|
defaultColor = "light",
|
|
cssVariablePrefix = "--shiki-"
|
|
} = options;
|
|
const themes = Object.entries(options.themes).filter((i) => i[1]).map((i) => ({ color: i[0], theme: i[1] })).sort((a, b) => a.color === defaultColor ? -1 : b.color === defaultColor ? 1 : 0);
|
|
if (themes.length === 0)
|
|
throw new ShikiError$1("`themes` option must not be empty");
|
|
const themeTokens = codeToTokensWithThemes(
|
|
internal,
|
|
code,
|
|
options
|
|
);
|
|
grammarState = getLastGrammarStateFromMap(themeTokens);
|
|
if (defaultColor && !themes.find((t) => t.color === defaultColor))
|
|
throw new ShikiError$1(`\`themes\` option must contain the defaultColor key \`${defaultColor}\``);
|
|
const themeRegs = themes.map((t) => internal.getTheme(t.theme));
|
|
const themesOrder = themes.map((t) => t.color);
|
|
tokens = themeTokens.map((line) => line.map((token) => mergeToken(token, themesOrder, cssVariablePrefix, defaultColor)));
|
|
if (grammarState)
|
|
setLastGrammarStateToMap(tokens, grammarState);
|
|
const themeColorReplacements = themes.map((t) => resolveColorReplacements(t.theme, options));
|
|
fg = themes.map((t, idx) => (idx === 0 && defaultColor ? "" : `${cssVariablePrefix + t.color}:`) + (applyColorReplacements(themeRegs[idx].fg, themeColorReplacements[idx]) || "inherit")).join(";");
|
|
bg = themes.map((t, idx) => (idx === 0 && defaultColor ? "" : `${cssVariablePrefix + t.color}-bg:`) + (applyColorReplacements(themeRegs[idx].bg, themeColorReplacements[idx]) || "inherit")).join(";");
|
|
themeName = `shiki-themes ${themeRegs.map((t) => t.name).join(" ")}`;
|
|
rootStyle = defaultColor ? undefined : [fg, bg].join(";");
|
|
} else if ("theme" in options) {
|
|
const colorReplacements = resolveColorReplacements(options.theme, options);
|
|
tokens = codeToTokensBase(
|
|
internal,
|
|
code,
|
|
options
|
|
);
|
|
const _theme = internal.getTheme(options.theme);
|
|
bg = applyColorReplacements(_theme.bg, colorReplacements);
|
|
fg = applyColorReplacements(_theme.fg, colorReplacements);
|
|
themeName = _theme.name;
|
|
grammarState = getLastGrammarStateFromMap(tokens);
|
|
} else {
|
|
throw new ShikiError$1("Invalid options, either `theme` or `themes` must be provided");
|
|
}
|
|
return {
|
|
tokens,
|
|
fg,
|
|
bg,
|
|
themeName,
|
|
rootStyle,
|
|
grammarState
|
|
};
|
|
}
|
|
function mergeToken(merged, variantsOrder, cssVariablePrefix, defaultColor) {
|
|
const token = {
|
|
content: merged.content,
|
|
explanation: merged.explanation,
|
|
offset: merged.offset
|
|
};
|
|
const styles = variantsOrder.map((t) => getTokenStyleObject(merged.variants[t]));
|
|
const styleKeys = new Set(styles.flatMap((t) => Object.keys(t)));
|
|
const mergedStyles = {};
|
|
styles.forEach((cur, idx) => {
|
|
for (const key of styleKeys) {
|
|
const value = cur[key] || "inherit";
|
|
if (idx === 0 && defaultColor) {
|
|
mergedStyles[key] = value;
|
|
} else {
|
|
const keyName = key === "color" ? "" : key === "background-color" ? "-bg" : `-${key}`;
|
|
const varKey = cssVariablePrefix + variantsOrder[idx] + (key === "color" ? "" : keyName);
|
|
mergedStyles[varKey] = value;
|
|
}
|
|
}
|
|
});
|
|
token.htmlStyle = mergedStyles;
|
|
return token;
|
|
}
|
|
|
|
function codeToHast(internal, code, options, transformerContext = {
|
|
meta: {},
|
|
options,
|
|
codeToHast: (_code, _options) => codeToHast(internal, _code, _options),
|
|
codeToTokens: (_code, _options) => codeToTokens(internal, _code, _options)
|
|
}) {
|
|
let input = code;
|
|
for (const transformer of getTransformers(options))
|
|
input = transformer.preprocess?.call(transformerContext, input, options) || input;
|
|
let {
|
|
tokens,
|
|
fg,
|
|
bg,
|
|
themeName,
|
|
rootStyle,
|
|
grammarState
|
|
} = codeToTokens(internal, input, options);
|
|
const {
|
|
mergeWhitespaces = true
|
|
} = options;
|
|
if (mergeWhitespaces === true)
|
|
tokens = mergeWhitespaceTokens(tokens);
|
|
else if (mergeWhitespaces === "never")
|
|
tokens = splitWhitespaceTokens(tokens);
|
|
const contextSource = {
|
|
...transformerContext,
|
|
get source() {
|
|
return input;
|
|
}
|
|
};
|
|
for (const transformer of getTransformers(options))
|
|
tokens = transformer.tokens?.call(contextSource, tokens) || tokens;
|
|
return tokensToHast(
|
|
tokens,
|
|
{
|
|
...options,
|
|
fg,
|
|
bg,
|
|
themeName,
|
|
rootStyle
|
|
},
|
|
contextSource,
|
|
grammarState
|
|
);
|
|
}
|
|
function tokensToHast(tokens, options, transformerContext, grammarState = getLastGrammarStateFromMap(tokens)) {
|
|
const transformers = getTransformers(options);
|
|
const lines = [];
|
|
const root = {
|
|
type: "root",
|
|
children: []
|
|
};
|
|
const {
|
|
structure = "classic",
|
|
tabindex = "0"
|
|
} = options;
|
|
let preNode = {
|
|
type: "element",
|
|
tagName: "pre",
|
|
properties: {
|
|
class: `shiki ${options.themeName || ""}`,
|
|
style: options.rootStyle || `background-color:${options.bg};color:${options.fg}`,
|
|
...tabindex !== false && tabindex != null ? {
|
|
tabindex: tabindex.toString()
|
|
} : {},
|
|
...Object.fromEntries(
|
|
Array.from(
|
|
Object.entries(options.meta || {})
|
|
).filter(([key]) => !key.startsWith("_"))
|
|
)
|
|
},
|
|
children: []
|
|
};
|
|
let codeNode = {
|
|
type: "element",
|
|
tagName: "code",
|
|
properties: {},
|
|
children: lines
|
|
};
|
|
const lineNodes = [];
|
|
const context = {
|
|
...transformerContext,
|
|
structure,
|
|
addClassToHast,
|
|
get source() {
|
|
return transformerContext.source;
|
|
},
|
|
get tokens() {
|
|
return tokens;
|
|
},
|
|
get options() {
|
|
return options;
|
|
},
|
|
get root() {
|
|
return root;
|
|
},
|
|
get pre() {
|
|
return preNode;
|
|
},
|
|
get code() {
|
|
return codeNode;
|
|
},
|
|
get lines() {
|
|
return lineNodes;
|
|
}
|
|
};
|
|
tokens.forEach((line, idx) => {
|
|
if (idx) {
|
|
if (structure === "inline")
|
|
root.children.push({ type: "element", tagName: "br", properties: {}, children: [] });
|
|
else if (structure === "classic")
|
|
lines.push({ type: "text", value: "\n" });
|
|
}
|
|
let lineNode = {
|
|
type: "element",
|
|
tagName: "span",
|
|
properties: { class: "line" },
|
|
children: []
|
|
};
|
|
let col = 0;
|
|
for (const token of line) {
|
|
let tokenNode = {
|
|
type: "element",
|
|
tagName: "span",
|
|
properties: {
|
|
...token.htmlAttrs
|
|
},
|
|
children: [{ type: "text", value: token.content }]
|
|
};
|
|
if (typeof token.htmlStyle === "string")
|
|
warnDeprecated("`htmlStyle` as a string is deprecated. Use an object instead.");
|
|
const style = stringifyTokenStyle(token.htmlStyle || getTokenStyleObject(token));
|
|
if (style)
|
|
tokenNode.properties.style = style;
|
|
for (const transformer of transformers)
|
|
tokenNode = transformer?.span?.call(context, tokenNode, idx + 1, col, lineNode, token) || tokenNode;
|
|
if (structure === "inline")
|
|
root.children.push(tokenNode);
|
|
else if (structure === "classic")
|
|
lineNode.children.push(tokenNode);
|
|
col += token.content.length;
|
|
}
|
|
if (structure === "classic") {
|
|
for (const transformer of transformers)
|
|
lineNode = transformer?.line?.call(context, lineNode, idx + 1) || lineNode;
|
|
lineNodes.push(lineNode);
|
|
lines.push(lineNode);
|
|
}
|
|
});
|
|
if (structure === "classic") {
|
|
for (const transformer of transformers)
|
|
codeNode = transformer?.code?.call(context, codeNode) || codeNode;
|
|
preNode.children.push(codeNode);
|
|
for (const transformer of transformers)
|
|
preNode = transformer?.pre?.call(context, preNode) || preNode;
|
|
root.children.push(preNode);
|
|
}
|
|
let result = root;
|
|
for (const transformer of transformers)
|
|
result = transformer?.root?.call(context, result) || result;
|
|
if (grammarState)
|
|
setLastGrammarStateToMap(result, grammarState);
|
|
return result;
|
|
}
|
|
function mergeWhitespaceTokens(tokens) {
|
|
return tokens.map((line) => {
|
|
const newLine = [];
|
|
let carryOnContent = "";
|
|
let firstOffset = 0;
|
|
line.forEach((token, idx) => {
|
|
const isUnderline = token.fontStyle && token.fontStyle & FontStyle.Underline;
|
|
const couldMerge = !isUnderline;
|
|
if (couldMerge && token.content.match(/^\s+$/) && line[idx + 1]) {
|
|
if (!firstOffset)
|
|
firstOffset = token.offset;
|
|
carryOnContent += token.content;
|
|
} else {
|
|
if (carryOnContent) {
|
|
if (couldMerge) {
|
|
newLine.push({
|
|
...token,
|
|
offset: firstOffset,
|
|
content: carryOnContent + token.content
|
|
});
|
|
} else {
|
|
newLine.push(
|
|
{
|
|
content: carryOnContent,
|
|
offset: firstOffset
|
|
},
|
|
token
|
|
);
|
|
}
|
|
firstOffset = 0;
|
|
carryOnContent = "";
|
|
} else {
|
|
newLine.push(token);
|
|
}
|
|
}
|
|
});
|
|
return newLine;
|
|
});
|
|
}
|
|
function splitWhitespaceTokens(tokens) {
|
|
return tokens.map((line) => {
|
|
return line.flatMap((token) => {
|
|
if (token.content.match(/^\s+$/))
|
|
return token;
|
|
const match = token.content.match(/^(\s*)(.*?)(\s*)$/);
|
|
if (!match)
|
|
return token;
|
|
const [, leading, content, trailing] = match;
|
|
if (!leading && !trailing)
|
|
return token;
|
|
const expanded = [{
|
|
...token,
|
|
offset: token.offset + leading.length,
|
|
content
|
|
}];
|
|
if (leading) {
|
|
expanded.unshift({
|
|
content: leading,
|
|
offset: token.offset
|
|
});
|
|
}
|
|
if (trailing) {
|
|
expanded.push({
|
|
content: trailing,
|
|
offset: token.offset + leading.length + content.length
|
|
});
|
|
}
|
|
return expanded;
|
|
});
|
|
});
|
|
}
|
|
|
|
function codeToHtml(internal, code, options) {
|
|
const context = {
|
|
meta: {},
|
|
options,
|
|
codeToHast: (_code, _options) => codeToHast(internal, _code, _options),
|
|
codeToTokens: (_code, _options) => codeToTokens(internal, _code, _options)
|
|
};
|
|
let result = toHtml(codeToHast(internal, code, options, context));
|
|
for (const transformer of getTransformers(options))
|
|
result = transformer.postprocess?.call(context, result, options) || result;
|
|
return result;
|
|
}
|
|
|
|
const VSCODE_FALLBACK_EDITOR_FG = { light: "#333333", dark: "#bbbbbb" };
|
|
const VSCODE_FALLBACK_EDITOR_BG = { light: "#fffffe", dark: "#1e1e1e" };
|
|
const RESOLVED_KEY = "__shiki_resolved";
|
|
function normalizeTheme(rawTheme) {
|
|
if (rawTheme?.[RESOLVED_KEY])
|
|
return rawTheme;
|
|
const theme = {
|
|
...rawTheme
|
|
};
|
|
if (theme.tokenColors && !theme.settings) {
|
|
theme.settings = theme.tokenColors;
|
|
delete theme.tokenColors;
|
|
}
|
|
theme.type ||= "dark";
|
|
theme.colorReplacements = { ...theme.colorReplacements };
|
|
theme.settings ||= [];
|
|
let { bg, fg } = theme;
|
|
if (!bg || !fg) {
|
|
const globalSetting = theme.settings ? theme.settings.find((s) => !s.name && !s.scope) : undefined;
|
|
if (globalSetting?.settings?.foreground)
|
|
fg = globalSetting.settings.foreground;
|
|
if (globalSetting?.settings?.background)
|
|
bg = globalSetting.settings.background;
|
|
if (!fg && theme?.colors?.["editor.foreground"])
|
|
fg = theme.colors["editor.foreground"];
|
|
if (!bg && theme?.colors?.["editor.background"])
|
|
bg = theme.colors["editor.background"];
|
|
if (!fg)
|
|
fg = theme.type === "light" ? VSCODE_FALLBACK_EDITOR_FG.light : VSCODE_FALLBACK_EDITOR_FG.dark;
|
|
if (!bg)
|
|
bg = theme.type === "light" ? VSCODE_FALLBACK_EDITOR_BG.light : VSCODE_FALLBACK_EDITOR_BG.dark;
|
|
theme.fg = fg;
|
|
theme.bg = bg;
|
|
}
|
|
if (!(theme.settings[0] && theme.settings[0].settings && !theme.settings[0].scope)) {
|
|
theme.settings.unshift({
|
|
settings: {
|
|
foreground: theme.fg,
|
|
background: theme.bg
|
|
}
|
|
});
|
|
}
|
|
let replacementCount = 0;
|
|
const replacementMap = /* @__PURE__ */ new Map();
|
|
function getReplacementColor(value) {
|
|
if (replacementMap.has(value))
|
|
return replacementMap.get(value);
|
|
replacementCount += 1;
|
|
const hex = `#${replacementCount.toString(16).padStart(8, "0").toLowerCase()}`;
|
|
if (theme.colorReplacements?.[`#${hex}`])
|
|
return getReplacementColor(value);
|
|
replacementMap.set(value, hex);
|
|
return hex;
|
|
}
|
|
theme.settings = theme.settings.map((setting) => {
|
|
const replaceFg = setting.settings?.foreground && !setting.settings.foreground.startsWith("#");
|
|
const replaceBg = setting.settings?.background && !setting.settings.background.startsWith("#");
|
|
if (!replaceFg && !replaceBg)
|
|
return setting;
|
|
const clone = {
|
|
...setting,
|
|
settings: {
|
|
...setting.settings
|
|
}
|
|
};
|
|
if (replaceFg) {
|
|
const replacement = getReplacementColor(setting.settings.foreground);
|
|
theme.colorReplacements[replacement] = setting.settings.foreground;
|
|
clone.settings.foreground = replacement;
|
|
}
|
|
if (replaceBg) {
|
|
const replacement = getReplacementColor(setting.settings.background);
|
|
theme.colorReplacements[replacement] = setting.settings.background;
|
|
clone.settings.background = replacement;
|
|
}
|
|
return clone;
|
|
});
|
|
for (const key of Object.keys(theme.colors || {})) {
|
|
if (key === "editor.foreground" || key === "editor.background" || key.startsWith("terminal.ansi")) {
|
|
if (!theme.colors[key]?.startsWith("#")) {
|
|
const replacement = getReplacementColor(theme.colors[key]);
|
|
theme.colorReplacements[replacement] = theme.colors[key];
|
|
theme.colors[key] = replacement;
|
|
}
|
|
}
|
|
}
|
|
Object.defineProperty(theme, RESOLVED_KEY, {
|
|
enumerable: false,
|
|
writable: false,
|
|
value: true
|
|
});
|
|
return theme;
|
|
}
|
|
|
|
async function resolveLangs(langs) {
|
|
return Array.from(new Set((await Promise.all(
|
|
langs.filter((l) => !isSpecialLang(l)).map(async (lang) => await normalizeGetter(lang).then((r) => Array.isArray(r) ? r : [r]))
|
|
)).flat()));
|
|
}
|
|
async function resolveThemes(themes) {
|
|
const resolved = await Promise.all(
|
|
themes.map(
|
|
async (theme) => isSpecialTheme(theme) ? null : normalizeTheme(await normalizeGetter(theme))
|
|
)
|
|
);
|
|
return resolved.filter((i) => !!i);
|
|
}
|
|
|
|
class Registry extends Registry$1 {
|
|
constructor(_resolver, _themes, _langs, _alias = {}) {
|
|
super(_resolver);
|
|
this._resolver = _resolver;
|
|
this._themes = _themes;
|
|
this._langs = _langs;
|
|
this._alias = _alias;
|
|
this._themes.map((t) => this.loadTheme(t));
|
|
this.loadLanguages(this._langs);
|
|
}
|
|
_resolvedThemes = /* @__PURE__ */ new Map();
|
|
_resolvedGrammars = /* @__PURE__ */ new Map();
|
|
_langMap = /* @__PURE__ */ new Map();
|
|
_langGraph = /* @__PURE__ */ new Map();
|
|
_textmateThemeCache = /* @__PURE__ */ new WeakMap();
|
|
_loadedThemesCache = null;
|
|
_loadedLanguagesCache = null;
|
|
getTheme(theme) {
|
|
if (typeof theme === "string")
|
|
return this._resolvedThemes.get(theme);
|
|
else
|
|
return this.loadTheme(theme);
|
|
}
|
|
loadTheme(theme) {
|
|
const _theme = normalizeTheme(theme);
|
|
if (_theme.name) {
|
|
this._resolvedThemes.set(_theme.name, _theme);
|
|
this._loadedThemesCache = null;
|
|
}
|
|
return _theme;
|
|
}
|
|
getLoadedThemes() {
|
|
if (!this._loadedThemesCache)
|
|
this._loadedThemesCache = [...this._resolvedThemes.keys()];
|
|
return this._loadedThemesCache;
|
|
}
|
|
// Override and re-implement this method to cache the textmate themes as `TextMateTheme.createFromRawTheme`
|
|
// is expensive. Themes can switch often especially for dual-theme support.
|
|
//
|
|
// The parent class also accepts `colorMap` as the second parameter, but since we don't use that,
|
|
// we omit here so it's easier to cache the themes.
|
|
setTheme(theme) {
|
|
let textmateTheme = this._textmateThemeCache.get(theme);
|
|
if (!textmateTheme) {
|
|
textmateTheme = Theme.createFromRawTheme(theme);
|
|
this._textmateThemeCache.set(theme, textmateTheme);
|
|
}
|
|
this._syncRegistry.setTheme(textmateTheme);
|
|
}
|
|
getGrammar(name) {
|
|
if (this._alias[name]) {
|
|
const resolved = /* @__PURE__ */ new Set([name]);
|
|
while (this._alias[name]) {
|
|
name = this._alias[name];
|
|
if (resolved.has(name))
|
|
throw new ShikiError(`Circular alias \`${Array.from(resolved).join(" -> ")} -> ${name}\``);
|
|
resolved.add(name);
|
|
}
|
|
}
|
|
return this._resolvedGrammars.get(name);
|
|
}
|
|
loadLanguage(lang) {
|
|
if (this.getGrammar(lang.name))
|
|
return;
|
|
const embeddedLazilyBy = new Set(
|
|
[...this._langMap.values()].filter((i) => i.embeddedLangsLazy?.includes(lang.name))
|
|
);
|
|
this._resolver.addLanguage(lang);
|
|
const grammarConfig = {
|
|
balancedBracketSelectors: lang.balancedBracketSelectors || ["*"],
|
|
unbalancedBracketSelectors: lang.unbalancedBracketSelectors || []
|
|
};
|
|
this._syncRegistry._rawGrammars.set(lang.scopeName, lang);
|
|
const g = this.loadGrammarWithConfiguration(lang.scopeName, 1, grammarConfig);
|
|
g.name = lang.name;
|
|
this._resolvedGrammars.set(lang.name, g);
|
|
if (lang.aliases) {
|
|
lang.aliases.forEach((alias) => {
|
|
this._alias[alias] = lang.name;
|
|
});
|
|
}
|
|
this._loadedLanguagesCache = null;
|
|
if (embeddedLazilyBy.size) {
|
|
for (const e of embeddedLazilyBy) {
|
|
this._resolvedGrammars.delete(e.name);
|
|
this._loadedLanguagesCache = null;
|
|
this._syncRegistry?._injectionGrammars?.delete(e.scopeName);
|
|
this._syncRegistry?._grammars?.delete(e.scopeName);
|
|
this.loadLanguage(this._langMap.get(e.name));
|
|
}
|
|
}
|
|
}
|
|
dispose() {
|
|
super.dispose();
|
|
this._resolvedThemes.clear();
|
|
this._resolvedGrammars.clear();
|
|
this._langMap.clear();
|
|
this._langGraph.clear();
|
|
this._loadedThemesCache = null;
|
|
}
|
|
loadLanguages(langs) {
|
|
for (const lang of langs)
|
|
this.resolveEmbeddedLanguages(lang);
|
|
const langsGraphArray = Array.from(this._langGraph.entries());
|
|
const missingLangs = langsGraphArray.filter(([_, lang]) => !lang);
|
|
if (missingLangs.length) {
|
|
const dependents = langsGraphArray.filter(([_, lang]) => lang && lang.embeddedLangs?.some((l) => missingLangs.map(([name]) => name).includes(l))).filter((lang) => !missingLangs.includes(lang));
|
|
throw new ShikiError(`Missing languages ${missingLangs.map(([name]) => `\`${name}\``).join(", ")}, required by ${dependents.map(([name]) => `\`${name}\``).join(", ")}`);
|
|
}
|
|
for (const [_, lang] of langsGraphArray)
|
|
this._resolver.addLanguage(lang);
|
|
for (const [_, lang] of langsGraphArray)
|
|
this.loadLanguage(lang);
|
|
}
|
|
getLoadedLanguages() {
|
|
if (!this._loadedLanguagesCache) {
|
|
this._loadedLanguagesCache = [
|
|
.../* @__PURE__ */ new Set([...this._resolvedGrammars.keys(), ...Object.keys(this._alias)])
|
|
];
|
|
}
|
|
return this._loadedLanguagesCache;
|
|
}
|
|
resolveEmbeddedLanguages(lang) {
|
|
this._langMap.set(lang.name, lang);
|
|
this._langGraph.set(lang.name, lang);
|
|
if (lang.embeddedLangs) {
|
|
for (const embeddedLang of lang.embeddedLangs)
|
|
this._langGraph.set(embeddedLang, this._langMap.get(embeddedLang));
|
|
}
|
|
}
|
|
}
|
|
|
|
class Resolver {
|
|
_langs = /* @__PURE__ */ new Map();
|
|
_scopeToLang = /* @__PURE__ */ new Map();
|
|
_injections = /* @__PURE__ */ new Map();
|
|
_onigLib;
|
|
constructor(engine, langs) {
|
|
this._onigLib = {
|
|
createOnigScanner: (patterns) => engine.createScanner(patterns),
|
|
createOnigString: (s) => engine.createString(s)
|
|
};
|
|
langs.forEach((i) => this.addLanguage(i));
|
|
}
|
|
get onigLib() {
|
|
return this._onigLib;
|
|
}
|
|
getLangRegistration(langIdOrAlias) {
|
|
return this._langs.get(langIdOrAlias);
|
|
}
|
|
loadGrammar(scopeName) {
|
|
return this._scopeToLang.get(scopeName);
|
|
}
|
|
addLanguage(l) {
|
|
this._langs.set(l.name, l);
|
|
if (l.aliases) {
|
|
l.aliases.forEach((a) => {
|
|
this._langs.set(a, l);
|
|
});
|
|
}
|
|
this._scopeToLang.set(l.scopeName, l);
|
|
if (l.injectTo) {
|
|
l.injectTo.forEach((i) => {
|
|
if (!this._injections.get(i))
|
|
this._injections.set(i, []);
|
|
this._injections.get(i).push(l.scopeName);
|
|
});
|
|
}
|
|
}
|
|
getInjections(scopeName) {
|
|
const scopeParts = scopeName.split(".");
|
|
let injections = [];
|
|
for (let i = 1; i <= scopeParts.length; i++) {
|
|
const subScopeName = scopeParts.slice(0, i).join(".");
|
|
injections = [...injections, ...this._injections.get(subScopeName) || []];
|
|
}
|
|
return injections;
|
|
}
|
|
}
|
|
|
|
let instancesCount = 0;
|
|
function createShikiInternalSync(options) {
|
|
instancesCount += 1;
|
|
if (options.warnings !== false && instancesCount >= 10 && instancesCount % 10 === 0)
|
|
console.warn(`[Shiki] ${instancesCount} instances have been created. Shiki is supposed to be used as a singleton, consider refactoring your code to cache your highlighter instance; Or call \`highlighter.dispose()\` to release unused instances.`);
|
|
let isDisposed = false;
|
|
if (!options.engine)
|
|
throw new ShikiError("`engine` option is required for synchronous mode");
|
|
const langs = (options.langs || []).flat(1);
|
|
const themes = (options.themes || []).flat(1).map(normalizeTheme);
|
|
const resolver = new Resolver(options.engine, langs);
|
|
const _registry = new Registry(resolver, themes, langs, options.langAlias);
|
|
let _lastTheme;
|
|
function getLanguage(name) {
|
|
ensureNotDisposed();
|
|
const _lang = _registry.getGrammar(typeof name === "string" ? name : name.name);
|
|
if (!_lang)
|
|
throw new ShikiError(`Language \`${name}\` not found, you may need to load it first`);
|
|
return _lang;
|
|
}
|
|
function getTheme(name) {
|
|
if (name === "none")
|
|
return { bg: "", fg: "", name: "none", settings: [], type: "dark" };
|
|
ensureNotDisposed();
|
|
const _theme = _registry.getTheme(name);
|
|
if (!_theme)
|
|
throw new ShikiError(`Theme \`${name}\` not found, you may need to load it first`);
|
|
return _theme;
|
|
}
|
|
function setTheme(name) {
|
|
ensureNotDisposed();
|
|
const theme = getTheme(name);
|
|
if (_lastTheme !== name) {
|
|
_registry.setTheme(theme);
|
|
_lastTheme = name;
|
|
}
|
|
const colorMap = _registry.getColorMap();
|
|
return {
|
|
theme,
|
|
colorMap
|
|
};
|
|
}
|
|
function getLoadedThemes() {
|
|
ensureNotDisposed();
|
|
return _registry.getLoadedThemes();
|
|
}
|
|
function getLoadedLanguages() {
|
|
ensureNotDisposed();
|
|
return _registry.getLoadedLanguages();
|
|
}
|
|
function loadLanguageSync(...langs2) {
|
|
ensureNotDisposed();
|
|
_registry.loadLanguages(langs2.flat(1));
|
|
}
|
|
async function loadLanguage(...langs2) {
|
|
return loadLanguageSync(await resolveLangs(langs2));
|
|
}
|
|
function loadThemeSync(...themes2) {
|
|
ensureNotDisposed();
|
|
for (const theme of themes2.flat(1)) {
|
|
_registry.loadTheme(theme);
|
|
}
|
|
}
|
|
async function loadTheme(...themes2) {
|
|
ensureNotDisposed();
|
|
return loadThemeSync(await resolveThemes(themes2));
|
|
}
|
|
function ensureNotDisposed() {
|
|
if (isDisposed)
|
|
throw new ShikiError("Shiki instance has been disposed");
|
|
}
|
|
function dispose() {
|
|
if (isDisposed)
|
|
return;
|
|
isDisposed = true;
|
|
_registry.dispose();
|
|
instancesCount -= 1;
|
|
}
|
|
return {
|
|
setTheme,
|
|
getTheme,
|
|
getLanguage,
|
|
getLoadedThemes,
|
|
getLoadedLanguages,
|
|
loadLanguage,
|
|
loadLanguageSync,
|
|
loadTheme,
|
|
loadThemeSync,
|
|
dispose,
|
|
[Symbol.dispose]: dispose
|
|
};
|
|
}
|
|
|
|
async function createShikiInternal(options = {}) {
|
|
if (options.loadWasm) {
|
|
warnDeprecated("`loadWasm` option is deprecated. Use `engine: createOnigurumaEngine(loadWasm)` instead.");
|
|
}
|
|
const [
|
|
themes,
|
|
langs,
|
|
engine
|
|
] = await Promise.all([
|
|
resolveThemes(options.themes || []),
|
|
resolveLangs(options.langs || []),
|
|
options.engine || createOnigurumaEngine$1(options.loadWasm || getDefaultWasmLoader())
|
|
]);
|
|
return createShikiInternalSync({
|
|
...options,
|
|
loadWasm: undefined,
|
|
themes,
|
|
langs,
|
|
engine
|
|
});
|
|
}
|
|
function getShikiInternal(options = {}) {
|
|
warnDeprecated("`getShikiInternal` is deprecated. Use `createShikiInternal` instead.");
|
|
return createShikiInternal(options);
|
|
}
|
|
|
|
async function createHighlighterCore(options = {}) {
|
|
const internal = await createShikiInternal(options);
|
|
return {
|
|
getLastGrammarState: (...args) => getLastGrammarState(internal, ...args),
|
|
codeToTokensBase: (code, options2) => codeToTokensBase(internal, code, options2),
|
|
codeToTokensWithThemes: (code, options2) => codeToTokensWithThemes(internal, code, options2),
|
|
codeToTokens: (code, options2) => codeToTokens(internal, code, options2),
|
|
codeToHast: (code, options2) => codeToHast(internal, code, options2),
|
|
codeToHtml: (code, options2) => codeToHtml(internal, code, options2),
|
|
...internal,
|
|
getInternalContext: () => internal
|
|
};
|
|
}
|
|
function createHighlighterCoreSync(options = {}) {
|
|
const internal = createShikiInternalSync(options);
|
|
return {
|
|
getLastGrammarState: (...args) => getLastGrammarState(internal, ...args),
|
|
codeToTokensBase: (code, options2) => codeToTokensBase(internal, code, options2),
|
|
codeToTokensWithThemes: (code, options2) => codeToTokensWithThemes(internal, code, options2),
|
|
codeToTokens: (code, options2) => codeToTokens(internal, code, options2),
|
|
codeToHast: (code, options2) => codeToHast(internal, code, options2),
|
|
codeToHtml: (code, options2) => codeToHtml(internal, code, options2),
|
|
...internal,
|
|
getInternalContext: () => internal
|
|
};
|
|
}
|
|
function makeSingletonHighlighterCore(createHighlighter) {
|
|
let _shiki;
|
|
async function getSingletonHighlighterCore2(options = {}) {
|
|
if (!_shiki) {
|
|
_shiki = createHighlighter({
|
|
...options,
|
|
themes: options.themes || [],
|
|
langs: options.langs || []
|
|
});
|
|
return _shiki;
|
|
} else {
|
|
const s = await _shiki;
|
|
await Promise.all([
|
|
s.loadTheme(...options.themes || []),
|
|
s.loadLanguage(...options.langs || [])
|
|
]);
|
|
return s;
|
|
}
|
|
}
|
|
return getSingletonHighlighterCore2;
|
|
}
|
|
const getSingletonHighlighterCore = /* @__PURE__ */ makeSingletonHighlighterCore(createHighlighterCore);
|
|
function getHighlighterCore(options = {}) {
|
|
warnDeprecated("`getHighlighterCore` is deprecated. Use `createHighlighterCore` or `getSingletonHighlighterCore` instead.");
|
|
return createHighlighterCore(options);
|
|
}
|
|
|
|
function createdBundledHighlighter(arg1, arg2, arg3) {
|
|
let bundledLanguages;
|
|
let bundledThemes;
|
|
let engine;
|
|
if (arg2) {
|
|
warnDeprecated("`createdBundledHighlighter` signature with `bundledLanguages` and `bundledThemes` is deprecated. Use the options object signature instead.");
|
|
bundledLanguages = arg1;
|
|
bundledThemes = arg2;
|
|
engine = () => createOnigurumaEngine(arg3);
|
|
} else {
|
|
const options = arg1;
|
|
bundledLanguages = options.langs;
|
|
bundledThemes = options.themes;
|
|
engine = options.engine;
|
|
}
|
|
async function createHighlighter(options) {
|
|
function resolveLang(lang) {
|
|
if (typeof lang === "string") {
|
|
if (isSpecialLang(lang))
|
|
return [];
|
|
const bundle = bundledLanguages[lang];
|
|
if (!bundle)
|
|
throw new ShikiError$1(`Language \`${lang}\` is not included in this bundle. You may want to load it from external source.`);
|
|
return bundle;
|
|
}
|
|
return lang;
|
|
}
|
|
function resolveTheme(theme) {
|
|
if (isSpecialTheme(theme))
|
|
return "none";
|
|
if (typeof theme === "string") {
|
|
const bundle = bundledThemes[theme];
|
|
if (!bundle)
|
|
throw new ShikiError$1(`Theme \`${theme}\` is not included in this bundle. You may want to load it from external source.`);
|
|
return bundle;
|
|
}
|
|
return theme;
|
|
}
|
|
const _themes = (options.themes ?? []).map((i) => resolveTheme(i));
|
|
const langs = (options.langs ?? []).map((i) => resolveLang(i));
|
|
const core = await createHighlighterCore({
|
|
engine: options.engine ?? engine(),
|
|
...options,
|
|
themes: _themes,
|
|
langs
|
|
});
|
|
return {
|
|
...core,
|
|
loadLanguage(...langs2) {
|
|
return core.loadLanguage(...langs2.map(resolveLang));
|
|
},
|
|
loadTheme(...themes) {
|
|
return core.loadTheme(...themes.map(resolveTheme));
|
|
}
|
|
};
|
|
}
|
|
return createHighlighter;
|
|
}
|
|
function makeSingletonHighlighter(createHighlighter) {
|
|
let _shiki;
|
|
async function getSingletonHighlighter(options = {}) {
|
|
if (!_shiki) {
|
|
_shiki = createHighlighter({
|
|
...options,
|
|
themes: options.themes || [],
|
|
langs: options.langs || []
|
|
});
|
|
return _shiki;
|
|
} else {
|
|
const s = await _shiki;
|
|
await Promise.all([
|
|
s.loadTheme(...options.themes || []),
|
|
s.loadLanguage(...options.langs || [])
|
|
]);
|
|
return s;
|
|
}
|
|
}
|
|
return getSingletonHighlighter;
|
|
}
|
|
function createSingletonShorthands(createHighlighter) {
|
|
const getSingletonHighlighter = makeSingletonHighlighter(createHighlighter);
|
|
return {
|
|
getSingletonHighlighter(options) {
|
|
return getSingletonHighlighter(options);
|
|
},
|
|
async codeToHtml(code, options) {
|
|
const shiki = await getSingletonHighlighter({
|
|
langs: [options.lang],
|
|
themes: "theme" in options ? [options.theme] : Object.values(options.themes)
|
|
});
|
|
return shiki.codeToHtml(code, options);
|
|
},
|
|
async codeToHast(code, options) {
|
|
const shiki = await getSingletonHighlighter({
|
|
langs: [options.lang],
|
|
themes: "theme" in options ? [options.theme] : Object.values(options.themes)
|
|
});
|
|
return shiki.codeToHast(code, options);
|
|
},
|
|
async codeToTokens(code, options) {
|
|
const shiki = await getSingletonHighlighter({
|
|
langs: [options.lang],
|
|
themes: "theme" in options ? [options.theme] : Object.values(options.themes)
|
|
});
|
|
return shiki.codeToTokens(code, options);
|
|
},
|
|
async codeToTokensBase(code, options) {
|
|
const shiki = await getSingletonHighlighter({
|
|
langs: [options.lang],
|
|
themes: [options.theme]
|
|
});
|
|
return shiki.codeToTokensBase(code, options);
|
|
},
|
|
async codeToTokensWithThemes(code, options) {
|
|
const shiki = await getSingletonHighlighter({
|
|
langs: [options.lang],
|
|
themes: Object.values(options.themes).filter(Boolean)
|
|
});
|
|
return shiki.codeToTokensWithThemes(code, options);
|
|
},
|
|
async getLastGrammarState(code, options) {
|
|
const shiki = await getSingletonHighlighter({
|
|
langs: [options.lang],
|
|
themes: [options.theme]
|
|
});
|
|
return shiki.getLastGrammarState(code, options);
|
|
}
|
|
};
|
|
}
|
|
|
|
function createJavaScriptRegexEngine(options) {
|
|
warnDeprecated("import `createJavaScriptRegexEngine` from `@shikijs/engine-javascript` or `shiki/engine/javascript` instead");
|
|
return createJavaScriptRegexEngine$1(options);
|
|
}
|
|
function defaultJavaScriptRegexConstructor(pattern) {
|
|
warnDeprecated("import `defaultJavaScriptRegexConstructor` from `@shikijs/engine-javascript` or `shiki/engine/javascript` instead");
|
|
return defaultJavaScriptRegexConstructor$1(pattern);
|
|
}
|
|
|
|
function createCssVariablesTheme(options = {}) {
|
|
const {
|
|
name = "css-variables",
|
|
variablePrefix = "--shiki-",
|
|
fontStyle = true
|
|
} = options;
|
|
const variable = (name2) => {
|
|
if (options.variableDefaults?.[name2])
|
|
return `var(${variablePrefix}${name2}, ${options.variableDefaults[name2]})`;
|
|
return `var(${variablePrefix}${name2})`;
|
|
};
|
|
const theme = {
|
|
name,
|
|
type: "dark",
|
|
colors: {
|
|
"editor.foreground": variable("foreground"),
|
|
"editor.background": variable("background"),
|
|
"terminal.ansiBlack": variable("ansi-black"),
|
|
"terminal.ansiRed": variable("ansi-red"),
|
|
"terminal.ansiGreen": variable("ansi-green"),
|
|
"terminal.ansiYellow": variable("ansi-yellow"),
|
|
"terminal.ansiBlue": variable("ansi-blue"),
|
|
"terminal.ansiMagenta": variable("ansi-magenta"),
|
|
"terminal.ansiCyan": variable("ansi-cyan"),
|
|
"terminal.ansiWhite": variable("ansi-white"),
|
|
"terminal.ansiBrightBlack": variable("ansi-bright-black"),
|
|
"terminal.ansiBrightRed": variable("ansi-bright-red"),
|
|
"terminal.ansiBrightGreen": variable("ansi-bright-green"),
|
|
"terminal.ansiBrightYellow": variable("ansi-bright-yellow"),
|
|
"terminal.ansiBrightBlue": variable("ansi-bright-blue"),
|
|
"terminal.ansiBrightMagenta": variable("ansi-bright-magenta"),
|
|
"terminal.ansiBrightCyan": variable("ansi-bright-cyan"),
|
|
"terminal.ansiBrightWhite": variable("ansi-bright-white")
|
|
},
|
|
tokenColors: [
|
|
{
|
|
scope: [
|
|
"keyword.operator.accessor",
|
|
"meta.group.braces.round.function.arguments",
|
|
"meta.template.expression",
|
|
"markup.fenced_code meta.embedded.block"
|
|
],
|
|
settings: {
|
|
foreground: variable("foreground")
|
|
}
|
|
},
|
|
{
|
|
scope: "emphasis",
|
|
settings: {
|
|
fontStyle: "italic"
|
|
}
|
|
},
|
|
{
|
|
scope: ["strong", "markup.heading.markdown", "markup.bold.markdown"],
|
|
settings: {
|
|
fontStyle: "bold"
|
|
}
|
|
},
|
|
{
|
|
scope: ["markup.italic.markdown"],
|
|
settings: {
|
|
fontStyle: "italic"
|
|
}
|
|
},
|
|
{
|
|
scope: "meta.link.inline.markdown",
|
|
settings: {
|
|
fontStyle: "underline",
|
|
foreground: variable("token-link")
|
|
}
|
|
},
|
|
{
|
|
scope: ["string", "markup.fenced_code", "markup.inline"],
|
|
settings: {
|
|
foreground: variable("token-string")
|
|
}
|
|
},
|
|
{
|
|
scope: ["comment", "string.quoted.docstring.multi"],
|
|
settings: {
|
|
foreground: variable("token-comment")
|
|
}
|
|
},
|
|
{
|
|
scope: [
|
|
"constant.numeric",
|
|
"constant.language",
|
|
"constant.other.placeholder",
|
|
"constant.character.format.placeholder",
|
|
"variable.language.this",
|
|
"variable.other.object",
|
|
"variable.other.class",
|
|
"variable.other.constant",
|
|
"meta.property-name",
|
|
"meta.property-value",
|
|
"support"
|
|
],
|
|
settings: {
|
|
foreground: variable("token-constant")
|
|
}
|
|
},
|
|
{
|
|
scope: [
|
|
"keyword",
|
|
"storage.modifier",
|
|
"storage.type",
|
|
"storage.control.clojure",
|
|
"entity.name.function.clojure",
|
|
"entity.name.tag.yaml",
|
|
"support.function.node",
|
|
"support.type.property-name.json",
|
|
"punctuation.separator.key-value",
|
|
"punctuation.definition.template-expression"
|
|
],
|
|
settings: {
|
|
foreground: variable("token-keyword")
|
|
}
|
|
},
|
|
{
|
|
scope: "variable.parameter.function",
|
|
settings: {
|
|
foreground: variable("token-parameter")
|
|
}
|
|
},
|
|
{
|
|
scope: [
|
|
"support.function",
|
|
"entity.name.type",
|
|
"entity.other.inherited-class",
|
|
"meta.function-call",
|
|
"meta.instance.constructor",
|
|
"entity.other.attribute-name",
|
|
"entity.name.function",
|
|
"constant.keyword.clojure"
|
|
],
|
|
settings: {
|
|
foreground: variable("token-function")
|
|
}
|
|
},
|
|
{
|
|
scope: [
|
|
"entity.name.tag",
|
|
"string.quoted",
|
|
"string.regexp",
|
|
"string.interpolated",
|
|
"string.template",
|
|
"string.unquoted.plain.out.yaml",
|
|
"keyword.other.template"
|
|
],
|
|
settings: {
|
|
foreground: variable("token-string-expression")
|
|
}
|
|
},
|
|
{
|
|
scope: [
|
|
"punctuation.definition.arguments",
|
|
"punctuation.definition.dict",
|
|
"punctuation.separator",
|
|
"meta.function-call.arguments"
|
|
],
|
|
settings: {
|
|
foreground: variable("token-punctuation")
|
|
}
|
|
},
|
|
{
|
|
// [Custom] Markdown links
|
|
scope: [
|
|
"markup.underline.link",
|
|
"punctuation.definition.metadata.markdown"
|
|
],
|
|
settings: {
|
|
foreground: variable("token-link")
|
|
}
|
|
},
|
|
{
|
|
// [Custom] Markdown list
|
|
scope: ["beginning.punctuation.definition.list.markdown"],
|
|
settings: {
|
|
foreground: variable("token-string")
|
|
}
|
|
},
|
|
{
|
|
// [Custom] Markdown punctuation definition brackets
|
|
scope: [
|
|
"punctuation.definition.string.begin.markdown",
|
|
"punctuation.definition.string.end.markdown",
|
|
"string.other.link.title.markdown",
|
|
"string.other.link.description.markdown"
|
|
],
|
|
settings: {
|
|
foreground: variable("token-keyword")
|
|
}
|
|
},
|
|
{
|
|
// [Custom] Diff
|
|
scope: [
|
|
"markup.inserted",
|
|
"meta.diff.header.to-file",
|
|
"punctuation.definition.inserted"
|
|
],
|
|
settings: {
|
|
foreground: variable("token-inserted")
|
|
}
|
|
},
|
|
{
|
|
scope: [
|
|
"markup.deleted",
|
|
"meta.diff.header.from-file",
|
|
"punctuation.definition.deleted"
|
|
],
|
|
settings: {
|
|
foreground: variable("token-deleted")
|
|
}
|
|
},
|
|
{
|
|
scope: [
|
|
"markup.changed",
|
|
"punctuation.definition.changed"
|
|
],
|
|
settings: {
|
|
foreground: variable("token-changed")
|
|
}
|
|
}
|
|
]
|
|
};
|
|
if (!fontStyle) {
|
|
theme.tokenColors = theme.tokenColors?.map((tokenColor) => {
|
|
if (tokenColor.settings?.fontStyle)
|
|
delete tokenColor.settings.fontStyle;
|
|
return tokenColor;
|
|
});
|
|
}
|
|
return theme;
|
|
}
|
|
|
|
export { addClassToHast, applyColorReplacements, codeToHast, codeToHtml, codeToTokens, codeToTokensBase, codeToTokensWithThemes, createCssVariablesTheme, createHighlighterCore, createHighlighterCoreSync, createJavaScriptRegexEngine, createOnigurumaEngine, createPositionConverter, createShikiInternal, createShikiInternalSync, createSingletonShorthands, createWasmOnigEngine, createdBundledHighlighter, defaultJavaScriptRegexConstructor, getHighlighterCore, getShikiInternal, getSingletonHighlighterCore, getTokenStyleObject, isNoneTheme, isPlainLang, isSpecialLang, isSpecialTheme, loadWasm, makeSingletonHighlighter, makeSingletonHighlighterCore, normalizeGetter, normalizeTheme, resolveColorReplacements, splitLines, splitToken, splitTokens, stringifyTokenStyle, toArray, tokenizeAnsiWithTheme, tokenizeWithTheme, tokensToHast, transformerDecorations, warnDeprecated };
|