internal/lsp: sort rename results
We recommend that gopls integrators apply the []TextEdit responses in
reverse order to get a correct resulting document. This strategy works
when the response is already sorted. Have gopls return sorted []TextEdit
for each file.
Fixes golang/go#33123
Change-Id: Ib570881c9623695d2ae3194fa8a97b0a681a3250
Reviewed-on: https://go-review.googlesource.com/c/tools/+/186258
Run-TryBot: Suzy Mueller <suzmue@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 7c8fdf8..f8a818c 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -582,7 +582,6 @@
func applyEdits(contents string, edits []source.TextEdit) string {
res := contents
- sortSourceTextEdits(edits)
// Apply the edits from the end of the file forward
// to preserve the offsets
@@ -596,15 +595,6 @@
return res
}
-func sortSourceTextEdits(d []source.TextEdit) {
- sort.Slice(d, func(i int, j int) bool {
- if r := span.Compare(d[i].Span, d[j].Span); r != 0 {
- return r < 0
- }
- return d[i].NewText < d[j].NewText
- })
-}
-
func (r *runner) Symbol(t *testing.T, data tests.Symbols) {
for uri, expectedSymbols := range data {
params := &protocol.DocumentSymbolParams{
diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go
index 54a5af9..d8eb3a3 100644
--- a/internal/lsp/source/rename.go
+++ b/internal/lsp/source/rename.go
@@ -85,7 +85,16 @@
return nil, fmt.Errorf(r.errors)
}
- return r.update()
+ changes, err := r.update()
+ if err != nil {
+ return nil, err
+ }
+
+ // Sort edits for each file.
+ for _, edits := range changes {
+ sortTextEdits(edits)
+ }
+ return changes, nil
}
// Rename all references to the identifier.
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index 7bd469e..a73a0af 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -547,7 +547,6 @@
func applyEdits(contents string, edits []source.TextEdit) string {
res := contents
- sortSourceTextEdits(edits)
// Apply the edits from the end of the file forward
// to preserve the offsets
@@ -561,15 +560,6 @@
return res
}
-func sortSourceTextEdits(d []source.TextEdit) {
- sort.Slice(d, func(i int, j int) bool {
- if r := span.Compare(d[i].Span, d[j].Span); r != 0 {
- return r < 0
- }
- return d[i].NewText < d[j].NewText
- })
-}
-
func (r *runner) Symbol(t *testing.T, data tests.Symbols) {
ctx := r.ctx
for uri, expectedSymbols := range data {
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index bc0ad76..4f7e782 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -9,6 +9,7 @@
"go/ast"
"go/token"
"go/types"
+ "sort"
"strings"
"golang.org/x/tools/go/analysis"
@@ -318,3 +319,10 @@
}
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
+ })
+}