mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
internal/lsp: abstract the diff library so it can be substituted
this moves the actual diff algorithm into a different package and then provides hooks so it can be easily replaced with an alternate algorithm. Change-Id: Ia0359f58878493599ea0e0fda8920f21100e16f1 Reviewed-on: https://go-review.googlesource.com/c/tools/+/190898 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
d9ab56aa29
commit
85edb9ef32
@ -9,12 +9,10 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/internal/lsp"
|
"golang.org/x/tools/internal/lsp"
|
||||||
"golang.org/x/tools/internal/lsp/diff"
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
errors "golang.org/x/xerrors"
|
errors "golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
@ -82,9 +80,7 @@ func (f *format) Run(ctx context.Context, args ...string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Errorf("%v: %v", spn, err)
|
return errors.Errorf("%v: %v", spn, err)
|
||||||
}
|
}
|
||||||
ops := source.EditsToDiff(sedits)
|
formatted := diff.ApplyEdits(string(file.mapper.Content), sedits)
|
||||||
lines := diff.SplitLines(string(file.mapper.Content))
|
|
||||||
formatted := strings.Join(diff.ApplyEdits(lines, ops), "")
|
|
||||||
printIt := true
|
printIt := true
|
||||||
if f.List {
|
if f.List {
|
||||||
printIt = false
|
printIt = false
|
||||||
@ -100,7 +96,7 @@ func (f *format) Run(ctx context.Context, args ...string) error {
|
|||||||
}
|
}
|
||||||
if f.Diff {
|
if f.Diff {
|
||||||
printIt = false
|
printIt = false
|
||||||
u := diff.ToUnified(filename+".orig", filename, lines, ops)
|
u := diff.ToUnified(filename+".orig", filename, string(file.mapper.Content), sedits)
|
||||||
fmt.Print(u)
|
fmt.Print(u)
|
||||||
}
|
}
|
||||||
if printIt {
|
if printIt {
|
||||||
|
32
internal/lsp/diff/hooks.go
Normal file
32
internal/lsp/diff/hooks.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2019 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 supports a pluggable diff algorithm.
|
||||||
|
package diff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TextEdit represents a change to a section of a document.
|
||||||
|
// The text within the specified span should be replaced by the supplied new text.
|
||||||
|
type TextEdit struct {
|
||||||
|
Span span.Span
|
||||||
|
NewText string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ComputeEdits func(uri span.URI, before, after string) []TextEdit
|
||||||
|
ApplyEdits func(before string, edits []TextEdit) string
|
||||||
|
ToUnified func(from, to string, before string, edits []TextEdit) string
|
||||||
|
)
|
||||||
|
|
||||||
|
func SortTextEdits(d []TextEdit) {
|
||||||
|
// Use a stable sort to maintain the order of edits inserted at the same position.
|
||||||
|
sort.SliceStable(d, func(i int, j int) bool {
|
||||||
|
return span.Compare(d[i].Span, d[j].Span) < 0
|
||||||
|
})
|
||||||
|
}
|
80
internal/lsp/diff/myers.go
Normal file
80
internal/lsp/diff/myers.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// Copyright 2019 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 (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/diff/myers"
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ComputeEdits = myersComputeEdits
|
||||||
|
ApplyEdits = myersApplyEdits
|
||||||
|
ToUnified = myersToUnified
|
||||||
|
}
|
||||||
|
|
||||||
|
func myersComputeEdits(uri span.URI, before, after string) []TextEdit {
|
||||||
|
u := myers.SplitLines(before)
|
||||||
|
f := myers.SplitLines(after)
|
||||||
|
return myersDiffToEdits(uri, myers.Operations(u, f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func myersApplyEdits(before string, edits []TextEdit) string {
|
||||||
|
ops := myersEditsToDiff(edits)
|
||||||
|
return strings.Join(myers.ApplyEdits(myers.SplitLines(before), ops), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func myersToUnified(from, to string, before string, edits []TextEdit) string {
|
||||||
|
u := myers.SplitLines(before)
|
||||||
|
ops := myersEditsToDiff(edits)
|
||||||
|
return fmt.Sprint(myers.ToUnified(from, to, u, ops))
|
||||||
|
}
|
||||||
|
|
||||||
|
func myersDiffToEdits(uri span.URI, ops []*myers.Op) []TextEdit {
|
||||||
|
edits := make([]TextEdit, 0, len(ops))
|
||||||
|
for _, op := range ops {
|
||||||
|
s := span.New(uri, span.NewPoint(op.I1+1, 1, 0), span.NewPoint(op.I2+1, 1, 0))
|
||||||
|
switch op.Kind {
|
||||||
|
case myers.Delete:
|
||||||
|
// Delete: unformatted[i1:i2] is deleted.
|
||||||
|
edits = append(edits, TextEdit{Span: s})
|
||||||
|
case myers.Insert:
|
||||||
|
// Insert: formatted[j1:j2] is inserted at unformatted[i1:i1].
|
||||||
|
if content := strings.Join(op.Content, ""); content != "" {
|
||||||
|
edits = append(edits, TextEdit{Span: s, NewText: content})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return edits
|
||||||
|
}
|
||||||
|
|
||||||
|
func myersEditsToDiff(edits []TextEdit) []*myers.Op {
|
||||||
|
iToJ := 0
|
||||||
|
ops := make([]*myers.Op, len(edits))
|
||||||
|
for i, edit := range edits {
|
||||||
|
i1 := edit.Span.Start().Line() - 1
|
||||||
|
i2 := edit.Span.End().Line() - 1
|
||||||
|
kind := myers.Insert
|
||||||
|
if edit.NewText == "" {
|
||||||
|
kind = myers.Delete
|
||||||
|
}
|
||||||
|
ops[i] = &myers.Op{
|
||||||
|
Kind: kind,
|
||||||
|
Content: myers.SplitLines(edit.NewText),
|
||||||
|
I1: i1,
|
||||||
|
I2: i2,
|
||||||
|
J1: i1 + iToJ,
|
||||||
|
}
|
||||||
|
if kind == myers.Insert {
|
||||||
|
iToJ += len(ops[i].Content)
|
||||||
|
} else {
|
||||||
|
iToJ -= i2 - i1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ops
|
||||||
|
}
|
@ -2,8 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Package diff implements the Myers diff algorithm.
|
// Package myers implements the Myers diff algorithm.
|
||||||
package diff
|
package myers
|
||||||
|
|
||||||
import "strings"
|
import "strings"
|
||||||
|
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package diff_test
|
package myers_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
@ -14,7 +14,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/lsp/diff"
|
"golang.org/x/tools/internal/lsp/diff/myers"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -28,22 +28,22 @@ var verifyDiff = flag.Bool("verify-diff", false, "Check that the unified diff ou
|
|||||||
func TestDiff(t *testing.T) {
|
func TestDiff(t *testing.T) {
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
a, b string
|
a, b string
|
||||||
lines []*diff.Op
|
lines []*myers.Op
|
||||||
operations []*diff.Op
|
operations []*myers.Op
|
||||||
unified string
|
unified string
|
||||||
nodiff bool
|
nodiff bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
a: "A\nB\nC\n",
|
a: "A\nB\nC\n",
|
||||||
b: "A\nB\nC\n",
|
b: "A\nB\nC\n",
|
||||||
operations: []*diff.Op{},
|
operations: []*myers.Op{},
|
||||||
unified: `
|
unified: `
|
||||||
`[1:]}, {
|
`[1:]}, {
|
||||||
a: "A\n",
|
a: "A\n",
|
||||||
b: "B\n",
|
b: "B\n",
|
||||||
operations: []*diff.Op{
|
operations: []*myers.Op{
|
||||||
&diff.Op{Kind: diff.Delete, I1: 0, I2: 1, J1: 0},
|
&myers.Op{Kind: myers.Delete, I1: 0, I2: 1, J1: 0},
|
||||||
&diff.Op{Kind: diff.Insert, Content: []string{"B\n"}, I1: 1, I2: 1, J1: 0},
|
&myers.Op{Kind: myers.Insert, Content: []string{"B\n"}, I1: 1, I2: 1, J1: 0},
|
||||||
},
|
},
|
||||||
unified: `
|
unified: `
|
||||||
@@ -1 +1 @@
|
@@ -1 +1 @@
|
||||||
@ -52,9 +52,9 @@ func TestDiff(t *testing.T) {
|
|||||||
`[1:]}, {
|
`[1:]}, {
|
||||||
a: "A",
|
a: "A",
|
||||||
b: "B",
|
b: "B",
|
||||||
operations: []*diff.Op{
|
operations: []*myers.Op{
|
||||||
&diff.Op{Kind: diff.Delete, I1: 0, I2: 1, J1: 0},
|
&myers.Op{Kind: myers.Delete, I1: 0, I2: 1, J1: 0},
|
||||||
&diff.Op{Kind: diff.Insert, Content: []string{"B"}, I1: 1, I2: 1, J1: 0},
|
&myers.Op{Kind: myers.Insert, Content: []string{"B"}, I1: 1, I2: 1, J1: 0},
|
||||||
},
|
},
|
||||||
unified: `
|
unified: `
|
||||||
@@ -1 +1 @@
|
@@ -1 +1 @@
|
||||||
@ -65,12 +65,12 @@ func TestDiff(t *testing.T) {
|
|||||||
`[1:]}, {
|
`[1:]}, {
|
||||||
a: "A\nB\nC\nA\nB\nB\nA\n",
|
a: "A\nB\nC\nA\nB\nB\nA\n",
|
||||||
b: "C\nB\nA\nB\nA\nC\n",
|
b: "C\nB\nA\nB\nA\nC\n",
|
||||||
operations: []*diff.Op{
|
operations: []*myers.Op{
|
||||||
&diff.Op{Kind: diff.Delete, I1: 0, I2: 1, J1: 0},
|
&myers.Op{Kind: myers.Delete, I1: 0, I2: 1, J1: 0},
|
||||||
&diff.Op{Kind: diff.Delete, I1: 1, I2: 2, J1: 0},
|
&myers.Op{Kind: myers.Delete, I1: 1, I2: 2, J1: 0},
|
||||||
&diff.Op{Kind: diff.Insert, Content: []string{"B\n"}, I1: 3, I2: 3, J1: 1},
|
&myers.Op{Kind: myers.Insert, Content: []string{"B\n"}, I1: 3, I2: 3, J1: 1},
|
||||||
&diff.Op{Kind: diff.Delete, I1: 5, I2: 6, J1: 4},
|
&myers.Op{Kind: myers.Delete, I1: 5, I2: 6, J1: 4},
|
||||||
&diff.Op{Kind: diff.Insert, Content: []string{"C\n"}, I1: 7, I2: 7, J1: 5},
|
&myers.Op{Kind: myers.Insert, Content: []string{"C\n"}, I1: 7, I2: 7, J1: 5},
|
||||||
},
|
},
|
||||||
unified: `
|
unified: `
|
||||||
@@ -1,7 +1,6 @@
|
@@ -1,7 +1,6 @@
|
||||||
@ -89,10 +89,10 @@ func TestDiff(t *testing.T) {
|
|||||||
{
|
{
|
||||||
a: "A\nB\n",
|
a: "A\nB\n",
|
||||||
b: "A\nC\n\n",
|
b: "A\nC\n\n",
|
||||||
operations: []*diff.Op{
|
operations: []*myers.Op{
|
||||||
&diff.Op{Kind: diff.Delete, I1: 1, I2: 2, J1: 1},
|
&myers.Op{Kind: myers.Delete, I1: 1, I2: 2, J1: 1},
|
||||||
&diff.Op{Kind: diff.Insert, Content: []string{"C\n"}, I1: 2, I2: 2, J1: 1},
|
&myers.Op{Kind: myers.Insert, Content: []string{"C\n"}, I1: 2, I2: 2, J1: 1},
|
||||||
&diff.Op{Kind: diff.Insert, Content: []string{"\n"}, I1: 2, I2: 2, J1: 2},
|
&myers.Op{Kind: myers.Insert, Content: []string{"\n"}, I1: 2, I2: 2, J1: 2},
|
||||||
},
|
},
|
||||||
unified: `
|
unified: `
|
||||||
@@ -1,2 +1,3 @@
|
@@ -1,2 +1,3 @@
|
||||||
@ -120,9 +120,9 @@ func TestDiff(t *testing.T) {
|
|||||||
+K
|
+K
|
||||||
`[1:]},
|
`[1:]},
|
||||||
} {
|
} {
|
||||||
a := diff.SplitLines(test.a)
|
a := myers.SplitLines(test.a)
|
||||||
b := diff.SplitLines(test.b)
|
b := myers.SplitLines(test.b)
|
||||||
ops := diff.Operations(a, b)
|
ops := myers.Operations(a, b)
|
||||||
if test.operations != nil {
|
if test.operations != nil {
|
||||||
if len(ops) != len(test.operations) {
|
if len(ops) != len(test.operations) {
|
||||||
t.Fatalf("expected %v operations, got %v", len(test.operations), len(ops))
|
t.Fatalf("expected %v operations, got %v", len(test.operations), len(ops))
|
||||||
@ -134,7 +134,7 @@ func TestDiff(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
applied := diff.ApplyEdits(a, ops)
|
applied := myers.ApplyEdits(a, ops)
|
||||||
for i, want := range applied {
|
for i, want := range applied {
|
||||||
got := b[i]
|
got := b[i]
|
||||||
if got != want {
|
if got != want {
|
||||||
@ -142,7 +142,7 @@ func TestDiff(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if test.unified != "" {
|
if test.unified != "" {
|
||||||
diff := diff.ToUnified(fileA, fileB, a, ops)
|
diff := myers.ToUnified(fileA, fileB, a, ops)
|
||||||
got := fmt.Sprint(diff)
|
got := fmt.Sprint(diff)
|
||||||
if !strings.HasPrefix(got, unifiedPrefix) {
|
if !strings.HasPrefix(got, unifiedPrefix) {
|
||||||
t.Errorf("expected prefix:\n%s\ngot:\n%s", unifiedPrefix, got)
|
t.Errorf("expected prefix:\n%s\ngot:\n%s", unifiedPrefix, got)
|
||||||
@ -166,7 +166,7 @@ func TestDiff(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getDiffOutput(a, b string) (string, error) {
|
func getDiffOutput(a, b string) (string, error) {
|
||||||
fileA, err := ioutil.TempFile("", "diff.in")
|
fileA, err := ioutil.TempFile("", "myers.in")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -177,7 +177,7 @@ func getDiffOutput(a, b string) (string, error) {
|
|||||||
if err := fileA.Close(); err != nil {
|
if err := fileA.Close(); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
fileB, err := ioutil.TempFile("", "diff.in")
|
fileB, err := ioutil.TempFile("", "myers.in")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package diff
|
package myers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -7,6 +7,7 @@ package lsp
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
@ -51,7 +52,7 @@ func spanToRange(ctx context.Context, view source.View, spn span.Span) (source.G
|
|||||||
return f, m, rng, nil
|
return f, m, rng, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToProtocolEdits(m *protocol.ColumnMapper, edits []source.TextEdit) ([]protocol.TextEdit, error) {
|
func ToProtocolEdits(m *protocol.ColumnMapper, edits []diff.TextEdit) ([]protocol.TextEdit, error) {
|
||||||
if edits == nil {
|
if edits == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -69,17 +70,17 @@ func ToProtocolEdits(m *protocol.ColumnMapper, edits []source.TextEdit) ([]proto
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromProtocolEdits(m *protocol.ColumnMapper, edits []protocol.TextEdit) ([]source.TextEdit, error) {
|
func FromProtocolEdits(m *protocol.ColumnMapper, edits []protocol.TextEdit) ([]diff.TextEdit, error) {
|
||||||
if edits == nil {
|
if edits == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
result := make([]source.TextEdit, len(edits))
|
result := make([]diff.TextEdit, len(edits))
|
||||||
for i, edit := range edits {
|
for i, edit := range edits {
|
||||||
spn, err := m.RangeSpan(edit.Range)
|
spn, err := m.RangeSpan(edit.Range)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result[i] = source.TextEdit{
|
result[i] = diff.TextEdit{
|
||||||
Span: spn,
|
Span: spn,
|
||||||
NewText: edit.NewText,
|
NewText: edit.NewText,
|
||||||
}
|
}
|
||||||
|
@ -287,8 +287,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
ops := source.EditsToDiff(sedits)
|
got := diff.ApplyEdits(string(m.Content), sedits)
|
||||||
got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(m.Content)), ops), "")
|
|
||||||
if gofmted != got {
|
if gofmted != got {
|
||||||
t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
|
t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
|
||||||
}
|
}
|
||||||
@ -334,8 +333,7 @@ func (r *runner) Import(t *testing.T, data tests.Imports) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
ops := source.EditsToDiff(sedits)
|
got := diff.ApplyEdits(string(m.Content), sedits)
|
||||||
got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(m.Content)), ops), "")
|
|
||||||
if goimported != got {
|
if goimported != got {
|
||||||
t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got)
|
t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got)
|
||||||
}
|
}
|
||||||
@ -549,7 +547,7 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyEdits(contents string, edits []source.TextEdit) string {
|
func applyEdits(contents string, edits []diff.TextEdit) string {
|
||||||
res := contents
|
res := contents
|
||||||
|
|
||||||
// Apply the edits from the end of the file forward
|
// Apply the edits from the end of the file forward
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
"golang.org/x/tools/internal/imports"
|
"golang.org/x/tools/internal/imports"
|
||||||
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
"golang.org/x/tools/internal/lsp/fuzzy"
|
"golang.org/x/tools/internal/lsp/fuzzy"
|
||||||
"golang.org/x/tools/internal/lsp/snippet"
|
"golang.org/x/tools/internal/lsp/snippet"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
@ -41,7 +42,7 @@ type CompletionItem struct {
|
|||||||
// Additional text edits should be used to change text unrelated to the current cursor position
|
// Additional text edits should be used to change text unrelated to the current cursor position
|
||||||
// (for example adding an import statement at the top of the file if the completion item will
|
// (for example adding an import statement at the top of the file if the completion item will
|
||||||
// insert an unqualified type).
|
// insert an unqualified type).
|
||||||
AdditionalTextEdits []TextEdit
|
AdditionalTextEdits []diff.TextEdit
|
||||||
|
|
||||||
// Depth is how many levels were searched to find this completion.
|
// Depth is how many levels were searched to find this completion.
|
||||||
// For example when completing "foo<>", "fooBar" is depth 0, and
|
// For example when completing "foo<>", "fooBar" is depth 0, and
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"go/types"
|
"go/types"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
"golang.org/x/tools/internal/lsp/snippet"
|
"golang.org/x/tools/internal/lsp/snippet"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
"golang.org/x/tools/internal/telemetry/log"
|
"golang.org/x/tools/internal/telemetry/log"
|
||||||
@ -35,7 +36,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
|||||||
kind CompletionItemKind
|
kind CompletionItemKind
|
||||||
plainSnippet *snippet.Builder
|
plainSnippet *snippet.Builder
|
||||||
placeholderSnippet *snippet.Builder
|
placeholderSnippet *snippet.Builder
|
||||||
addlEdits []TextEdit
|
addlEdits []diff.TextEdit
|
||||||
)
|
)
|
||||||
|
|
||||||
// expandFuncCall mutates the completion label, detail, and snippets
|
// expandFuncCall mutates the completion label, detail, and snippets
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
"golang.org/x/tools/go/analysis/passes/unsafeptr"
|
"golang.org/x/tools/go/analysis/passes/unsafeptr"
|
||||||
"golang.org/x/tools/go/analysis/passes/unusedresult"
|
"golang.org/x/tools/go/analysis/passes/unusedresult"
|
||||||
"golang.org/x/tools/go/packages"
|
"golang.org/x/tools/go/packages"
|
||||||
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/lsp/telemetry"
|
"golang.org/x/tools/internal/lsp/telemetry"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
@ -55,7 +56,7 @@ type Diagnostic struct {
|
|||||||
|
|
||||||
type SuggestedFixes struct {
|
type SuggestedFixes struct {
|
||||||
Title string
|
Title string
|
||||||
Edits []TextEdit
|
Edits []diff.TextEdit
|
||||||
}
|
}
|
||||||
|
|
||||||
type DiagnosticSeverity int
|
type DiagnosticSeverity int
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Format formats a file with a given range.
|
// Format formats a file with a given range.
|
||||||
func Format(ctx context.Context, f GoFile, rng span.Range) ([]TextEdit, error) {
|
func Format(ctx context.Context, f GoFile, rng span.Range) ([]diff.TextEdit, error) {
|
||||||
ctx, done := trace.StartSpan(ctx, "source.Format")
|
ctx, done := trace.StartSpan(ctx, "source.Format")
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ func formatSource(ctx context.Context, file File) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Imports formats a file using the goimports tool.
|
// Imports formats a file using the goimports tool.
|
||||||
func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]TextEdit, error) {
|
func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]diff.TextEdit, error) {
|
||||||
ctx, done := trace.StartSpan(ctx, "source.Imports")
|
ctx, done := trace.StartSpan(ctx, "source.Imports")
|
||||||
defer done()
|
defer done()
|
||||||
data, _, err := f.Handle(ctx).Read(ctx)
|
data, _, err := f.Handle(ctx).Read(ctx)
|
||||||
@ -112,14 +112,14 @@ func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]TextEd
|
|||||||
|
|
||||||
type ImportFix struct {
|
type ImportFix struct {
|
||||||
Fix *imports.ImportFix
|
Fix *imports.ImportFix
|
||||||
Edits []TextEdit
|
Edits []diff.TextEdit
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllImportsFixes formats f for each possible fix to the imports.
|
// AllImportsFixes formats f for each possible fix to the imports.
|
||||||
// In addition to returning the result of applying all edits,
|
// In addition to returning the result of applying all edits,
|
||||||
// it returns a list of fixes that could be applied to the file, with the
|
// it returns a list of fixes that could be applied to the file, with the
|
||||||
// corresponding TextEdits that would be needed to apply that fix.
|
// corresponding TextEdits that would be needed to apply that fix.
|
||||||
func AllImportsFixes(ctx context.Context, view View, f GoFile, rng span.Range) (edits []TextEdit, editsPerFix []*ImportFix, err error) {
|
func AllImportsFixes(ctx context.Context, view View, f GoFile, rng span.Range) (edits []diff.TextEdit, editsPerFix []*ImportFix, err error) {
|
||||||
ctx, done := trace.StartSpan(ctx, "source.AllImportsFixes")
|
ctx, done := trace.StartSpan(ctx, "source.AllImportsFixes")
|
||||||
defer done()
|
defer done()
|
||||||
data, _, err := f.Handle(ctx).Read(ctx)
|
data, _, err := f.Handle(ctx).Read(ctx)
|
||||||
@ -224,7 +224,7 @@ func hasListErrors(errors []packages.Error) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeTextEdits(ctx context.Context, file File, formatted string) (edits []TextEdit) {
|
func computeTextEdits(ctx context.Context, file File, formatted string) (edits []diff.TextEdit) {
|
||||||
ctx, done := trace.StartSpan(ctx, "source.computeTextEdits")
|
ctx, done := trace.StartSpan(ctx, "source.computeTextEdits")
|
||||||
defer done()
|
defer done()
|
||||||
data, _, err := file.Handle(ctx).Read(ctx)
|
data, _, err := file.Handle(ctx).Read(ctx)
|
||||||
@ -232,7 +232,5 @@ func computeTextEdits(ctx context.Context, file File, formatted string) (edits [
|
|||||||
log.Error(ctx, "Cannot compute text edits", err)
|
log.Error(ctx, "Cannot compute text edits", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
u := diff.SplitLines(string(data))
|
return diff.ComputeEdits(file.URI(), string(data), formatted)
|
||||||
f := diff.SplitLines(formatted)
|
|
||||||
return DiffToEdits(file.URI(), diff.Operations(u, f))
|
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ import (
|
|||||||
// import pathpkg "path"
|
// import pathpkg "path"
|
||||||
//
|
//
|
||||||
// addNamedImport only returns edits that affect the import declarations.
|
// addNamedImport only returns edits that affect the import declarations.
|
||||||
func addNamedImport(fset *token.FileSet, f *ast.File, name, path string) (edits []TextEdit, err error) {
|
func addNamedImport(fset *token.FileSet, f *ast.File, name, path string) (edits []diff.TextEdit, err error) {
|
||||||
if alreadyImports(f, name, path) {
|
if alreadyImports(f, name, path) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -178,7 +179,7 @@ func addNamedImport(fset *token.FileSet, f *ast.File, name, path string) (edits
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
edits = append(edits, TextEdit{
|
edits = append(edits, diff.TextEdit{
|
||||||
Span: spn,
|
Span: spn,
|
||||||
NewText: newText,
|
NewText: newText,
|
||||||
})
|
})
|
||||||
|
@ -8,6 +8,8 @@ import (
|
|||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
)
|
)
|
||||||
|
|
||||||
var fset = token.NewFileSet()
|
var fset = token.NewFileSet()
|
||||||
@ -125,7 +127,7 @@ package main // Here is a comment after`,
|
|||||||
name: "package statement multiline comments",
|
name: "package statement multiline comments",
|
||||||
pkg: "os",
|
pkg: "os",
|
||||||
in: `package main /* This is a multiline comment
|
in: `package main /* This is a multiline comment
|
||||||
and it extends
|
and it extends
|
||||||
further down*/`,
|
further down*/`,
|
||||||
want: []importInfo{
|
want: []importInfo{
|
||||||
importInfo{
|
importInfo{
|
||||||
@ -137,7 +139,7 @@ further down*/`,
|
|||||||
{
|
{
|
||||||
name: "import c",
|
name: "import c",
|
||||||
pkg: "os",
|
pkg: "os",
|
||||||
in: `package main
|
in: `package main
|
||||||
|
|
||||||
import "C"
|
import "C"
|
||||||
`,
|
`,
|
||||||
@ -155,7 +157,7 @@ import "C"
|
|||||||
{
|
{
|
||||||
name: "existing imports",
|
name: "existing imports",
|
||||||
pkg: "os",
|
pkg: "os",
|
||||||
in: `package main
|
in: `package main
|
||||||
|
|
||||||
import "io"
|
import "io"
|
||||||
`,
|
`,
|
||||||
@ -173,7 +175,7 @@ import "io"
|
|||||||
{
|
{
|
||||||
name: "existing imports with comment",
|
name: "existing imports with comment",
|
||||||
pkg: "os",
|
pkg: "os",
|
||||||
in: `package main
|
in: `package main
|
||||||
|
|
||||||
import "io" // A comment
|
import "io" // A comment
|
||||||
`,
|
`,
|
||||||
@ -191,7 +193,7 @@ import "io" // A comment
|
|||||||
{
|
{
|
||||||
name: "existing imports multiline comment",
|
name: "existing imports multiline comment",
|
||||||
pkg: "os",
|
pkg: "os",
|
||||||
in: `package main
|
in: `package main
|
||||||
|
|
||||||
import "io" /* A comment
|
import "io" /* A comment
|
||||||
that
|
that
|
||||||
@ -212,7 +214,7 @@ extends */
|
|||||||
name: "renamed import",
|
name: "renamed import",
|
||||||
renamedPkg: "o",
|
renamedPkg: "o",
|
||||||
pkg: "os",
|
pkg: "os",
|
||||||
in: `package main
|
in: `package main
|
||||||
`,
|
`,
|
||||||
want: []importInfo{
|
want: []importInfo{
|
||||||
importInfo{
|
importInfo{
|
||||||
@ -314,7 +316,7 @@ func compareImports(t *testing.T, prefix string, got []*ast.ImportSpec, want []i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyEdits(contents string, edits []TextEdit) string {
|
func applyEdits(contents string, edits []diff.TextEdit) string {
|
||||||
res := contents
|
res := contents
|
||||||
|
|
||||||
// Apply the edits from the end of the file forward
|
// Apply the edits from the end of the file forward
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"golang.org/x/tools/go/types/typeutil"
|
"golang.org/x/tools/go/types/typeutil"
|
||||||
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
"golang.org/x/tools/internal/telemetry/trace"
|
"golang.org/x/tools/internal/telemetry/trace"
|
||||||
"golang.org/x/tools/refactor/satisfy"
|
"golang.org/x/tools/refactor/satisfy"
|
||||||
@ -35,7 +36,7 @@ type renamer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package.
|
// Rename returns a map of TextEdits for each file modified when renaming a given identifier within a package.
|
||||||
func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.URI][]TextEdit, error) {
|
func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.URI][]diff.TextEdit, error) {
|
||||||
ctx, done := trace.StartSpan(ctx, "source.Rename")
|
ctx, done := trace.StartSpan(ctx, "source.Rename")
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
@ -93,14 +94,14 @@ func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.U
|
|||||||
|
|
||||||
// Sort edits for each file.
|
// Sort edits for each file.
|
||||||
for _, edits := range changes {
|
for _, edits := range changes {
|
||||||
sortTextEdits(edits)
|
diff.SortTextEdits(edits)
|
||||||
}
|
}
|
||||||
return changes, nil
|
return changes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename all references to the identifier.
|
// Rename all references to the identifier.
|
||||||
func (r *renamer) update() (map[span.URI][]TextEdit, error) {
|
func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) {
|
||||||
result := make(map[span.URI][]TextEdit)
|
result := make(map[span.URI][]diff.TextEdit)
|
||||||
seen := make(map[span.Span]bool)
|
seen := make(map[span.Span]bool)
|
||||||
|
|
||||||
docRegexp, err := regexp.Compile(`\b` + r.from + `\b`)
|
docRegexp, err := regexp.Compile(`\b` + r.from + `\b`)
|
||||||
@ -129,7 +130,7 @@ func (r *renamer) update() (map[span.URI][]TextEdit, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replace the identifier with r.to.
|
// Replace the identifier with r.to.
|
||||||
edit := TextEdit{
|
edit := diff.TextEdit{
|
||||||
Span: refSpan,
|
Span: refSpan,
|
||||||
NewText: r.to,
|
NewText: r.to,
|
||||||
}
|
}
|
||||||
@ -153,7 +154,7 @@ func (r *renamer) update() (map[span.URI][]TextEdit, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result[spn.URI()] = append(result[spn.URI()], TextEdit{
|
result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
|
||||||
Span: spn,
|
Span: spn,
|
||||||
NewText: r.to,
|
NewText: r.to,
|
||||||
})
|
})
|
||||||
@ -194,7 +195,7 @@ func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// updatePkgName returns the updates to rename a pkgName in the import spec
|
// updatePkgName returns the updates to rename a pkgName in the import spec
|
||||||
func (r *renamer) updatePkgName(pkgName *types.PkgName) (*TextEdit, error) {
|
func (r *renamer) updatePkgName(pkgName *types.PkgName) (*diff.TextEdit, error) {
|
||||||
// Modify ImportSpec syntax to add or remove the Name as needed.
|
// Modify ImportSpec syntax to add or remove the Name as needed.
|
||||||
pkg := r.packages[pkgName.Pkg()]
|
pkg := r.packages[pkgName.Pkg()]
|
||||||
_, path, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, pkgName.Pos(), pkgName.Pos())
|
_, path, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, pkgName.Pos(), pkgName.Pos())
|
||||||
@ -229,7 +230,7 @@ func (r *renamer) updatePkgName(pkgName *types.PkgName) (*TextEdit, error) {
|
|||||||
format.Node(&buf, r.fset, updated)
|
format.Node(&buf, r.fset, updated)
|
||||||
newText := buf.String()
|
newText := buf.String()
|
||||||
|
|
||||||
return &TextEdit{
|
return &diff.TextEdit{
|
||||||
Span: spn,
|
Span: spn,
|
||||||
NewText: newText,
|
NewText: newText,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -289,13 +289,12 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ops := source.EditsToDiff(edits)
|
|
||||||
data, _, err := f.Handle(ctx).Read(ctx)
|
data, _, err := f.Handle(ctx).Read(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(data)), ops), "")
|
got := diff.ApplyEdits(string(data), edits)
|
||||||
if gofmted != got {
|
if gofmted != got {
|
||||||
t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
|
t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
|
||||||
}
|
}
|
||||||
@ -331,13 +330,12 @@ func (r *runner) Import(t *testing.T, data tests.Imports) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ops := source.EditsToDiff(edits)
|
|
||||||
data, _, err := f.Handle(ctx).Read(ctx)
|
data, _, err := f.Handle(ctx).Read(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(data)), ops), "")
|
got := diff.ApplyEdits(string(data), edits)
|
||||||
if goimported != got {
|
if goimported != got {
|
||||||
t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got)
|
t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got)
|
||||||
}
|
}
|
||||||
@ -538,7 +536,7 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyEdits(contents string, edits []source.TextEdit) string {
|
func applyEdits(contents string, edits []diff.TextEdit) string {
|
||||||
res := contents
|
res := contents
|
||||||
|
|
||||||
// Apply the edits from the end of the file forward
|
// Apply the edits from the end of the file forward
|
||||||
|
@ -2,7 +2,9 @@ package source
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,7 +18,7 @@ func getCodeActions(fset *token.FileSet, diag analysis.Diagnostic) ([]SuggestedF
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ca.Edits = append(ca.Edits, TextEdit{span, string(te.NewText)})
|
ca.Edits = append(ca.Edits, diff.TextEdit{Span: span, NewText: string(te.NewText)})
|
||||||
}
|
}
|
||||||
cas = append(cas, ca)
|
cas = append(cas, ca)
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,10 @@ import (
|
|||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/analysis"
|
"golang.org/x/tools/go/analysis"
|
||||||
"golang.org/x/tools/go/packages"
|
"golang.org/x/tools/go/packages"
|
||||||
"golang.org/x/tools/internal/imports"
|
"golang.org/x/tools/internal/imports"
|
||||||
"golang.org/x/tools/internal/lsp/diff"
|
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -304,63 +301,3 @@ type Package interface {
|
|||||||
// GetActionGraph returns the action graph for the given package.
|
// GetActionGraph returns the action graph for the given package.
|
||||||
GetActionGraph(ctx context.Context, a *analysis.Analyzer) (*Action, error)
|
GetActionGraph(ctx context.Context, a *analysis.Analyzer) (*Action, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextEdit represents a change to a section of a document.
|
|
||||||
// The text within the specified span should be replaced by the supplied new text.
|
|
||||||
type TextEdit struct {
|
|
||||||
Span span.Span
|
|
||||||
NewText string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DiffToEdits converts from a sequence of diff operations to a sequence of
|
|
||||||
// source.TextEdit
|
|
||||||
func DiffToEdits(uri span.URI, ops []*diff.Op) []TextEdit {
|
|
||||||
edits := make([]TextEdit, 0, len(ops))
|
|
||||||
for _, op := range ops {
|
|
||||||
s := span.New(uri, span.NewPoint(op.I1+1, 1, 0), span.NewPoint(op.I2+1, 1, 0))
|
|
||||||
switch op.Kind {
|
|
||||||
case diff.Delete:
|
|
||||||
// Delete: unformatted[i1:i2] is deleted.
|
|
||||||
edits = append(edits, TextEdit{Span: s})
|
|
||||||
case diff.Insert:
|
|
||||||
// Insert: formatted[j1:j2] is inserted at unformatted[i1:i1].
|
|
||||||
if content := strings.Join(op.Content, ""); content != "" {
|
|
||||||
edits = append(edits, TextEdit{Span: s, NewText: content})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return edits
|
|
||||||
}
|
|
||||||
|
|
||||||
func EditsToDiff(edits []TextEdit) []*diff.Op {
|
|
||||||
iToJ := 0
|
|
||||||
ops := make([]*diff.Op, len(edits))
|
|
||||||
for i, edit := range edits {
|
|
||||||
i1 := edit.Span.Start().Line() - 1
|
|
||||||
i2 := edit.Span.End().Line() - 1
|
|
||||||
kind := diff.Insert
|
|
||||||
if edit.NewText == "" {
|
|
||||||
kind = diff.Delete
|
|
||||||
}
|
|
||||||
ops[i] = &diff.Op{
|
|
||||||
Kind: kind,
|
|
||||||
Content: diff.SplitLines(edit.NewText),
|
|
||||||
I1: i1,
|
|
||||||
I2: i2,
|
|
||||||
J1: i1 + iToJ,
|
|
||||||
}
|
|
||||||
if kind == diff.Insert {
|
|
||||||
iToJ += len(ops[i].Content)
|
|
||||||
} else {
|
|
||||||
iToJ -= i2 - i1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ops
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortTextEdits(d []TextEdit) {
|
|
||||||
// Use a stable sort to maintain the order of edits inserted at the same position.
|
|
||||||
sort.SliceStable(d, func(i int, j int) bool {
|
|
||||||
return span.Compare(d[i].Span, d[j].Span) < 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user