site/node_modules/@shikijs/core/dist/index.mjs

5679 lines
162 KiB
JavaScript
Raw Normal View History

2024-10-14 06:09:33 +00:00
import { StackElementMetadata, INITIAL, Registry as Registry$1, Theme } from './textmate.mjs';
import { FontStyle } from './types.mjs';
function toArray(x) {
return Array.isArray(x) ? x : [x];
}
/**
* Slipt a string into lines, each line preserves the line ending.
*/
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;
}
/**
* Check if the language is plaintext that is ignored by Shiki.
*
* Hard-coded plain text languages: `plaintext`, `txt`, `text`, `plain`.
*/
function isPlainLang(lang) {
return !lang || ['plaintext', 'txt', 'text', 'plain'].includes(lang);
}
/**
* Check if the language is specially handled or bypassed by Shiki.
*
* Hard-coded languages: `ansi` and plaintexts like `plaintext`, `txt`, `text`, `plain`.
*/
function isSpecialLang(lang) {
return lang === 'ansi' || isPlainLang(lang);
}
/**
* Check if the theme is specially handled or bypassed by Shiki.
*
* Hard-coded themes: `none`.
*/
function isNoneTheme(theme) {
return theme === 'none';
}
/**
* Check if the theme is specially handled or bypassed by Shiki.
*
* Hard-coded themes: `none`.
*/
function isSpecialTheme(theme) {
return isNoneTheme(theme);
}
/**
* Utility to append class to a hast node
*
* If the `property.class` is a string, it will be splitted by space and converted to an array.
*/
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;
}
/**
* Split a token into multiple tokens by given offsets.
*
* The offsets are relative to the token, and should be sorted.
*/
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;
}
/**
* Split 2D tokens array by given breakpoints.
*/
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);
});
});
}
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) {
return Object.entries(token).map(([key, value]) => `${key}:${value}`).join(';');
}
/**
* Creates a converter between index and position in a code block.
*/
function createPositionConverter(code) {
const lines = splitLines(code, true).map(([line]) => line);
function indexToPos(index) {
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,
};
}
// 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,
};
}));
}
/**
* Adds 50% alpha to a hex color string or the "-dim" postfix to a CSS variable
*/
function dimColor(color) {
const hexMatch = color.match(/#([0-9a-f]{3})([0-9a-f]{3})?([0-9a-f]{2})?/);
if (hexMatch) {
if (hexMatch[3]) {
// convert from #rrggbbaa to #rrggbb(aa/2)
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]) {
// convert from #rrggbb to #rrggbb80
return `#${hexMatch[1]}${hexMatch[2]}80`;
}
else {
// convert from #rgb to #rrggbb80
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;
}
/**
* Code to tokens, with a simple theme.
*/
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);
return tokenizeWithTheme(code, _grammar, theme, colorMap, options);
}
function tokenizeWithTheme(code, grammar, theme, colorMap, options) {
const colorReplacements = resolveColorReplacements(theme, options);
const { tokenizeMaxLineLength = 0, tokenizeTimeLimit = 500, } = options;
const lines = splitLines(code);
let ruleStack = INITIAL;
let actual = [];
const final = [];
const themeSettingsSelectors = [];
if (options.includeExplanation) {
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(/ /)),
});
}
}
for (let i = 0, len = lines.length; i < len; i++) {
const [line, lineOffset] = lines[i];
if (line === '') {
actual = [];
final.push([]);
continue;
}
// Do not attempt to tokenize if the line length is longer than the `tokenizationMaxLineLength`
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, ruleStack);
tokensWithScopes = resultWithScopes.tokens;
tokensWithScopesIndex = 0;
}
const result = grammar.tokenizeLine2(line, ruleStack, 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[StackElementMetadata.getForeground(metadata)], colorReplacements);
const fontStyle = StackElementMetadata.getFontStyle(metadata);
const token = {
content: line.substring(startIndex, nextStartIndex),
offset: lineOffset + startIndex,
color,
fontStyle,
};
if (options.includeExplanation) {
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: explainThemeScopes(themeSettingsSelectors, tokenWithScopes.scopes),
});
tokensWithScopesIndex += 1;
}
}
actual.push(token);
}
final.push(actual);
actual = [];
ruleStack = result.ruleStack;
}
return final;
}
function explainThemeScopes(themeSelectors, scopes) {
const result = [];
for (let i = 0, len = scopes.length; i < len; i++) {
const parentScopes = scopes.slice(0, i);
const scope = scopes[i];
result[i] = {
scopeName: scope,
themeMatches: explainThemeScope(themeSelectors, scope, parentScopes),
};
}
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; // continue to the next theme settings
}
}
}
return result;
}
/**
* Get tokens with multiple themes
*/
function codeToTokensWithThemes(internal, code, options) {
const themes = Object.entries(options.themes)
.filter(i => i[1])
.map(i => ({ color: i[0], theme: i[1] }));
const tokens = syncThemesTokenization(...themes.map(t => codeToTokensBase(internal, code, {
...options,
theme: t.theme,
})));
const mergedTokens = tokens[0]
.map((line, lineIdx) => line
.map((_token, tokenIdx) => {
const mergedToken = {
content: _token.content,
variants: {},
offset: _token.offset,
};
tokens.forEach((t, themeIdx) => {
const { content: _, explanation: __, offset: ___, ...styles } = t[lineIdx][tokenIdx];
mergedToken.variants[themes[themeIdx].color] = styles;
});
return mergedToken;
}));
return mergedTokens;
}
/**
* Break tokens from multiple themes into same tokenization.
*
* For example, given two themes that tokenize `console.log("hello")` as:
*
* - `console . log (" hello ")` (6 tokens)
* - `console .log ( "hello" )` (5 tokens)
*
* This function will return:
*
* - `console . log ( " hello " )` (8 tokens)
* - `console . log ( " hello " )` (8 tokens)
*/
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, i) => t.push(outLines[i]));
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;
}
class ShikiError extends Error {
constructor(message) {
super(message);
this.name = 'ShikiError';
}
}
/**
* High-level code-to-tokens API.
*
* It will use `codeToTokensWithThemes` or `codeToTokensBase` based on the options.
*/
function codeToTokens(internal, code, options) {
let bg;
let fg;
let tokens;
let themeName;
let rootStyle;
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('`themes` option must not be empty');
const themeTokens = codeToTokensWithThemes(internal, code, options);
if (defaultColor && !themes.find(t => t.color === defaultColor))
throw new ShikiError(`\`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)));
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.colorReplacements);
tokens = codeToTokensBase(internal, code, options);
const _theme = internal.getTheme(options.theme);
bg = applyColorReplacements(_theme.bg, colorReplacements);
fg = applyColorReplacements(_theme.fg, colorReplacements);
themeName = _theme.name;
}
else {
throw new ShikiError('Invalid options, either `theme` or `themes` must be provided');
}
return {
tokens,
fg,
bg,
themeName,
rootStyle,
};
}
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]));
// Get all style keys, for themes that missing some style, we put `inherit` to override as needed
const styleKeys = new Set(styles.flatMap(t => Object.keys(t)));
const mergedStyles = styles.reduce((acc, cur, idx) => {
for (const key of styleKeys) {
const value = cur[key] || 'inherit';
if (idx === 0 && defaultColor) {
acc[key] = value;
}
else {
const keyName = key === 'color' ? '' : key === 'background-color' ? '-bg' : `-${key}`;
const varKey = cssVariablePrefix + variantsOrder[idx] + (key === 'color' ? '' : keyName);
if (acc[key])
acc[key] += `;${varKey}:${value}`;
else
acc[key] = `${varKey}:${value}`;
}
}
return acc;
}, {});
token.htmlStyle = defaultColor
? stringifyTokenStyle(mergedStyles)
: Object.values(mergedStyles).join(';');
return token;
}
/**
* A built-in transformer to add decorations to the highlighted code.
*/
function transformerDecorations() {
const map = new WeakMap();
function getContext(shiki) {
if (!map.has(shiki.meta)) {
const converter = createPositionConverter(shiki.source);
function normalizePosition(p) {
if (typeof p === 'number') {
return {
...converter.indexToPos(p),
offset: p,
};
}
else {
return {
...p,
offset: converter.posToIndex(p.line, p.character),
};
}
}
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);
}
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; // nested
if (isBarHasFooStart && isBarHasFooEnd)
continue; // nested
throw new ShikiError(`Decorations ${JSON.stringify(foo.start)} and ${JSON.stringify(bar.start)} intersect.`);
}
}
}
}
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;
function stringify(el) {
if (el.type === 'text')
return el.value;
if (el.type === 'element')
return el.children.map(stringify).join('');
return '';
}
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);
// Full line decoration
if (!decoration.alwaysWrap && children.length === lineEl.children.length) {
applyDecoration(lineEl, decoration, 'line');
}
// Single token decoration
else if (!decoration.alwaysWrap && children.length === 1 && children[0].type === 'element') {
applyDecoration(children[0], decoration, 'token');
}
// Create a wrapper for the decoration
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 = [];
// Apply decorations in reverse order so the nested ones get applied first.
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());
},
};
}
const builtInTransformers = [
/* @__PURE__ */ transformerDecorations(),
];
function getTransformers(options) {
return [
...options.transformers || [],
...builtInTransformers,
];
}
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, } = 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);
}
function tokensToHast(tokens, options, transformerContext) {
const transformers = getTransformers(options);
const lines = [];
const root = {
type: 'root',
children: [],
};
const { structure = 'classic', } = options;
let preNode = {
type: 'element',
tagName: 'pre',
properties: {
class: `shiki ${options.themeName || ''}`,
style: options.rootStyle || `background-color:${options.bg};color:${options.fg}`,
tabindex: '0',
...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: {},
children: [{ type: 'text', value: token.content }],
};
const style = token.htmlStyle || stringifyTokenStyle(getTokenStyleObject(token));
if (style)
tokenNode.properties.style = style;
for (const transformer of transformers)
tokenNode = transformer?.span?.call(context, tokenNode, idx + 1, col, lineNode) || 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;
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;
// eslint-disable-next-line regexp/no-super-linear-backtracking
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;
});
});
}
/**
* List of HTML void tag names.
*
* @type {Array<string>}
*/
const htmlVoidElements = [
'area',
'base',
'basefont',
'bgsound',
'br',
'col',
'command',
'embed',
'frame',
'hr',
'image',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr'
];
/**
* @typedef {import('./info.js').Info} Info
* @typedef {Record<string, Info>} Properties
* @typedef {Record<string, string>} Normal
*/
class Schema {
/**
* @constructor
* @param {Properties} property
* @param {Normal} normal
* @param {string} [space]
*/
constructor(property, normal, space) {
this.property = property;
this.normal = normal;
if (space) {
this.space = space;
}
}
}
/** @type {Properties} */
Schema.prototype.property = {};
/** @type {Normal} */
Schema.prototype.normal = {};
/** @type {string|null} */
Schema.prototype.space = null;
/**
* @typedef {import('./schema.js').Properties} Properties
* @typedef {import('./schema.js').Normal} Normal
*/
/**
* @param {Schema[]} definitions
* @param {string} [space]
* @returns {Schema}
*/
function merge(definitions, space) {
/** @type {Properties} */
const property = {};
/** @type {Normal} */
const normal = {};
let index = -1;
while (++index < definitions.length) {
Object.assign(property, definitions[index].property);
Object.assign(normal, definitions[index].normal);
}
return new Schema(property, normal, space)
}
/**
* @param {string} value
* @returns {string}
*/
function normalize(value) {
return value.toLowerCase()
}
class Info {
/**
* @constructor
* @param {string} property
* @param {string} attribute
*/
constructor(property, attribute) {
/** @type {string} */
this.property = property;
/** @type {string} */
this.attribute = attribute;
}
}
/** @type {string|null} */
Info.prototype.space = null;
Info.prototype.boolean = false;
Info.prototype.booleanish = false;
Info.prototype.overloadedBoolean = false;
Info.prototype.number = false;
Info.prototype.commaSeparated = false;
Info.prototype.spaceSeparated = false;
Info.prototype.commaOrSpaceSeparated = false;
Info.prototype.mustUseProperty = false;
Info.prototype.defined = false;
let powers = 0;
const boolean = increment();
const booleanish = increment();
const overloadedBoolean = increment();
const number = increment();
const spaceSeparated = increment();
const commaSeparated = increment();
const commaOrSpaceSeparated = increment();
function increment() {
return 2 ** ++powers
}
var types = /*#__PURE__*/Object.freeze({
__proto__: null,
boolean: boolean,
booleanish: booleanish,
commaOrSpaceSeparated: commaOrSpaceSeparated,
commaSeparated: commaSeparated,
number: number,
overloadedBoolean: overloadedBoolean,
spaceSeparated: spaceSeparated
});
/** @type {Array<keyof types>} */
// @ts-expect-error: hush.
const checks = Object.keys(types);
class DefinedInfo extends Info {
/**
* @constructor
* @param {string} property
* @param {string} attribute
* @param {number|null} [mask]
* @param {string} [space]
*/
constructor(property, attribute, mask, space) {
let index = -1;
super(property, attribute);
mark(this, 'space', space);
if (typeof mask === 'number') {
while (++index < checks.length) {
const check = checks[index];
mark(this, checks[index], (mask & types[check]) === types[check]);
}
}
}
}
DefinedInfo.prototype.defined = true;
/**
* @param {DefinedInfo} values
* @param {string} key
* @param {unknown} value
*/
function mark(values, key, value) {
if (value) {
// @ts-expect-error: assume `value` matches the expected value of `key`.
values[key] = value;
}
}
/**
* @typedef {import('./schema.js').Properties} Properties
* @typedef {import('./schema.js').Normal} Normal
*
* @typedef {Record<string, string>} Attributes
*
* @typedef {Object} Definition
* @property {Record<string, number|null>} properties
* @property {(attributes: Attributes, property: string) => string} transform
* @property {string} [space]
* @property {Attributes} [attributes]
* @property {Array<string>} [mustUseProperty]
*/
const own$3 = {}.hasOwnProperty;
/**
* @param {Definition} definition
* @returns {Schema}
*/
function create(definition) {
/** @type {Properties} */
const property = {};
/** @type {Normal} */
const normal = {};
/** @type {string} */
let prop;
for (prop in definition.properties) {
if (own$3.call(definition.properties, prop)) {
const value = definition.properties[prop];
const info = new DefinedInfo(
prop,
definition.transform(definition.attributes || {}, prop),
value,
definition.space
);
if (
definition.mustUseProperty &&
definition.mustUseProperty.includes(prop)
) {
info.mustUseProperty = true;
}
property[prop] = info;
normal[normalize(prop)] = prop;
normal[normalize(info.attribute)] = prop;
}
}
return new Schema(property, normal, definition.space)
}
const xlink = create({
space: 'xlink',
transform(_, prop) {
return 'xlink:' + prop.slice(5).toLowerCase()
},
properties: {
xLinkActuate: null,
xLinkArcRole: null,
xLinkHref: null,
xLinkRole: null,
xLinkShow: null,
xLinkTitle: null,
xLinkType: null
}
});
const xml = create({
space: 'xml',
transform(_, prop) {
return 'xml:' + prop.slice(3).toLowerCase()
},
properties: {xmlLang: null, xmlBase: null, xmlSpace: null}
});
/**
* @param {Record<string, string>} attributes
* @param {string} attribute
* @returns {string}
*/
function caseSensitiveTransform(attributes, attribute) {
return attribute in attributes ? attributes[attribute] : attribute
}
/**
* @param {Record<string, string>} attributes
* @param {string} property
* @returns {string}
*/
function caseInsensitiveTransform(attributes, property) {
return caseSensitiveTransform(attributes, property.toLowerCase())
}
const xmlns = create({
space: 'xmlns',
attributes: {xmlnsxlink: 'xmlns:xlink'},
transform: caseInsensitiveTransform,
properties: {xmlns: null, xmlnsXLink: null}
});
const aria = create({
transform(_, prop) {
return prop === 'role' ? prop : 'aria-' + prop.slice(4).toLowerCase()
},
properties: {
ariaActiveDescendant: null,
ariaAtomic: booleanish,
ariaAutoComplete: null,
ariaBusy: booleanish,
ariaChecked: booleanish,
ariaColCount: number,
ariaColIndex: number,
ariaColSpan: number,
ariaControls: spaceSeparated,
ariaCurrent: null,
ariaDescribedBy: spaceSeparated,
ariaDetails: null,
ariaDisabled: booleanish,
ariaDropEffect: spaceSeparated,
ariaErrorMessage: null,
ariaExpanded: booleanish,
ariaFlowTo: spaceSeparated,
ariaGrabbed: booleanish,
ariaHasPopup: null,
ariaHidden: booleanish,
ariaInvalid: null,
ariaKeyShortcuts: null,
ariaLabel: null,
ariaLabelledBy: spaceSeparated,
ariaLevel: number,
ariaLive: null,
ariaModal: booleanish,
ariaMultiLine: booleanish,
ariaMultiSelectable: booleanish,
ariaOrientation: null,
ariaOwns: spaceSeparated,
ariaPlaceholder: null,
ariaPosInSet: number,
ariaPressed: booleanish,
ariaReadOnly: booleanish,
ariaRelevant: null,
ariaRequired: booleanish,
ariaRoleDescription: spaceSeparated,
ariaRowCount: number,
ariaRowIndex: number,
ariaRowSpan: number,
ariaSelected: booleanish,
ariaSetSize: number,
ariaSort: null,
ariaValueMax: number,
ariaValueMin: number,
ariaValueNow: number,
ariaValueText: null,
role: null
}
});
const html$3 = create({
space: 'html',
attributes: {
acceptcharset: 'accept-charset',
classname: 'class',
htmlfor: 'for',
httpequiv: 'http-equiv'
},
transform: caseInsensitiveTransform,
mustUseProperty: ['checked', 'multiple', 'muted', 'selected'],
properties: {
// Standard Properties.
abbr: null,
accept: commaSeparated,
acceptCharset: spaceSeparated,
accessKey: spaceSeparated,
action: null,
allow: null,
allowFullScreen: boolean,
allowPaymentRequest: boolean,
allowUserMedia: boolean,
alt: null,
as: null,
async: boolean,
autoCapitalize: null,
autoComplete: spaceSeparated,
autoFocus: boolean,
autoPlay: boolean,
blocking: spaceSeparated,
capture: null,
charSet: null,
checked: boolean,
cite: null,
className: spaceSeparated,
cols: number,
colSpan: null,
content: null,
contentEditable: booleanish,
controls: boolean,
controlsList: spaceSeparated,
coords: number | commaSeparated,
crossOrigin: null,
data: null,
dateTime: null,
decoding: null,
default: boolean,
defer: boolean,
dir: null,
dirName: null,
disabled: boolean,
download: overloadedBoolean,
draggable: booleanish,
encType: null,
enterKeyHint: null,
fetchPriority: null,
form: null,
formAction: null,
formEncType: null,
formMethod: null,
formNoValidate: boolean,
formTarget: null,
headers: spaceSeparated,
height: number,
hidden: boolean,
high: number,
href: null,
hrefLang: null,
htmlFor: spaceSeparated,
httpEquiv: spaceSeparated,
id: null,
imageSizes: null,
imageSrcSet: null,
inert: boolean,
inputMode: null,
integrity: null,
is: null,
isMap: boolean,
itemId: null,
itemProp: spaceSeparated,
itemRef: spaceSeparated,
itemScope: boolean,
itemType: spaceSeparated,
kind: null,
label: null,
lang: null,
language: null,
list: null,
loading: null,
loop: boolean,
low: number,
manifest: null,
max: null,
maxLength: number,
media: null,
method: null,
min: null,
minLength: number,
multiple: boolean,
muted: boolean,
name: null,
nonce: null,
noModule: boolean,
noValidate: boolean,
onAbort: null,
onAfterPrint: null,
onAuxClick: null,
onBeforeMatch: null,
onBeforePrint: null,
onBeforeToggle: null,
onBeforeUnload: null,
onBlur: null,
onCancel: null,
onCanPlay: null,
onCanPlayThrough: null,
onChange: null,
onClick: null,
onClose: null,
onContextLost: null,
onContextMenu: null,
onContextRestored: null,
onCopy: null,
onCueChange: null,
onCut: null,
onDblClick: null,
onDrag: null,
onDragEnd: null,
onDragEnter: null,
onDragExit: null,
onDragLeave: null,
onDragOver: null,
onDragStart: null,
onDrop: null,
onDurationChange: null,
onEmptied: null,
onEnded: null,
onError: null,
onFocus: null,
onFormData: null,
onHashChange: null,
onInput: null,
onInvalid: null,
onKeyDown: null,
onKeyPress: null,
onKeyUp: null,
onLanguageChange: null,
onLoad: null,
onLoadedData: null,
onLoadedMetadata: null,
onLoadEnd: null,
onLoadStart: null,
onMessage: null,
onMessageError: null,
onMouseDown: null,
onMouseEnter: null,
onMouseLeave: null,
onMouseMove: null,
onMouseOut: null,
onMouseOver: null,
onMouseUp: null,
onOffline: null,
onOnline: null,
onPageHide: null,
onPageShow: null,
onPaste: null,
onPause: null,
onPlay: null,
onPlaying: null,
onPopState: null,
onProgress: null,
onRateChange: null,
onRejectionHandled: null,
onReset: null,
onResize: null,
onScroll: null,
onScrollEnd: null,
onSecurityPolicyViolation: null,
onSeeked: null,
onSeeking: null,
onSelect: null,
onSlotChange: null,
onStalled: null,
onStorage: null,
onSubmit: null,
onSuspend: null,
onTimeUpdate: null,
onToggle: null,
onUnhandledRejection: null,
onUnload: null,
onVolumeChange: null,
onWaiting: null,
onWheel: null,
open: boolean,
optimum: number,
pattern: null,
ping: spaceSeparated,
placeholder: null,
playsInline: boolean,
popover: null,
popoverTarget: null,
popoverTargetAction: null,
poster: null,
preload: null,
readOnly: boolean,
referrerPolicy: null,
rel: spaceSeparated,
required: boolean,
reversed: boolean,
rows: number,
rowSpan: number,
sandbox: spaceSeparated,
scope: null,
scoped: boolean,
seamless: boolean,
selected: boolean,
shadowRootDelegatesFocus: boolean,
shadowRootMode: null,
shape: null,
size: number,
sizes: null,
slot: null,
span: number,
spellCheck: booleanish,
src: null,
srcDoc: null,
srcLang: null,
srcSet: null,
start: number,
step: null,
style: null,
tabIndex: number,
target: null,
title: null,
translate: null,
type: null,
typeMustMatch: boolean,
useMap: null,
value: booleanish,
width: number,
wrap: null,
// Legacy.
// See: https://html.spec.whatwg.org/#other-elements,-attributes-and-apis
align: null, // Several. Use CSS `text-align` instead,
aLink: null, // `<body>`. Use CSS `a:active {color}` instead
archive: spaceSeparated, // `<object>`. List of URIs to archives
axis: null, // `<td>` and `<th>`. Use `scope` on `<th>`
background: null, // `<body>`. Use CSS `background-image` instead
bgColor: null, // `<body>` and table elements. Use CSS `background-color` instead
border: number, // `<table>`. Use CSS `border-width` instead,
borderColor: null, // `<table>`. Use CSS `border-color` instead,
bottomMargin: number, // `<body>`
cellPadding: null, // `<table>`
cellSpacing: null, // `<table>`
char: null, // Several table elements. When `align=char`, sets the character to align on
charOff: null, // Several table elements. When `char`, offsets the alignment
classId: null, // `<object>`
clear: null, // `<br>`. Use CSS `clear` instead
code: null, // `<object>`
codeBase: null, // `<object>`
codeType: null, // `<object>`
color: null, // `<font>` and `<hr>`. Use CSS instead
compact: boolean, // Lists. Use CSS to reduce space between items instead
declare: boolean, // `<object>`
event: null, // `<script>`
face: null, // `<font>`. Use CSS instead
frame: null, // `<table>`
frameBorder: null, // `<iframe>`. Use CSS `border` instead
hSpace: number, // `<img>` and `<object>`
leftMargin: number, // `<body>`
link: null, // `<body>`. Use CSS `a:link {color: *}` instead
longDesc: null, // `<frame>`, `<iframe>`, and `<img>`. Use an `<a>`
lowSrc: null, // `<img>`. Use a `<picture>`
marginHeight: number, // `<body>`
marginWidth: number, // `<body>`
noResize: boolean, // `<frame>`
noHref: boolean, // `<area>`. Use no href instead of an explicit `nohref`
noShade: boolean, // `<hr>`. Use background-color and height instead of borders
noWrap: boolean, // `<td>` and `<th>`
object: null, // `<applet>`
profile: null, // `<head>`
prompt: null, // `<isindex>`
rev: null, // `<link>`
rightMargin: number, // `<body>`
rules: null, // `<table>`
scheme: null, // `<meta>`
scrolling: booleanish, // `<frame>`. Use overflow in the child context
standby: null, // `<object>`
summary: null, // `<table>`
text: null, // `<body>`. Use CSS `color` instead
topMargin: number, // `<body>`
valueType: null, // `<param>`
version: null, // `<html>`. Use a doctype.
vAlign: null, // Several. Use CSS `vertical-align` instead
vLink: null, // `<body>`. Use CSS `a:visited {color}` instead
vSpace: number, // `<img>` and `<object>`
// Non-standard Properties.
allowTransparency: null,
autoCorrect: null,
autoSave: null,
disablePictureInPicture: boolean,
disableRemotePlayback: boolean,
prefix: null,
property: null,
results: number,
security: null,
unselectable: null
}
});
const svg$1 = create({
space: 'svg',
attributes: {
accentHeight: 'accent-height',
alignmentBaseline: 'alignment-baseline',
arabicForm: 'arabic-form',
baselineShift: 'baseline-shift',
capHeight: 'cap-height',
className: 'class',
clipPath: 'clip-path',
clipRule: 'clip-rule',
colorInterpolation: 'color-interpolation',
colorInterpolationFilters: 'color-interpolation-filters',
colorProfile: 'color-profile',
colorRendering: 'color-rendering',
crossOrigin: 'crossorigin',
dataType: 'datatype',
dominantBaseline: 'dominant-baseline',
enableBackground: 'enable-background',
fillOpacity: 'fill-opacity',
fillRule: 'fill-rule',
floodColor: 'flood-color',
floodOpacity: 'flood-opacity',
fontFamily: 'font-family',
fontSize: 'font-size',
fontSizeAdjust: 'font-size-adjust',
fontStretch: 'font-stretch',
fontStyle: 'font-style',
fontVariant: 'font-variant',
fontWeight: 'font-weight',
glyphName: 'glyph-name',
glyphOrientationHorizontal: 'glyph-orientation-horizontal',
glyphOrientationVertical: 'glyph-orientation-vertical',
hrefLang: 'hreflang',
horizAdvX: 'horiz-adv-x',
horizOriginX: 'horiz-origin-x',
horizOriginY: 'horiz-origin-y',
imageRendering: 'image-rendering',
letterSpacing: 'letter-spacing',
lightingColor: 'lighting-color',
markerEnd: 'marker-end',
markerMid: 'marker-mid',
markerStart: 'marker-start',
navDown: 'nav-down',
navDownLeft: 'nav-down-left',
navDownRight: 'nav-down-right',
navLeft: 'nav-left',
navNext: 'nav-next',
navPrev: 'nav-prev',
navRight: 'nav-right',
navUp: 'nav-up',
navUpLeft: 'nav-up-left',
navUpRight: 'nav-up-right',
onAbort: 'onabort',
onActivate: 'onactivate',
onAfterPrint: 'onafterprint',
onBeforePrint: 'onbeforeprint',
onBegin: 'onbegin',
onCancel: 'oncancel',
onCanPlay: 'oncanplay',
onCanPlayThrough: 'oncanplaythrough',
onChange: 'onchange',
onClick: 'onclick',
onClose: 'onclose',
onCopy: 'oncopy',
onCueChange: 'oncuechange',
onCut: 'oncut',
onDblClick: 'ondblclick',
onDrag: 'ondrag',
onDragEnd: 'ondragend',
onDragEnter: 'ondragenter',
onDragExit: 'ondragexit',
onDragLeave: 'ondragleave',
onDragOver: 'ondragover',
onDragStart: 'ondragstart',
onDrop: 'ondrop',
onDurationChange: 'ondurationchange',
onEmptied: 'onemptied',
onEnd: 'onend',
onEnded: 'onended',
onError: 'onerror',
onFocus: 'onfocus',
onFocusIn: 'onfocusin',
onFocusOut: 'onfocusout',
onHashChange: 'onhashchange',
onInput: 'oninput',
onInvalid: 'oninvalid',
onKeyDown: 'onkeydown',
onKeyPress: 'onkeypress',
onKeyUp: 'onkeyup',
onLoad: 'onload',
onLoadedData: 'onloadeddata',
onLoadedMetadata: 'onloadedmetadata',
onLoadStart: 'onloadstart',
onMessage: 'onmessage',
onMouseDown: 'onmousedown',
onMouseEnter: 'onmouseenter',
onMouseLeave: 'onmouseleave',
onMouseMove: 'onmousemove',
onMouseOut: 'onmouseout',
onMouseOver: 'onmouseover',
onMouseUp: 'onmouseup',
onMouseWheel: 'onmousewheel',
onOffline: 'onoffline',
onOnline: 'ononline',
onPageHide: 'onpagehide',
onPageShow: 'onpageshow',
onPaste: 'onpaste',
onPause: 'onpause',
onPlay: 'onplay',
onPlaying: 'onplaying',
onPopState: 'onpopstate',
onProgress: 'onprogress',
onRateChange: 'onratechange',
onRepeat: 'onrepeat',
onReset: 'onreset',
onResize: 'onresize',
onScroll: 'onscroll',
onSeeked: 'onseeked',
onSeeking: 'onseeking',
onSelect: 'onselect',
onShow: 'onshow',
onStalled: 'onstalled',
onStorage: 'onstorage',
onSubmit: 'onsubmit',
onSuspend: 'onsuspend',
onTimeUpdate: 'ontimeupdate',
onToggle: 'ontoggle',
onUnload: 'onunload',
onVolumeChange: 'onvolumechange',
onWaiting: 'onwaiting',
onZoom: 'onzoom',
overlinePosition: 'overline-position',
overlineThickness: 'overline-thickness',
paintOrder: 'paint-order',
panose1: 'panose-1',
pointerEvents: 'pointer-events',
referrerPolicy: 'referrerpolicy',
renderingIntent: 'rendering-intent',
shapeRendering: 'shape-rendering',
stopColor: 'stop-color',
stopOpacity: 'stop-opacity',
strikethroughPosition: 'strikethrough-position',
strikethroughThickness: 'strikethrough-thickness',
strokeDashArray: 'stroke-dasharray',
strokeDashOffset: 'stroke-dashoffset',
strokeLineCap: 'stroke-linecap',
strokeLineJoin: 'stroke-linejoin',
strokeMiterLimit: 'stroke-miterlimit',
strokeOpacity: 'stroke-opacity',
strokeWidth: 'stroke-width',
tabIndex: 'tabindex',
textAnchor: 'text-anchor',
textDecoration: 'text-decoration',
textRendering: 'text-rendering',
transformOrigin: 'transform-origin',
typeOf: 'typeof',
underlinePosition: 'underline-position',
underlineThickness: 'underline-thickness',
unicodeBidi: 'unicode-bidi',
unicodeRange: 'unicode-range',
unitsPerEm: 'units-per-em',
vAlphabetic: 'v-alphabetic',
vHanging: 'v-hanging',
vIdeographic: 'v-ideographic',
vMathematical: 'v-mathematical',
vectorEffect: 'vector-effect',
vertAdvY: 'vert-adv-y',
vertOriginX: 'vert-origin-x',
vertOriginY: 'vert-origin-y',
wordSpacing: 'word-spacing',
writingMode: 'writing-mode',
xHeight: 'x-height',
// These were camelcased in Tiny. Now lowercased in SVG 2
playbackOrder: 'playbackorder',
timelineBegin: 'timelinebegin'
},
transform: caseSensitiveTransform,
properties: {
about: commaOrSpaceSeparated,
accentHeight: number,
accumulate: null,
additive: null,
alignmentBaseline: null,
alphabetic: number,
amplitude: number,
arabicForm: null,
ascent: number,
attributeName: null,
attributeType: null,
azimuth: number,
bandwidth: null,
baselineShift: null,
baseFrequency: null,
baseProfile: null,
bbox: null,
begin: null,
bias: number,
by: null,
calcMode: null,
capHeight: number,
className: spaceSeparated,
clip: null,
clipPath: null,
clipPathUnits: null,
clipRule: null,
color: null,
colorInterpolation: null,
colorInterpolationFilters: null,
colorProfile: null,
colorRendering: null,
content: null,
contentScriptType: null,
contentStyleType: null,
crossOrigin: null,
cursor: null,
cx: null,
cy: null,
d: null,
dataType: null,
defaultAction: null,
descent: number,
diffuseConstant: number,
direction: null,
display: null,
dur: null,
divisor: number,
dominantBaseline: null,
download: boolean,
dx: null,
dy: null,
edgeMode: null,
editable: null,
elevation: number,
enableBackground: null,
end: null,
event: null,
exponent: number,
externalResourcesRequired: null,
fill: null,
fillOpacity: number,
fillRule: null,
filter: null,
filterRes: null,
filterUnits: null,
floodColor: null,
floodOpacity: null,
focusable: null,
focusHighlight: null,
fontFamily: null,
fontSize: null,
fontSizeAdjust: null,
fontStretch: null,
fontStyle: null,
fontVariant: null,
fontWeight: null,
format: null,
fr: null,
from: null,
fx: null,
fy: null,
g1: commaSeparated,
g2: commaSeparated,
glyphName: commaSeparated,
glyphOrientationHorizontal: null,
glyphOrientationVertical: null,
glyphRef: null,
gradientTransform: null,
gradientUnits: null,
handler: null,
hanging: number,
hatchContentUnits: null,
hatchUnits: null,
height: null,
href: null,
hrefLang: null,
horizAdvX: number,
horizOriginX: number,
horizOriginY: number,
id: null,
ideographic: number,
imageRendering: null,
initialVisibility: null,
in: null,
in2: null,
intercept: number,
k: number,
k1: number,
k2: number,
k3: number,
k4: number,
kernelMatrix: commaOrSpaceSeparated,
kernelUnitLength: null,
keyPoints: null, // SEMI_COLON_SEPARATED
keySplines: null, // SEMI_COLON_SEPARATED
keyTimes: null, // SEMI_COLON_SEPARATED
kerning: null,
lang: null,
lengthAdjust: null,
letterSpacing: null,
lightingColor: null,
limitingConeAngle: number,
local: null,
markerEnd: null,
markerMid: null,
markerStart: null,
markerHeight: null,
markerUnits: null,
markerWidth: null,
mask: null,
maskContentUnits: null,
maskUnits: null,
mathematical: null,
max: null,
media: null,
mediaCharacterEncoding: null,
mediaContentEncodings: null,
mediaSize: number,
mediaTime: null,
method: null,
min: null,
mode: null,
name: null,
navDown: null,
navDownLeft: null,
navDownRight: null,
navLeft: null,
navNext: null,
navPrev: null,
navRight: null,
navUp: null,
navUpLeft: null,
navUpRight: null,
numOctaves: null,
observer: null,
offset: null,
onAbort: null,
onActivate: null,
onAfterPrint: null,
onBeforePrint: null,
onBegin: null,
onCancel: null,
onCanPlay: null,
onCanPlayThrough: null,
onChange: null,
onClick: null,
onClose: null,
onCopy: null,
onCueChange: null,
onCut: null,
onDblClick: null,
onDrag: null,
onDragEnd: null,
onDragEnter: null,
onDragExit: null,
onDragLeave: null,
onDragOver: null,
onDragStart: null,
onDrop: null,
onDurationChange: null,
onEmptied: null,
onEnd: null,
onEnded: null,
onError: null,
onFocus: null,
onFocusIn: null,
onFocusOut: null,
onHashChange: null,
onInput: null,
onInvalid: null,
onKeyDown: null,
onKeyPress: null,
onKeyUp: null,
onLoad: null,
onLoadedData: null,
onLoadedMetadata: null,
onLoadStart: null,
onMessage: null,
onMouseDown: null,
onMouseEnter: null,
onMouseLeave: null,
onMouseMove: null,
onMouseOut: null,
onMouseOver: null,
onMouseUp: null,
onMouseWheel: null,
onOffline: null,
onOnline: null,
onPageHide: null,
onPageShow: null,
onPaste: null,
onPause: null,
onPlay: null,
onPlaying: null,
onPopState: null,
onProgress: null,
onRateChange: null,
onRepeat: null,
onReset: null,
onResize: null,
onScroll: null,
onSeeked: null,
onSeeking: null,
onSelect: null,
onShow: null,
onStalled: null,
onStorage: null,
onSubmit: null,
onSuspend: null,
onTimeUpdate: null,
onToggle: null,
onUnload: null,
onVolumeChange: null,
onWaiting: null,
onZoom: null,
opacity: null,
operator: null,
order: null,
orient: null,
orientation: null,
origin: null,
overflow: null,
overlay: null,
overlinePosition: number,
overlineThickness: number,
paintOrder: null,
panose1: null,
path: null,
pathLength: number,
patternContentUnits: null,
patternTransform: null,
patternUnits: null,
phase: null,
ping: spaceSeparated,
pitch: null,
playbackOrder: null,
pointerEvents: null,
points: null,
pointsAtX: number,
pointsAtY: number,
pointsAtZ: number,
preserveAlpha: null,
preserveAspectRatio: null,
primitiveUnits: null,
propagate: null,
property: commaOrSpaceSeparated,
r: null,
radius: null,
referrerPolicy: null,
refX: null,
refY: null,
rel: commaOrSpaceSeparated,
rev: commaOrSpaceSeparated,
renderingIntent: null,
repeatCount: null,
repeatDur: null,
requiredExtensions: commaOrSpaceSeparated,
requiredFeatures: commaOrSpaceSeparated,
requiredFonts: commaOrSpaceSeparated,
requiredFormats: commaOrSpaceSeparated,
resource: null,
restart: null,
result: null,
rotate: null,
rx: null,
ry: null,
scale: null,
seed: null,
shapeRendering: null,
side: null,
slope: null,
snapshotTime: null,
specularConstant: number,
specularExponent: number,
spreadMethod: null,
spacing: null,
startOffset: null,
stdDeviation: null,
stemh: null,
stemv: null,
stitchTiles: null,
stopColor: null,
stopOpacity: null,
strikethroughPosition: number,
strikethroughThickness: number,
string: null,
stroke: null,
strokeDashArray: commaOrSpaceSeparated,
strokeDashOffset: null,
strokeLineCap: null,
strokeLineJoin: null,
strokeMiterLimit: number,
strokeOpacity: number,
strokeWidth: null,
style: null,
surfaceScale: number,
syncBehavior: null,
syncBehaviorDefault: null,
syncMaster: null,
syncTolerance: null,
syncToleranceDefault: null,
systemLanguage: commaOrSpaceSeparated,
tabIndex: number,
tableValues: null,
target: null,
targetX: number,
targetY: number,
textAnchor: null,
textDecoration: null,
textRendering: null,
textLength: null,
timelineBegin: null,
title: null,
transformBehavior: null,
type: null,
typeOf: commaOrSpaceSeparated,
to: null,
transform: null,
transformOrigin: null,
u1: null,
u2: null,
underlinePosition: number,
underlineThickness: number,
unicode: null,
unicodeBidi: null,
unicodeRange: null,
unitsPerEm: number,
values: null,
vAlphabetic: number,
vMathematical: number,
vectorEffect: null,
vHanging: number,
vIdeographic: number,
version: null,
vertAdvY: number,
vertOriginX: number,
vertOriginY: number,
viewBox: null,
viewTarget: null,
visibility: null,
width: null,
widths: null,
wordSpacing: null,
writingMode: null,
x: null,
x1: null,
x2: null,
xChannelSelector: null,
xHeight: number,
y: null,
y1: null,
y2: null,
yChannelSelector: null,
z: null,
zoomAndPan: null
}
});
/**
* @typedef {import('./util/schema.js').Schema} Schema
*/
const valid = /^data[-\w.:]+$/i;
const dash = /-[a-z]/g;
const cap = /[A-Z]/g;
/**
* @param {Schema} schema
* @param {string} value
* @returns {Info}
*/
function find(schema, value) {
const normal = normalize(value);
let prop = value;
let Type = Info;
if (normal in schema.normal) {
return schema.property[schema.normal[normal]]
}
if (normal.length > 4 && normal.slice(0, 4) === 'data' && valid.test(value)) {
// Attribute or property.
if (value.charAt(4) === '-') {
// Turn it into a property.
const rest = value.slice(5).replace(dash, camelcase);
prop = 'data' + rest.charAt(0).toUpperCase() + rest.slice(1);
} else {
// Turn it into an attribute.
const rest = value.slice(4);
if (!dash.test(rest)) {
let dashes = rest.replace(cap, kebab);
if (dashes.charAt(0) !== '-') {
dashes = '-' + dashes;
}
value = 'data' + dashes;
}
}
Type = DefinedInfo;
}
return new Type(prop, value)
}
/**
* @param {string} $0
* @returns {string}
*/
function kebab($0) {
return '-' + $0.toLowerCase()
}
/**
* @param {string} $0
* @returns {string}
*/
function camelcase($0) {
return $0.charAt(1).toUpperCase()
}
/**
* @typedef {import('./lib/util/info.js').Info} Info
* @typedef {import('./lib/util/schema.js').Schema} Schema
*/
const html$2 = merge([xml, xlink, xmlns, aria, html$3], 'html');
const svg = merge([xml, xlink, xmlns, aria, svg$1], 'svg');
/**
* @callback Handler
* Handle a value, with a certain ID field set to a certain value.
* The ID field is passed to `zwitch`, and its value is this functions
* place on the `handlers` record.
* @param {...any} parameters
* Arbitrary parameters passed to the zwitch.
* The first will be an object with a certain ID field set to a certain value.
* @returns {any}
* Anything!
*/
/**
* @callback UnknownHandler
* Handle values that do have a certain ID field, but its set to a value
* that is not listed in the `handlers` record.
* @param {unknown} value
* An object with a certain ID field set to an unknown value.
* @param {...any} rest
* Arbitrary parameters passed to the zwitch.
* @returns {any}
* Anything!
*/
/**
* @callback InvalidHandler
* Handle values that do not have a certain ID field.
* @param {unknown} value
* Any unknown value.
* @param {...any} rest
* Arbitrary parameters passed to the zwitch.
* @returns {void|null|undefined|never}
* This should crash or return nothing.
*/
/**
* @template {InvalidHandler} [Invalid=InvalidHandler]
* @template {UnknownHandler} [Unknown=UnknownHandler]
* @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
* @typedef Options
* Configuration (required).
* @property {Invalid} [invalid]
* Handler to use for invalid values.
* @property {Unknown} [unknown]
* Handler to use for unknown values.
* @property {Handlers} [handlers]
* Handlers to use.
*/
const own$2 = {}.hasOwnProperty;
/**
* Handle values based on a field.
*
* @template {InvalidHandler} [Invalid=InvalidHandler]
* @template {UnknownHandler} [Unknown=UnknownHandler]
* @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
* @param {string} key
* Field to switch on.
* @param {Options<Invalid, Unknown, Handlers>} [options]
* Configuration (required).
* @returns {{unknown: Unknown, invalid: Invalid, handlers: Handlers, (...parameters: Parameters<Handlers[keyof Handlers]>): ReturnType<Handlers[keyof Handlers]>, (...parameters: Parameters<Unknown>): ReturnType<Unknown>}}
*/
function zwitch(key, options) {
const settings = options || {};
/**
* Handle one value.
*
* Based on the bound `key`, a respective handler will be called.
* If `value` is not an object, or doesnt have a `key` property, the special
* invalid handler will be called.
* If `value` has an unknown `key`, the special unknown handler will be
* called.
*
* All arguments, and the context object, are passed through to the handler,
* and its result is returned.
*
* @this {unknown}
* Any context object.
* @param {unknown} [value]
* Any value.
* @param {...unknown} parameters
* Arbitrary parameters passed to the zwitch.
* @property {Handler} invalid
* Handle for values that do not have a certain ID field.
* @property {Handler} unknown
* Handle values that do have a certain ID field, but its set to a value
* that is not listed in the `handlers` record.
* @property {Handlers} handlers
* Record of handlers.
* @returns {unknown}
* Anything.
*/
function one(value, ...parameters) {
/** @type {Handler|undefined} */
let fn = one.invalid;
const handlers = one.handlers;
if (value && own$2.call(value, key)) {
// @ts-expect-error Indexable.
const id = String(value[key]);
// @ts-expect-error Indexable.
fn = own$2.call(handlers, id) ? handlers[id] : one.unknown;
}
if (fn) {
return fn.call(this, value, ...parameters)
}
}
one.handlers = settings.handlers || {};
one.invalid = settings.invalid;
one.unknown = settings.unknown;
// @ts-expect-error: matches!
return one
}
/**
* @typedef CoreOptions
* @property {Array<string>} [subset=[]]
* Whether to only escape the given subset of characters.
* @property {boolean} [escapeOnly=false]
* Whether to only escape possibly dangerous characters.
* Those characters are `"`, `&`, `'`, `<`, `>`, and `` ` ``.
*
* @typedef FormatOptions
* @property {(code: number, next: number, options: CoreWithFormatOptions) => string} format
* Format strategy.
*
* @typedef {CoreOptions & FormatOptions & import('./util/format-smart.js').FormatSmartOptions} CoreWithFormatOptions
*/
/**
* Encode certain characters in `value`.
*
* @param {string} value
* @param {CoreWithFormatOptions} options
* @returns {string}
*/
function core(value, options) {
value = value.replace(
options.subset ? charactersToExpression(options.subset) : /["&'<>`]/g,
basic
);
if (options.subset || options.escapeOnly) {
return value
}
return (
value
// Surrogate pairs.
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, surrogate)
// BMP control characters (C0 except for LF, CR, SP; DEL; and some more
// non-ASCII ones).
.replace(
// eslint-disable-next-line no-control-regex, unicorn/no-hex-escape
/[\x01-\t\v\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g,
basic
)
)
/**
* @param {string} pair
* @param {number} index
* @param {string} all
*/
function surrogate(pair, index, all) {
return options.format(
(pair.charCodeAt(0) - 0xd800) * 0x400 +
pair.charCodeAt(1) -
0xdc00 +
0x10000,
all.charCodeAt(index + 2),
options
)
}
/**
* @param {string} character
* @param {number} index
* @param {string} all
*/
function basic(character, index, all) {
return options.format(
character.charCodeAt(0),
all.charCodeAt(index + 1),
options
)
}
}
/**
* @param {Array<string>} subset
* @returns {RegExp}
*/
function charactersToExpression(subset) {
/** @type {Array<string>} */
const groups = [];
let index = -1;
while (++index < subset.length) {
groups.push(subset[index].replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'));
}
return new RegExp('(?:' + groups.join('|') + ')', 'g')
}
/**
* Configurable ways to encode characters as hexadecimal references.
*
* @param {number} code
* @param {number} next
* @param {boolean|undefined} omit
* @returns {string}
*/
function toHexadecimal(code, next, omit) {
const value = '&#x' + code.toString(16).toUpperCase();
return omit && next && !/[\dA-Fa-f]/.test(String.fromCharCode(next))
? value
: value + ';'
}
/**
* Configurable ways to encode characters as decimal references.
*
* @param {number} code
* @param {number} next
* @param {boolean|undefined} omit
* @returns {string}
*/
function toDecimal(code, next, omit) {
const value = '&#' + String(code);
return omit && next && !/\d/.test(String.fromCharCode(next))
? value
: value + ';'
}
/**
* List of legacy HTML named character references that dont need a trailing semicolon.
*
* @type {Array<string>}
*/
const characterEntitiesLegacy = [
'AElig',
'AMP',
'Aacute',
'Acirc',
'Agrave',
'Aring',
'Atilde',
'Auml',
'COPY',
'Ccedil',
'ETH',
'Eacute',
'Ecirc',
'Egrave',
'Euml',
'GT',
'Iacute',
'Icirc',
'Igrave',
'Iuml',
'LT',
'Ntilde',
'Oacute',
'Ocirc',
'Ograve',
'Oslash',
'Otilde',
'Ouml',
'QUOT',
'REG',
'THORN',
'Uacute',
'Ucirc',
'Ugrave',
'Uuml',
'Yacute',
'aacute',
'acirc',
'acute',
'aelig',
'agrave',
'amp',
'aring',
'atilde',
'auml',
'brvbar',
'ccedil',
'cedil',
'cent',
'copy',
'curren',
'deg',
'divide',
'eacute',
'ecirc',
'egrave',
'eth',
'euml',
'frac12',
'frac14',
'frac34',
'gt',
'iacute',
'icirc',
'iexcl',
'igrave',
'iquest',
'iuml',
'laquo',
'lt',
'macr',
'micro',
'middot',
'nbsp',
'not',
'ntilde',
'oacute',
'ocirc',
'ograve',
'ordf',
'ordm',
'oslash',
'otilde',
'ouml',
'para',
'plusmn',
'pound',
'quot',
'raquo',
'reg',
'sect',
'shy',
'sup1',
'sup2',
'sup3',
'szlig',
'thorn',
'times',
'uacute',
'ucirc',
'ugrave',
'uml',
'uuml',
'yacute',
'yen',
'yuml'
];
/**
* Map of named character references from HTML 4.
*
* @type {Record<string, string>}
*/
const characterEntitiesHtml4 = {
nbsp: ' ',
iexcl: '¡',
cent: '¢',
pound: '£',
curren: '¤',
yen: '¥',
brvbar: '¦',
sect: '§',
uml: '¨',
copy: '©',
ordf: 'ª',
laquo: '«',
not: '¬',
shy: '­',
reg: '®',
macr: '¯',
deg: '°',
plusmn: '±',
sup2: '²',
sup3: '³',
acute: '´',
micro: 'µ',
para: '¶',
middot: '·',
cedil: '¸',
sup1: '¹',
ordm: 'º',
raquo: '»',
frac14: '¼',
frac12: '½',
frac34: '¾',
iquest: '¿',
Agrave: 'À',
Aacute: 'Á',
Acirc: 'Â',
Atilde: 'Ã',
Auml: 'Ä',
Aring: 'Å',
AElig: 'Æ',
Ccedil: 'Ç',
Egrave: 'È',
Eacute: 'É',
Ecirc: 'Ê',
Euml: 'Ë',
Igrave: 'Ì',
Iacute: 'Í',
Icirc: 'Î',
Iuml: 'Ï',
ETH: 'Ð',
Ntilde: 'Ñ',
Ograve: 'Ò',
Oacute: 'Ó',
Ocirc: 'Ô',
Otilde: 'Õ',
Ouml: 'Ö',
times: '×',
Oslash: 'Ø',
Ugrave: 'Ù',
Uacute: 'Ú',
Ucirc: 'Û',
Uuml: 'Ü',
Yacute: 'Ý',
THORN: 'Þ',
szlig: 'ß',
agrave: 'à',
aacute: 'á',
acirc: 'â',
atilde: 'ã',
auml: 'ä',
aring: 'å',
aelig: 'æ',
ccedil: 'ç',
egrave: 'è',
eacute: 'é',
ecirc: 'ê',
euml: 'ë',
igrave: 'ì',
iacute: 'í',
icirc: 'î',
iuml: 'ï',
eth: 'ð',
ntilde: 'ñ',
ograve: 'ò',
oacute: 'ó',
ocirc: 'ô',
otilde: 'õ',
ouml: 'ö',
divide: '÷',
oslash: 'ø',
ugrave: 'ù',
uacute: 'ú',
ucirc: 'û',
uuml: 'ü',
yacute: 'ý',
thorn: 'þ',
yuml: 'ÿ',
fnof: 'ƒ',
Alpha: 'Α',
Beta: 'Β',
Gamma: 'Γ',
Delta: 'Δ',
Epsilon: 'Ε',
Zeta: 'Ζ',
Eta: 'Η',
Theta: 'Θ',
Iota: 'Ι',
Kappa: 'Κ',
Lambda: 'Λ',
Mu: 'Μ',
Nu: 'Ν',
Xi: 'Ξ',
Omicron: 'Ο',
Pi: 'Π',
Rho: 'Ρ',
Sigma: 'Σ',
Tau: 'Τ',
Upsilon: 'Υ',
Phi: 'Φ',
Chi: 'Χ',
Psi: 'Ψ',
Omega: 'Ω',
alpha: 'α',
beta: 'β',
gamma: 'γ',
delta: 'δ',
epsilon: 'ε',
zeta: 'ζ',
eta: 'η',
theta: 'θ',
iota: 'ι',
kappa: 'κ',
lambda: 'λ',
mu: 'μ',
nu: 'ν',
xi: 'ξ',
omicron: 'ο',
pi: 'π',
rho: 'ρ',
sigmaf: 'ς',
sigma: 'σ',
tau: 'τ',
upsilon: 'υ',
phi: 'φ',
chi: 'χ',
psi: 'ψ',
omega: 'ω',
thetasym: 'ϑ',
upsih: 'ϒ',
piv: 'ϖ',
bull: '•',
hellip: '…',
prime: '',
Prime: '″',
oline: '‾',
frasl: '',
weierp: '℘',
image: '',
real: '',
trade: '™',
alefsym: 'ℵ',
larr: '←',
uarr: '↑',
rarr: '→',
darr: '↓',
harr: '↔',
crarr: '↵',
lArr: '⇐',
uArr: '⇑',
rArr: '⇒',
dArr: '⇓',
hArr: '⇔',
forall: '∀',
part: '∂',
exist: '∃',
empty: '∅',
nabla: '∇',
isin: '∈',
notin: '∉',
ni: '∋',
prod: '∏',
sum: '∑',
minus: '',
lowast: '',
radic: '√',
prop: '∝',
infin: '∞',
ang: '∠',
and: '∧',
or: '',
cap: '∩',
cup: '',
int: '∫',
there4: '∴',
sim: '',
cong: '≅',
asymp: '≈',
ne: '≠',
equiv: '≡',
le: '≤',
ge: '≥',
sub: '⊂',
sup: '⊃',
nsub: '⊄',
sube: '⊆',
supe: '⊇',
oplus: '⊕',
otimes: '⊗',
perp: '⊥',
sdot: '⋅',
lceil: '⌈',
rceil: '⌉',
lfloor: '⌊',
rfloor: '⌋',
lang: '〈',
rang: '〉',
loz: '◊',
spades: '♠',
clubs: '♣',
hearts: '♥',
diams: '♦',
quot: '"',
amp: '&',
lt: '<',
gt: '>',
OElig: 'Œ',
oelig: 'œ',
Scaron: 'Š',
scaron: 'š',
Yuml: 'Ÿ',
circ: 'ˆ',
tilde: '˜',
ensp: '',
emsp: '',
thinsp: '',
zwnj: '',
zwj: '',
lrm: '',
rlm: '',
ndash: '',
mdash: '—',
lsquo: '',
rsquo: '',
sbquo: '',
ldquo: '“',
rdquo: '”',
bdquo: '„',
dagger: '†',
Dagger: '‡',
permil: '‰',
lsaquo: '',
rsaquo: '',
euro: '€'
};
/**
* List of legacy (that dont need a trailing `;`) named references which could,
* depending on what follows them, turn into a different meaning
*
* @type {Array<string>}
*/
const dangerous = [
'cent',
'copy',
'divide',
'gt',
'lt',
'not',
'para',
'times'
];
const own$1 = {}.hasOwnProperty;
/**
* `characterEntitiesHtml4` but inverted.
*
* @type {Record<string, string>}
*/
const characters = {};
/** @type {string} */
let key;
for (key in characterEntitiesHtml4) {
if (own$1.call(characterEntitiesHtml4, key)) {
characters[characterEntitiesHtml4[key]] = key;
}
}
/**
* Configurable ways to encode characters as named references.
*
* @param {number} code
* @param {number} next
* @param {boolean|undefined} omit
* @param {boolean|undefined} attribute
* @returns {string}
*/
function toNamed(code, next, omit, attribute) {
const character = String.fromCharCode(code);
if (own$1.call(characters, character)) {
const name = characters[character];
const value = '&' + name;
if (
omit &&
characterEntitiesLegacy.includes(name) &&
!dangerous.includes(name) &&
(!attribute ||
(next &&
next !== 61 /* `=` */ &&
/[^\da-z]/i.test(String.fromCharCode(next))))
) {
return value
}
return value + ';'
}
return ''
}
/**
* @typedef FormatSmartOptions
* @property {boolean} [useNamedReferences=false]
* Prefer named character references (`&amp;`) where possible.
* @property {boolean} [useShortestReferences=false]
* Prefer the shortest possible reference, if that results in less bytes.
* **Note**: `useNamedReferences` can be omitted when using `useShortestReferences`.
* @property {boolean} [omitOptionalSemicolons=false]
* Whether to omit semicolons when possible.
* **Note**: This creates what HTML calls parse errors but is otherwise still valid HTML dont use this except when building a minifier.
* Omitting semicolons is possible for certain named and numeric references in some cases.
* @property {boolean} [attribute=false]
* Create character references which dont fail in attributes.
* **Note**: `attribute` only applies when operating dangerously with
* `omitOptionalSemicolons: true`.
*/
/**
* Configurable ways to encode a character yielding pretty or small results.
*
* @param {number} code
* @param {number} next
* @param {FormatSmartOptions} options
* @returns {string}
*/
function formatSmart(code, next, options) {
let numeric = toHexadecimal(code, next, options.omitOptionalSemicolons);
/** @type {string|undefined} */
let named;
if (options.useNamedReferences || options.useShortestReferences) {
named = toNamed(
code,
next,
options.omitOptionalSemicolons,
options.attribute
);
}
// Use the shortest numeric reference when requested.
// A simple algorithm would use decimal for all code points under 100, as
// those are shorter than hexadecimal:
//
// * `&#99;` vs `&#x63;` (decimal shorter)
// * `&#100;` vs `&#x64;` (equal)
//
// However, because we take `next` into consideration when `omit` is used,
// And it would be possible that decimals are shorter on bigger values as
// well if `next` is hexadecimal but not decimal, we instead compare both.
if (
(options.useShortestReferences || !named) &&
options.useShortestReferences
) {
const decimal = toDecimal(code, next, options.omitOptionalSemicolons);
if (decimal.length < numeric.length) {
numeric = decimal;
}
}
return named &&
(!options.useShortestReferences || named.length < numeric.length)
? named
: numeric
}
/**
* @typedef {import('./core.js').CoreOptions & import('./util/format-smart.js').FormatSmartOptions} Options
* @typedef {import('./core.js').CoreOptions} LightOptions
*/
/**
* Encode special characters in `value`.
*
* @param {string} value
* Value to encode.
* @param {Options} [options]
* Configuration.
* @returns {string}
* Encoded value.
*/
function stringifyEntities(value, options) {
return core(value, Object.assign({format: formatSmart}, options))
}
/**
* @typedef {import('hast').Comment} Comment
* @typedef {import('hast').Parents} Parents
*
* @typedef {import('../index.js').State} State
*/
const htmlCommentRegex = /^>|^->|<!--|-->|--!>|<!-$/g;
// Declare arrays as variables so it can be cached by `stringifyEntities`
const bogusCommentEntitySubset = ['>'];
const commentEntitySubset = ['<', '>'];
/**
* Serialize a comment.
*
* @param {Comment} node
* Node to handle.
* @param {number | undefined} _1
* Index of `node` in `parent.
* @param {Parents | undefined} _2
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function comment(node, _1, _2, state) {
// See: <https://html.spec.whatwg.org/multipage/syntax.html#comments>
return state.settings.bogusComments
? '<?' +
stringifyEntities(
node.value,
Object.assign({}, state.settings.characterReferences, {
subset: bogusCommentEntitySubset
})
) +
'>'
: '<!--' + node.value.replace(htmlCommentRegex, encode) + '-->'
/**
* @param {string} $0
*/
function encode($0) {
return stringifyEntities(
$0,
Object.assign({}, state.settings.characterReferences, {
subset: commentEntitySubset
})
)
}
}
/**
* @typedef {import('hast').Doctype} Doctype
* @typedef {import('hast').Parents} Parents
*
* @typedef {import('../index.js').State} State
*/
/**
* Serialize a doctype.
*
* @param {Doctype} _1
* Node to handle.
* @param {number | undefined} _2
* Index of `node` in `parent.
* @param {Parents | undefined} _3
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function doctype(_1, _2, _3, state) {
return (
'<!' +
(state.settings.upperDoctype ? 'DOCTYPE' : 'doctype') +
(state.settings.tightDoctype ? '' : ' ') +
'html>'
)
}
/**
* Count how often a character (or substring) is used in a string.
*
* @param {string} value
* Value to search in.
* @param {string} character
* Character (or substring) to look for.
* @return {number}
* Number of times `character` occurred in `value`.
*/
function ccount(value, character) {
const source = String(value);
if (typeof character !== 'string') {
throw new TypeError('Expected character')
}
let count = 0;
let index = source.indexOf(character);
while (index !== -1) {
count++;
index = source.indexOf(character, index + character.length);
}
return count
}
/**
* @typedef Options
* Configuration for `stringify`.
* @property {boolean} [padLeft=true]
* Whether to pad a space before a token.
* @property {boolean} [padRight=false]
* Whether to pad a space after a token.
*/
/**
* Serialize an array of strings or numbers to comma-separated tokens.
*
* @param {Array<string|number>} values
* List of tokens.
* @param {Options} [options]
* Configuration for `stringify` (optional).
* @returns {string}
* Comma-separated tokens.
*/
function stringify$1(values, options) {
const settings = options || {};
// Ensure the last empty entry is seen.
const input = values[values.length - 1] === '' ? [...values, ''] : values;
return input
.join(
(settings.padRight ? ' ' : '') +
',' +
(settings.padLeft === false ? '' : ' ')
)
.trim()
}
/**
* Parse space-separated tokens to an array of strings.
*
* @param {string} value
* Space-separated tokens.
* @returns {Array<string>}
* List of tokens.
*/
/**
* Serialize an array of strings as space separated-tokens.
*
* @param {Array<string|number>} values
* List of tokens.
* @returns {string}
* Space-separated tokens.
*/
function stringify(values) {
return values.join(' ').trim()
}
/**
* @typedef {import('hast').Nodes} Nodes
*/
// HTML whitespace expression.
// See <https://infra.spec.whatwg.org/#ascii-whitespace>.
const re = /[ \t\n\f\r]/g;
/**
* Check if the given value is *inter-element whitespace*.
*
* @param {Nodes | string} thing
* Thing to check (`Node` or `string`).
* @returns {boolean}
* Whether the `value` is inter-element whitespace (`boolean`): consisting of
* zero or more of space, tab (`\t`), line feed (`\n`), carriage return
* (`\r`), or form feed (`\f`); if a node is passed it must be a `Text` node,
* whose `value` field is checked.
*/
function whitespace(thing) {
return typeof thing === 'object'
? thing.type === 'text'
? empty(thing.value)
: false
: empty(thing)
}
/**
* @param {string} value
* @returns {boolean}
*/
function empty(value) {
return value.replace(re, '') === ''
}
/**
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').RootContent} RootContent
*/
const siblingAfter = siblings(1);
const siblingBefore = siblings(-1);
/** @type {Array<RootContent>} */
const emptyChildren$1 = [];
/**
* Factory to check siblings in a direction.
*
* @param {number} increment
*/
function siblings(increment) {
return sibling
/**
* Find applicable siblings in a direction.
*
* @template {Parents} Parent
* Parent type.
* @param {Parent | undefined} parent
* Parent.
* @param {number | undefined} index
* Index of child in `parent`.
* @param {boolean | undefined} [includeWhitespace=false]
* Whether to include whitespace (default: `false`).
* @returns {Parent extends {children: Array<infer Child>} ? Child | undefined : never}
* Child of parent.
*/
function sibling(parent, index, includeWhitespace) {
const siblings = parent ? parent.children : emptyChildren$1;
let offset = (index || 0) + increment;
let next = siblings[offset];
if (!includeWhitespace) {
while (next && whitespace(next)) {
offset += increment;
next = siblings[offset];
}
}
// @ts-expect-error: its a correct child.
return next
}
}
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Parents} Parents
*/
/**
* @callback OmitHandle
* Check if a tag can be omitted.
* @param {Element} element
* Element to check.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether to omit a tag.
*
*/
const own = {}.hasOwnProperty;
/**
* Factory to check if a given node can have a tag omitted.
*
* @param {Record<string, OmitHandle>} handlers
* Omission handlers, where each key is a tag name, and each value is the
* corresponding handler.
* @returns {OmitHandle}
* Whether to omit a tag of an element.
*/
function omission(handlers) {
return omit
/**
* Check if a given node can have a tag omitted.
*
* @type {OmitHandle}
*/
function omit(node, index, parent) {
return (
own.call(handlers, node.tagName) &&
handlers[node.tagName](node, index, parent)
)
}
}
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Parents} Parents
*/
const closing = omission({
body: body$1,
caption: headOrColgroupOrCaption,
colgroup: headOrColgroupOrCaption,
dd,
dt,
head: headOrColgroupOrCaption,
html: html$1,
li,
optgroup,
option,
p,
rp: rubyElement,
rt: rubyElement,
tbody: tbody$1,
td: cells,
tfoot,
th: cells,
thead,
tr
});
/**
* Macro for `</head>`, `</colgroup>`, and `</caption>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function headOrColgroupOrCaption(_, index, parent) {
const next = siblingAfter(parent, index, true);
return (
!next ||
(next.type !== 'comment' &&
!(next.type === 'text' && whitespace(next.value.charAt(0))))
)
}
/**
* Whether to omit `</html>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function html$1(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || next.type !== 'comment'
}
/**
* Whether to omit `</body>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function body$1(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || next.type !== 'comment'
}
/**
* Whether to omit `</p>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function p(_, index, parent) {
const next = siblingAfter(parent, index);
return next
? next.type === 'element' &&
(next.tagName === 'address' ||
next.tagName === 'article' ||
next.tagName === 'aside' ||
next.tagName === 'blockquote' ||
next.tagName === 'details' ||
next.tagName === 'div' ||
next.tagName === 'dl' ||
next.tagName === 'fieldset' ||
next.tagName === 'figcaption' ||
next.tagName === 'figure' ||
next.tagName === 'footer' ||
next.tagName === 'form' ||
next.tagName === 'h1' ||
next.tagName === 'h2' ||
next.tagName === 'h3' ||
next.tagName === 'h4' ||
next.tagName === 'h5' ||
next.tagName === 'h6' ||
next.tagName === 'header' ||
next.tagName === 'hgroup' ||
next.tagName === 'hr' ||
next.tagName === 'main' ||
next.tagName === 'menu' ||
next.tagName === 'nav' ||
next.tagName === 'ol' ||
next.tagName === 'p' ||
next.tagName === 'pre' ||
next.tagName === 'section' ||
next.tagName === 'table' ||
next.tagName === 'ul')
: !parent ||
// Confusing parent.
!(
parent.type === 'element' &&
(parent.tagName === 'a' ||
parent.tagName === 'audio' ||
parent.tagName === 'del' ||
parent.tagName === 'ins' ||
parent.tagName === 'map' ||
parent.tagName === 'noscript' ||
parent.tagName === 'video')
)
}
/**
* Whether to omit `</li>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function li(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || (next.type === 'element' && next.tagName === 'li')
}
/**
* Whether to omit `</dt>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function dt(_, index, parent) {
const next = siblingAfter(parent, index);
return Boolean(
next &&
next.type === 'element' &&
(next.tagName === 'dt' || next.tagName === 'dd')
)
}
/**
* Whether to omit `</dd>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function dd(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'dt' || next.tagName === 'dd'))
)
}
/**
* Whether to omit `</rt>` or `</rp>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function rubyElement(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'rp' || next.tagName === 'rt'))
)
}
/**
* Whether to omit `</optgroup>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function optgroup(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || (next.type === 'element' && next.tagName === 'optgroup')
}
/**
* Whether to omit `</option>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function option(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'option' || next.tagName === 'optgroup'))
)
}
/**
* Whether to omit `</thead>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function thead(_, index, parent) {
const next = siblingAfter(parent, index);
return Boolean(
next &&
next.type === 'element' &&
(next.tagName === 'tbody' || next.tagName === 'tfoot')
)
}
/**
* Whether to omit `</tbody>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function tbody$1(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'tbody' || next.tagName === 'tfoot'))
)
}
/**
* Whether to omit `</tfoot>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function tfoot(_, index, parent) {
return !siblingAfter(parent, index)
}
/**
* Whether to omit `</tr>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function tr(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || (next.type === 'element' && next.tagName === 'tr')
}
/**
* Whether to omit `</td>` or `</th>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function cells(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'td' || next.tagName === 'th'))
)
}
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Parents} Parents
*/
const opening = omission({
body,
colgroup,
head,
html,
tbody
});
/**
* Whether to omit `<html>`.
*
* @param {Element} node
* Element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function html(node) {
const head = siblingAfter(node, -1);
return !head || head.type !== 'comment'
}
/**
* Whether to omit `<head>`.
*
* @param {Element} node
* Element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function head(node) {
const children = node.children;
/** @type {Array<string>} */
const seen = [];
let index = -1;
while (++index < children.length) {
const child = children[index];
if (
child.type === 'element' &&
(child.tagName === 'title' || child.tagName === 'base')
) {
if (seen.includes(child.tagName)) return false
seen.push(child.tagName);
}
}
return children.length > 0
}
/**
* Whether to omit `<body>`.
*
* @param {Element} node
* Element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function body(node) {
const head = siblingAfter(node, -1, true);
return (
!head ||
(head.type !== 'comment' &&
!(head.type === 'text' && whitespace(head.value.charAt(0))) &&
!(
head.type === 'element' &&
(head.tagName === 'meta' ||
head.tagName === 'link' ||
head.tagName === 'script' ||
head.tagName === 'style' ||
head.tagName === 'template')
))
)
}
/**
* Whether to omit `<colgroup>`.
* The spec describes some logic for the opening tag, but its easier to
* implement in the closing tag, to the same effect, so we handle it there
* instead.
*
* @param {Element} node
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function colgroup(node, index, parent) {
const previous = siblingBefore(parent, index);
const head = siblingAfter(node, -1, true);
// Previous colgroup was already omitted.
if (
parent &&
previous &&
previous.type === 'element' &&
previous.tagName === 'colgroup' &&
closing(previous, parent.children.indexOf(previous), parent)
) {
return false
}
return Boolean(head && head.type === 'element' && head.tagName === 'col')
}
/**
* Whether to omit `<tbody>`.
*
* @param {Element} node
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function tbody(node, index, parent) {
const previous = siblingBefore(parent, index);
const head = siblingAfter(node, -1);
// Previous table section was already omitted.
if (
parent &&
previous &&
previous.type === 'element' &&
(previous.tagName === 'thead' || previous.tagName === 'tbody') &&
closing(previous, parent.children.indexOf(previous), parent)
) {
return false
}
return Boolean(head && head.type === 'element' && head.tagName === 'tr')
}
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').Properties} Properties
*
* @typedef {import('../index.js').State} State
*/
/**
* Maps of subsets.
*
* Each value is a matrix of tuples.
* The value at `0` causes parse errors, the value at `1` is valid.
* Of both, the value at `0` is unsafe, and the value at `1` is safe.
*
* @type {Record<'double' | 'name' | 'single' | 'unquoted', Array<[Array<string>, Array<string>]>>}
*/
const constants = {
// See: <https://html.spec.whatwg.org/#attribute-name-state>.
name: [
['\t\n\f\r &/=>'.split(''), '\t\n\f\r "&\'/=>`'.split('')],
['\0\t\n\f\r "&\'/<=>'.split(''), '\0\t\n\f\r "&\'/<=>`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(unquoted)-state>.
unquoted: [
['\t\n\f\r &>'.split(''), '\0\t\n\f\r "&\'<=>`'.split('')],
['\0\t\n\f\r "&\'<=>`'.split(''), '\0\t\n\f\r "&\'<=>`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(single-quoted)-state>.
single: [
["&'".split(''), '"&\'`'.split('')],
["\0&'".split(''), '\0"&\'`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(double-quoted)-state>.
double: [
['"&'.split(''), '"&\'`'.split('')],
['\0"&'.split(''), '\0"&\'`'.split('')]
]
};
/**
* Serialize an element node.
*
* @param {Element} node
* Node to handle.
* @param {number | undefined} index
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function element(node, index, parent, state) {
const schema = state.schema;
const omit = schema.space === 'svg' ? false : state.settings.omitOptionalTags;
let selfClosing =
schema.space === 'svg'
? state.settings.closeEmptyElements
: state.settings.voids.includes(node.tagName.toLowerCase());
/** @type {Array<string>} */
const parts = [];
/** @type {string} */
let last;
if (schema.space === 'html' && node.tagName === 'svg') {
state.schema = svg;
}
const attributes = serializeAttributes(state, node.properties);
const content = state.all(
schema.space === 'html' && node.tagName === 'template' ? node.content : node
);
state.schema = schema;
// If the node is categorised as void, but it has children, remove the
// categorisation.
// This enables for example `menuitem`s, which are void in W3C HTML but not
// void in WHATWG HTML, to be stringified properly.
// Note: `menuitem` has since been removed from the HTML spec, and so is no
// longer void.
if (content) selfClosing = false;
if (attributes || !omit || !opening(node, index, parent)) {
parts.push('<', node.tagName, attributes ? ' ' + attributes : '');
if (
selfClosing &&
(schema.space === 'svg' || state.settings.closeSelfClosing)
) {
last = attributes.charAt(attributes.length - 1);
if (
!state.settings.tightSelfClosing ||
last === '/' ||
(last && last !== '"' && last !== "'")
) {
parts.push(' ');
}
parts.push('/');
}
parts.push('>');
}
parts.push(content);
if (!selfClosing && (!omit || !closing(node, index, parent))) {
parts.push('</' + node.tagName + '>');
}
return parts.join('')
}
/**
* @param {State} state
* @param {Properties | null | undefined} properties
* @returns {string}
*/
function serializeAttributes(state, properties) {
/** @type {Array<string>} */
const values = [];
let index = -1;
/** @type {string} */
let key;
if (properties) {
for (key in properties) {
if (properties[key] !== null && properties[key] !== undefined) {
const value = serializeAttribute(state, key, properties[key]);
if (value) values.push(value);
}
}
}
while (++index < values.length) {
const last = state.settings.tightAttributes
? values[index].charAt(values[index].length - 1)
: undefined;
// In tight mode, dont add a space after quoted attributes.
if (index !== values.length - 1 && last !== '"' && last !== "'") {
values[index] += ' ';
}
}
return values.join('')
}
/**
* @param {State} state
* @param {string} key
* @param {Properties[keyof Properties]} value
* @returns {string}
*/
function serializeAttribute(state, key, value) {
const info = find(state.schema, key);
const x =
state.settings.allowParseErrors && state.schema.space === 'html' ? 0 : 1;
const y = state.settings.allowDangerousCharacters ? 0 : 1;
let quote = state.quote;
/** @type {string | undefined} */
let result;
if (info.overloadedBoolean && (value === info.attribute || value === '')) {
value = true;
} else if (
info.boolean ||
(info.overloadedBoolean && typeof value !== 'string')
) {
value = Boolean(value);
}
if (
value === null ||
value === undefined ||
value === false ||
(typeof value === 'number' && Number.isNaN(value))
) {
return ''
}
const name = stringifyEntities(
info.attribute,
Object.assign({}, state.settings.characterReferences, {
// Always encode without parse errors in non-HTML.
subset: constants.name[x][y]
})
);
// No value.
// There is currently only one boolean property in SVG: `[download]` on
// `<a>`.
// This property does not seem to work in browsers (Firefox, Safari, Chrome),
// so I cant test if dropping the value works.
// But I assume that it should:
//
// ```html
// <!doctype html>
// <svg viewBox="0 0 100 100">
// <a href=https://example.com download>
// <circle cx=50 cy=40 r=35 />
// </a>
// </svg>
// ```
//
// See: <https://github.com/wooorm/property-information/blob/main/lib/svg.js>
if (value === true) return name
// `spaces` doesnt accept a second argument, but its given here just to
// keep the code cleaner.
value = Array.isArray(value)
? (info.commaSeparated ? stringify$1 : stringify)(value, {
padLeft: !state.settings.tightCommaSeparatedLists
})
: String(value);
if (state.settings.collapseEmptyAttributes && !value) return name
// Check unquoted value.
if (state.settings.preferUnquoted) {
result = stringifyEntities(
value,
Object.assign({}, state.settings.characterReferences, {
attribute: true,
subset: constants.unquoted[x][y]
})
);
}
// If we dont want unquoted, or if `value` contains character references when
// unquoted…
if (result !== value) {
// If the alternative is less common than `quote`, switch.
if (
state.settings.quoteSmart &&
ccount(value, quote) > ccount(value, state.alternative)
) {
quote = state.alternative;
}
result =
quote +
stringifyEntities(
value,
Object.assign({}, state.settings.characterReferences, {
// Always encode without parse errors in non-HTML.
subset: (quote === "'" ? constants.single : constants.double)[x][y],
attribute: true
})
) +
quote;
}
// Dont add a `=` for unquoted empties.
return name + (result ? '=' + result : result)
}
/**
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').Text} Text
*
* @typedef {import('mdast-util-to-hast').Raw} Raw
*
* @typedef {import('../index.js').State} State
*/
// Declare array as variable so it can be cached by `stringifyEntities`
const textEntitySubset = ['<', '&'];
/**
* Serialize a text node.
*
* @param {Raw | Text} node
* Node to handle.
* @param {number | undefined} _
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function text(node, _, parent, state) {
// Check if content of `node` should be escaped.
return parent &&
parent.type === 'element' &&
(parent.tagName === 'script' || parent.tagName === 'style')
? node.value
: stringifyEntities(
node.value,
Object.assign({}, state.settings.characterReferences, {
subset: textEntitySubset
})
)
}
/**
* @typedef {import('hast').Parents} Parents
*
* @typedef {import('mdast-util-to-hast').Raw} Raw
*
* @typedef {import('../index.js').State} State
*/
/**
* Serialize a raw node.
*
* @param {Raw} node
* Node to handle.
* @param {number | undefined} index
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function raw(node, index, parent, state) {
return state.settings.allowDangerousHtml
? node.value
: text(node, index, parent, state)
}
/**
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').Root} Root
*
* @typedef {import('../index.js').State} State
*/
/**
* Serialize a root.
*
* @param {Root} node
* Node to handle.
* @param {number | undefined} _1
* Index of `node` in `parent.
* @param {Parents | undefined} _2
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function root(node, _1, _2, state) {
return state.all(node)
}
/**
* @typedef {import('hast').Nodes} Nodes
* @typedef {import('hast').Parents} Parents
*
* @typedef {import('../index.js').State} State
*/
/**
* @type {(node: Nodes, index: number | undefined, parent: Parents | undefined, state: State) => string}
*/
const handle = zwitch('type', {
invalid,
unknown,
handlers: {comment, doctype, element, raw, root, text}
});
/**
* Fail when a non-node is found in the tree.
*
* @param {unknown} node
* Unknown value.
* @returns {never}
* Never.
*/
function invalid(node) {
throw new Error('Expected node, not `' + node + '`')
}
/**
* Fail when a node with an unknown type is found in the tree.
*
* @param {unknown} node_
* Unknown node.
* @returns {never}
* Never.
*/
function unknown(node_) {
// `type` is guaranteed by runtime JS.
const node = /** @type {Nodes} */ (node_);
throw new Error('Cannot compile unknown node `' + node.type + '`')
}
/**
* @typedef {import('hast').Nodes} Nodes
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').RootContent} RootContent
*
* @typedef {import('property-information').Schema} Schema
*
* @typedef {import('stringify-entities').Options} StringifyEntitiesOptions
*/
/** @type {Options} */
const emptyOptions = {};
/** @type {CharacterReferences} */
const emptyCharacterReferences = {};
/** @type {Array<never>} */
const emptyChildren = [];
/**
* Serialize hast as HTML.
*
* @param {Array<RootContent> | Nodes} tree
* Tree to serialize.
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {string}
* Serialized HTML.
*/
function toHtml(tree, options) {
const options_ = options || emptyOptions;
const quote = options_.quote || '"';
const alternative = quote === '"' ? "'" : '"';
if (quote !== '"' && quote !== "'") {
throw new Error('Invalid quote `' + quote + '`, expected `\'` or `"`')
}
/** @type {State} */
const state = {
one,
all,
settings: {
omitOptionalTags: options_.omitOptionalTags || false,
allowParseErrors: options_.allowParseErrors || false,
allowDangerousCharacters: options_.allowDangerousCharacters || false,
quoteSmart: options_.quoteSmart || false,
preferUnquoted: options_.preferUnquoted || false,
tightAttributes: options_.tightAttributes || false,
upperDoctype: options_.upperDoctype || false,
tightDoctype: options_.tightDoctype || false,
bogusComments: options_.bogusComments || false,
tightCommaSeparatedLists: options_.tightCommaSeparatedLists || false,
tightSelfClosing: options_.tightSelfClosing || false,
collapseEmptyAttributes: options_.collapseEmptyAttributes || false,
allowDangerousHtml: options_.allowDangerousHtml || false,
voids: options_.voids || htmlVoidElements,
characterReferences:
options_.characterReferences || emptyCharacterReferences,
closeSelfClosing: options_.closeSelfClosing || false,
closeEmptyElements: options_.closeEmptyElements || false
},
schema: options_.space === 'svg' ? svg : html$2,
quote,
alternative
};
return state.one(
Array.isArray(tree) ? {type: 'root', children: tree} : tree,
undefined,
undefined
)
}
/**
* Serialize a node.
*
* @this {State}
* Info passed around about the current state.
* @param {Nodes} node
* Node to handle.
* @param {number | undefined} index
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @returns {string}
* Serialized node.
*/
function one(node, index, parent) {
return handle(node, index, parent, this)
}
/**
* Serialize all children of `parent`.
*
* @this {State}
* Info passed around about the current state.
* @param {Parents | undefined} parent
* Parent whose children to serialize.
* @returns {string}
*/
function all(parent) {
/** @type {Array<string>} */
const results = [];
const children = (parent && parent.children) || emptyChildren;
let index = -1;
while (++index < children.length) {
results[index] = this.one(children[index], index, parent);
}
return results.join('')
}
/**
* Get highlighted code in HTML.
*/
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;
}
async function main(init) {
let wasmMemory;
let buffer;
const binding = {};
function updateGlobalBufferAndViews(buf) {
buffer = buf;
binding.HEAPU8 = new Uint8Array(buf);
binding.HEAPU32 = new Uint32Array(buf);
}
function _emscripten_get_now() {
return typeof performance !== 'undefined' ? performance.now() : Date.now();
}
function _emscripten_memcpy_big(dest, src, num) {
binding.HEAPU8.copyWithin(dest, src, src + num);
}
function getHeapMax() {
return 2147483648;
}
function emscripten_realloc_buffer(size) {
try {
wasmMemory.grow((size - buffer.byteLength + 65535) >>> 16);
updateGlobalBufferAndViews(wasmMemory.buffer);
return 1;
}
catch (e) { }
}
function _emscripten_resize_heap(requestedSize) {
const oldSize = binding.HEAPU8.length;
requestedSize = requestedSize >>> 0;
const maxHeapSize = getHeapMax();
if (requestedSize > maxHeapSize)
return false;
const alignUp = (x, multiple) => x + ((multiple - (x % multiple)) % multiple);
for (let cutDown = 1; cutDown <= 4; cutDown *= 2) {
let overGrownHeapSize = oldSize * (1 + 0.2 / cutDown);
overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296);
const newSize = Math.min(maxHeapSize, alignUp(Math.max(requestedSize, overGrownHeapSize), 65536));
const replacement = emscripten_realloc_buffer(newSize);
if (replacement)
return true;
}
return false;
}
const UTF8Decoder = typeof TextDecoder != 'undefined' ? new TextDecoder('utf8') : undefined;
function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead = 1024) {
const endIdx = idx + maxBytesToRead;
let endPtr = idx;
while (heapOrArray[endPtr] && !(endPtr >= endIdx))
++endPtr;
if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
}
let str = '';
while (idx < endPtr) {
let u0 = heapOrArray[idx++];
if (!(u0 & 128)) {
str += String.fromCharCode(u0);
continue;
}
const u1 = heapOrArray[idx++] & 63;
if ((u0 & 224) === 192) {
str += String.fromCharCode(((u0 & 31) << 6) | u1);
continue;
}
const u2 = heapOrArray[idx++] & 63;
if ((u0 & 240) === 224) {
u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
}
else {
u0 = ((u0 & 7) << 18)
| (u1 << 12)
| (u2 << 6)
| (heapOrArray[idx++] & 63);
}
if (u0 < 65536) {
str += String.fromCharCode(u0);
}
else {
const ch = u0 - 65536;
str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023));
}
}
return str;
}
function UTF8ToString(ptr, maxBytesToRead) {
return ptr ? UTF8ArrayToString(binding.HEAPU8, ptr, maxBytesToRead) : '';
}
const asmLibraryArg = {
emscripten_get_now: _emscripten_get_now,
emscripten_memcpy_big: _emscripten_memcpy_big,
emscripten_resize_heap: _emscripten_resize_heap,
fd_write: () => 0,
};
async function createWasm() {
const info = {
env: asmLibraryArg,
wasi_snapshot_preview1: asmLibraryArg,
};
const exports = await init(info);
wasmMemory = exports.memory;
updateGlobalBufferAndViews(wasmMemory.buffer);
Object.assign(binding, exports);
binding.UTF8ToString = UTF8ToString;
}
await createWasm();
return binding;
}
/* ---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*-------------------------------------------------------- */
let onigBinding = null;
let defaultDebugCall = false;
function throwLastOnigError(onigBinding) {
throw new ShikiError(onigBinding.UTF8ToString(onigBinding.getLastOnigError()));
}
class UtfString {
static _utf8ByteLength(str) {
let result = 0;
for (let i = 0, len = str.length; i < len; i++) {
const charCode = str.charCodeAt(i);
let codepoint = charCode;
let wasSurrogatePair = false;
if (charCode >= 0xD800 && charCode <= 0xDBFF) {
// Hit a high surrogate, try to look for a matching low surrogate
if (i + 1 < len) {
const nextCharCode = str.charCodeAt(i + 1);
if (nextCharCode >= 0xDC00 && nextCharCode <= 0xDFFF) {
// Found the matching low surrogate
codepoint = (((charCode - 0xD800) << 10) + 0x10000) | (nextCharCode - 0xDC00);
wasSurrogatePair = true;
}
}
}
if (codepoint <= 0x7F)
result += 1;
else if (codepoint <= 0x7FF)
result += 2;
else if (codepoint <= 0xFFFF)
result += 3;
else
result += 4;
if (wasSurrogatePair)
i++;
}
return result;
}
utf16Length;
utf8Length;
utf16Value;
utf8Value;
utf16OffsetToUtf8;
utf8OffsetToUtf16;
constructor(str) {
const utf16Length = str.length;
const utf8Length = UtfString._utf8ByteLength(str);
const computeIndicesMapping = (utf8Length !== utf16Length);
const utf16OffsetToUtf8 = computeIndicesMapping ? new Uint32Array(utf16Length + 1) : null;
if (computeIndicesMapping)
utf16OffsetToUtf8[utf16Length] = utf8Length;
const utf8OffsetToUtf16 = computeIndicesMapping ? new Uint32Array(utf8Length + 1) : null;
if (computeIndicesMapping)
utf8OffsetToUtf16[utf8Length] = utf16Length;
const utf8Value = new Uint8Array(utf8Length);
let i8 = 0;
for (let i16 = 0; i16 < utf16Length; i16++) {
const charCode = str.charCodeAt(i16);
let codePoint = charCode;
let wasSurrogatePair = false;
if (charCode >= 0xD800 && charCode <= 0xDBFF) {
// Hit a high surrogate, try to look for a matching low surrogate
if (i16 + 1 < utf16Length) {
const nextCharCode = str.charCodeAt(i16 + 1);
if (nextCharCode >= 0xDC00 && nextCharCode <= 0xDFFF) {
// Found the matching low surrogate
codePoint = (((charCode - 0xD800) << 10) + 0x10000) | (nextCharCode - 0xDC00);
wasSurrogatePair = true;
}
}
}
if (computeIndicesMapping) {
utf16OffsetToUtf8[i16] = i8;
if (wasSurrogatePair)
utf16OffsetToUtf8[i16 + 1] = i8;
if (codePoint <= 0x7F) {
utf8OffsetToUtf16[i8 + 0] = i16;
}
else if (codePoint <= 0x7FF) {
utf8OffsetToUtf16[i8 + 0] = i16;
utf8OffsetToUtf16[i8 + 1] = i16;
}
else if (codePoint <= 0xFFFF) {
utf8OffsetToUtf16[i8 + 0] = i16;
utf8OffsetToUtf16[i8 + 1] = i16;
utf8OffsetToUtf16[i8 + 2] = i16;
}
else {
utf8OffsetToUtf16[i8 + 0] = i16;
utf8OffsetToUtf16[i8 + 1] = i16;
utf8OffsetToUtf16[i8 + 2] = i16;
utf8OffsetToUtf16[i8 + 3] = i16;
}
}
if (codePoint <= 0x7F) {
utf8Value[i8++] = codePoint;
}
else if (codePoint <= 0x7FF) {
utf8Value[i8++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
}
else if (codePoint <= 0xFFFF) {
utf8Value[i8++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
}
else {
utf8Value[i8++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
}
if (wasSurrogatePair)
i16++;
}
this.utf16Length = utf16Length;
this.utf8Length = utf8Length;
this.utf16Value = str;
this.utf8Value = utf8Value;
this.utf16OffsetToUtf8 = utf16OffsetToUtf8;
this.utf8OffsetToUtf16 = utf8OffsetToUtf16;
}
createString(onigBinding) {
const result = onigBinding.omalloc(this.utf8Length);
onigBinding.HEAPU8.set(this.utf8Value, result);
return result;
}
}
class OnigString {
static LAST_ID = 0;
static _sharedPtr = 0; // a pointer to a string of 10000 bytes
static _sharedPtrInUse = false;
id = (++OnigString.LAST_ID);
_onigBinding;
content;
utf16Length;
utf8Length;
utf16OffsetToUtf8;
utf8OffsetToUtf16;
ptr;
constructor(str) {
if (!onigBinding)
throw new ShikiError('Must invoke loadWasm first.');
this._onigBinding = onigBinding;
this.content = str;
const utfString = new UtfString(str);
this.utf16Length = utfString.utf16Length;
this.utf8Length = utfString.utf8Length;
this.utf16OffsetToUtf8 = utfString.utf16OffsetToUtf8;
this.utf8OffsetToUtf16 = utfString.utf8OffsetToUtf16;
if (this.utf8Length < 10000 && !OnigString._sharedPtrInUse) {
if (!OnigString._sharedPtr)
OnigString._sharedPtr = onigBinding.omalloc(10000);
OnigString._sharedPtrInUse = true;
onigBinding.HEAPU8.set(utfString.utf8Value, OnigString._sharedPtr);
this.ptr = OnigString._sharedPtr;
}
else {
this.ptr = utfString.createString(onigBinding);
}
}
convertUtf8OffsetToUtf16(utf8Offset) {
if (this.utf8OffsetToUtf16) {
if (utf8Offset < 0)
return 0;
if (utf8Offset > this.utf8Length)
return this.utf16Length;
return this.utf8OffsetToUtf16[utf8Offset];
}
return utf8Offset;
}
convertUtf16OffsetToUtf8(utf16Offset) {
if (this.utf16OffsetToUtf8) {
if (utf16Offset < 0)
return 0;
if (utf16Offset > this.utf16Length)
return this.utf8Length;
return this.utf16OffsetToUtf8[utf16Offset];
}
return utf16Offset;
}
dispose() {
if (this.ptr === OnigString._sharedPtr)
OnigString._sharedPtrInUse = false;
else
this._onigBinding.ofree(this.ptr);
}
}
class OnigScanner {
_onigBinding;
_ptr;
constructor(patterns) {
if (!onigBinding)
throw new ShikiError('Must invoke loadWasm first.');
const strPtrsArr = [];
const strLenArr = [];
for (let i = 0, len = patterns.length; i < len; i++) {
const utfString = new UtfString(patterns[i]);
strPtrsArr[i] = utfString.createString(onigBinding);
strLenArr[i] = utfString.utf8Length;
}
const strPtrsPtr = onigBinding.omalloc(4 * patterns.length);
onigBinding.HEAPU32.set(strPtrsArr, strPtrsPtr / 4);
const strLenPtr = onigBinding.omalloc(4 * patterns.length);
onigBinding.HEAPU32.set(strLenArr, strLenPtr / 4);
const scannerPtr = onigBinding.createOnigScanner(strPtrsPtr, strLenPtr, patterns.length);
for (let i = 0, len = patterns.length; i < len; i++)
onigBinding.ofree(strPtrsArr[i]);
onigBinding.ofree(strLenPtr);
onigBinding.ofree(strPtrsPtr);
if (scannerPtr === 0)
throwLastOnigError(onigBinding);
this._onigBinding = onigBinding;
this._ptr = scannerPtr;
}
dispose() {
this._onigBinding.freeOnigScanner(this._ptr);
}
findNextMatchSync(string, startPosition, arg) {
let debugCall = defaultDebugCall;
let options = 0 /* FindOption.None */;
if (typeof arg === 'number') {
if (arg & 8 /* FindOption.DebugCall */)
debugCall = true;
options = arg;
}
else if (typeof arg === 'boolean') {
debugCall = arg;
}
if (typeof string === 'string') {
string = new OnigString(string);
const result = this._findNextMatchSync(string, startPosition, debugCall, options);
string.dispose();
return result;
}
return this._findNextMatchSync(string, startPosition, debugCall, options);
}
_findNextMatchSync(string, startPosition, debugCall, options) {
const onigBinding = this._onigBinding;
let resultPtr;
if (debugCall)
resultPtr = onigBinding.findNextOnigScannerMatchDbg(this._ptr, string.id, string.ptr, string.utf8Length, string.convertUtf16OffsetToUtf8(startPosition), options);
else
resultPtr = onigBinding.findNextOnigScannerMatch(this._ptr, string.id, string.ptr, string.utf8Length, string.convertUtf16OffsetToUtf8(startPosition), options);
if (resultPtr === 0) {
// no match
return null;
}
const HEAPU32 = onigBinding.HEAPU32;
let offset = resultPtr / 4; // byte offset -> uint32 offset
const index = HEAPU32[offset++];
const count = HEAPU32[offset++];
const captureIndices = [];
for (let i = 0; i < count; i++) {
const beg = string.convertUtf8OffsetToUtf16(HEAPU32[offset++]);
const end = string.convertUtf8OffsetToUtf16(HEAPU32[offset++]);
captureIndices[i] = {
start: beg,
end,
length: end - beg,
};
}
return {
index,
captureIndices,
};
}
}
function isInstantiatorOptionsObject(dataOrOptions) {
return (typeof dataOrOptions.instantiator === 'function');
}
function isInstantiatorModule(dataOrOptions) {
return (typeof dataOrOptions.default === 'function');
}
function isDataOptionsObject(dataOrOptions) {
return (typeof dataOrOptions.data !== 'undefined');
}
function isResponse(dataOrOptions) {
return (typeof Response !== 'undefined' && dataOrOptions instanceof Response);
}
function isArrayBuffer(data) {
return (typeof ArrayBuffer !== 'undefined' && (data instanceof ArrayBuffer || ArrayBuffer.isView(data)))
// eslint-disable-next-line node/prefer-global/buffer
|| (typeof Buffer !== 'undefined' && Buffer.isBuffer?.(data))
|| (typeof SharedArrayBuffer !== 'undefined' && data instanceof SharedArrayBuffer)
|| (typeof Uint32Array !== 'undefined' && data instanceof Uint32Array);
}
let initPromise;
function loadWasm(options) {
if (initPromise)
return initPromise;
async function _load() {
onigBinding = await main(async (info) => {
let instance = options;
instance = await instance;
if (typeof instance === 'function')
instance = await instance(info);
if (typeof instance === 'function')
instance = await instance(info);
if (isInstantiatorOptionsObject(instance)) {
instance = await instance.instantiator(info);
}
else if (isInstantiatorModule(instance)) {
instance = await instance.default(info);
}
else {
if (isDataOptionsObject(instance))
instance = instance.data;
if (isResponse(instance)) {
if (typeof WebAssembly.instantiateStreaming === 'function')
instance = await _makeResponseStreamingLoader(instance)(info);
else
instance = await _makeResponseNonStreamingLoader(instance)(info);
}
else if (isArrayBuffer(instance)) {
instance = await _makeArrayBufferLoader(instance)(info);
}
// import("shiki/onig.wasm") returns `{ default: WebAssembly.Module }` on cloudflare workers
// https://developers.cloudflare.com/workers/wrangler/bundling/
else if (instance instanceof WebAssembly.Module) {
instance = await _makeArrayBufferLoader(instance)(info);
}
else if ('default' in instance && instance.default instanceof WebAssembly.Module) {
instance = await _makeArrayBufferLoader(instance.default)(info);
}
}
if ('instance' in instance)
instance = instance.instance;
if ('exports' in instance)
instance = instance.exports;
return instance;
});
}
initPromise = _load();
return initPromise;
}
function _makeArrayBufferLoader(data) {
return importObject => WebAssembly.instantiate(data, importObject);
}
function _makeResponseStreamingLoader(data) {
return importObject => WebAssembly.instantiateStreaming(data, importObject);
}
function _makeResponseNonStreamingLoader(data) {
return async (importObject) => {
const arrayBuffer = await data.arrayBuffer();
return WebAssembly.instantiate(arrayBuffer, importObject);
};
}
function createOnigString(str) {
return new OnigString(str);
}
function createOnigScanner(patterns) {
return new OnigScanner(patterns);
}
/**
* https://github.com/microsoft/vscode/blob/f7f05dee53fb33fe023db2e06e30a89d3094488f/src/vs/platform/theme/common/colorRegistry.ts#L258-L268
*/
const VSCODE_FALLBACK_EDITOR_FG = { light: '#333333', dark: '#bbbbbb' };
const VSCODE_FALLBACK_EDITOR_BG = { light: '#fffffe', dark: '#1e1e1e' };
const RESOLVED_KEY = '__shiki_resolved';
/**
* Normalize a textmate theme to shiki theme
*/
function normalizeTheme(rawTheme) {
// @ts-expect-error private field
if (rawTheme?.[RESOLVED_KEY])
return rawTheme;
const theme = {
...rawTheme,
};
// Fallback settings
if (theme.tokenColors && !theme.settings) {
theme.settings = theme.tokenColors;
delete theme.tokenColors;
}
theme.type ||= 'dark';
theme.colorReplacements = { ...theme.colorReplacements };
theme.settings ||= [];
// Guess fg/bg colors
let { bg, fg } = theme;
if (!bg || !fg) {
/**
* First try:
* Theme might contain a global `tokenColor` without `name` or `scope`
* Used as default value for foreground/background
*/
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;
/**
* Second try:
* If there's no global `tokenColor` without `name` or `scope`
* Use `editor.foreground` and `editor.background`
*/
if (!fg && theme?.colors?.['editor.foreground'])
fg = theme.colors['editor.foreground'];
if (!bg && theme?.colors?.['editor.background'])
bg = theme.colors['editor.background'];
/**
* Last try:
* If there's no fg/bg color specified in theme, use default
*/
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;
}
// Push a no-scope setting with fallback colors
if (!(theme.settings[0] && theme.settings[0].settings && !theme.settings[0].scope)) {
theme.settings.unshift({
settings: {
foreground: theme.fg,
background: theme.bg,
},
});
}
// Push non-hex colors to color replacements, as `vscode-textmate` doesn't support them
let replacementCount = 0;
const replacementMap = 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}`]) // already exists
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 || {})) {
// Only patch for known keys
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;
}
class Registry extends Registry$1 {
_resolver;
_themes;
_langs;
_alias;
_resolvedThemes = new Map();
_resolvedGrammars = new Map();
_langMap = new Map();
_langGraph = new Map();
_textmateThemeCache = new WeakMap();
_loadedThemesCache = null;
_loadedLanguagesCache = null;
constructor(_resolver, _themes, _langs, _alias = {}) {
super(_resolver);
this._resolver = _resolver;
this._themes = _themes;
this._langs = _langs;
this._alias = _alias;
_themes.forEach(t => this.loadTheme(t));
_langs.forEach(l => this.loadLanguage(l));
}
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);
// Reset cache
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);
}
// @ts-expect-error Access private `_syncRegistry`, but should work in runtime
this._syncRegistry.setTheme(textmateTheme);
}
getGrammar(name) {
if (this._alias[name]) {
const resolved = 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);
}
async 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 || [],
};
// @ts-expect-error Private members, set this to override the previous grammar (that can be a stub)
this._syncRegistry._rawGrammars.set(lang.scopeName, lang);
const g = await this.loadGrammarWithConfiguration(lang.scopeName, 1, grammarConfig);
this._resolvedGrammars.set(lang.name, g);
if (lang.aliases) {
lang.aliases.forEach((alias) => {
this._alias[alias] = lang.name;
});
}
// Reset cache
this._loadedLanguagesCache = null;
// If there is a language that embeds this language lazily, we need to reload it
if (embeddedLazilyBy.size) {
for (const e of embeddedLazilyBy) {
this._resolvedGrammars.delete(e.name);
// Reset cache
this._loadedLanguagesCache = null;
// @ts-expect-error clear cache
this._syncRegistry?._injectionGrammars?.delete(e.scopeName);
// @ts-expect-error clear cache
this._syncRegistry?._grammars?.delete(e.scopeName);
await this.loadLanguage(this._langMap.get(e.name));
}
}
}
async init() {
this._themes.map(t => this.loadTheme(t));
await this.loadLanguages(this._langs);
}
dispose() {
super.dispose();
this._resolvedThemes.clear();
this._resolvedGrammars.clear();
this._langMap.clear();
this._langGraph.clear();
this._loadedThemesCache = null;
}
async 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)
await this.loadLanguage(lang);
}
getLoadedLanguages() {
if (!this._loadedLanguagesCache) {
this._loadedLanguagesCache = [
...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 = new Map();
_scopeToLang = new Map();
_injections = new Map();
_onigLibPromise;
constructor(onigLibPromise, langs) {
this._onigLibPromise = onigLibPromise;
langs.forEach(i => this.addLanguage(i));
}
get onigLib() {
return this._onigLibPromise;
}
getLangRegistration(langIdOrAlias) {
return this._langs.get(langIdOrAlias);
}
async 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 _defaultWasmLoader;
/**
* Set the default wasm loader for `loadWasm`.
* @internal
*/
function setDefaultWasmLoader(_loader) {
_defaultWasmLoader = _loader;
}
let instancesCount = 0;
/**
* Get the minimal shiki context for rendering.
*/
async function createShikiInternal(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;
async function normalizeGetter(p) {
return Promise.resolve(typeof p === 'function' ? p() : p).then(r => r.default || r);
}
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()));
}
const wasmLoader = options.loadWasm || _defaultWasmLoader;
const [themes, langs,] = await Promise.all([
Promise.all((options.themes || []).map(normalizeGetter)).then(r => r.map(normalizeTheme)),
resolveLangs(options.langs || []),
wasmLoader ? loadWasm(wasmLoader) : undefined,
]);
const resolver = new Resolver(Promise.resolve({
createOnigScanner(patterns) {
return createOnigScanner(patterns);
},
createOnigString(s) {
return createOnigString(s);
},
}), langs);
const _registry = new Registry(resolver, themes, langs, options.langAlias);
await _registry.init();
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();
}
async function loadLanguage(...langs) {
ensureNotDisposed();
await _registry.loadLanguages(await resolveLangs(langs));
}
async function loadTheme(...themes) {
ensureNotDisposed();
await Promise.all(themes.map(async (theme) => isSpecialTheme(theme)
? null
: _registry.loadTheme(await normalizeGetter(theme))));
}
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,
loadTheme,
dispose,
[Symbol.dispose]: dispose,
};
}
/**
* @deprecated Use `createShikiInternal` instead.
*/
function getShikiInternal(options = {}) {
// TODO: next: console.warn('`getShikiInternal` is deprecated. Use `createShikiInternal` instead.')
return createShikiInternal(options);
}
/**
* Create a Shiki core highlighter instance, with no languages or themes bundled.
* Wasm and each language and theme must be loaded manually.
*
* @see http://shiki.style/guide/install#fine-grained-bundle
*/
async function createHighlighterCore(options = {}) {
const internal = await createShikiInternal(options);
return {
codeToTokensBase: (code, options) => codeToTokensBase(internal, code, options),
codeToTokensWithThemes: (code, options) => codeToTokensWithThemes(internal, code, options),
codeToTokens: (code, options) => codeToTokens(internal, code, options),
codeToHast: (code, options) => codeToHast(internal, code, options),
codeToHtml: (code, options) => codeToHtml(internal, code, options),
...internal,
getInternalContext: () => internal,
};
}
function makeSingletonHighlighterCore(createHighlighter) {
let _shiki;
async function getSingletonHighlighterCore(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 getSingletonHighlighterCore;
}
const getSingletonHighlighterCore = /* @__PURE__ */ makeSingletonHighlighterCore(createHighlighterCore);
/**
* @deprecated Use `createHighlighterCore` or `getSingletonHighlighterCore` instead.
*/
/* v8 ignore next 5 */
function getHighlighterCore(options = {}) {
// TODO: next: console.warn('`getHighlighterCore` is deprecated. Use `createHighlighterCore` or `getSingletonHighlighterCore` instead.')
return createHighlighterCore(options);
}
/**
* Create a `createHighlighter` function with bundled themes and languages.
*
* @param bundledLanguages
* @param bundledThemes
* @param loadWasm
*/
function createdBundledHighlighter(bundledLanguages, bundledThemes, loadWasm) {
async function createHighlighter(options) {
function resolveLang(lang) {
if (typeof lang === 'string') {
if (isSpecialLang(lang))
return [];
const bundle = bundledLanguages[lang];
if (!bundle)
throw new ShikiError(`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(`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({
...options,
themes: _themes,
langs,
loadWasm,
});
return {
...core,
loadLanguage(...langs) {
return core.loadLanguage(...langs.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);
},
};
}
export { FontStyle, ShikiError, addClassToHast, applyColorReplacements, codeToHast, codeToHtml, codeToTokens, codeToTokensBase, codeToTokensWithThemes, createHighlighterCore, createPositionConverter, createShikiInternal, createSingletonShorthands, createdBundledHighlighter, getHighlighterCore, getShikiInternal, getSingletonHighlighterCore, getTokenStyleObject, toHtml as hastToHtml, isNoneTheme, isPlainLang, isSpecialLang, isSpecialTheme, loadWasm, makeSingletonHighlighter, makeSingletonHighlighterCore, normalizeTheme, resolveColorReplacements, setDefaultWasmLoader, splitLines, splitToken, splitTokens, stringifyTokenStyle, toArray, tokenizeAnsiWithTheme, tokenizeWithTheme, tokensToHast, transformerDecorations };