Files
pi-agent-integrated/pi-web/lib/patch.ts
T

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();
}