GoPLS Viewer

Home|gopls/internal/diff/myers/diff.go
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.
6package myers
7
8import (
9    "strings"
10
11    "golang.org/x/tools/internal/diff"
12)
13
14// Sources:
15// https://blog.jcoglan.com/2017/02/17/the-myers-diff-algorithm-part-3/
16// https://www.codeproject.com/Articles/42279/%2FArticles%2F42279%2FInvestigating-Myers-diff-algorithm-Part-1-of-2
17
18func ComputeEdits(beforeafter string) []diff.Edit {
19    beforeLines := splitLines(before)
20    ops := operations(beforeLinessplitLines(after))
21
22    // Build a table mapping line number to offset.
23    lineOffsets := make([]int0len(beforeLines)+1)
24    total := 0
25    for i := range beforeLines {
26        lineOffsets = append(lineOffsetstotal)
27        total += len(beforeLines[i])
28    }
29    lineOffsets = append(lineOffsetstotal// EOF
30
31    edits := make([]diff.Edit0len(ops))
32    for _op := range ops {
33        startend := lineOffsets[op.I1], lineOffsets[op.I2]
34        switch op.Kind {
35        case diff.Delete:
36            // Delete: before[I1:I2] is deleted.
37            edits = append(editsdiff.Edit{StartstartEndend})
38        case diff.Insert:
39            // Insert: after[J1:J2] is inserted at before[I1:I1].
40            if content := strings.Join(op.Content""); content != "" {
41                edits = append(editsdiff.Edit{StartstartEndendNewcontent})
42            }
43        }
44    }
45    return edits
46}
47
48type operation struct {
49    Kind    diff.OpKind
50    Content []string // content from b
51    I1I2  int      // indices of the line in a
52    J1      int      // indices of the line in b, J2 implied by len(Content)
53}
54
55// operations returns the list of operations to convert a into b, consolidating
56// operations for multiple lines and not including equal lines.
57func operations(ab []string) []*operation {
58    if len(a) == 0 && len(b) == 0 {
59        return nil
60    }
61
62    traceoffset := shortestEditSequence(ab)
63    snakes := backtrack(tracelen(a), len(b), offset)
64
65    MN := len(a), len(b)
66
67    var i int
68    solution := make([]*operationlen(a)+len(b))
69
70    add := func(op *operationi2j2 int) {
71        if op == nil {
72            return
73        }
74        op.I2 = i2
75        if op.Kind == diff.Insert {
76            op.Content = b[op.J1:j2]
77        }
78        solution[i] = op
79        i++
80    }
81    xy := 00
82    for _snake := range snakes {
83        if len(snake) < 2 {
84            continue
85        }
86        var op *operation
87        // delete (horizontal)
88        for snake[0]-snake[1] > x-y {
89            if op == nil {
90                op = &operation{
91                    Kinddiff.Delete,
92                    I1:   x,
93                    J1:   y,
94                }
95            }
96            x++
97            if x == M {
98                break
99            }
100        }
101        add(opxy)
102        op = nil
103        // insert (vertical)
104        for snake[0]-snake[1] < x-y {
105            if op == nil {
106                op = &operation{
107                    Kinddiff.Insert,
108                    I1:   x,
109                    J1:   y,
110                }
111            }
112            y++
113        }
114        add(opxy)
115        op = nil
116        // equal (diagonal)
117        for x < snake[0] {
118            x++
119            y++
120        }
121        if x >= M && y >= N {
122            break
123        }
124    }
125    return solution[:i]
126}
127
128// backtrack uses the trace for the edit sequence computation and returns the
129// "snakes" that make up the solution. A "snake" is a single deletion or
130// insertion followed by zero or diagonals.
131func backtrack(trace [][]intxyoffset int) [][]int {
132    snakes := make([][]intlen(trace))
133    d := len(trace) - 1
134    for ; x > 0 && y > 0 && d > 0d-- {
135        V := trace[d]
136        if len(V) == 0 {
137            continue
138        }
139        snakes[d] = []int{xy}
140
141        k := x - y
142
143        var kPrev int
144        if k == -d || (k != d && V[k-1+offset] < V[k+1+offset]) {
145            kPrev = k + 1
146        } else {
147            kPrev = k - 1
148        }
149
150        x = V[kPrev+offset]
151        y = x - kPrev
152    }
153    if x < 0 || y < 0 {
154        return snakes
155    }
156    snakes[d] = []int{xy}
157    return snakes
158}
159
160// shortestEditSequence returns the shortest edit sequence that converts a into b.
161func shortestEditSequence(ab []string) ([][]intint) {
162    MN := len(a), len(b)
163    V := make([]int2*(N+M)+1)
164    offset := N + M
165    trace := make([][]intN+M+1)
166
167    // Iterate through the maximum possible length of the SES (N+M).
168    for d := 0d <= N+Md++ {
169        copyV := make([]intlen(V))
170        // k lines are represented by the equation y = x - k. We move in
171        // increments of 2 because end points for even d are on even k lines.
172        for k := -dk <= dk += 2 {
173            // At each point, we either go down or to the right. We go down if
174            // k == -d, and we go to the right if k == d. We also prioritize
175            // the maximum x value, because we prefer deletions to insertions.
176            var x int
177            if k == -d || (k != d && V[k-1+offset] < V[k+1+offset]) {
178                x = V[k+1+offset// down
179            } else {
180                x = V[k-1+offset] + 1 // right
181            }
182
183            y := x - k
184
185            // Diagonal moves while we have equal contents.
186            for x < M && y < N && a[x] == b[y] {
187                x++
188                y++
189            }
190
191            V[k+offset] = x
192
193            // Return if we've exceeded the maximum values.
194            if x == M && y == N {
195                // Makes sure to save the state of the array before returning.
196                copy(copyVV)
197                trace[d] = copyV
198                return traceoffset
199            }
200        }
201
202        // Save the state of the array.
203        copy(copyVV)
204        trace[d] = copyV
205    }
206    return nil0
207}
208
209func splitLines(text string) []string {
210    lines := strings.SplitAfter(text"\n")
211    if lines[len(lines)-1] == "" {
212        lines = lines[:len(lines)-1]
213    }
214    return lines
215}
216
MembersX
diff
operation.I1
operations.a
shortestEditSequence.BlockStmt.BlockStmt.x
operation.J1
operations.N
operations.RangeStmt_2222.snake
backtrack.BlockStmt.kPrev
shortestEditSequence.N
backtrack.x
ComputeEdits.after
ComputeEdits.RangeStmt_744.i
operation.Kind
operation.I2
shortestEditSequence
splitLines
splitLines.text
shortestEditSequence.a
operations.i
ComputeEdits.beforeLines
ComputeEdits.RangeStmt_942.op
operations.b
operations.offset
operations.x
backtrack.offset
ComputeEdits.lineOffsets
operations.solution
operations.RangeStmt_2222.BlockStmt.op
shortestEditSequence.trace
operation
operations.y
backtrack.y
shortestEditSequence.b
shortestEditSequence.BlockStmt.copyV
ComputeEdits
backtrack.snakes
ComputeEdits.ops
ComputeEdits.total
operations.M
shortestEditSequence.V
shortestEditSequence.d
ComputeEdits.edits
operations.snakes
shortestEditSequence.M
shortestEditSequence.BlockStmt.k
splitLines.lines
strings
backtrack.trace
ComputeEdits.before
ComputeEdits.RangeStmt_942.BlockStmt.BlockStmt.content
operation.Content
operations
operations.trace
backtrack
Members
X