From 27d1b4e4f3c75e4e2c92ae1b99a67c7f34185617 Mon Sep 17 00:00:00 2001 From: Michael Matloob Date: Fri, 6 Sep 2019 17:07:41 -0400 Subject: [PATCH] internal/lsp/diff: rewrite ApplyEdits to work with sub-line diffs This replaces the definition of ApplyEdits to be more like that in go vet -fix, so that we can apply the results of suggested fixes. Change-Id: Ib5724139464954e3790bc51ed1edc3ce4b2115ff Reviewed-on: https://go-review.googlesource.com/c/tools/+/193959 Run-TryBot: Michael Matloob TryBot-Result: Gobot Gobot Reviewed-by: Rebecca Stambler --- internal/lsp/diff/apply_edits.go | 58 +++++++++++++++++++++++++++ internal/lsp/diff/apply_edits_test.go | 29 ++++++++++++++ internal/lsp/diff/myers.go | 6 --- 3 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 internal/lsp/diff/apply_edits.go create mode 100644 internal/lsp/diff/apply_edits_test.go diff --git a/internal/lsp/diff/apply_edits.go b/internal/lsp/diff/apply_edits.go new file mode 100644 index 0000000000..b7a23d1c37 --- /dev/null +++ b/internal/lsp/diff/apply_edits.go @@ -0,0 +1,58 @@ +package diff + +import ( + "bytes" + "sort" + + "golang.org/x/tools/internal/span" +) + +func init() { + ApplyEdits = applyEdits +} + +func applyEdits(before string, edits []TextEdit) string { + // Preconditions: + // - all of the edits apply to before + // - and all the spans for each TextEdit have the same URI + + // copy edits so we don't make a mess of the caller's slice + s := make([]TextEdit, len(edits)) + copy(s, edits) + edits = s + + // TODO(matloob): Initialize the Converter Once? + var conv span.Converter = span.NewContentConverter("", []byte(before)) + offset := func(point span.Point) int { + if point.HasOffset() { + return point.Offset() + } + offset, err := conv.ToOffset(point.Line(), point.Column()) + if err != nil { + panic(err) + } + return offset + } + + // sort the copy + sort.Slice(edits, func(i, j int) bool { return offset(edits[i].Span.Start()) < offset(edits[j].Span.Start()) }) + + var after bytes.Buffer + beforeOffset := 0 + for _, edit := range edits { + if offset(edit.Span.Start()) < beforeOffset { + panic("overlapping edits") // TODO(matloob): ApplyEdits doesn't return an error. What do we do? + } else if offset(edit.Span.Start()) > beforeOffset { + after.WriteString(before[beforeOffset:offset(edit.Span.Start())]) + beforeOffset = offset(edit.Span.Start()) + } + // offset(edit.Span.Start) is now equal to beforeOffset + after.WriteString(edit.NewText) + beforeOffset += offset(edit.Span.End()) - offset(edit.Span.Start()) + } + if beforeOffset < len(before) { + after.WriteString(before[beforeOffset:]) + beforeOffset = len(before[beforeOffset:]) // just to preserve invariants + } + return after.String() +} diff --git a/internal/lsp/diff/apply_edits_test.go b/internal/lsp/diff/apply_edits_test.go new file mode 100644 index 0000000000..d9fe85e037 --- /dev/null +++ b/internal/lsp/diff/apply_edits_test.go @@ -0,0 +1,29 @@ +package diff + +import ( + "testing" + + "golang.org/x/tools/internal/span" +) + +func TestApplyEdits(t *testing.T) { + var testCases = []struct { + before string + edits []TextEdit + want string + }{ + {"", nil, ""}, + {"X", []TextEdit{{newSpan(0, 1), "Y"}}, "Y"}, + {" X ", []TextEdit{{newSpan(1, 2), "Y"}}, " Y "}, + {" X X ", []TextEdit{{newSpan(1, 2), "Y"}, {newSpan(3, 4), "Z"}}, " Y Z "}, + } + for _, tc := range testCases { + if got := applyEdits(tc.before, tc.edits); got != tc.want { + t.Errorf("applyEdits(%v, %v): got %v, want %v", tc.before, tc.edits, got, tc.want) + } + } +} + +func newSpan(start, end int) span.Span { + return span.New("", span.NewPoint(0, 0, start), span.NewPoint(0, 0, end)) +} diff --git a/internal/lsp/diff/myers.go b/internal/lsp/diff/myers.go index eab3e2cfe6..a05bc42454 100644 --- a/internal/lsp/diff/myers.go +++ b/internal/lsp/diff/myers.go @@ -14,7 +14,6 @@ import ( func init() { ComputeEdits = myersComputeEdits - ApplyEdits = myersApplyEdits ToUnified = myersToUnified } @@ -24,11 +23,6 @@ func myersComputeEdits(uri span.URI, before, after string) []TextEdit { 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)