diff view: normalize original content for display to avoid false deletion on EOF append; revert chat-side collapse

This commit is contained in:
Hannes Rudolph 2025-11-11 13:13:09 -07:00
parent 3a7c18bc52
commit a398b578ea
3 changed files with 13 additions and 63 deletions

View file

@ -31,6 +31,9 @@ export class DiffViewProvider {
private documentWasOpen = false
private relPath?: string
private newContent?: string
// Original content used for the left (original) side of VS Code's diff.
// We may normalize trailing newline for display-only to improve hunk alignment.
private originalContentForDisplay?: string
private activeDiffEditor?: vscode.TextEditor
private fadedOverlayController?: DecorationController
private activeLineController?: DecorationController
@ -69,8 +72,14 @@ export class DiffViewProvider {
if (fileExists) {
this.originalContent = await fs.readFile(absolutePath, "utf-8")
// Normalize trailing newline for display only to avoid false replace of last line
this.originalContentForDisplay = this.originalContent.endsWith("\n")
? this.originalContent
: this.originalContent + "\n"
} else {
this.originalContent = ""
// For new files, keep original empty to show all additions
this.originalContentForDisplay = this.originalContent
}
// For new files, create any necessary directories and keep track of new
@ -554,7 +563,9 @@ export class DiffViewProvider {
return vscode.commands.executeCommand(
"vscode.diff",
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${fileName}`).with({
query: Buffer.from(this.originalContent ?? "").toString("base64"),
query: Buffer.from(this.originalContentForDisplay ?? this.originalContent ?? "").toString(
"base64",
),
}),
uri,
`${fileName}: ${fileExists ? `${DIFF_VIEW_LABEL_CHANGES}` : "New File"} (Editable)`,

View file

@ -1,31 +0,0 @@
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,37 +88,7 @@ export function parseUnifiedDiff(source: string, filePath?: string): DiffLine[]
prevHunk = hunk
}
// 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)
return lines
} catch {
// swallow parse errors and render nothing rather than breaking the UI
return []