Files
wehub-resource-sync e7738de6d2
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
chore: import upstream snapshot with attribution
2026-07-13 12:29:30 +08:00

117 lines
4.0 KiB
TypeScript

import { highlight, highlightLanguage } from "./highlight";
type HastNode = {
type?: string;
tagName?: string;
value?: string;
properties?: Record<string, unknown>;
children?: HastNode[];
};
type NodePredicate = (node: HastNode) => boolean;
type NodeCallback = (node: HastNode) => void;
const ZERO_TOKEN_COLORS: Record<string, { light: string; dark: string }> = {
comment: { light: "#6A737D", dark: "#6A737D" },
string: { light: "#032F62", dark: "#9ECBFF" },
char: { light: "#032F62", dark: "#9ECBFF" },
keyword: { light: "#D73A49", dark: "#F97583" },
type: { light: "#005CC5", dark: "#79B8FF" },
function: { light: "#6F42C1", dark: "#B392F0" },
number: { light: "#005CC5", dark: "#79B8FF" },
variable: { light: "#24292E", dark: "#E1E4E8" },
id: { light: "#6F42C1", dark: "#B392F0" },
key: { light: "#D73A49", dark: "#F97583" },
boolean: { light: "#005CC5", dark: "#79B8FF" },
punctuation: { light: "#24292E", dark: "#E1E4E8" },
operator: { light: "#D73A49", dark: "#F97583" },
};
function visit(node: HastNode | undefined, predicate: NodePredicate, callback: NodeCallback): void {
if (!node) return;
if (predicate(node)) callback(node);
if (Array.isArray(node.children)) {
for (const child of node.children) visit(child, predicate, callback);
}
}
function highlightToHast(code: string, language: string): HastNode[] {
const parts = highlight(code, language).split("\n");
const lines = [];
for (let li = 0; li < parts.length; li++) {
const lineHtml = parts[li];
const tokens = [];
const tokenRegex = /<span class="hl-([A-Za-z0-9_-]+)(?:\s+[^"]*)?">([\s\S]*?)<\/span>|([^<]+)/g;
let match: RegExpExecArray | null;
while ((match = tokenRegex.exec(lineHtml)) !== null) {
if (match[1]) {
const color = ZERO_TOKEN_COLORS[match[1]];
const text = decodeEntities(match[2]);
tokens.push({
type: "element",
tagName: "span",
properties: color
? { style: `--shiki-light:${color.light};--shiki-dark:${color.dark}` }
: {},
children: [{ type: "text", value: text }],
});
} else if (match[3]) {
tokens.push({ type: "text", value: decodeEntities(match[3]) });
}
}
lines.push({
type: "element",
tagName: "span",
properties: { "data-line": "" },
children: tokens.length > 0 ? tokens : [{ type: "text", value: "" }],
});
}
return lines;
}
function decodeEntities(value: string): string {
return value
.replaceAll("&amp;", "&")
.replaceAll("&lt;", "<")
.replaceAll("&gt;", ">")
.replaceAll("&quot;", '"')
.replaceAll("&#039;", "'");
}
function getNodeText(node: HastNode | undefined): string {
if (!node) return "";
if (node.type === "text") return node.value ?? "";
if (!Array.isArray(node.children)) return "";
return node.children.map(getNodeText).join("");
}
function getCodeLanguage(node: HastNode): string {
const dataLanguage = node.properties?.dataLanguage ?? node.properties?.["data-language"];
if (typeof dataLanguage === "string") return dataLanguage;
const className = node.properties?.className;
const classes = Array.isArray(className) ? className : typeof className === "string" ? className.split(/\s+/) : [];
const languageClass = classes.find((value) => typeof value === "string" && value.startsWith("language-"));
return typeof languageClass === "string" ? languageClass.slice("language-".length) : "";
}
export function rehypeZeroHighlight(): (tree: HastNode) => void {
return (tree: HastNode) => {
visit(
tree,
(node) =>
node.type === "element" &&
node.tagName === "code",
(codeNode) => {
const text = getNodeText(codeNode).replace(/\n$/, "");
const language = highlightLanguage(getCodeLanguage(codeNode), text);
if (!language || language === "bash") return;
codeNode.children = highlightToHast(text, language);
codeNode.properties = {
...codeNode.properties,
style: "display:grid",
};
},
);
};
}