From 04840ef8f354469960ddc08905bfc0fd625238fa Mon Sep 17 00:00:00 2001 From: Rebecca Stambler Date: Thu, 5 Sep 2019 18:54:05 -0400 Subject: [PATCH] internal/lsp: switch to using protocol positions for document symbols Change-Id: I8e550b753328b8e536bff3bb61b4ff4486fcd4f9 Reviewed-on: https://go-review.googlesource.com/c/tools/+/193722 Run-TryBot: Rebecca Stambler TryBot-Result: Gobot Gobot Reviewed-by: Ian Cottrell --- internal/lsp/highlight.go | 40 ++---- internal/lsp/lsp_test.go | 27 ++-- internal/lsp/source/completion_format.go | 2 +- internal/lsp/source/enums.go | 25 +--- internal/lsp/source/highlight.go | 29 +++-- internal/lsp/source/identifier.go | 47 +------ internal/lsp/source/rename.go | 2 +- internal/lsp/source/signature_help.go | 2 +- internal/lsp/source/source_test.go | 65 +++++----- internal/lsp/source/symbols.go | 154 +++++++++-------------- internal/lsp/source/util.go | 53 ++++++++ internal/lsp/symbols.go | 60 +-------- internal/lsp/tests/tests.go | 21 +++- 13 files changed, 210 insertions(+), 317 deletions(-) diff --git a/internal/lsp/highlight.go b/internal/lsp/highlight.go index 44ce3ff755..48d1438aaa 100644 --- a/internal/lsp/highlight.go +++ b/internal/lsp/highlight.go @@ -9,47 +9,29 @@ import ( "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" + "golang.org/x/tools/internal/lsp/telemetry" "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/telemetry/log" - "golang.org/x/tools/internal/telemetry/tag" ) func (s *Server) documentHighlight(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.DocumentHighlight, error) { uri := span.NewURI(params.TextDocument.URI) view := s.session.ViewOf(uri) - f, err := getGoFile(ctx, view, uri) + rngs, err := source.Highlight(ctx, view, uri, params.Position) if err != nil { - return nil, err + log.Error(ctx, "no highlight", err, telemetry.URI.Of(uri)) } - m, err := getMapper(ctx, f) - if err != nil { - return nil, err - } - spn, err := m.PointSpan(params.Position) - if err != nil { - return nil, err - } - rng, err := spn.Range(m.Converter) - if err != nil { - return nil, err - } - spans, err := source.Highlight(ctx, f, rng.Start) - if err != nil { - log.Error(ctx, "no highlight", err, tag.Of("Span", spn)) - } - return toProtocolHighlight(m, spans), nil + return toProtocolHighlight(rngs), nil } -func toProtocolHighlight(m *protocol.ColumnMapper, spans []span.Span) []protocol.DocumentHighlight { - result := make([]protocol.DocumentHighlight, 0, len(spans)) +func toProtocolHighlight(rngs []protocol.Range) []protocol.DocumentHighlight { + result := make([]protocol.DocumentHighlight, 0, len(rngs)) kind := protocol.Text - for _, span := range spans { - r, err := m.Range(span) - if err != nil { - continue - } - h := protocol.DocumentHighlight{Kind: &kind, Range: r} - result = append(result, h) + for _, rng := range rngs { + result = append(result, protocol.DocumentHighlight{ + Kind: &kind, + Range: rng, + }) } return result } diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index cb13886a1f..c6373184b5 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -678,7 +678,6 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) { if err != nil { t.Fatal(err) } - sedits, err := source.FromProtocolEdits(m, edits) if err != nil { t.Error(err) @@ -791,30 +790,22 @@ func (r *runner) Symbol(t *testing.T, data tests.Symbols) { } } -func (r *runner) diffSymbols(t *testing.T, uri span.URI, want []source.Symbol, got []protocol.DocumentSymbol) string { +func (r *runner) diffSymbols(t *testing.T, uri span.URI, want []protocol.DocumentSymbol, got []protocol.DocumentSymbol) string { sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name }) sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name }) - m, err := r.mapper(uri) - if err != nil { - t.Fatal(err) - } if len(got) != len(want) { - return summarizeSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want)) + return summarizeSymbols(t, -1, want, got, "different lengths got %v want %v", len(got), len(want)) } for i, w := range want { g := got[i] if w.Name != g.Name { - return summarizeSymbols(i, want, got, "incorrect name got %v want %v", g.Name, w.Name) + return summarizeSymbols(t, i, want, got, "incorrect name got %v want %v", g.Name, w.Name) } - if wkind := toProtocolSymbolKind(w.Kind); wkind != g.Kind { - return summarizeSymbols(i, want, got, "incorrect kind got %v want %v", g.Kind, wkind) + if w.Kind != g.Kind { + return summarizeSymbols(t, i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind) } - spn, err := m.RangeSpan(g.SelectionRange) - if err != nil { - return summarizeSymbols(i, want, got, "%v", err) - } - if w.SelectionSpan != spn { - return summarizeSymbols(i, want, got, "incorrect span got %v want %v", spn, w.SelectionSpan) + if protocol.CompareRange(g.SelectionRange, w.SelectionRange) != 0 { + return summarizeSymbols(t, i, want, got, "incorrect span got %v want %v", g.SelectionRange, w.SelectionRange) } if msg := r.diffSymbols(t, uri, w.Children, g.Children); msg != "" { return fmt.Sprintf("children of %s: %s", w.Name, msg) @@ -823,7 +814,7 @@ func (r *runner) diffSymbols(t *testing.T, uri span.URI, want []source.Symbol, g return "" } -func summarizeSymbols(i int, want []source.Symbol, got []protocol.DocumentSymbol, reason string, args ...interface{}) string { +func summarizeSymbols(t *testing.T, i int, want []protocol.DocumentSymbol, got []protocol.DocumentSymbol, reason string, args ...interface{}) string { msg := &bytes.Buffer{} fmt.Fprint(msg, "document symbols failed") if i >= 0 { @@ -833,7 +824,7 @@ func summarizeSymbols(i int, want []source.Symbol, got []protocol.DocumentSymbol fmt.Fprintf(msg, reason, args...) fmt.Fprint(msg, ":\nexpected:\n") for _, s := range want { - fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionSpan) + fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange) } fmt.Fprintf(msg, "got:\n") for _, s := range got { diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go index 508f7bf66f..587c5fc6a0 100644 --- a/internal/lsp/source/completion_format.go +++ b/internal/lsp/source/completion_format.go @@ -117,7 +117,7 @@ func (c *completer) item(cand candidate) (CompletionItem, error) { if !c.opts.Documentation { return item, nil } - declRange, err := objToRange(c.ctx, c.view, obj) + declRange, err := objToMappedRange(c.ctx, c.view, obj) if err != nil { return item, nil } diff --git a/internal/lsp/source/enums.go b/internal/lsp/source/enums.go index 9cb88ccaf6..d6b033a054 100644 --- a/internal/lsp/source/enums.go +++ b/internal/lsp/source/enums.go @@ -4,27 +4,16 @@ package source -import "fmt" +import ( + "fmt" +) var ( - namesSymbolKind [int(FieldSymbol) + 1]string namesDiagnosticSeverity [int(SeverityError) + 1]string namesCompletionItemKind [int(PackageCompletionItem) + 1]string ) func init() { - namesSymbolKind[PackageSymbol] = "Package" - namesSymbolKind[StructSymbol] = "Struct" - namesSymbolKind[VariableSymbol] = "Variable" - namesSymbolKind[ConstantSymbol] = "Constant" - namesSymbolKind[FunctionSymbol] = "Function" - namesSymbolKind[MethodSymbol] = "Method" - namesSymbolKind[InterfaceSymbol] = "Interface" - namesSymbolKind[NumberSymbol] = "Number" - namesSymbolKind[StringSymbol] = "String" - namesSymbolKind[BooleanSymbol] = "Boolean" - namesSymbolKind[FieldSymbol] = "Field" - namesDiagnosticSeverity[SeverityWarning] = "Warning" namesDiagnosticSeverity[SeverityError] = "Error" @@ -62,14 +51,6 @@ func parseEnum(s string, names []string) int { return 0 } -func (e SymbolKind) Format(f fmt.State, c rune) { - formatEnum(f, c, int(e), namesSymbolKind[:], "SymbolKind") -} - -func ParseSymbolKind(s string) SymbolKind { - return SymbolKind(parseEnum(s, namesSymbolKind[:])) -} - func (e DiagnosticSeverity) Format(f fmt.State, c rune) { formatEnum(f, c, int(e), namesDiagnosticSeverity[:], "DiagnosticSeverity") } diff --git a/internal/lsp/source/highlight.go b/internal/lsp/source/highlight.go index d627f7f5f9..ff13837caf 100644 --- a/internal/lsp/source/highlight.go +++ b/internal/lsp/source/highlight.go @@ -7,38 +7,45 @@ package source import ( "context" "go/ast" - "go/token" "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/span" "golang.org/x/tools/internal/telemetry/trace" errors "golang.org/x/xerrors" ) -func Highlight(ctx context.Context, f GoFile, pos token.Pos) ([]span.Span, error) { +func Highlight(ctx context.Context, view View, uri span.URI, pos protocol.Position) ([]protocol.Range, error) { ctx, done := trace.StartSpan(ctx, "source.Highlight") defer done() - file, err := f.GetAST(ctx, ParseFull) - if file == nil { + file, _, m, err := fileToMapper(ctx, view, uri) + if err != nil { return nil, err } - fset := f.FileSet() - path, _ := astutil.PathEnclosingInterval(file, pos, pos) + spn, err := m.PointSpan(pos) + if err != nil { + return nil, err + } + rng, err := spn.Range(m.Converter) + if err != nil { + return nil, err + } + path, _ := astutil.PathEnclosingInterval(file, rng.Start, rng.Start) if len(path) == 0 { - return nil, errors.Errorf("no enclosing position found for %s", fset.Position(pos)) + return nil, errors.Errorf("no enclosing position found for %s", pos) } id, ok := path[0].(*ast.Ident) if !ok { - return nil, errors.Errorf("%s is not an identifier", fset.Position(pos)) + return nil, errors.Errorf("%s is not an identifier", pos) } - var result []span.Span + var result []protocol.Range if id.Obj != nil { ast.Inspect(path[len(path)-1], func(n ast.Node) bool { if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj { - s, err := nodeSpan(n, fset) + rng, err := nodeToProtocolRange(ctx, view, n) if err == nil { - result = append(result, s) + result = append(result, rng) } } return true diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go index 69f58134ff..373d347c2f 100644 --- a/internal/lsp/source/identifier.go +++ b/internal/lsp/source/identifier.go @@ -140,7 +140,7 @@ func identifier(ctx context.Context, view View, f GoFile, pkg Package, file *ast return nil, errors.Errorf("no declaration for %s", result.Name) } result.Declaration.node = decl - if result.Declaration.mappedRange, err = nameToRange(ctx, view, decl.Pos(), result.Name); err != nil { + if result.Declaration.mappedRange, err = nameToMappedRange(ctx, view, decl.Pos(), result.Name); err != nil { return nil, err } return result, nil @@ -165,7 +165,7 @@ func identifier(ctx context.Context, view View, f GoFile, pkg Package, file *ast } } - if result.Declaration.mappedRange, err = objToRange(ctx, view, result.Declaration.obj); err != nil { + if result.Declaration.mappedRange, err = objToMappedRange(ctx, view, result.Declaration.obj); err != nil { return nil, err } if result.Declaration.node, err = objToNode(ctx, f.View(), pkg.GetTypes(), result.Declaration.obj, result.Declaration.mappedRange.spanRange); err != nil { @@ -182,7 +182,7 @@ func identifier(ctx context.Context, view View, f GoFile, pkg Package, file *ast if hasErrorType(result.Type.Object) { return result, nil } - if result.Type.mappedRange, err = objToRange(ctx, view, result.Type.Object); err != nil { + if result.Type.mappedRange, err = objToMappedRange(ctx, view, result.Type.Object); err != nil { return nil, err } } @@ -204,47 +204,6 @@ func hasErrorType(obj types.Object) bool { return types.IsInterface(obj.Type()) && obj.Pkg() == nil && obj.Name() == "error" } -func objToRange(ctx context.Context, view View, obj types.Object) (mappedRange, error) { - if pkgName, ok := obj.(*types.PkgName); ok { - // An imported Go package has a package-local, unqualified name. - // When the name matches the imported package name, there is no - // identifier in the import spec with the local package name. - // - // For example: - // import "go/ast" // name "ast" matches package name - // import a "go/ast" // name "a" does not match package name - // - // When the identifier does not appear in the source, have the range - // of the object be the point at the beginning of the declaration. - if pkgName.Imported().Name() == pkgName.Name() { - return nameToRange(ctx, view, obj.Pos(), "") - } - } - return nameToRange(ctx, view, obj.Pos(), obj.Name()) -} - -func nameToRange(ctx context.Context, view View, pos token.Pos, name string) (mappedRange, error) { - return posToRange(ctx, view, pos, pos+token.Pos(len(name))) -} - -func posToRange(ctx context.Context, view View, pos, end token.Pos) (mappedRange, error) { - if !pos.IsValid() { - return mappedRange{}, errors.Errorf("invalid position for %v", pos) - } - if !end.IsValid() { - return mappedRange{}, errors.Errorf("invalid position for %v", end) - } - posn := view.Session().Cache().FileSet().Position(pos) - _, m, err := cachedFileToMapper(ctx, view, span.FileURI(posn.Filename)) - if err != nil { - return mappedRange{}, err - } - return mappedRange{ - m: m, - spanRange: span.NewRange(view.Session().Cache().FileSet(), pos, end), - }, nil -} - func objToNode(ctx context.Context, view View, originPkg *types.Package, obj types.Object, rng span.Range) (ast.Decl, error) { s, err := rng.Span() if err != nil { diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go index e3eb481565..1586016399 100644 --- a/internal/lsp/source/rename.go +++ b/internal/lsp/source/rename.go @@ -185,7 +185,7 @@ func getPkgNameIdentifier(ctx context.Context, ident *IdentifierInfo, pkgName *t wasImplicit: true, } var err error - if decl.mappedRange, err = objToRange(ctx, ident.File.View(), decl.obj); err != nil { + if decl.mappedRange, err = objToMappedRange(ctx, ident.File.View(), decl.obj); err != nil { return nil, err } if decl.node, err = objToNode(ctx, ident.File.View(), ident.pkg.GetTypes(), decl.obj, decl.mappedRange.spanRange); err != nil { diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go index 3d0b995416..4fea5a0e4b 100644 --- a/internal/lsp/source/signature_help.go +++ b/internal/lsp/source/signature_help.go @@ -105,7 +105,7 @@ FindCall: comment *ast.CommentGroup ) if obj != nil { - rng, err := objToRange(ctx, view, obj) + rng, err := objToMappedRange(ctx, view, obj) if err != nil { return nil, err } diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go index 5881cd529a..d307d0152d 100644 --- a/internal/lsp/source/source_test.go +++ b/internal/lsp/source/source_test.go @@ -492,7 +492,7 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) { if err != nil { t.Fatalf("failed for %v: %v", d.Src, err) } - srcRng, err := spanToRange(r.data, d.Src) + _, srcRng, err := spanToRange(r.data, d.Src) if err != nil { t.Fatal(err) } @@ -529,7 +529,7 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) { t.Errorf("for %v got %q want %q", d.Src, hover, expectHover) } } else if !d.OnlyHover { - if defRng, err := spanToRange(r.data, d.Def); err != nil { + if _, defRng, err := spanToRange(r.data, d.Def); err != nil { t.Fatal(err) } else if rng != defRng { t.Errorf("for %v got %v want %v", d.Src, rng, d.Def) @@ -544,25 +544,24 @@ func (r *runner) Highlight(t *testing.T, data tests.Highlights) { ctx := r.ctx for name, locations := range data { src := locations[0] - f, err := r.view.GetFile(ctx, src.URI()) + m, srcRng, err := spanToRange(r.data, src) if err != nil { - t.Fatalf("failed for %v: %v", src, err) + t.Fatal(err) } - tok, err := f.(source.GoFile).GetToken(ctx) - if err != nil { - t.Fatalf("failed to get token for %s: %v", src.URI(), err) - } - pos := tok.Pos(src.Start().Offset()) - highlights, err := source.Highlight(ctx, f.(source.GoFile), pos) + highlights, err := source.Highlight(ctx, r.view, src.URI(), srcRng.Start) if err != nil { t.Errorf("highlight failed for %s: %v", src.URI(), err) } if len(highlights) != len(locations) { t.Errorf("got %d highlights for %s, expected %d", len(highlights), name, len(locations)) } - for i, h := range highlights { - if h != locations[i] { - t.Errorf("want %v, got %v\n", locations[i], h) + for i, got := range highlights { + want, err := m.Range(locations[i]) + if err != nil { + t.Fatal(err) + } + if got != want { + t.Errorf("want %v, got %v\n", want, got) } } } @@ -575,7 +574,7 @@ func (r *runner) Reference(t *testing.T, data tests.References) { if err != nil { t.Fatalf("failed for %v: %v", src, err) } - srcRng, err := spanToRange(r.data, src) + _, srcRng, err := spanToRange(r.data, src) if err != nil { t.Fatal(err) } @@ -624,7 +623,7 @@ func (r *runner) Rename(t *testing.T, data tests.Renames) { if err != nil { t.Fatalf("failed for %v: %v", spn, err) } - srcRng, err := spanToRange(r.data, spn) + _, srcRng, err := spanToRange(r.data, spn) if err != nil { t.Fatal(err) } @@ -704,7 +703,7 @@ func (r *runner) PrepareRename(t *testing.T, data tests.PrepareRenames) { if err != nil { t.Fatal(err) } - srcRng, err := spanToRange(r.data, src) + _, srcRng, err := spanToRange(r.data, src) if err != nil { t.Fatal(err) } @@ -751,7 +750,7 @@ func (r *runner) Symbol(t *testing.T, data tests.Symbols) { if err != nil { t.Fatalf("failed for %v: %v", uri, err) } - symbols, err := source.DocumentSymbols(ctx, f.(source.GoFile)) + symbols, err := source.DocumentSymbols(ctx, r.view, f.(source.GoFile)) if err != nil { t.Errorf("symbols failed for %s: %v", uri, err) } @@ -759,37 +758,37 @@ func (r *runner) Symbol(t *testing.T, data tests.Symbols) { t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols)) continue } - if diff := r.diffSymbols(uri, expectedSymbols, symbols); diff != "" { + if diff := r.diffSymbols(t, uri, expectedSymbols, symbols); diff != "" { t.Error(diff) } } } -func (r *runner) diffSymbols(uri span.URI, want []source.Symbol, got []source.Symbol) string { +func (r *runner) diffSymbols(t *testing.T, uri span.URI, want, got []protocol.DocumentSymbol) string { sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name }) sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name }) if len(got) != len(want) { - return summarizeSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want)) + return summarizeSymbols(t, -1, want, got, "different lengths got %v want %v", len(got), len(want)) } for i, w := range want { g := got[i] if w.Name != g.Name { - return summarizeSymbols(i, want, got, "incorrect name got %v want %v", g.Name, w.Name) + return summarizeSymbols(t, i, want, got, "incorrect name got %v want %v", g.Name, w.Name) } if w.Kind != g.Kind { - return summarizeSymbols(i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind) + return summarizeSymbols(t, i, want, got, "incorrect kind got %v want %v", g.Kind, w.Kind) } - if w.SelectionSpan != g.SelectionSpan { - return summarizeSymbols(i, want, got, "incorrect span got %v want %v", g.SelectionSpan, w.SelectionSpan) + if protocol.CompareRange(w.SelectionRange, g.SelectionRange) != 0 { + return summarizeSymbols(t, i, want, got, "incorrect span got %v want %v", g.SelectionRange, w.SelectionRange) } - if msg := r.diffSymbols(uri, w.Children, g.Children); msg != "" { + if msg := r.diffSymbols(t, uri, w.Children, g.Children); msg != "" { return fmt.Sprintf("children of %s: %s", w.Name, msg) } } return "" } -func summarizeSymbols(i int, want []source.Symbol, got []source.Symbol, reason string, args ...interface{}) string { +func summarizeSymbols(t *testing.T, i int, want, got []protocol.DocumentSymbol, reason string, args ...interface{}) string { msg := &bytes.Buffer{} fmt.Fprint(msg, "document symbols failed") if i >= 0 { @@ -799,11 +798,11 @@ func summarizeSymbols(i int, want []source.Symbol, got []source.Symbol, reason s fmt.Fprintf(msg, reason, args...) fmt.Fprint(msg, ":\nexpected:\n") for _, s := range want { - fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionSpan) + fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange) } fmt.Fprintf(msg, "got:\n") for _, s := range got { - fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionSpan) + fmt.Fprintf(msg, " %v %v %v\n", s.Name, s.Kind, s.SelectionRange) } return msg.String() } @@ -815,7 +814,7 @@ func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) { if err != nil { t.Fatalf("failed for %v: %v", spn, err) } - rng, err := spanToRange(r.data, spn) + _, rng, err := spanToRange(r.data, spn) if err != nil { t.Fatal(err) } @@ -863,15 +862,15 @@ func (r *runner) Link(t *testing.T, data tests.Links) { // This is a pure LSP feature, no source level functionality to be tested. } -func spanToRange(data *tests.Data, span span.Span) (protocol.Range, error) { +func spanToRange(data *tests.Data, span span.Span) (*protocol.ColumnMapper, protocol.Range, error) { contents, err := data.Exported.FileContents(span.URI().Filename()) if err != nil { - return protocol.Range{}, err + return nil, protocol.Range{}, err } m := protocol.NewColumnMapper(span.URI(), span.URI().Filename(), data.Exported.ExpectFileSet, nil, contents) srcRng, err := m.Range(span) if err != nil { - return protocol.Range{}, err + return nil, protocol.Range{}, err } - return srcRng, nil + return m, srcRng, nil } diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go index dd0ddce670..7f2eb91403 100644 --- a/internal/lsp/source/symbols.go +++ b/internal/lsp/source/symbols.go @@ -8,44 +8,16 @@ import ( "context" "fmt" "go/ast" - "go/token" "go/types" - "golang.org/x/tools/internal/span" + "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/telemetry/trace" - errors "golang.org/x/xerrors" ) -type SymbolKind int - -const ( - PackageSymbol SymbolKind = iota - StructSymbol - VariableSymbol - ConstantSymbol - FunctionSymbol - MethodSymbol - InterfaceSymbol - NumberSymbol - StringSymbol - BooleanSymbol - FieldSymbol -) - -type Symbol struct { - Name string - Detail string - Span span.Span - SelectionSpan span.Span - Kind SymbolKind - Children []Symbol -} - -func DocumentSymbols(ctx context.Context, f GoFile) ([]Symbol, error) { +func DocumentSymbols(ctx context.Context, view View, f GoFile) ([]protocol.DocumentSymbol, error) { ctx, done := trace.StartSpan(ctx, "source.DocumentSymbols") defer done() - fset := f.FileSet() file, err := f.GetAST(ctx, ParseFull) if file == nil { return nil, err @@ -57,14 +29,14 @@ func DocumentSymbols(ctx context.Context, f GoFile) ([]Symbol, error) { info := pkg.GetTypesInfo() q := qualifier(file, pkg.GetTypes(), info) - methodsToReceiver := make(map[types.Type][]Symbol) + methodsToReceiver := make(map[types.Type][]protocol.DocumentSymbol) symbolsToReceiver := make(map[types.Type]int) - var symbols []Symbol + var symbols []protocol.DocumentSymbol for _, decl := range file.Decls { switch decl := decl.(type) { case *ast.FuncDecl: if obj := info.ObjectOf(decl.Name); obj != nil { - if fs := funcSymbol(decl, obj, fset, q); fs.Kind == MethodSymbol { + if fs := funcSymbol(ctx, view, decl, obj, q); fs.Kind == protocol.Method { // Store methods separately, as we want them to appear as children // of the corresponding type (which we may not have seen yet). rtype := obj.Type().(*types.Signature).Recv().Type() @@ -78,14 +50,14 @@ func DocumentSymbols(ctx context.Context, f GoFile) ([]Symbol, error) { switch spec := spec.(type) { case *ast.TypeSpec: if obj := info.ObjectOf(spec.Name); obj != nil { - ts := typeSymbol(info, spec, obj, fset, q) + ts := typeSymbol(ctx, view, info, spec, obj, q) symbols = append(symbols, ts) symbolsToReceiver[obj.Type()] = len(symbols) - 1 } case *ast.ValueSpec: for _, name := range spec.Names { if obj := info.ObjectOf(name); obj != nil { - symbols = append(symbols, varSymbol(decl, name, obj, fset, q)) + symbols = append(symbols, varSymbol(ctx, view, decl, name, obj, q)) } } } @@ -109,21 +81,21 @@ func DocumentSymbols(ctx context.Context, f GoFile) ([]Symbol, error) { return symbols, nil } -func funcSymbol(decl *ast.FuncDecl, obj types.Object, fset *token.FileSet, q types.Qualifier) Symbol { - s := Symbol{ +func funcSymbol(ctx context.Context, view View, decl *ast.FuncDecl, obj types.Object, q types.Qualifier) protocol.DocumentSymbol { + s := protocol.DocumentSymbol{ Name: obj.Name(), - Kind: FunctionSymbol, + Kind: protocol.Function, } - if span, err := nodeSpan(decl, fset); err == nil { - s.Span = span + if span, err := nodeToProtocolRange(ctx, view, decl); err == nil { + s.Range = span } - if span, err := nodeSpan(decl.Name, fset); err == nil { - s.SelectionSpan = span + if span, err := nodeToProtocolRange(ctx, view, decl.Name); err == nil { + s.SelectionRange = span } sig, _ := obj.Type().(*types.Signature) if sig != nil { if sig.Recv() != nil { - s.Kind = MethodSymbol + s.Kind = protocol.Method } s.Detail += "(" for i := 0; i < sig.Params().Len(); i++ { @@ -142,16 +114,16 @@ func funcSymbol(decl *ast.FuncDecl, obj types.Object, fset *token.FileSet, q typ return s } -func setKind(s *Symbol, typ types.Type, q types.Qualifier) { +func setKind(s *protocol.DocumentSymbol, typ types.Type, q types.Qualifier) { switch typ := typ.Underlying().(type) { case *types.Interface: - s.Kind = InterfaceSymbol + s.Kind = protocol.Interface case *types.Struct: - s.Kind = StructSymbol + s.Kind = protocol.Struct case *types.Signature: - s.Kind = FunctionSymbol + s.Kind = protocol.Function if typ.Recv() != nil { - s.Kind = MethodSymbol + s.Kind = protocol.Method } case *types.Named: setKind(s, typ.Underlying(), q) @@ -159,45 +131,48 @@ func setKind(s *Symbol, typ types.Type, q types.Qualifier) { i := typ.Info() switch { case i&types.IsNumeric != 0: - s.Kind = NumberSymbol + s.Kind = protocol.Number case i&types.IsBoolean != 0: - s.Kind = BooleanSymbol + s.Kind = protocol.Boolean case i&types.IsString != 0: - s.Kind = StringSymbol + s.Kind = protocol.String } default: - s.Kind = VariableSymbol + s.Kind = protocol.Variable } } -func typeSymbol(info *types.Info, spec *ast.TypeSpec, obj types.Object, fset *token.FileSet, q types.Qualifier) Symbol { - s := Symbol{Name: obj.Name()} +func typeSymbol(ctx context.Context, view View, info *types.Info, spec *ast.TypeSpec, obj types.Object, q types.Qualifier) protocol.DocumentSymbol { + s := protocol.DocumentSymbol{ + Name: obj.Name(), + } s.Detail, _ = formatType(obj.Type(), q) setKind(&s, obj.Type(), q) - if span, err := nodeSpan(spec, fset); err == nil { - s.Span = span + if span, err := nodeToProtocolRange(ctx, view, spec); err == nil { + s.Range = span } - if span, err := nodeSpan(spec.Name, fset); err == nil { - s.SelectionSpan = span + if span, err := nodeToProtocolRange(ctx, view, spec.Name); err == nil { + s.SelectionRange = span } - t, objIsStruct := obj.Type().Underlying().(*types.Struct) st, specIsStruct := spec.Type.(*ast.StructType) if objIsStruct && specIsStruct { for i := 0; i < t.NumFields(); i++ { f := t.Field(i) - child := Symbol{Name: f.Name(), Kind: FieldSymbol} + child := protocol.DocumentSymbol{ + Name: f.Name(), + Kind: protocol.Field, + } child.Detail, _ = formatType(f.Type(), q) spanNode, selectionNode := nodesForStructField(i, st) - if span, err := nodeSpan(spanNode, fset); err == nil { - child.Span = span + if span, err := nodeToProtocolRange(ctx, view, spanNode); err == nil { + child.Range = span } - if span, err := nodeSpan(selectionNode, fset); err == nil { - child.SelectionSpan = span + if span, err := nodeToProtocolRange(ctx, view, selectionNode); err == nil { + child.SelectionRange = span } - s.Children = append(s.Children, child) } } @@ -207,9 +182,9 @@ func typeSymbol(info *types.Info, spec *ast.TypeSpec, obj types.Object, fset *to if objIsInterface && specIsInterface { for i := 0; i < ti.NumExplicitMethods(); i++ { method := ti.ExplicitMethod(i) - child := Symbol{ + child := protocol.DocumentSymbol{ Name: method.Name(), - Kind: MethodSymbol, + Kind: protocol.Method, } var spanNode, selectionNode ast.Node @@ -222,11 +197,11 @@ func typeSymbol(info *types.Info, spec *ast.TypeSpec, obj types.Object, fset *to } } } - if span, err := nodeSpan(spanNode, fset); err == nil { - child.Span = span + if span, err := nodeToProtocolRange(ctx, view, spanNode); err == nil { + child.Range = span } - if span, err := nodeSpan(selectionNode, fset); err == nil { - child.SelectionSpan = span + if span, err := nodeToProtocolRange(ctx, view, selectionNode); err == nil { + child.SelectionRange = span } s.Children = append(s.Children, child) } @@ -238,7 +213,9 @@ func typeSymbol(info *types.Info, spec *ast.TypeSpec, obj types.Object, fset *to continue } - child := Symbol{Name: types.TypeString(embedded, q)} + child := protocol.DocumentSymbol{ + Name: types.TypeString(embedded, q), + } setKind(&child, embedded, q) var spanNode, selectionNode ast.Node Embeddeds: @@ -252,12 +229,11 @@ func typeSymbol(info *types.Info, spec *ast.TypeSpec, obj types.Object, fset *to break Embeddeds } } - - if span, err := nodeSpan(spanNode, fset); err == nil { - child.Span = span + if rng, err := nodeToProtocolRange(ctx, view, spanNode); err == nil { + child.Range = rng } - if span, err := nodeSpan(selectionNode, fset); err == nil { - child.SelectionSpan = span + if span, err := nodeToProtocolRange(ctx, view, selectionNode); err == nil { + child.SelectionRange = span } s.Children = append(s.Children, child) } @@ -285,28 +261,20 @@ func nodesForStructField(i int, st *ast.StructType) (span, selection ast.Node) { return nil, nil } -func varSymbol(decl ast.Node, name *ast.Ident, obj types.Object, fset *token.FileSet, q types.Qualifier) Symbol { - s := Symbol{ +func varSymbol(ctx context.Context, view View, decl ast.Node, name *ast.Ident, obj types.Object, q types.Qualifier) protocol.DocumentSymbol { + s := protocol.DocumentSymbol{ Name: obj.Name(), - Kind: VariableSymbol, + Kind: protocol.Variable, } if _, ok := obj.(*types.Const); ok { - s.Kind = ConstantSymbol + s.Kind = protocol.Constant } - if span, err := nodeSpan(decl, fset); err == nil { - s.Span = span + if rng, err := nodeToProtocolRange(ctx, view, decl); err == nil { + s.Range = rng } - if span, err := nodeSpan(name, fset); err == nil { - s.SelectionSpan = span + if span, err := nodeToProtocolRange(ctx, view, name); err == nil { + s.SelectionRange = span } s.Detail = types.TypeString(obj.Type(), q) return s } - -func nodeSpan(n ast.Node, fset *token.FileSet) (span.Span, error) { - if n == nil { - return span.Span{}, errors.New("no span for nil node") - } - r := span.NewRange(fset, n.Pos(), n.End()) - return r.Span() -} diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go index cc19699340..dc7b6f1e68 100644 --- a/internal/lsp/source/util.go +++ b/internal/lsp/source/util.go @@ -158,6 +158,59 @@ func IsGenerated(ctx context.Context, view View, uri span.URI) bool { return false } +func nodeToProtocolRange(ctx context.Context, view View, n ast.Node) (protocol.Range, error) { + mrng, err := nodeToMappedRange(ctx, view, n) + if err != nil { + return protocol.Range{}, err + } + return mrng.Range() +} + +func objToMappedRange(ctx context.Context, view View, obj types.Object) (mappedRange, error) { + if pkgName, ok := obj.(*types.PkgName); ok { + // An imported Go package has a package-local, unqualified name. + // When the name matches the imported package name, there is no + // identifier in the import spec with the local package name. + // + // For example: + // import "go/ast" // name "ast" matches package name + // import a "go/ast" // name "a" does not match package name + // + // When the identifier does not appear in the source, have the range + // of the object be the point at the beginning of the declaration. + if pkgName.Imported().Name() == pkgName.Name() { + return nameToMappedRange(ctx, view, obj.Pos(), "") + } + } + return nameToMappedRange(ctx, view, obj.Pos(), obj.Name()) +} + +func nameToMappedRange(ctx context.Context, view View, pos token.Pos, name string) (mappedRange, error) { + return posToRange(ctx, view, pos, pos+token.Pos(len(name))) +} + +func nodeToMappedRange(ctx context.Context, view View, n ast.Node) (mappedRange, error) { + return posToRange(ctx, view, n.Pos(), n.End()) +} + +func posToRange(ctx context.Context, view View, pos, end token.Pos) (mappedRange, error) { + if !pos.IsValid() { + return mappedRange{}, errors.Errorf("invalid position for %v", pos) + } + if !end.IsValid() { + return mappedRange{}, errors.Errorf("invalid position for %v", end) + } + posn := view.Session().Cache().FileSet().Position(pos) + _, m, err := cachedFileToMapper(ctx, view, span.FileURI(posn.Filename)) + if err != nil { + return mappedRange{}, err + } + return mappedRange{ + m: m, + spanRange: span.NewRange(view.Session().Cache().FileSet(), pos, end), + }, nil +} + // Matches cgo generated comment as well as the proposed standard: // https://golang.org/s/generatedcode var generatedRx = regexp.MustCompile(`// .*DO NOT EDIT\.?`) diff --git a/internal/lsp/symbols.go b/internal/lsp/symbols.go index 5081662b71..addbe443e0 100644 --- a/internal/lsp/symbols.go +++ b/internal/lsp/symbols.go @@ -16,68 +16,12 @@ import ( func (s *Server) documentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) { ctx, done := trace.StartSpan(ctx, "lsp.Server.documentSymbol") defer done() + uri := span.NewURI(params.TextDocument.URI) view := s.session.ViewOf(uri) f, err := getGoFile(ctx, view, uri) if err != nil { return nil, err } - m, err := getMapper(ctx, f) - if err != nil { - return nil, err - } - symbols, err := source.DocumentSymbols(ctx, f) - if err != nil { - return nil, err - } - return toProtocolDocumentSymbols(m, symbols), nil -} - -func toProtocolDocumentSymbols(m *protocol.ColumnMapper, symbols []source.Symbol) []protocol.DocumentSymbol { - result := make([]protocol.DocumentSymbol, 0, len(symbols)) - for _, s := range symbols { - ps := protocol.DocumentSymbol{ - Name: s.Name, - Kind: toProtocolSymbolKind(s.Kind), - Detail: s.Detail, - Children: toProtocolDocumentSymbols(m, s.Children), - } - if r, err := m.Range(s.Span); err == nil { - ps.Range = r - } - if r, err := m.Range(s.SelectionSpan); err == nil { - ps.SelectionRange = r - } - result = append(result, ps) - } - return result -} - -func toProtocolSymbolKind(kind source.SymbolKind) protocol.SymbolKind { - switch kind { - case source.StructSymbol: - return protocol.Struct - case source.PackageSymbol: - return protocol.Package - case source.VariableSymbol: - return protocol.Variable - case source.ConstantSymbol: - return protocol.Constant - case source.FunctionSymbol: - return protocol.Function - case source.MethodSymbol: - return protocol.Method - case source.InterfaceSymbol: - return protocol.Interface - case source.NumberSymbol: - return protocol.Number - case source.StringSymbol: - return protocol.String - case source.BooleanSymbol: - return protocol.Boolean - case source.FieldSymbol: - return protocol.Field - default: - return 0 - } + return source.DocumentSymbols(ctx, view, f) } diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 3c3d33a1da..98ec4df693 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -67,8 +67,8 @@ type Highlights map[string][]span.Span type References map[span.Span][]span.Span type Renames map[span.Span]string type PrepareRenames map[span.Span]*source.PrepareItem -type Symbols map[span.URI][]source.Symbol -type SymbolsChildren map[string][]source.Symbol +type Symbols map[span.URI][]protocol.DocumentSymbol +type SymbolsChildren map[string][]protocol.DocumentSymbol type Signatures map[span.Span]*source.SignatureInformation type Links map[span.URI][]Link @@ -642,10 +642,19 @@ func (data *Data) collectPrepareRenames(src span.Span, rng span.Range, placehold } func (data *Data) collectSymbols(name string, spn span.Span, kind string, parentName string) { - sym := source.Symbol{ - Name: name, - Kind: source.ParseSymbolKind(kind), - SelectionSpan: spn, + contents, err := data.Exported.FileContents(spn.URI().Filename()) + if err != nil { + return + } + m := protocol.NewColumnMapper(spn.URI(), spn.URI().Filename(), data.Exported.ExpectFileSet, nil, contents) + rng, err := m.Range(spn) + if err != nil { + return + } + sym := protocol.DocumentSymbol{ + Name: name, + Kind: protocol.ParseSymbolKind(kind), + SelectionRange: rng, } if parentName == "" { data.Symbols[spn.URI()] = append(data.Symbols[spn.URI()], sym)