@rtdui/code-highlight
Version:
React code highlight component based on refractor
198 lines (196 loc) • 6.18 kB
JavaScript
'use client';
function getNodeText(node) {
function one(node2) {
if (node2.type === "text") {
return node2.value;
}
return "children" in node2 ? all(node2) : "";
}
function all(node2) {
let index = -1;
const result = [];
while (++index < node2.children.length) {
result[index] = one(node2.children[index]);
}
return result.join("");
}
if ("children" in node) {
return all(node);
}
return "value" in node ? node.value : "";
}
function filterTree(tree, test) {
function preorder(node, index, parentNode) {
const children = [];
if (!test(node, index, parentNode))
return void 0;
if ("children" in node && node.children !== void 0) {
let childIndex = -1;
while (++childIndex < node.children.length) {
const result = preorder(node.children[childIndex], childIndex, node);
if (result) {
children.push(result);
}
}
if (node.children.length > 0 && children.length === 0) {
return void 0;
}
}
const next = {};
for (const key in node) {
if (Object.prototype.hasOwnProperty.call(node, key)) {
next[key] = key === "children" ? children : node[key];
}
}
return next;
}
return preorder(tree);
}
function rangeParser(strRange) {
const res = [];
let m;
for (const str of strRange.split(",").map((str2) => str2.trim())) {
if (/^-?\d+$/.test(str)) {
res.push(Number.parseInt(str, 10));
} else if (m = str.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)) {
const [_, lhs, sep, rhs] = m;
if (lhs && rhs) {
const n_lhs = Number.parseInt(lhs, 10);
let n_rhs = Number.parseInt(rhs, 10);
const incr = n_lhs < n_rhs ? 1 : -1;
if (sep === "-" || sep === ".." || sep === "\u2025")
n_rhs += incr;
for (let i = n_lhs; i !== n_rhs; i += incr)
res.push(i);
}
}
}
return res;
}
const calculateLinesToHighlight = (meta) => {
const RE = /{([\d,-]+)}/;
const parsedMeta = meta.split(",").map((str) => str.trim()).join();
if (RE.test(parsedMeta)) {
const strlineNumbers = (RE.exec(parsedMeta) || [])[1];
const lineNumbers = rangeParser(strlineNumbers);
return (index) => lineNumbers.includes(index + 1);
}
return () => false;
};
const calculateStartingLine = (meta) => {
const RE = /showLineNumbers=(?<lines>\d+)/i;
if (RE.test(meta)) {
const { groups } = RE.exec(meta);
return Number(groups.lines);
}
return 1;
};
const createLineNodes = (number) => {
const a = new Array(number);
for (let i = 0; i < number; i++) {
a[i] = {
type: "element",
tagName: "span",
properties: { className: [] },
children: []
};
}
return a;
};
const addNodePositionClosure = () => {
let startLineNum = 1;
const addNodePosition = (ast) => {
return ast.reduce((result, node) => {
if (node.type === "text") {
const { value } = node;
const numLines = (value.match(/\n/g) || "").length;
if (numLines === 0) {
node.position = {
// column: 1 is needed to avoid error with @next/mdx
// https://github.com/timlrx/rehype-prism-plus/issues/44
start: { line: startLineNum, column: 1 },
end: { line: startLineNum, column: 1 }
};
result.push(node);
} else {
const lines = value.split("\n");
for (const [i, line] of lines.entries()) {
result.push({
type: "text",
value: i === lines.length - 1 ? line : `${line}
`,
position: {
start: { line: startLineNum + i, column: 1 },
end: { line: startLineNum + i, column: 1 }
}
});
}
}
startLineNum += numLines;
return result;
}
if (Object.prototype.hasOwnProperty.call(node, "children")) {
const initialLineNum = startLineNum;
node.children = addNodePosition(node.children);
result.push(node);
node.position = {
start: { line: initialLineNum, column: 1 },
end: { line: startLineNum, column: 1 }
};
return result;
}
result.push(node);
return result;
}, []);
};
return addNodePosition;
};
function decorator(refractorRoot, diff, meta) {
refractorRoot.children = addNodePositionClosure()(refractorRoot.children);
if (refractorRoot.children.length > 0) {
refractorRoot.position = {
start: {
line: refractorRoot.children[0].position.start.line,
column: 0
},
end: {
line: refractorRoot.children[refractorRoot.children.length - 1].position.end.line,
column: 0
}
};
} else {
refractorRoot.position = {
start: { line: 0, column: 0 },
end: { line: 0, column: 0 }
};
}
const shouldHighlightLine = calculateLinesToHighlight(meta);
const startingLineNumber = calculateStartingLine(meta);
const codeLineArray = createLineNodes(refractorRoot.position.end.line);
for (const [i, line] of codeLineArray.entries()) {
line.properties.className = ["code-line"];
const treeExtract = filterTree(
refractorRoot,
(node) => node.position.start.line <= i + 1 && node.position.end.line >= i + 1
);
line.children = treeExtract.children;
if (meta.toLowerCase().includes("showLineNumbers".toLowerCase())) {
line.properties.line = [(i + startingLineNumber).toString()];
line.properties.className.push("line-number");
}
if (shouldHighlightLine(i)) {
line.properties.className.push("highlight-line");
}
if (diff && getNodeText(line).trim().substring(0, 1) === "-") {
line.properties.className.push("deleted");
} else if (diff && getNodeText(line).trim().substring(0, 1) === "+") {
line.properties.className.push("inserted");
}
}
if (codeLineArray.length > 0 && getNodeText(codeLineArray[codeLineArray.length - 1]).trim() === "") {
codeLineArray.pop();
}
refractorRoot.children = codeLineArray;
}
export { decorator };
//# sourceMappingURL=decorator.mjs.map