mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 11:09:34 +00:00
118 lines
3.1 KiB
TypeScript
118 lines
3.1 KiB
TypeScript
export type SplitDiffCellType = "context" | "removed" | "added" | "empty";
|
|
|
|
export interface SplitDiffCell {
|
|
lineNo: number | null;
|
|
text: string;
|
|
type: SplitDiffCellType;
|
|
}
|
|
|
|
export type SplitDiffRow =
|
|
| { type: "hunk"; text: string }
|
|
| { type: "line"; left: SplitDiffCell; right: SplitDiffCell };
|
|
|
|
export interface SplitDiffFile {
|
|
oldPath?: string;
|
|
newPath?: string;
|
|
rows: SplitDiffRow[];
|
|
}
|
|
|
|
interface PendingChangeLine {
|
|
lineNo: number;
|
|
text: string;
|
|
}
|
|
|
|
export function parseUnifiedPatch(text: string): SplitDiffFile[] | null {
|
|
const files: SplitDiffFile[] = [];
|
|
let current: SplitDiffFile | null = null;
|
|
let pendingOldPath: string | undefined;
|
|
let oldLineNo = 0;
|
|
let newLineNo = 0;
|
|
let removed: PendingChangeLine[] = [];
|
|
let added: PendingChangeLine[] = [];
|
|
|
|
const emptyCell = (): SplitDiffCell => ({ lineNo: null, text: "", type: "empty" });
|
|
const flushChanges = () => {
|
|
if (!current) {
|
|
removed = [];
|
|
added = [];
|
|
return;
|
|
}
|
|
const count = Math.max(removed.length, added.length);
|
|
for (let i = 0; i < count; i++) {
|
|
const left = removed[i]
|
|
? { lineNo: removed[i].lineNo, text: removed[i].text, type: "removed" as const }
|
|
: emptyCell();
|
|
const right = added[i]
|
|
? { lineNo: added[i].lineNo, text: added[i].text, type: "added" as const }
|
|
: emptyCell();
|
|
current.rows.push({ type: "line", left, right });
|
|
}
|
|
removed = [];
|
|
added = [];
|
|
};
|
|
|
|
for (const line of text.split(/\r?\n/)) {
|
|
if (line.startsWith("--- ")) {
|
|
flushChanges();
|
|
pendingOldPath = cleanPatchPath(line.slice(4));
|
|
continue;
|
|
}
|
|
|
|
if (line.startsWith("+++ ")) {
|
|
flushChanges();
|
|
current = { oldPath: pendingOldPath, newPath: cleanPatchPath(line.slice(4)), rows: [] };
|
|
files.push(current);
|
|
continue;
|
|
}
|
|
|
|
const hunk = line.match(/^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
if (hunk) {
|
|
if (!current) {
|
|
current = { rows: [] };
|
|
files.push(current);
|
|
}
|
|
flushChanges();
|
|
oldLineNo = Number(hunk[1]);
|
|
newLineNo = Number(hunk[2]);
|
|
current.rows.push({ type: "hunk", text: line });
|
|
continue;
|
|
}
|
|
|
|
if (!current) continue;
|
|
|
|
if (line.startsWith("\\ ")) {
|
|
flushChanges();
|
|
current.rows.push({ type: "hunk", text: line });
|
|
continue;
|
|
}
|
|
|
|
const prefix = line[0];
|
|
const content = line.slice(1);
|
|
|
|
if (prefix === " ") {
|
|
flushChanges();
|
|
current.rows.push({
|
|
type: "line",
|
|
left: { lineNo: oldLineNo++, text: content, type: "context" },
|
|
right: { lineNo: newLineNo++, text: content, type: "context" },
|
|
});
|
|
} else if (prefix === "-") {
|
|
removed.push({ lineNo: oldLineNo++, text: content });
|
|
} else if (prefix === "+") {
|
|
added.push({ lineNo: newLineNo++, text: content });
|
|
} else if (line !== "") {
|
|
flushChanges();
|
|
current.rows.push({ type: "hunk", text: line });
|
|
}
|
|
}
|
|
|
|
flushChanges();
|
|
|
|
const parsed = files.filter((file) => file.rows.some((row) => row.type === "line"));
|
|
return parsed.length > 0 ? parsed : null;
|
|
}
|
|
|
|
function cleanPatchPath(path: string): string {
|
|
return path.split("\t")[0].trim();
|
|
}
|