mirror of
https://github.com/golang/go
synced 2024-11-18 16:54:43 -07: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"
|
||||
"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 @@ func (f *format) Run(ctx context.Context, args ...string) error {
|
||||
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 @@ func (f *format) Run(ctx context.Context, args ...string) error {
|
||||
}
|
||||
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 {
|
||||
|
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
|
||||
// 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"
|
||||
|
@ -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 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/diff"
|
||||
"golang.org/x/tools/internal/lsp/diff/myers"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -28,22 +28,22 @@ var verifyDiff = flag.Bool("verify-diff", false, "Check that the unified diff ou
|
||||
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 @@ func TestDiff(t *testing.T) {
|
||||
`[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 @@ func TestDiff(t *testing.T) {
|
||||
`[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 @@ func TestDiff(t *testing.T) {
|
||||
{
|
||||
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 @@ func TestDiff(t *testing.T) {
|
||||
+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 @@ func TestDiff(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
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 @@ func TestDiff(t *testing.T) {
|
||||
}
|
||||
}
|
||||
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 TestDiff(t *testing.T) {
|
||||
}
|
||||
|
||||
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 @@ func getDiffOutput(a, b string) (string, error) {
|
||||
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
|
||||
}
|
@ -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"
|
@ -7,6 +7,7 @@ package lsp
|
||||
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 @@ func spanToRange(ctx context.Context, view source.View, spn span.Span) (source.G
|
||||
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 @@ func ToProtocolEdits(m *protocol.ColumnMapper, edits []source.TextEdit) ([]proto
|
||||
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,
|
||||
}
|
||||
|
@ -287,8 +287,7 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
|
||||
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 @@ func (r *runner) Import(t *testing.T, data tests.Imports) {
|
||||
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 (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
|
||||
|
||||
// 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/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 @@ type CompletionItem struct {
|
||||
// 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
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"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 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
||||
kind CompletionItemKind
|
||||
plainSnippet *snippet.Builder
|
||||
placeholderSnippet *snippet.Builder
|
||||
addlEdits []TextEdit
|
||||
addlEdits []diff.TextEdit
|
||||
)
|
||||
|
||||
// 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/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 Diagnostic struct {
|
||||
|
||||
type SuggestedFixes struct {
|
||||
Title string
|
||||
Edits []TextEdit
|
||||
Edits []diff.TextEdit
|
||||
}
|
||||
|
||||
type DiagnosticSeverity int
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
)
|
||||
|
||||
// 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 @@ func formatSource(ctx context.Context, file File) ([]byte, error) {
|
||||
}
|
||||
|
||||
// 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 @@ func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]TextEd
|
||||
|
||||
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 @@ func hasListErrors(errors []packages.Error) bool {
|
||||
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 @@ func computeTextEdits(ctx context.Context, file File, formatted string) (edits [
|
||||
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)
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/diff"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
@ -35,7 +36,7 @@ import (
|
||||
// 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 @@ func addNamedImport(fset *token.FileSet, f *ast.File, name, path string) (edits
|
||||
return nil, err
|
||||
}
|
||||
|
||||
edits = append(edits, TextEdit{
|
||||
edits = append(edits, diff.TextEdit{
|
||||
Span: spn,
|
||||
NewText: newText,
|
||||
})
|
||||
|
@ -8,6 +8,8 @@ import (
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/diff"
|
||||
)
|
||||
|
||||
var fset = token.NewFileSet()
|
||||
@ -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
|
||||
|
||||
// Apply the edits from the end of the file forward
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"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 @@ type renamer struct {
|
||||
}
|
||||
|
||||
// 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 @@ func (i *IdentifierInfo) Rename(ctx context.Context, newName string) (map[span.U
|
||||
|
||||
// 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 @@ func (r *renamer) update() (map[span.URI][]TextEdit, error) {
|
||||
}
|
||||
|
||||
// Replace the identifier with r.to.
|
||||
edit := TextEdit{
|
||||
edit := diff.TextEdit{
|
||||
Span: refSpan,
|
||||
NewText: r.to,
|
||||
}
|
||||
@ -153,7 +154,7 @@ func (r *renamer) update() (map[span.URI][]TextEdit, error) {
|
||||
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 @@ func (r *renamer) docComment(pkg Package, id *ast.Ident) *ast.CommentGroup {
|
||||
}
|
||||
|
||||
// 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 @@ func (r *renamer) updatePkgName(pkgName *types.PkgName) (*TextEdit, error) {
|
||||
format.Node(&buf, r.fset, updated)
|
||||
newText := buf.String()
|
||||
|
||||
return &TextEdit{
|
||||
return &diff.TextEdit{
|
||||
Span: spn,
|
||||
NewText: newText,
|
||||
}, nil
|
||||
|
@ -289,13 +289,12 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
|
||||
}
|
||||
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 @@ func (r *runner) Import(t *testing.T, data tests.Imports) {
|
||||
}
|
||||
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 (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
|
||||
|
||||
// Apply the edits from the end of the file forward
|
||||
|
@ -2,7 +2,9 @@ package source
|
||||
|
||||
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 @@ func getCodeActions(fset *token.FileSet, diag analysis.Diagnostic) ([]SuggestedF
|
||||
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)
|
||||
}
|
||||
|
@ -10,13 +10,10 @@ import (
|
||||
"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 @@ type Package interface {
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user