diff --git a/internal/suggestdiff/diff.go b/internal/suggestdiff/diff.go index aabab7c..922dd3c 100644 --- a/internal/suggestdiff/diff.go +++ b/internal/suggestdiff/diff.go @@ -51,7 +51,13 @@ func ComputeLineDiff(oldLines, newLines []string) []DiffLine { back := make([]DiffLine, 0, max(m, n)*2) for i > 0 || j > 0 { if i > 0 && j > 0 && strings.EqualFold(strings.TrimSpace(oldLines[i-1]), strings.TrimSpace(newLines[j-1])) { - back = append(back, DiffLine{Type: DiffContext, Content: oldLines[i-1]}) + if oldLines[i-1] == newLines[j-1] { + back = append(back, DiffLine{Type: DiffContext, Content: oldLines[i-1]}) + } else { + // Fuzzy equality is only for alignment; preserve raw changes in the rendered diff. + back = append(back, DiffLine{Type: DiffAdded, Content: newLines[j-1]}) + back = append(back, DiffLine{Type: DiffDeleted, Content: oldLines[i-1]}) + } i-- j-- } else if j > 0 && (i == 0 || lcs[i][j-1] >= lcs[i-1][j]) { diff --git a/internal/suggestdiff/diff_test.go b/internal/suggestdiff/diff_test.go index a0d2916..5c8b912 100644 --- a/internal/suggestdiff/diff_test.go +++ b/internal/suggestdiff/diff_test.go @@ -72,14 +72,6 @@ func TestComputeLineDiff(t *testing.T) { wantAdds: 0, wantDels: 1, }, - { - name: "case insensitive match with whitespace", - old: []string{" Hello "}, - new: []string{"hello"}, - wantLen: 1, - wantAdds: 0, - wantDels: 0, - }, { name: "multi-line edit", old: []string{"func main() {", " fmt.Println(\"old\")", "}"}, @@ -115,6 +107,70 @@ func TestComputeLineDiff(t *testing.T) { } } +func TestComputeLineDiff_FuzzyMatchPreservesRawChanges(t *testing.T) { + tests := []struct { + name string + old string + new string + }{ + { + name: "case-only change", + old: "const maxRetries = 3", + new: "const MaxRetries = 3", + }, + { + name: "indentation-only change", + old: "\treturn nil", + new: "\t\treturn nil", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ComputeLineDiff([]string{tt.old}, []string{tt.new}) + if len(got) != 2 { + t.Fatalf("diff = %v, want delete + add", got) + } + if got[0].Type != DiffDeleted || got[0].Content != tt.old { + t.Errorf("deleted line = %+v, want %q", got[0], tt.old) + } + if got[1].Type != DiffAdded || got[1].Content != tt.new { + t.Errorf("added line = %+v, want %q", got[1], tt.new) + } + }) + } +} + +func TestComputeLineDiff_FuzzyMatchPreservesRawChangesWithContext(t *testing.T) { + old := []string{ + "func validate() error {", + "\treturn nil", + "}", + } + new := []string{ + "func validate() error {", + "\t\treturn nil", + "}", + } + + got := ComputeLineDiff(old, new) + want := []DiffLine{ + {Type: DiffContext, Content: "func validate() error {"}, + {Type: DiffDeleted, Content: "\treturn nil"}, + {Type: DiffAdded, Content: "\t\treturn nil"}, + {Type: DiffContext, Content: "}"}, + } + + if len(got) != len(want) { + t.Fatalf("diff = %v, want %v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Errorf("diff[%d] = %+v, want %+v", i, got[i], want[i]) + } + } +} + func TestComputeLineDiff_ContextContent(t *testing.T) { old := []string{"a", "b", "c"} new := []string{"a", "x", "c"}