Highlighting reuses the existing code-block tokenizer and LRU cache, runs lazily on expand, and falls back to plain text above a size cap so a large Read can't stall the main thread. The diff highlighter reconstructs the old and new file text by position rather than from @@ line ranges, so diffs that arrive without real hunk headers still highlight. Read content is normalized server-side: the cat -n line-number gutter some providers emit is stripped so content is uniformly raw source, with the first line number surfaced as offset for the client to rebuild the gutter.
92 lines
3.3 KiB
TypeScript
92 lines
3.3 KiB
TypeScript
import { isLanguageSupported, type HighlightToken } from "@getpaseo/highlight";
|
|
import { extensionFromPath, tokenizeToLines } from "@/utils/highlight-cache";
|
|
import type { DiffLine } from "@/utils/tool-call-parsers";
|
|
|
|
// The leading diff marker glyph for a line. The code on the line is the content
|
|
// with this prefix removed; we render the glyph separately so token colors
|
|
// apply only to the code.
|
|
export function diffLinePrefix(line: DiffLine): string {
|
|
switch (line.type) {
|
|
case "add":
|
|
return "+";
|
|
case "remove":
|
|
return "-";
|
|
case "context":
|
|
return " ";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function diffLineCode(line: DiffLine): string {
|
|
const { content, type } = line;
|
|
if (type === "add" || type === "remove") {
|
|
return content.startsWith(type === "add" ? "+" : "-") ? content.slice(1) : content;
|
|
}
|
|
if (type === "context") {
|
|
return content.startsWith(" ") ? content.slice(1) : content;
|
|
}
|
|
return content;
|
|
}
|
|
|
|
// Attach syntax-highlight tokens to each diff line. Language comes from the file
|
|
// path (extension only). We reconstruct the old and new document text from the
|
|
// diff lines by position — counting context/remove into "old" and context/add
|
|
// into "new" — so this works regardless of whether the source diff carried real
|
|
// `@@ -n,m +n,m @@` line ranges (Codex emits bare `@@`). Each document is
|
|
// highlighted as a whole so the parser has cross-line context (multi-line
|
|
// strings, template literals, comments). Returns the input unchanged when the
|
|
// language is unsupported or the content exceeds the highlighter size cap.
|
|
export function highlightDiffLines(
|
|
diffLines: DiffLine[],
|
|
filePath: string | null | undefined,
|
|
): DiffLine[] {
|
|
const ext = extensionFromPath(filePath);
|
|
// Gate on real grammar support: an unsupported language would tokenize to a
|
|
// single style-less token per line, which would shadow the word-level change
|
|
// segments the diff already computes. Better to keep those.
|
|
if (!ext || diffLines.length === 0 || !isLanguageSupported(`x.${ext}`)) {
|
|
return diffLines;
|
|
}
|
|
|
|
const oldCode: string[] = [];
|
|
const newCode: string[] = [];
|
|
const positions = diffLines.map((line) => {
|
|
const code = diffLineCode(line);
|
|
if (line.type === "context") {
|
|
const position = { oldIndex: oldCode.length, newIndex: newCode.length };
|
|
oldCode.push(code);
|
|
newCode.push(code);
|
|
return position;
|
|
}
|
|
if (line.type === "remove") {
|
|
const position = { oldIndex: oldCode.length, newIndex: -1 };
|
|
oldCode.push(code);
|
|
return position;
|
|
}
|
|
if (line.type === "add") {
|
|
const position = { oldIndex: -1, newIndex: newCode.length };
|
|
newCode.push(code);
|
|
return position;
|
|
}
|
|
return { oldIndex: -1, newIndex: -1 };
|
|
});
|
|
|
|
const oldTokens = oldCode.length > 0 ? tokenizeToLines(oldCode.join("\n"), ext) : null;
|
|
const newTokens = newCode.length > 0 ? tokenizeToLines(newCode.join("\n"), ext) : null;
|
|
if (!oldTokens && !newTokens) {
|
|
return diffLines;
|
|
}
|
|
|
|
return diffLines.map((line, index) => {
|
|
const { oldIndex, newIndex } = positions[index];
|
|
let tokens: HighlightToken[] | undefined;
|
|
if ((line.type === "add" || line.type === "context") && newTokens) {
|
|
tokens = newTokens[newIndex];
|
|
} else if (line.type === "remove" && oldTokens) {
|
|
tokens = oldTokens[oldIndex];
|
|
}
|
|
return tokens ? { ...line, tokens } : line;
|
|
});
|
|
}
|