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  }