webview-ui: fix chat diff mis-render for EOF append by collapsing identical -/+ pairs into context; add unit tests

This commit is contained in:
Hannes Rudolph 2025-11-11 12:56:30 -07:00
parent 6e6341346e
commit 3a7c18bc52
2 changed files with 62 additions and 1 deletions

View file

@ -0,0 +1,31 @@
import { parseUnifiedDiff } from "@/utils/parseUnifiedDiff"
describe("parseUnifiedDiff - collapse identical -/+ pairs", () => {
it("collapses deletion+addition of identical text into a single context line", () => {
// Typical trailing-newline-only change at EOF with an additional appended line
const diff = ["@@ -1,1 +1,2 @@", "-old", "+old", "+new", ""].join("\n")
const lines = parseUnifiedDiff(diff)
// Should normalize the replace of identical line into context, plus the appended line
expect(lines.map((l) => l.type)).toEqual(["context", "addition"])
expect(lines[0].content).toBe("old")
expect(lines[1].content).toBe("new")
// Line numbers should be preserved appropriately
expect(lines[0].oldLineNum).toBe(1)
expect(lines[0].newLineNum).toBe(1)
expect(lines[1].oldLineNum).toBeNull()
expect(lines[1].newLineNum).toBe(2)
})
it("does not collapse when content differs (true replacement)", () => {
const diff = ["@@ -1,1 +1,1 @@", "-old", "+new", ""].join("\n")
const lines = parseUnifiedDiff(diff)
// Keep as deletion + addition for a real replacement
expect(lines.map((l) => l.type)).toEqual(["deletion", "addition"])
expect(lines[0].content).toBe("old")
expect(lines[1].content).toBe("new")
})
})

View file

@ -88,7 +88,37 @@ export function parseUnifiedDiff(source: string, filePath?: string): DiffLine[]
prevHunk = hunk
}
return lines
// Collapse "- line" then "+ same line" pairs into a single context line.
// This normalizes diffs where the only change is adding a trailing newline
// (common when appending to a file missing EOF newline). VS Code's diff
// shows these as unchanged; our chat view should too.
const collapseReplacePairs = (input: DiffLine[]): DiffLine[] => {
const out: DiffLine[] = []
for (let i = 0; i < input.length; i++) {
const cur = input[i]
const next = input[i + 1]
if (
cur &&
next &&
cur.type === "deletion" &&
next.type === "addition" &&
cur.content === next.content
) {
out.push({
oldLineNum: cur.oldLineNum,
newLineNum: next.newLineNum,
type: "context",
content: cur.content,
})
i++ // skip the paired addition
continue
}
out.push(cur)
}
return out
}
return collapseReplacePairs(lines)
} catch {
// swallow parse errors and render nothing rather than breaking the UI
return []