diff --git a/internal/lsp/cmd/cmd.go b/internal/lsp/cmd/cmd.go index 0d00c38fc0..c320a5efb7 100644 --- a/internal/lsp/cmd/cmd.go +++ b/internal/lsp/cmd/cmd.go @@ -150,6 +150,7 @@ func (app *Application) commands() []tool.Application { &rename{app: app}, &signature{app: app}, &suggestedfix{app: app}, + &symbols{app: app}, &version{app: app}, } } diff --git a/internal/lsp/cmd/symbols.go b/internal/lsp/cmd/symbols.go new file mode 100644 index 0000000000..6c2b34d518 --- /dev/null +++ b/internal/lsp/cmd/symbols.go @@ -0,0 +1,80 @@ +// 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" +) + +// references implements the references verb for gopls +type symbols struct { + app *Application +} + +func (r *symbols) Name() string { return "symbols" } +func (r *symbols) Usage() string { return "" } +func (r *symbols) ShortHelp() string { return "display selected file's symbols" } +func (r *symbols) DetailedHelp(f *flag.FlagSet) { + fmt.Fprint(f.Output(), ` +Example: + $ gopls symbols helper/helper.go +`) + f.PrintDefaults() +} +func (r *symbols) Run(ctx context.Context, args ...string) error { + if len(args) != 1 { + return tool.CommandLineErrorf("symbols expects 1 argument (position)") + } + + conn, err := r.app.connect(ctx) + if err != nil { + return err + } + defer conn.terminate(ctx) + + from := span.Parse(args[0]) + p := protocol.DocumentSymbolParams{ + TextDocument: protocol.TextDocumentIdentifier{ + URI: string(from.URI()), + }, + } + + symbols, err := conn.DocumentSymbol(ctx, &p) + if err != nil { + return err + } + + for _, s := range symbols { + fmt.Println(symbolToString(s)) + // Sort children for consistency + sort.Slice(s.Children, func(i, j int) bool { + return s.Children[i].Name < s.Children[j].Name + }) + for _, c := range s.Children { + fmt.Println("\t" + symbolToString(c)) + } + } + + return nil +} + +func symbolToString(symbol protocol.DocumentSymbol) string { + r := symbol.SelectionRange + // convert ranges to user friendly 1-based positions + position := fmt.Sprintf("%v:%v-%v:%v", + r.Start.Line+1, + r.Start.Character+1, + r.End.Line+1, + r.End.Character+1, + ) + return fmt.Sprintf("%s %s %s", symbol.Name, symbol.Kind, position) +} diff --git a/internal/lsp/cmd/test/cmdtest.go b/internal/lsp/cmd/test/cmdtest.go index adced2a303..b4d2bfc367 100644 --- a/internal/lsp/cmd/test/cmdtest.go +++ b/internal/lsp/cmd/test/cmdtest.go @@ -16,7 +16,6 @@ import ( "testing" "golang.org/x/tools/go/packages/packagestest" - "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/lsp/tests" "golang.org/x/tools/internal/span" @@ -78,10 +77,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) Symbol(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { - //TODO: add command line symbol tests when it works -} - func (r *runner) Implementation(t *testing.T, spn span.Span, imp tests.Implementations) { //TODO: add implements tests when it works } diff --git a/internal/lsp/cmd/test/symbols.go b/internal/lsp/cmd/test/symbols.go new file mode 100644 index 0000000000..05f00abc59 --- /dev/null +++ b/internal/lsp/cmd/test/symbols.go @@ -0,0 +1,33 @@ +// 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 ( + "testing" + + "fmt" + + "golang.org/x/tools/internal/lsp/cmd" + "golang.org/x/tools/internal/lsp/protocol" + "golang.org/x/tools/internal/span" + "golang.org/x/tools/internal/tool" +) + +func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { + filename := uri.Filename() + 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", "symbols"}, filename)) + if err != nil { + fmt.Println(err) + } + }) + expect := string(r.data.Golden("symbols", filename, func() ([]byte, error) { + return []byte(got), nil + })) + if expect != got { + t.Errorf("symbols failed for %s expected:\n%s\ngot:\n%s", filename, expect, got) + } +} diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index 7e3a555c7b..36be0fde58 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -654,7 +654,7 @@ func applyEdits(contents string, edits []diff.TextEdit) string { return res } -func (r *runner) Symbol(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { +func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { params := &protocol.DocumentSymbolParams{ TextDocument: protocol.TextDocumentIdentifier{ URI: string(uri), diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index c4ab1bb0c7..d3590e532e 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -770,7 +770,7 @@ func (r *runner) PrepareRename(t *testing.T, src span.Span, want *source.Prepare } } -func (r *runner) Symbol(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { +func (r *runner) Symbols(t *testing.T, uri span.URI, expectedSymbols []protocol.DocumentSymbol) { ctx := r.ctx f, err := r.view.GetFile(ctx, uri) if err != nil { diff --git a/internal/lsp/testdata/symbols/main.go.golden b/internal/lsp/testdata/symbols/main.go.golden new file mode 100644 index 0000000000..22cc38aedf --- /dev/null +++ b/internal/lsp/testdata/symbols/main.go.golden @@ -0,0 +1,29 @@ +-- symbols -- +x Variable 7:5-7:6 +y Constant 9:7-9:8 +Number Number 11:6-11:12 +Alias String 13:6-13:11 +NumberAlias Number 15:6-15:17 +Boolean Boolean 18:2-18:9 +BoolAlias Boolean 19:2-19:11 +Foo Struct 22:6-22:9 + Bar Field 25:2-25:5 + Baz Method 33:14-33:17 + Quux Field 23:2-23:6 + W Field 24:2-24:3 + baz Field 26:2-26:5 +Quux Struct 29:6-29:10 + Do Method 37:16-37:18 + X Field 30:2-30:3 + Y Field 30:5-30:6 +main Function 39:6-39:10 +Stringer Interface 43:6-43:14 + String Method 44:2-44:8 +ABer Interface 47:6-47:10 + A Method 49:2-49:3 + B Method 48:2-48:3 +WithEmbeddeds Interface 52:6-52:19 + ABer Interface 54:2-54:6 + Do Method 53:2-53:4 + io.Writer Interface 55:2-55:11 + diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 64225f820b..4b47ff6183 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -116,7 +116,7 @@ type Tests interface { 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) + Symbols(*testing.T, span.URI, []protocol.DocumentSymbol) SignatureHelp(*testing.T, span.Span, *source.SignatureInformation) Link(*testing.T, span.URI, []Link) } @@ -534,7 +534,7 @@ func Run(t *testing.T, tests Tests, data *Data) { for uri, expectedSymbols := range data.Symbols { t.Run(uriName(uri), func(t *testing.T) { t.Helper() - tests.Symbol(t, uri, expectedSymbols) + tests.Symbols(t, uri, expectedSymbols) }) } })