github.com/v2fly/tools@v0.100.0/internal/lsp/diff/myers/diff.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package myers implements the Myers diff algorithm. 6 package myers 7 8 import ( 9 "strings" 10 11 "github.com/v2fly/tools/internal/lsp/diff" 12 "github.com/v2fly/tools/internal/span" 13 ) 14 15 // Sources: 16 // https://blog.jcoglan.com/2017/02/17/the-myers-diff-algorithm-part-3/ 17 // https://www.codeproject.com/Articles/42279/%2FArticles%2F42279%2FInvestigating-Myers-diff-algorithm-Part-1-of-2 18 19 func ComputeEdits(uri span.URI, before, after string) ([]diff.TextEdit, error) { 20 ops := operations(splitLines(before), splitLines(after)) 21 edits := make([]diff.TextEdit, 0, len(ops)) 22 for _, op := range ops { 23 s := span.New(uri, span.NewPoint(op.I1+1, 1, 0), span.NewPoint(op.I2+1, 1, 0)) 24 switch op.Kind { 25 case diff.Delete: 26 // Delete: unformatted[i1:i2] is deleted. 27 edits = append(edits, diff.TextEdit{Span: s}) 28 case diff.Insert: 29 // Insert: formatted[j1:j2] is inserted at unformatted[i1:i1]. 30 if content := strings.Join(op.Content, ""); content != "" { 31 edits = append(edits, diff.TextEdit{Span: s, NewText: content}) 32 } 33 } 34 } 35 return edits, nil 36 } 37 38 type operation struct { 39 Kind diff.OpKind 40 Content []string // content from b 41 I1, I2 int // indices of the line in a 42 J1 int // indices of the line in b, J2 implied by len(Content) 43 } 44 45 // operations returns the list of operations to convert a into b, consolidating 46 // operations for multiple lines and not including equal lines. 47 func operations(a, b []string) []*operation { 48 if len(a) == 0 && len(b) == 0 { 49 return nil 50 } 51 52 trace, offset := shortestEditSequence(a, b) 53 snakes := backtrack(trace, len(a), len(b), offset) 54 55 M, N := len(a), len(b) 56 57 var i int 58 solution := make([]*operation, len(a)+len(b)) 59 60 add := func(op *operation, i2, j2 int) { 61 if op == nil { 62 return 63 } 64 op.I2 = i2 65 if op.Kind == diff.Insert { 66 op.Content = b[op.J1:j2] 67 } 68 solution[i] = op 69 i++ 70 } 71 x, y := 0, 0 72 for _, snake := range snakes { 73 if len(snake) < 2 { 74 continue 75 } 76 var op *operation 77 // delete (horizontal) 78 for snake[0]-snake[1] > x-y { 79 if op == nil { 80 op = &operation{ 81 Kind: diff.Delete, 82 I1: x, 83 J1: y, 84 } 85 } 86 x++ 87 if x == M { 88 break 89 } 90 } 91 add(op, x, y) 92 op = nil 93 // insert (vertical) 94 for snake[0]-snake[1] < x-y { 95 if op == nil { 96 op = &operation{ 97 Kind: diff.Insert, 98 I1: x, 99 J1: y, 100 } 101 } 102 y++ 103 } 104 add(op, x, y) 105 op = nil 106 // equal (diagonal) 107 for x < snake[0] { 108 x++ 109 y++ 110 } 111 if x >= M && y >= N { 112 break 113 } 114 } 115 return solution[:i] 116 } 117 118 // backtrack uses the trace for the edit sequence computation and returns the 119 // "snakes" that make up the solution. A "snake" is a single deletion or 120 // insertion followed by zero or diagonals. 121 func backtrack(trace [][]int, x, y, offset int) [][]int { 122 snakes := make([][]int, len(trace)) 123 d := len(trace) - 1 124 for ; x > 0 && y > 0 && d > 0; d-- { 125 V := trace[d] 126 if len(V) == 0 { 127 continue 128 } 129 snakes[d] = []int{x, y} 130 131 k := x - y 132 133 var kPrev int 134 if k == -d || (k != d && V[k-1+offset] < V[k+1+offset]) { 135 kPrev = k + 1 136 } else { 137 kPrev = k - 1 138 } 139 140 x = V[kPrev+offset] 141 y = x - kPrev 142 } 143 if x < 0 || y < 0 { 144 return snakes 145 } 146 snakes[d] = []int{x, y} 147 return snakes 148 } 149 150 // shortestEditSequence returns the shortest edit sequence that converts a into b. 151 func shortestEditSequence(a, b []string) ([][]int, int) { 152 M, N := len(a), len(b) 153 V := make([]int, 2*(N+M)+1) 154 offset := N + M 155 trace := make([][]int, N+M+1) 156 157 // Iterate through the maximum possible length of the SES (N+M). 158 for d := 0; d <= N+M; d++ { 159 copyV := make([]int, len(V)) 160 // k lines are represented by the equation y = x - k. We move in 161 // increments of 2 because end points for even d are on even k lines. 162 for k := -d; k <= d; k += 2 { 163 // At each point, we either go down or to the right. We go down if 164 // k == -d, and we go to the right if k == d. We also prioritize 165 // the maximum x value, because we prefer deletions to insertions. 166 var x int 167 if k == -d || (k != d && V[k-1+offset] < V[k+1+offset]) { 168 x = V[k+1+offset] // down 169 } else { 170 x = V[k-1+offset] + 1 // right 171 } 172 173 y := x - k 174 175 // Diagonal moves while we have equal contents. 176 for x < M && y < N && a[x] == b[y] { 177 x++ 178 y++ 179 } 180 181 V[k+offset] = x 182 183 // Return if we've exceeded the maximum values. 184 if x == M && y == N { 185 // Makes sure to save the state of the array before returning. 186 copy(copyV, V) 187 trace[d] = copyV 188 return trace, offset 189 } 190 } 191 192 // Save the state of the array. 193 copy(copyV, V) 194 trace[d] = copyV 195 } 196 return nil, 0 197 } 198 199 func splitLines(text string) []string { 200 lines := strings.SplitAfter(text, "\n") 201 if lines[len(lines)-1] == "" { 202 lines = lines[:len(lines)-1] 203 } 204 return lines 205 }