diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go index d74a5af6eb..a87234b0cd 100644 --- a/internal/lsp/cmd/cmd.go +++ b/internal/lsp/cmd/cmd.go @@ -144,6 +144,7 @@ func (app *Application) commands() []tool.Application { &check{app: app}, &format{app: app}, &query{app: app}, + &references{app: app}, &rename{app: app}, &version{app: app}, } diff --git a/internal/lsp/cmd/references.go b/internal/lsp/cmd/references.go new file mode 100644 index 0000000000..03c170939b --- /dev/null +++ b/internal/lsp/cmd/references.go @@ -0,0 +1,98 @@ +// 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" + "golang.org/x/tools/internal/lsp/protocol" + "golang.org/x/tools/internal/span" + "golang.org/x/tools/internal/tool" + "sort" +) + +// references implements the references verb for gopls +type references struct { + IncludeDeclaration bool `flag:"d" help:"include the declaration of the specified identifier in the results"` + + app *Application +} + +func (r *references) Name() string { return "references" } +func (r *references) Usage() string { return "" } +func (r *references) ShortHelp() string { return "display selected identifier's references" } +func (r *references) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + + $ # 1-indexed location (:line:column or :#offset) of the target identifier + $ gopls references helper/helper.go:8:6 + $ gopls references helper/helper.go:#53 + + gopls references flags are: +`) + f.PrintDefaults() +} + +func (r *references) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("references expects 1 argument (position)") + } + + conn, err := r.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.ReferenceParams{ + Context: protocol.ReferenceContext{ + IncludeDeclaration: r.IncludeDeclaration, + }, + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI}, + Position: loc.Range.Start, + }, + } + locations, err := conn.References(ctx, &p) + if err != nil { + return err + } + + if len(locations) == 0 { + return tool.CommandLineErrorf("%v: not an identifier", from) + } + + var spans []string + for _, l := range locations { + f := conn.AddFile(ctx, span.NewURI(l.URI)) + // convert location to span for user-friendly 1-indexed line + // and column numbers + span, err := f.mapper.Span(l) + 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 8aa8638f6b..02579aef9c 100644 --- a/internal/lsp/cmd/test/cmdtest.go +++ b/internal/lsp/cmd/test/cmdtest.go @@ -74,10 +74,6 @@ func (r *runner) Highlight(t *testing.T, name string, locations []span.Span) { //TODO: add command line highlight tests when it works } -func (r *runner) Reference(t *testing.T, src span.Span, itemList []span.Span) { - //TODO: add command line references tests when it works -} - func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.PrepareItem) { //TODO: add command line prepare rename tests when it works } diff --git a/internal/lsp/cmd/test/references.go b/internal/lsp/cmd/test/references.go new file mode 100644 index 0000000000..55bbf9183f --- /dev/null +++ b/internal/lsp/cmd/test/references.go @@ -0,0 +1,38 @@ +// 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" + "golang.org/x/tools/internal/lsp/cmd" + "golang.org/x/tools/internal/tool" + "testing" + + "golang.org/x/tools/internal/span" +) + +func (r *runner) References(t *testing.T, spn span.Span, itemList []span.Span) { + var expect string + for _, i := range itemList { + expect += fmt.Sprintln(i) + } + + uri := spn.URI() + filename := uri.Filename() + target := filename + fmt.Sprintf(":%v:%v", spn.Start().Line(), spn.Start().Column()) + + app := cmd.New("gopls-test", r.data.Config.Dir, r.data.Config.Env, r.options) + got := CaptureStdOut(t, func() { + err := tool.Run(r.ctx, app, append([]string{"-remote=internal", "references"}, target)) + if err != nil { + fmt.Println(spn.Start().Line()) + fmt.Println(err) + } + }) + + if expect != got { + t.Errorf("references failed for %s expected:\n%s\ngot:\n%s", target, expect, got) + } +} diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index fde6fc39fa..568883b1c8 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -458,7 +458,7 @@ func (r *runner) Highlight(t *testing.T, name string, locations []span.Span) { } } -func (r *runner) Reference(t *testing.T, src span.Span, itemList []span.Span) { +func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) { sm, err := r.data.Mapper(src.URI()) if err != nil { t.Fatal(err) diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index c85cdd8e9d..f8ec138922 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -566,7 +566,7 @@ func (r *runner) Highlight(t *testing.T, name string, locations []span.Span) { } } -func (r *runner) Reference(t *testing.T, src span.Span, itemList []span.Span) { +func (r *runner) References(t *testing.T, src span.Span, itemList []span.Span) { ctx := r.ctx f, err := r.view.GetFile(ctx, src.URI()) if err != nil { diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 0bcc69777f..34c6273647 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -110,7 +110,7 @@ type Tests interface { SuggestedFix(*testing.T, span.Span) Definition(*testing.T, span.Span, Definition) Highlight(*testing.T, string, []span.Span) - Reference(*testing.T, span.Span, []span.Span) + References(*testing.T, span.Span, []span.Span) Rename(*testing.T, span.Span, string) PrepareRename(*testing.T, span.Span, *source.PrepareItem) Symbol(*testing.T, span.URI, []protocol.DocumentSymbol) @@ -484,7 +484,7 @@ func Run(t *testing.T, tests Tests, data *Data) { for src, itemList := range data.References { t.Run(spanName(src), func(t *testing.T) { t.Helper() - tests.Reference(t, src, itemList) + tests.References(t, src, itemList) }) } })