UNPKG

@scalar/code-highlight

Version:

Central methods and themes for code highlighting in Scalar projects

95 lines (92 loc) 3.31 kB
import { visit } from 'unist-util-visit'; // --------------------------------------------------------------------------- // Line Numbering plugin function isText(element) { return element?.type === 'text'; } function isElement(node) { return node?.type === 'element'; } function textElement(value) { return { type: 'text', value }; } function lineBreak() { return { type: 'text', value: '\n' }; } /** * Adds lines to code blocks */ function codeBlockLinesPlugin() { return (tree) => { visit(tree, 'element', (node, _i, parent) => { if (parent?.type === 'element' && parent.tagName === 'pre' && node.tagName === 'code') { let numLines = 0; // Wraps each line in a span node.children = addLines(node); // Adds a line break to the end of each line node.children.forEach((child) => { if (child.type === 'element' && child.tagName === 'span') { const lastChild = child.children[child.children.length - 1]; if (lastChild && (!isText(lastChild) || (isText(lastChild) && !hasLineBreak(lastChild)))) { child.children.push(lineBreak()); numLines++; } } }); // We need to maintain a count of the total lines to allow space for the labels node.properties.style = [`--line-count: ${numLines};`, `--line-digits: ${numLines.toString().length};`]; } }); // console.log('NUMBER OF LINES IS: ', numLines) }; } /** * Adds lines to a node recursively and returns them * * @param node - The node to add lines to * @param lines - The current lines * @param copyParent - Whether to copy the parent node to save the original node styles */ function addLines(node, lines = [], copyParent) { const line = () => lines[lines.length - 1] ?? (lines.push(createLine()) && lines[lines.length - 1]); node.children.forEach((child) => { if (isText(child) && hasLineBreak(child)) { const split = child.value.split(/\n/); split.forEach((content, i) => { copyParent ? line().children.push({ ...node, children: [textElement(content)] }) : line().children.push(textElement(content)); i !== split.length - 1 && lines.push(createLine()); }); } else if (isElement(child) && child.children.some(hasLineBreak)) { addLines(child, lines, true); } else { line().children.push(child); } }); return lines; } /** * Creates a new line element * * @param children - The children the line should have initially */ function createLine(...children) { return { type: 'element', tagName: 'span', properties: { class: ['line'] }, children, }; } /** * Checks if a node has a line break * * @param node - The node to check */ function hasLineBreak(node) { return (isText(node) && /\r?\n/.test(node.value)) || (isElement(node) && node.children.some(hasLineBreak)); } export { codeBlockLinesPlugin, isElement, isText, lineBreak, textElement };