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>
diff --git a/internal/lsp/cmd/format.go b/internal/lsp/cmd/format.go
index 93d041d..bbe9f7a 100644
--- a/internal/lsp/cmd/format.go
+++ b/internal/lsp/cmd/format.go
@@ -9,12 +9,10 @@
"flag"
"fmt"
"io/ioutil"
- "strings"
"golang.org/x/tools/internal/lsp"
"golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/lsp/protocol"
- "golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
)
@@ -82,9 +80,7 @@
if err != nil {
return errors.Errorf("%v: %v", spn, err)
}
- ops := source.EditsToDiff(sedits)
- lines := diff.SplitLines(string(file.mapper.Content))
- formatted := strings.Join(diff.ApplyEdits(lines, ops), "")
+ formatted := diff.ApplyEdits(string(file.mapper.Content), sedits)
printIt := true
if f.List {
printIt = false
@@ -100,7 +96,7 @@
}
if f.Diff {
printIt = false
- u := diff.ToUnified(filename+".orig", filename, lines, ops)
+ u := diff.ToUnified(filename+".orig", filename, string(file.mapper.Content), sedits)
fmt.Print(u)
}
if printIt {
diff --git a/internal/lsp/diff/hooks.go b/internal/lsp/diff/hooks.go
new file mode 100644
index 0000000..16c51d9
--- /dev/null
+++ b/internal/lsp/diff/hooks.go
@@ -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
+ })
+}
diff --git a/internal/lsp/diff/myers.go b/internal/lsp/diff/myers.go
new file mode 100644
index 0000000..eab3e2c
--- /dev/null
+++ b/internal/lsp/diff/myers.go
@@ -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
+}
diff --git a/internal/lsp/diff/diff.go b/internal/lsp/diff/myers/diff.go
similarity index 98%
rename from internal/lsp/diff/diff.go
rename to internal/lsp/diff/myers/diff.go
index cf7158d..ddf04ca 100644
--- a/internal/lsp/diff/diff.go
+++ b/internal/lsp/diff/myers/diff.go
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Package diff implements the Myers diff algorithm.
-package diff
+// Package myers implements the Myers diff algorithm.
+package myers
import "strings"
diff --git a/internal/lsp/diff/diff_test.go b/internal/lsp/diff/myers/diff_test.go
similarity index 70%
rename from internal/lsp/diff/diff_test.go
rename to internal/lsp/diff/myers/diff_test.go
index 1fe4292..f60f73b 100644
--- a/internal/lsp/diff/diff_test.go
+++ b/internal/lsp/diff/myers/diff_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package diff_test
+package myers_test
import (
"flag"
@@ -14,7 +14,7 @@
"strings"
"testing"
- "golang.org/x/tools/internal/lsp/diff"
+ "golang.org/x/tools/internal/lsp/diff/myers"
)
const (
@@ -28,22 +28,22 @@
func TestDiff(t *testing.T) {
for _, test := range []struct {
a, b string
- lines []*diff.Op
- operations []*diff.Op
+ lines []*myers.Op
+ operations []*myers.Op
unified string
nodiff bool
}{
{
a: "A\nB\nC\n",
b: "A\nB\nC\n",
- operations: []*diff.Op{},
+ operations: []*myers.Op{},
unified: `
`[1:]}, {
a: "A\n",
b: "B\n",
- operations: []*diff.Op{
- &diff.Op{Kind: diff.Delete, I1: 0, I2: 1, J1: 0},
- &diff.Op{Kind: diff.Insert, Content: []string{"B\n"}, I1: 1, I2: 1, J1: 0},
+ operations: []*myers.Op{
+ &myers.Op{Kind: myers.Delete, I1: 0, I2: 1, J1: 0},
+ &myers.Op{Kind: myers.Insert, Content: []string{"B\n"}, I1: 1, I2: 1, J1: 0},
},
unified: `
@@ -1 +1 @@
@@ -52,9 +52,9 @@
`[1:]}, {
a: "A",
b: "B",
- operations: []*diff.Op{
- &diff.Op{Kind: diff.Delete, I1: 0, I2: 1, J1: 0},
- &diff.Op{Kind: diff.Insert, Content: []string{"B"}, I1: 1, I2: 1, J1: 0},
+ operations: []*myers.Op{
+ &myers.Op{Kind: myers.Delete, I1: 0, I2: 1, J1: 0},
+ &myers.Op{Kind: myers.Insert, Content: []string{"B"}, I1: 1, I2: 1, J1: 0},
},
unified: `
@@ -1 +1 @@
@@ -65,12 +65,12 @@
`[1:]}, {
a: "A\nB\nC\nA\nB\nB\nA\n",
b: "C\nB\nA\nB\nA\nC\n",
- operations: []*diff.Op{
- &diff.Op{Kind: diff.Delete, I1: 0, I2: 1, J1: 0},
- &diff.Op{Kind: diff.Delete, I1: 1, I2: 2, J1: 0},
- &diff.Op{Kind: diff.Insert, Content: []string{"B\n"}, I1: 3, I2: 3, J1: 1},
- &diff.Op{Kind: diff.Delete, I1: 5, I2: 6, J1: 4},
- &diff.Op{Kind: diff.Insert, Content: []string{"C\n"}, I1: 7, I2: 7, J1: 5},
+ operations: []*myers.Op{
+ &myers.Op{Kind: myers.Delete, I1: 0, I2: 1, J1: 0},
+ &myers.Op{Kind: myers.Delete, I1: 1, I2: 2, J1: 0},
+ &myers.Op{Kind: myers.Insert, Content: []string{"B\n"}, I1: 3, I2: 3, J1: 1},
+ &myers.Op{Kind: myers.Delete, I1: 5, I2: 6, J1: 4},
+ &myers.Op{Kind: myers.Insert, Content: []string{"C\n"}, I1: 7, I2: 7, J1: 5},
},
unified: `
@@ -1,7 +1,6 @@
@@ -89,10 +89,10 @@
{
a: "A\nB\n",
b: "A\nC\n\n",
- operations: []*diff.Op{
- &diff.Op{Kind: diff.Delete, I1: 1, I2: 2, J1: 1},
- &diff.Op{Kind: diff.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},
+ operations: []*myers.Op{
+ &myers.Op{Kind: myers.Delete, I1: 1, I2: 2, J1: 1},
+ &myers.Op{Kind: myers.Insert, Content: []string{"C\n"}, I1: 2, I2: 2, J1: 1},
+ &myers.Op{Kind: myers.Insert, Content: []string{"\n"}, I1: 2, I2: 2, J1: 2},
},
unified: `
@@ -1,2 +1,3 @@
@@ -120,9 +120,9 @@
+K
`[1:]},
} {
- a := diff.SplitLines(test.a)
- b := diff.SplitLines(test.b)
- ops := diff.Operations(a, b)
+ a := myers.SplitLines(test.a)
+ b := myers.SplitLines(test.b)
+ ops := myers.Operations(a, b)
if test.operations != nil {
if len(ops) != len(test.operations) {
t.Fatalf("expected %v operations, got %v", len(test.operations), len(ops))
@@ -134,7 +134,7 @@
}
}
}
- applied := diff.ApplyEdits(a, ops)
+ applied := myers.ApplyEdits(a, ops)
for i, want := range applied {
got := b[i]
if got != want {
@@ -142,7 +142,7 @@
}
}
if test.unified != "" {
- diff := diff.ToUnified(fileA, fileB, a, ops)
+ diff := myers.ToUnified(fileA, fileB, a, ops)
got := fmt.Sprint(diff)
if !strings.HasPrefix(got, unifiedPrefix) {
t.Errorf("expected prefix:\n%s\ngot:\n%s", unifiedPrefix, got)
@@ -166,7 +166,7 @@
}
func getDiffOutput(a, b string) (string, error) {
- fileA, err := ioutil.TempFile("", "diff.in")
+ fileA, err := ioutil.TempFile("", "myers.in")
if err != nil {
return "", err
}
@@ -177,7 +177,7 @@
if err := fileA.Close(); err != nil {
return "", err
}
- fileB, err := ioutil.TempFile("", "diff.in")
+ fileB, err := ioutil.TempFile("", "myers.in")
if err != nil {
return "", err
}
diff --git a/internal/lsp/diff/unified.go b/internal/lsp/diff/myers/unified.go
similarity index 99%
rename from internal/lsp/diff/unified.go
rename to internal/lsp/diff/myers/unified.go
index 427a871..8339b10 100644
--- a/internal/lsp/diff/unified.go
+++ b/internal/lsp/diff/myers/unified.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package diff
+package myers
import (
"fmt"
diff --git a/internal/lsp/format.go b/internal/lsp/format.go
index 6e5f400..95c9fd3 100644
--- a/internal/lsp/format.go
+++ b/internal/lsp/format.go
@@ -7,6 +7,7 @@
import (
"context"
+ "golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
@@ -51,7 +52,7 @@
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 {
return nil, nil
}
@@ -69,17 +70,17 @@
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 {
return nil, nil
}
- result := make([]source.TextEdit, len(edits))
+ result := make([]diff.TextEdit, len(edits))
for i, edit := range edits {
spn, err := m.RangeSpan(edit.Range)
if err != nil {
return nil, err
}
- result[i] = source.TextEdit{
+ result[i] = diff.TextEdit{
Span: spn,
NewText: edit.NewText,
}
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 248f293..59dbe0b 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -287,8 +287,7 @@
if err != nil {
t.Error(err)
}
- ops := source.EditsToDiff(sedits)
- got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(m.Content)), ops), "")
+ got := diff.ApplyEdits(string(m.Content), sedits)
if gofmted != got {
t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
}
@@ -334,8 +333,7 @@
if err != nil {
t.Error(err)
}
- ops := source.EditsToDiff(sedits)
- got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(m.Content)), ops), "")
+ got := diff.ApplyEdits(string(m.Content), sedits)
if goimported != got {
t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got)
}
@@ -549,7 +547,7 @@
}
}
-func applyEdits(contents string, edits []source.TextEdit) string {
+func applyEdits(contents string, edits []diff.TextEdit) string {
res := contents
// Apply the edits from the end of the file forward
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index 762195b..4506ce0 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -13,6 +13,7 @@
"golang.org/x/tools/go/ast/astutil"
"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/snippet"
"golang.org/x/tools/internal/span"
@@ -41,7 +42,7 @@
// 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
// insert an unqualified type).
- AdditionalTextEdits []TextEdit
+ AdditionalTextEdits []diff.TextEdit
// Depth is how many levels were searched to find this completion.
// For example when completing "foo<>", "fooBar" is depth 0, and
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
index 3d02feb..115120d 100644
--- a/internal/lsp/source/completion_format.go
+++ b/internal/lsp/source/completion_format.go
@@ -13,6 +13,7 @@
"go/types"
"strings"
+ "golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/lsp/snippet"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/log"
@@ -35,7 +36,7 @@
kind CompletionItemKind
plainSnippet *snippet.Builder
placeholderSnippet *snippet.Builder
- addlEdits []TextEdit
+ addlEdits []diff.TextEdit
)
// expandFuncCall mutates the completion label, detail, and snippets
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index 442ae78..5829209 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -35,6 +35,7 @@
"golang.org/x/tools/go/analysis/passes/unsafeptr"
"golang.org/x/tools/go/analysis/passes/unusedresult"
"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/telemetry"
"golang.org/x/tools/internal/span"
@@ -55,7 +56,7 @@
type SuggestedFixes struct {
Title string
- Edits []TextEdit
+ Edits []diff.TextEdit
}
type DiagnosticSeverity int
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index 9141869..a1448cd 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -21,7 +21,7 @@
)
// 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")
defer done()
@@ -74,7 +74,7 @@
}
// 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")
defer done()
data, _, err := f.Handle(ctx).Read(ctx)
@@ -112,14 +112,14 @@
type ImportFix struct {
Fix *imports.ImportFix
- Edits []TextEdit
+ Edits []diff.TextEdit
}
// AllImportsFixes formats f for each possible fix to the imports.
// 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
// 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")
defer done()
data, _, err := f.Handle(ctx).Read(ctx)
@@ -224,7 +224,7 @@
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")
defer done()
data, _, err := file.Handle(ctx).Read(ctx)
@@ -232,7 +232,5 @@
log.Error(ctx, "Cannot compute text edits", err)
return nil
}
- u := diff.SplitLines(string(data))
- f := diff.SplitLines(formatted)
- return DiffToEdits(file.URI(), diff.Operations(u, f))
+ return diff.ComputeEdits(file.URI(), string(data), formatted)
}
diff --git a/internal/lsp/source/imports.go b/internal/lsp/source/imports.go
index 1e80d10..22121d6 100644
--- a/internal/lsp/source/imports.go
+++ b/internal/lsp/source/imports.go
@@ -13,6 +13,7 @@
"strconv"
"strings"
+ "golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/span"
)
@@ -35,7 +36,7 @@
// import pathpkg "path"
//
// 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) {
return nil, nil
}
@@ -178,7 +179,7 @@
return nil, err
}
- edits = append(edits, TextEdit{
+ edits = append(edits, diff.TextEdit{
Span: spn,
NewText: newText,
})
diff --git a/internal/lsp/source/imports_test.go b/internal/lsp/source/imports_test.go
index 2d054db..e10c082 100644
--- a/internal/lsp/source/imports_test.go
+++ b/internal/lsp/source/imports_test.go
@@ -8,6 +8,8 @@
"go/parser"
"go/token"
"testing"
+
+ "golang.org/x/tools/internal/lsp/diff"
)
var fset = token.NewFileSet()
@@ -125,7 +127,7 @@
name: "package statement multiline comments",
pkg: "os",
in: `package main /* This is a multiline comment
-and it extends
+and it extends
further down*/`,
want: []importInfo{
importInfo{
@@ -137,7 +139,7 @@
{
name: "import c",
pkg: "os",
- in: `package main
+ in: `package main
import "C"
`,
@@ -155,7 +157,7 @@
{
name: "existing imports",
pkg: "os",
- in: `package main
+ in: `package main
import "io"
`,
@@ -173,7 +175,7 @@
{
name: "existing imports with comment",
pkg: "os",
- in: `package main
+ in: `package main
import "io" // A comment
`,
@@ -191,7 +193,7 @@
{
name: "existing imports multiline comment",
pkg: "os",
- in: `package main
+ in: `package main
import "io" /* A comment
that
@@ -212,7 +214,7 @@
name: "renamed import",
renamedPkg: "o",
pkg: "os",
- in: `package main
+ in: `package main
`,
want: []importInfo{
importInfo{
@@ -314,7 +316,7 @@
}
}
-func applyEdits(contents string, edits []TextEdit) string {
+func applyEdits(contents string, edits []diff.TextEdit) string {
res := contents
// Apply the edits from the end of the file forward
diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go
index 7164f81..56e6dfa 100644
--- a/internal/lsp/source/rename.go
+++ b/internal/lsp/source/rename.go
@@ -14,6 +14,7 @@
"regexp"
"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/telemetry/trace"
"golang.org/x/tools/refactor/satisfy"
@@ -35,7 +36,7 @@
}
// 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")
defer done()
@@ -93,14 +94,14 @@
// Sort edits for each file.
for _, edits := range changes {
- sortTextEdits(edits)
+ diff.SortTextEdits(edits)
}
return changes, nil
}
// Rename all references to the identifier.
-func (r *renamer) update() (map[span.URI][]TextEdit, error) {
- result := make(map[span.URI][]TextEdit)
+func (r *renamer) update() (map[span.URI][]diff.TextEdit, error) {
+ result := make(map[span.URI][]diff.TextEdit)
seen := make(map[span.Span]bool)
docRegexp, err := regexp.Compile(`\b` + r.from + `\b`)
@@ -129,7 +130,7 @@
}
// Replace the identifier with r.to.
- edit := TextEdit{
+ edit := diff.TextEdit{
Span: refSpan,
NewText: r.to,
}
@@ -153,7 +154,7 @@
if err != nil {
return nil, err
}
- result[spn.URI()] = append(result[spn.URI()], TextEdit{
+ result[spn.URI()] = append(result[spn.URI()], diff.TextEdit{
Span: spn,
NewText: r.to,
})
@@ -194,7 +195,7 @@
}
// 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.
pkg := r.packages[pkgName.Pkg()]
_, path, _ := pathEnclosingInterval(r.ctx, r.fset, pkg, pkgName.Pos(), pkgName.Pos())
@@ -229,7 +230,7 @@
format.Node(&buf, r.fset, updated)
newText := buf.String()
- return &TextEdit{
+ return &diff.TextEdit{
Span: spn,
NewText: newText,
}, nil
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 16d92f2..854edaa 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -289,13 +289,12 @@
}
continue
}
- ops := source.EditsToDiff(edits)
data, _, err := f.Handle(ctx).Read(ctx)
if err != nil {
t.Error(err)
continue
}
- got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(data)), ops), "")
+ got := diff.ApplyEdits(string(data), edits)
if gofmted != got {
t.Errorf("format failed for %s, expected:\n%v\ngot:\n%v", filename, gofmted, got)
}
@@ -331,13 +330,12 @@
}
continue
}
- ops := source.EditsToDiff(edits)
data, _, err := f.Handle(ctx).Read(ctx)
if err != nil {
t.Error(err)
continue
}
- got := strings.Join(diff.ApplyEdits(diff.SplitLines(string(data)), ops), "")
+ got := diff.ApplyEdits(string(data), edits)
if goimported != got {
t.Errorf("import failed for %s, expected:\n%v\ngot:\n%v", filename, goimported, got)
}
@@ -538,7 +536,7 @@
}
}
-func applyEdits(contents string, edits []source.TextEdit) string {
+func applyEdits(contents string, edits []diff.TextEdit) string {
res := contents
// Apply the edits from the end of the file forward
diff --git a/internal/lsp/source/suggested_fix.go b/internal/lsp/source/suggested_fix.go
index a433723..0e4d847 100644
--- a/internal/lsp/source/suggested_fix.go
+++ b/internal/lsp/source/suggested_fix.go
@@ -2,7 +2,9 @@
import (
"go/token"
+
"golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/span"
)
@@ -16,7 +18,7 @@
if err != nil {
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)
}
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 3a0fb8f..4a0c073 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -10,13 +10,10 @@
"go/ast"
"go/token"
"go/types"
- "sort"
- "strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/internal/imports"
- "golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/span"
)
@@ -304,63 +301,3 @@
// GetActionGraph returns the action graph for the given package.
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
- })
-}