mirror of https://github.com/golang/go.git
131 lines
3.5 KiB
Go
131 lines
3.5 KiB
Go
// Copyright 2022 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package diff
|
|
|
|
import (
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"golang.org/x/tools/internal/diff/lcs"
|
|
"golang.org/x/tools/internal/span"
|
|
)
|
|
|
|
// maxDiffs is a limit on how deeply the lcs algorithm should search
|
|
// the value is just a guess
|
|
const maxDiffs = 30
|
|
|
|
// NComputeEdits computes TextEdits for strings
|
|
// (both it and the diff in the myers package have type ComputeEdits, which
|
|
// is why the arguments are strings, not []bytes.)
|
|
func NComputeEdits(uri span.URI, before, after string) ([]TextEdit, error) {
|
|
if before == after {
|
|
// very frequently true
|
|
return nil, nil
|
|
}
|
|
// the diffs returned by the lcs package use indexes into whatever slice
|
|
// was passed in. TextEdits need a span.Span which is computed with
|
|
// byte offsets, so rune or line offsets need to be converted.
|
|
if needrunes(before) || needrunes(after) {
|
|
diffs, _ := lcs.Compute([]rune(before), []rune(after), maxDiffs/2)
|
|
diffs = runeOffsets(diffs, []rune(before))
|
|
ans, err := convertDiffs(uri, diffs, []byte(before))
|
|
return ans, err
|
|
} else {
|
|
diffs, _ := lcs.Compute([]byte(before), []byte(after), maxDiffs/2)
|
|
ans, err := convertDiffs(uri, diffs, []byte(before))
|
|
return ans, err
|
|
}
|
|
}
|
|
|
|
// NComputeLineEdits computes TextEdits for []strings
|
|
func NComputeLineEdits(uri span.URI, before, after []string) ([]TextEdit, error) {
|
|
diffs, _ := lcs.Compute(before, after, maxDiffs/2)
|
|
diffs = lineOffsets(diffs, before)
|
|
ans, err := convertDiffs(uri, diffs, []byte(strJoin(before)))
|
|
// the code is not coping with possible missing \ns at the ends
|
|
return ans, err
|
|
}
|
|
|
|
// convert diffs with byte offsets into diffs with line and column
|
|
func convertDiffs(uri span.URI, diffs []lcs.Diff, src []byte) ([]TextEdit, error) {
|
|
ans := make([]TextEdit, len(diffs))
|
|
tf := span.NewTokenFile(uri.Filename(), src)
|
|
for i, d := range diffs {
|
|
s := newSpan(uri, d.Start, d.End)
|
|
s, err := s.WithPosition(tf)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ans[i] = TextEdit{s, d.Text}
|
|
}
|
|
return ans, nil
|
|
}
|
|
|
|
// convert diffs with rune offsets into diffs with byte offsets
|
|
func runeOffsets(diffs []lcs.Diff, src []rune) []lcs.Diff {
|
|
var idx int
|
|
var tmp strings.Builder // string because []byte([]rune) is illegal
|
|
for i, d := range diffs {
|
|
tmp.WriteString(string(src[idx:d.Start]))
|
|
v := tmp.Len()
|
|
tmp.WriteString(string(src[d.Start:d.End]))
|
|
d.Start = v
|
|
idx = d.End
|
|
d.End = tmp.Len()
|
|
diffs[i] = d
|
|
}
|
|
return diffs
|
|
}
|
|
|
|
// convert diffs with line offsets into diffs with byte offsets
|
|
func lineOffsets(diffs []lcs.Diff, src []string) []lcs.Diff {
|
|
var idx int
|
|
var tmp strings.Builder // bytes/
|
|
for i, d := range diffs {
|
|
tmp.WriteString(strJoin(src[idx:d.Start]))
|
|
v := tmp.Len()
|
|
tmp.WriteString(strJoin(src[d.Start:d.End]))
|
|
d.Start = v
|
|
idx = d.End
|
|
d.End = tmp.Len()
|
|
diffs[i] = d
|
|
}
|
|
return diffs
|
|
}
|
|
|
|
// join lines. (strings.Join doesn't add a trailing separator)
|
|
func strJoin(elems []string) string {
|
|
if len(elems) == 0 {
|
|
return ""
|
|
}
|
|
n := 0
|
|
for i := 0; i < len(elems); i++ {
|
|
n += len(elems[i])
|
|
}
|
|
|
|
var b strings.Builder
|
|
b.Grow(n)
|
|
for _, s := range elems {
|
|
b.WriteString(s)
|
|
//b.WriteByte('\n')
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func newSpan(uri span.URI, left, right int) span.Span {
|
|
return span.New(uri, span.NewPoint(0, 0, left), span.NewPoint(0, 0, right))
|
|
}
|
|
|
|
// need runes is true if the string needs to be converted to []rune
|
|
// for random access
|
|
func needrunes(s string) bool {
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] >= utf8.RuneSelf {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|