import { addClassToHast } from 'shikiji'; import { addClassToHast as addClassToHast$1 } from 'shikiji/core'; function separateContinuousSpaces(inputs) { const result = []; let current = ""; function bump() { if (current.length) result.push(current); current = ""; } inputs.forEach((part, idx) => { if (isTab(part)) { bump(); result.push(part); } else if (isSpace(part) && (isSpace(inputs[idx - 1]) || isSpace(inputs[idx + 1]))) { bump(); result.push(part); } else { current += part; } }); bump(); return result; } function isTab(part) { return part === " "; } function isSpace(part) { return part === " " || part === " "; } function splitSpaces(parts, type, renderContinuousSpaces = true) { if (type === "all") return parts; let leftCount = 0; let rightCount = 0; if (type === "boundary") { for (let i = 0; i < parts.length; i++) { if (isSpace(parts[i])) leftCount++; else break; } } if (type === "boundary" || type === "trailing") { for (let i = parts.length - 1; i >= 0; i--) { if (isSpace(parts[i])) rightCount++; else break; } } const middle = parts.slice(leftCount, parts.length - rightCount); return [ ...parts.slice(0, leftCount), ...renderContinuousSpaces ? separateContinuousSpaces(middle) : [middle.join("")], ...parts.slice(parts.length - rightCount) ]; } function transformerRenderWhitespace(options = {}) { const classMap = { " ": options.classSpace ?? "space", " ": options.classTab ?? "tab" }; const position = options.position ?? "all"; const keys = Object.keys(classMap); return { name: "shikiji-transformers:render-whitespace", // We use `root` hook here to ensure it runs after all other transformers root(root) { const pre = root.children[0]; const code = pre.children[0]; code.children.forEach( (line) => { if (line.type !== "element") return; const elements = line.children.filter((token) => token.type === "element"); const last = elements.length - 1; line.children = line.children.flatMap((token) => { if (token.type !== "element") return token; const index = elements.indexOf(token); if (position === "boundary" && index !== 0 && index !== last) return token; if (position === "trailing" && index !== last) return token; const node = token.children[0]; if (node.type !== "text" || !node.value) return token; const parts = splitSpaces( node.value.split(/([ \t])/).filter((i) => i.length), position === "boundary" && index === last && last !== 0 ? "trailing" : position, position !== "trailing" ); if (parts.length <= 1) return token; return parts.map((part) => { const clone = { ...token, properties: { ...token.properties } }; clone.children = [{ type: "text", value: part }]; if (keys.includes(part)) { addClassToHast(clone, classMap[part]); delete clone.properties.style; } return clone; }); }); } ); } }; } function transformerRemoveLineBreak() { return { name: "shikiji-transformers:remove-line-break", code(code) { code.children = code.children.filter((line) => !(line.type === "text" && line.value === "\n")); } }; } function transformerCompactLineOptions(lineOptions = []) { return { name: "shikiji-transformers:compact-line-options", line(node, line) { const lineOption = lineOptions.find((o) => o.line === line); if (lineOption?.classes) addClassToHast(node, lineOption.classes); return node; } }; } function createCommentNotationTransformer(name, regex, onMatch, removeEmptyLines = false) { return { name, code(code) { const lines = code.children.filter((i) => i.type === "element"); const linesToRemove = []; lines.forEach((line, idx) => { let nodeToRemove; for (const child of line.children) { if (child.type !== "element") continue; const text = child.children[0]; if (text.type !== "text") continue; let replaced = false; text.value = text.value.replace(regex, (...match) => { if (onMatch.call(this, match, line, child, lines, idx)) { replaced = true; return ""; } return match[0]; }); if (replaced && !text.value.trim()) nodeToRemove = child; } if (nodeToRemove) { line.children.splice(line.children.indexOf(nodeToRemove), 1); if (line.children.length === 0) { linesToRemove.push(line); if (removeEmptyLines) { const next = code.children[code.children.indexOf(line) + 1]; if (next && next.type === "text" && next.value === "\n") linesToRemove.push(next); } } } }); for (const line of linesToRemove) code.children.splice(code.children.indexOf(line), 1); } }; } function escapeRegExp(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function transformerNotationMap(options = {}, name = "shikiji-transformers:notation-map") { const { classMap = {}, classActivePre = void 0 } = options; return createCommentNotationTransformer( name, new RegExp(`\\s*(?://|/\\*|)?`), function([_, match, range = ":1"], _line, _comment, lines, index) { const lineNum = Number.parseInt(range.slice(1), 10); lines.slice(index, index + lineNum).forEach((line) => { addClassToHast(line, classMap[match]); }); if (classActivePre) addClassToHast(this.pre, classActivePre); return true; } ); } function transformerNotationFocus(options = {}) { const { classActiveLine = "focused", classActivePre = "has-focused" } = options; return transformerNotationMap( { classMap: { focus: classActiveLine }, classActivePre }, "shikiji-transformers:notation-focus" ); } function transformerNotationHighlight(options = {}) { const { classActiveLine = "highlighted", classActivePre = "has-highlighted" } = options; return transformerNotationMap( { classMap: { highlight: classActiveLine, hl: classActiveLine }, classActivePre }, "shikiji-transformers:notation-highlight" ); } function highlightWordInLine(line, ignoredElement, word, className) { line.children = line.children.flatMap((span) => { if (span.type !== "element" || span.tagName !== "span" || span === ignoredElement) return span; const textNode = span.children[0]; if (textNode.type !== "text") return span; return replaceSpan(span, textNode.value, word, className) ?? span; }); } function inheritElement(original, overrides) { return { ...original, properties: { ...original.properties }, ...overrides }; } function replaceSpan(span, text, word, className) { const index = text.indexOf(word); if (index === -1) return; const createNode = (value) => inheritElement(span, { children: [ { type: "text", value } ] }); const nodes = []; if (index > 0) nodes.push(createNode(text.slice(0, index))); const highlightedNode = createNode(word); addClassToHast$1(highlightedNode, className); nodes.push(highlightedNode); if (index + word.length < text.length) nodes.push(createNode(text.slice(index + word.length))); return nodes; } function transformerNotationWordHighlight(options = {}) { const { classActiveWord = "highlighted-word", classActivePre = void 0 } = options; return createCommentNotationTransformer( "shikiji-transformers:notation-highlight-word", // comment-start | marker | word | range | comment-end /^\s*(?:\/\/|\/\*|)?/, function([_, word, range], _line, comment, lines, index) { const lineNum = range ? Number.parseInt(range.slice(1), 10) : lines.length; word = word.replace(/\\(.)/g, "$1"); lines.slice(index + 1, index + 1 + lineNum).forEach((line) => highlightWordInLine(line, comment, word, classActiveWord)); if (classActivePre) addClassToHast(this.pre, classActivePre); return true; }, true // remove empty lines ); } function parseMetaHighlightWords(meta) { if (!meta) return []; const match = Array.from(meta.matchAll(/\/((?:\\.|[^\/])+?)\//ig)); return match.map((v) => v[1].replace(/\\(.)/g, "$1")); } function transformerMetaWordHighlight(options = {}) { const { className = "highlighted-word" } = options; return { name: "shikiji-transformers:meta-word-highlight", line(node) { if (!this.options.meta?.__raw) return; const words = parseMetaHighlightWords(this.options.meta.__raw); for (const word of words) highlightWordInLine(node, null, word, className); return node; } }; } function transformerNotationDiff(options = {}) { const { classLineAdd = "diff add", classLineRemove = "diff remove", classActivePre = "has-diff" } = options; return transformerNotationMap( { classMap: { "++": classLineAdd, "--": classLineRemove }, classActivePre }, "shikiji-transformers:notation-diff" ); } function transformerNotationErrorLevel(options = {}) { const { classMap = { error: ["highlighted", "error"], warning: ["highlighted", "warning"] }, classActivePre = "has-highlighted" } = options; return transformerNotationMap( { classMap, classActivePre }, "shikiji-transformers:notation-error-level" ); } function parseMetaHighlightString(meta) { if (!meta) return null; const match = meta.match(/{([\d,-]+)}/); if (!match) return null; const lines = match[1].split(",").flatMap((v) => { const num = v.split("-").map((v2) => Number.parseInt(v2, 10)); if (num.length === 1) return [num[0]]; else return Array.from({ length: num[1] - num[0] + 1 }, (_, i) => i + num[0]); }); return lines; } const symbol = Symbol("highlighted-lines"); function transformerMetaHighlight(options = {}) { const { className = "highlighted" } = options; return { name: "shikiji-transformers:meta-highlight", line(node, line) { var _a; if (!this.options.meta?.__raw) return; (_a = this.meta)[symbol] || (_a[symbol] = parseMetaHighlightString(this.options.meta.__raw)); const lines = this.meta[symbol] || []; if (lines.includes(line)) addClassToHast(node, className); return node; } }; } export { createCommentNotationTransformer, parseMetaHighlightString, parseMetaHighlightWords, transformerCompactLineOptions, transformerMetaHighlight, transformerMetaWordHighlight, transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight, transformerNotationWordHighlight, transformerRemoveLineBreak, transformerRenderWhitespace };