ComputeLineDiff returns a line-level diff between oldLines and newLines. Uses Myers-style LCS to find common subsequences, then emits context/added/deleted lines.
(oldLines, newLines []string)
| 22 | // ComputeLineDiff returns a line-level diff between oldLines and newLines. |
| 23 | // Uses Myers-style LCS to find common subsequences, then emits context/added/deleted lines. |
| 24 | func ComputeLineDiff(oldLines, newLines []string) []DiffLine { |
| 25 | m, n := len(oldLines), len(newLines) |
| 26 | if m == 0 && n == 0 { |
| 27 | return nil |
| 28 | } |
| 29 | |
| 30 | // LCS DP table |
| 31 | lcs := make([][]int, m+1) |
| 32 | for i := range lcs { |
| 33 | lcs[i] = make([]int, n+1) |
| 34 | } |
| 35 | for i := 1; i <= m; i++ { |
| 36 | for j := 1; j <= n; j++ { |
| 37 | if strings.EqualFold(strings.TrimSpace(oldLines[i-1]), strings.TrimSpace(newLines[j-1])) { |
| 38 | lcs[i][j] = lcs[i-1][j-1] + 1 |
| 39 | } else { |
| 40 | lcs[i][j] = max(lcs[i-1][j], lcs[i][j-1]) |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | // Backtrack to produce diff |
| 46 | var result []DiffLine |
| 47 | i, j := m, n |
| 48 | back := make([]DiffLine, 0, max(m, n)*2) |
| 49 | for i > 0 || j > 0 { |
| 50 | if i > 0 && j > 0 && strings.EqualFold(strings.TrimSpace(oldLines[i-1]), strings.TrimSpace(newLines[j-1])) { |
| 51 | back = append(back, DiffLine{Type: DiffContext, Content: oldLines[i-1]}) |
| 52 | i-- |
| 53 | j-- |
| 54 | } else if j > 0 && (i == 0 || lcs[i][j-1] >= lcs[i-1][j]) { |
| 55 | back = append(back, DiffLine{Type: DiffAdded, Content: newLines[j-1]}) |
| 56 | j-- |
| 57 | } else { |
| 58 | back = append(back, DiffLine{Type: DiffDeleted, Content: oldLines[i-1]}) |
| 59 | i-- |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | // Reverse |
| 64 | for idx := len(back) - 1; idx >= 0; idx-- { |
| 65 | result = append(result, back[idx]) |
| 66 | } |
| 67 | return result |
| 68 | } |
| 69 | |
| 70 | func max(a, b int) int { |
| 71 | if a > b { |