gopls/internal/lsp: Replace input text when completing a definition
Completing a definition is based on a parsed identifier and should
replace the range of that identifier with the completion.
Fixes golang/go#56852
Change-Id: I44c751d1db6d55f90113fb918dda344bb7d62f87
Reviewed-on: https://go-review.googlesource.com/c/tools/+/453335
TryBot-Result: Gopher Robot <gobot@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Peter Weinberger <pjw@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Peter Weinberger <pjw@google.com>
diff --git a/gopls/internal/lsp/source/completion/definition.go b/gopls/internal/lsp/source/completion/definition.go
index fe41c55..cc7b42e 100644
--- a/gopls/internal/lsp/source/completion/definition.go
+++ b/gopls/internal/lsp/source/completion/definition.go
@@ -36,11 +36,12 @@
// can't happen
return nil, nil
}
- pos := path[0].Pos()
+ start := path[0].Pos()
+ end := path[0].End()
sel := &Selection{
content: "",
- cursor: pos,
- rng: span.NewRange(tokFile, pos, pos),
+ cursor: start,
+ rng: span.NewRange(tokFile, start, end),
}
var ans []CompletionItem
diff --git a/gopls/internal/regtest/completion/completion_test.go b/gopls/internal/regtest/completion/completion_test.go
index caa70e8..3d5afe4 100644
--- a/gopls/internal/regtest/completion/completion_test.go
+++ b/gopls/internal/regtest/completion/completion_test.go
@@ -12,6 +12,7 @@
"golang.org/x/tools/gopls/internal/hooks"
. "golang.org/x/tools/gopls/internal/lsp/regtest"
"golang.org/x/tools/internal/bug"
+ "golang.org/x/tools/internal/testenv"
"golang.org/x/tools/gopls/internal/lsp/fake"
"golang.org/x/tools/gopls/internal/lsp/protocol"
@@ -596,6 +597,90 @@
})
}
+// Test that completing a definition replaces source text when applied, golang/go#56852.
+// Note: With go <= 1.16 the completions does not add parameters and fails these tests.
+func TestDefinitionReplaceRange(t *testing.T) {
+ testenv.NeedsGo1Point(t, 17)
+
+ const mod = `
+-- go.mod --
+module mod.com
+
+go 1.17
+`
+
+ tests := []struct {
+ name string
+ before, after string
+ }{
+ {
+ name: "func TestMa",
+ before: `
+package foo_test
+
+func TestMa
+`,
+ after: `
+package foo_test
+
+func TestMain(m *testing.M)
+`,
+ },
+ {
+ name: "func TestSome",
+ before: `
+package foo_test
+
+func TestSome
+`,
+ after: `
+package foo_test
+
+func TestSome(t *testing.T)
+`,
+ },
+ {
+ name: "func Bench",
+ before: `
+package foo_test
+
+func Bench
+`,
+ // Note: Snippet with escaped }.
+ after: `
+package foo_test
+
+func Benchmark${1:Xxx}(b *testing.B) {
+$0
+\}
+`,
+ },
+ }
+
+ Run(t, mod, func(t *testing.T, env *Env) {
+ env.CreateBuffer("foo_test.go", "")
+
+ for _, tst := range tests {
+ tst.before = strings.Trim(tst.before, "\n")
+ tst.after = strings.Trim(tst.after, "\n")
+ env.SetBufferContent("foo_test.go", tst.before)
+
+ pos := env.RegexpSearch("foo_test.go", tst.name)
+ pos.Column = len(tst.name)
+ completions := env.Completion("foo_test.go", pos)
+ if len(completions.Items) == 0 {
+ t.Fatalf("no completion items")
+ }
+
+ env.AcceptCompletion("foo_test.go", pos, completions.Items[0])
+ env.Await(env.DoneWithChange())
+ if buf := env.Editor.BufferText("foo_test.go"); buf != tst.after {
+ t.Errorf("incorrect completion: got %q, want %q", buf, tst.after)
+ }
+ }
+ })
+}
+
func TestGoWorkCompletion(t *testing.T) {
const files = `
-- go.work --