diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 3c3e6e2ab0f..842ae35676a 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -125,6 +125,7 @@ func (s *server) Initialize(ctx context.Context, params *protocol.InitializePara DefinitionProvider: true, DocumentFormattingProvider: true, DocumentRangeFormattingProvider: true, + DocumentSymbolProvider: true, HoverProvider: true, SignatureHelpProvider: &protocol.SignatureHelpOptions{ TriggerCharacters: []string{"(", ","}, @@ -426,8 +427,13 @@ func (s *server) DocumentHighlight(context.Context, *protocol.TextDocumentPositi return nil, notImplemented("DocumentHighlight") } -func (s *server) DocumentSymbol(context.Context, *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) { - return nil, notImplemented("DocumentSymbol") +func (s *server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) { + f, m, err := newColumnMap(ctx, s.view, span.URI(params.TextDocument.URI)) + if err != nil { + return nil, err + } + symbols := source.DocumentSymbols(ctx, f) + return toProtocolDocumentSymbols(m, symbols), nil } func (s *server) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go new file mode 100644 index 00000000000..3a0b5ce284a --- /dev/null +++ b/internal/lsp/source/symbols.go @@ -0,0 +1,129 @@ +// 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 source + +import ( + "bytes" + "context" + "go/ast" + "go/format" + "go/token" + + "golang.org/x/tools/internal/span" +) + +type SymbolKind int + +const ( + PackageSymbol SymbolKind = iota + StructSymbol + VariableSymbol + ConstantSymbol + FunctionSymbol + MethodSymbol +) + +type Symbol struct { + Name string + Detail string + Span span.Span + Kind SymbolKind + Children []Symbol +} + +func DocumentSymbols(ctx context.Context, f File) []Symbol { + var symbols []Symbol + fset := f.GetFileSet(ctx) + astFile := f.GetAST(ctx) + for _, decl := range astFile.Decls { + switch decl := decl.(type) { + case *ast.FuncDecl: + symbols = append(symbols, funcSymbol(decl, fset)) + case *ast.GenDecl: + for _, spec := range decl.Specs { + switch spec := spec.(type) { + case *ast.ImportSpec: + symbols = append(symbols, importSymbol(spec, fset)) + case *ast.TypeSpec: + symbols = append(symbols, typeSymbol(spec, fset)) + case *ast.ValueSpec: + for _, name := range spec.Names { + symbols = append(symbols, varSymbol(decl, name, fset)) + } + } + } + } + } + return symbols +} + +func funcSymbol(decl *ast.FuncDecl, fset *token.FileSet) Symbol { + s := Symbol{ + Name: decl.Name.String(), + Kind: FunctionSymbol, + } + + if decl.Recv != nil { + s.Kind = MethodSymbol + } + + span, err := nodeSpan(decl, fset) + if err == nil { + s.Span = span + } + buf := &bytes.Buffer{} + if err := format.Node(buf, fset, decl); err == nil { + s.Detail = buf.String() + } + return s +} + +func importSymbol(spec *ast.ImportSpec, fset *token.FileSet) Symbol { + s := Symbol{ + Name: spec.Path.Value, + Kind: PackageSymbol, + Detail: "import " + spec.Path.Value, + } + span, err := nodeSpan(spec, fset) + if err == nil { + s.Span = span + } + return s +} + +func typeSymbol(spec *ast.TypeSpec, fset *token.FileSet) Symbol { + s := Symbol{ + Name: spec.Name.String(), + Kind: StructSymbol, + } + span, err := nodeSpan(spec, fset) + if err == nil { + s.Span = span + } + return s +} + +func varSymbol(decl *ast.GenDecl, name *ast.Ident, fset *token.FileSet) Symbol { + s := Symbol{ + Name: name.Name, + Kind: VariableSymbol, + } + + if decl.Tok == token.CONST { + s.Kind = ConstantSymbol + } + + span, err := nodeSpan(name, fset) + if err == nil { + s.Span = span + } + + return s +} + +func nodeSpan(n ast.Node, fset *token.FileSet) (span.Span, error) { + r := span.NewRange(fset, n.Pos(), n.End()) + return r.Span() +} diff --git a/internal/lsp/symbols.go b/internal/lsp/symbols.go new file mode 100644 index 00000000000..794a79f91e0 --- /dev/null +++ b/internal/lsp/symbols.go @@ -0,0 +1,48 @@ +// 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 lsp + +import ( + "golang.org/x/tools/internal/lsp/source" + + "golang.org/x/tools/internal/lsp/protocol" +) + +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 + 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 + default: + return 0 + } +}