@scalar/code-highlight
Version:
Central methods and themes for code highlighting in Scalar projects
95 lines (94 loc) • 3.32 kB
JavaScript
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
*/
export 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]) || undefined);
node.children.forEach((child) => {
if (isText(child) && hasLineBreak(child)) {
const split = child.value.split(/\n/);
split.forEach((content, i) => {
if (copyParent) {
line()?.children.push({ ...node, children: [textElement(content)] });
}
else {
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));
}