From 59b2cd78f8f2c1ec068e814f6755791ea7223f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Szczepaniak?= Date: Thu, 21 Nov 2019 22:28:59 +0100 Subject: [PATCH] tools/gopls: add cmd support for implementation This change adds command line support for implementation. Example: $ gopls implementation ~/tmp/foo/main.go:8:6 $ gopls implementation ~/tmp/foo/main.go:#53 Updates golang/go#32875 Change-Id: I3aa89a788ded886368b07c1824325069f3cba88a Reviewed-on: https://go-review.googlesource.com/c/tools/+/208357 Run-TryBot: Rebecca Stambler TryBot-Result: Gobot Gobot Reviewed-by: Rebecca Stambler --- internal/lsp/cmd/cmd.go | 1 + internal/lsp/cmd/implementation.go | 89 +++++++++++++++++++++++++ internal/lsp/cmd/test/cmdtest.go | 4 -- internal/lsp/cmd/test/implementation.go | 36 ++++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 internal/lsp/cmd/implementation.go create mode 100644 internal/lsp/cmd/test/implementation.go diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go index 2f179f8af7..078e974534 100644 --- a/internal/lsp/cmd/cmd.go +++ b/internal/lsp/cmd/cmd.go @@ -145,6 +145,7 @@ func (app *Application) commands() []tool.Application { &foldingRanges{app: app}, &format{app: app}, &links{app: app}, + &implementation{app: app}, &imports{app: app}, &query{app: app}, &references{app: app}, diff --git a/internal/lsp/cmd/implementation.go b/internal/lsp/cmd/implementation.go new file mode 100644 index 0000000000..98a6b8abbe --- /dev/null +++ b/internal/lsp/cmd/implementation.go @@ -0,0 +1,89 @@ +// 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 cmd + +import ( + "context" + "flag" + "fmt" + "sort" + + "golang.org/x/tools/internal/lsp/protocol" + "golang.org/x/tools/internal/span" + "golang.org/x/tools/internal/tool" +) + +// implementation implements the implementation verb for gopls +type implementation struct { + app *Application +} + +func (i *implementation) Name() string { return "implementation" } +func (i *implementation) Usage() string { return "" } +func (i *implementation) ShortHelp() string { return "display selected identifier's implementation" } +func (i *implementation) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls implementation helper/helper.go:8:6 + $ gopls implementation helper/helper.go:#53 + + gopls implementation flags are: +`) + f.PrintDefaults() +} + +func (i *implementation) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("implementation expects 1 argument (position)") + } + + conn, err := i.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := span.Parse(args[0]) + file := conn.AddFile(ctx, from.URI()) + if file.err != nil { + return file.err + } + + loc, err := file.mapper.Location(from) + if err != nil { + return err + } + + p := protocol.ImplementationParams{ + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + Position: loc.Range.Start, + }, + } + + implementations, err := conn.Implementation(ctx, &p) + if err != nil { + return err + } + + var spans []string + for _, impl := range implementations { + f := conn.AddFile(ctx, span.NewURI(impl.URI)) + span, err := f.mapper.Span(impl) + if err != nil { + return err + } + spans = append(spans, fmt.Sprint(span)) + } + sort.Strings(spans) + + for _, s := range spans { + fmt.Println(s) + } + + return nil +} diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go index 7c8e21bdf7..fa857379da 100644 --- a/internal/lsp/cmd/test/cmdtest.go +++ b/internal/lsp/cmd/test/cmdtest.go @@ -103,10 +103,6 @@ func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.Prepare //TODO: add command line prepare rename tests when it works } -func (r *runner) Implementation(t *testing.T, spn span.Span, imp tests.Implementations) { - //TODO: add implements tests when it works -} - func (r *runner) RunGoplsCmd(t testing.TB, args ...string) (string, string) { rStdout, wStdout, err := os.Pipe() if err != nil { diff --git a/internal/lsp/cmd/test/implementation.go b/internal/lsp/cmd/test/implementation.go new file mode 100644 index 0000000000..3bd4b5f33f --- /dev/null +++ b/internal/lsp/cmd/test/implementation.go @@ -0,0 +1,36 @@ +// 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 cmdtest + +import ( + "fmt" + "sort" + "testing" + + "golang.org/x/tools/internal/lsp/tests" + "golang.org/x/tools/internal/span" +) + +func (r *runner) Implementation(t *testing.T, spn span.Span, imp tests.Implementations) { + var itemStrings []string + for _, i := range imp.Implementations { + itemStrings = append(itemStrings, fmt.Sprint(i)) + } + sort.Strings(itemStrings) + var expect string + for _, i := range itemStrings { + expect += i + "\n" + } + expect = r.Normalize(expect) + + uri := spn.URI() + filename := uri.Filename() + target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column()) + + got, _ := r.NormalizeGoplsCmd(t, "implementation", target) + if expect != got { + t.Errorf("implementation failed for %s expected:\n%s\ngot:\n%s", target, expect, got) + } +}