diff --git a/src/integrations/editor/DiffViewProvider.ts b/src/integrations/editor/DiffViewProvider.ts index d42eba082c..678b574a92 100644 --- a/src/integrations/editor/DiffViewProvider.ts +++ b/src/integrations/editor/DiffViewProvider.ts @@ -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)`, diff --git a/webview-ui/src/utils/__tests__/parseUnifiedDiff.spec.ts b/webview-ui/src/utils/__tests__/parseUnifiedDiff.spec.ts deleted file mode 100644 index a465891b11..0000000000 --- a/webview-ui/src/utils/__tests__/parseUnifiedDiff.spec.ts +++ /dev/null @@ -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") - }) -}) diff --git a/webview-ui/src/utils/parseUnifiedDiff.ts b/webview-ui/src/utils/parseUnifiedDiff.ts index 72bef6bb91..bed84c4ca9 100644 --- a/webview-ui/src/utils/parseUnifiedDiff.ts +++ b/webview-ui/src/utils/parseUnifiedDiff.ts @@ -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 []