site/node_modules/@shikijs/core/dist/index.mjs
2024-10-14 08:09:33 +02:00

5679 lines
162 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 };