1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:34:51 -07:00

internal/lsp: use ast.Nodes for hover information

This change associates an ast.Node for some object declarations.
In this case, we only handle type declarations, but future changes will
support other objects as well. This is the first step in adding
documentation on hover.

Updates golang/go#29151

Change-Id: I39ddccf4130ee3b106725286375cd74bc51bcd38
Reviewed-on: https://go-review.googlesource.com/c/tools/+/172661
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-04-17 18:21:47 -04:00
parent 6732636ccd
commit 4796d4bd3d
7 changed files with 120 additions and 14 deletions

View File

@ -40,6 +40,11 @@ func (f *File) URI() span.URI {
return f.uris[0]
}
// View returns the view associated with the file.
func (f *File) View() source.View {
return f.view
}
// GetContent returns the contents of the file, reading it from file system if needed.
func (f *File) GetContent(ctx context.Context) []byte {
f.view.mu.Lock()

View File

@ -164,6 +164,10 @@ func (s *Server) processConfig(view *cache.View, config interface{}) error {
if usePlaceholders, ok := c["usePlaceholders"].(bool); ok {
s.usePlaceholders = usePlaceholders
}
// Check if enhancedHover is enabled.
if enhancedHover, ok := c["enhancedHover"].(bool); ok {
s.enhancedHover = enhancedHover
}
return nil
}

View File

@ -6,6 +6,7 @@ package lsp
import (
"context"
"fmt"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
@ -31,7 +32,7 @@ func (s *Server) hover(ctx context.Context, params *protocol.TextDocumentPositio
if err != nil {
return nil, err
}
content, err := ident.Hover(ctx, nil)
decl, doc, err := ident.Hover(ctx, nil, s.enhancedHover)
if err != nil {
return nil, err
}
@ -44,20 +45,23 @@ func (s *Server) hover(ctx context.Context, params *protocol.TextDocumentPositio
return nil, err
}
return &protocol.Hover{
Contents: markupContent(content, s.preferredContentFormat),
Contents: markupContent(decl, doc, s.preferredContentFormat),
Range: &rng,
}, nil
}
func markupContent(content string, kind protocol.MarkupKind) protocol.MarkupContent {
func markupContent(decl, doc string, kind protocol.MarkupKind) protocol.MarkupContent {
result := protocol.MarkupContent{
Kind: kind,
}
switch kind {
case protocol.PlainText:
result.Value = content
result.Value = decl
case protocol.Markdown:
result.Value = "```go\n" + content + "\n```"
result.Value = "```go\n" + decl + "\n```"
}
if doc != "" {
result.Value = fmt.Sprintf("%s\n%s", doc, result.Value)
}
return result
}

View File

@ -74,6 +74,7 @@ type Server struct {
// Configurations.
// TODO(rstambler): Separate these into their own struct?
usePlaceholders bool
enhancedHover bool
insertTextFormat protocol.InsertTextFormat
configurationSupported bool
dynamicConfigurationSupported bool

View File

@ -0,0 +1,69 @@
// Copyright 201p 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"
"fmt"
"go/ast"
"go/format"
"go/token"
"go/types"
)
func (i *IdentifierInfo) Hover(ctx context.Context, q types.Qualifier, enhancedHover bool) (string, string, error) {
file := i.File.GetAST(ctx)
if q == nil {
pkg := i.File.GetPackage(ctx)
q = qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
}
// TODO(rstambler): Remove this configuration when hover behavior is stable.
if enhancedHover {
switch obj := i.Declaration.Object.(type) {
case *types.TypeName:
if node, ok := i.Declaration.Node.(*ast.GenDecl); ok {
if decl, doc, err := formatTypeName(i.File.GetFileSet(ctx), node, obj, q); err == nil {
return decl, doc, nil
} else {
// Swallow errors so we can return a best-effort response using types.TypeString.
i.File.View().Logger().Errorf(ctx, "no hover for TypeName %v: %v", obj.Name(), err)
}
}
return types.TypeString(obj.Type(), q), "", nil
default:
return types.ObjectString(obj, q), "", nil
}
}
return types.ObjectString(i.Declaration.Object, q), "", nil
}
func formatTypeName(fset *token.FileSet, decl *ast.GenDecl, obj *types.TypeName, q types.Qualifier) (string, string, error) {
if types.IsInterface(obj.Type()) {
return "", "", fmt.Errorf("no support for interfaces yet")
}
switch t := obj.Type().(type) {
case *types.Struct:
return formatStructType(fset, decl, t)
case *types.Named:
if under, ok := t.Underlying().(*types.Struct); ok {
return formatStructType(fset, decl, under)
}
}
return "", "", fmt.Errorf("no supported for %v, which is of type %T", obj.Name(), obj.Type())
}
func formatStructType(fset *token.FileSet, decl *ast.GenDecl, typ *types.Struct) (string, string, error) {
if len(decl.Specs) != 1 {
return "", "", fmt.Errorf("expected 1 TypeSpec got %v", len(decl.Specs))
}
b := bytes.NewBuffer(nil)
if err := format.Node(b, fset, decl.Specs[0]); err != nil {
return "", "", err
}
doc := decl.Doc.Text()
return b.String(), doc, nil
}

View File

@ -26,6 +26,7 @@ type IdentifierInfo struct {
}
Declaration struct {
Range span.Range
Node ast.Decl
Object types.Object
}
@ -49,15 +50,6 @@ func Identifier(ctx context.Context, v View, f File, pos token.Pos) (*Identifier
return result, err
}
func (i *IdentifierInfo) Hover(ctx context.Context, q types.Qualifier) (string, error) {
if q == nil {
fAST := i.File.GetAST(ctx)
pkg := i.File.GetPackage(ctx)
q = qualifier(fAST, pkg.GetTypes(), pkg.GetTypesInfo())
}
return types.ObjectString(i.Declaration.Object, q), nil
}
// identifier checks a single position for a potential identifier.
func identifier(ctx context.Context, v View, f File, pos token.Pos) (*IdentifierInfo, error) {
fAST := f.GetAST(ctx)
@ -105,6 +97,9 @@ func identifier(ctx context.Context, v View, f File, pos token.Pos) (*Identifier
if result.Declaration.Range, err = objToRange(ctx, v, result.Declaration.Object); err != nil {
return nil, err
}
if result.Declaration.Node, err = objToNode(ctx, v, result.Declaration.Object, result.Declaration.Range); err != nil {
return nil, err
}
typ := pkg.GetTypesInfo().TypeOf(result.ident)
if typ == nil {
return nil, fmt.Errorf("no type for %s", result.Name)
@ -140,3 +135,30 @@ func objToRange(ctx context.Context, v View, obj types.Object) (span.Range, erro
}
return span.NewRange(v.FileSet(), p, p+token.Pos(len(obj.Name()))), nil
}
func objToNode(ctx context.Context, v View, obj types.Object, rng span.Range) (ast.Decl, error) {
s, err := rng.Span()
if err != nil {
return nil, err
}
declFile, err := v.GetFile(ctx, s.URI())
if err != nil {
return nil, err
}
declAST := declFile.GetAST(ctx)
path, _ := astutil.PathEnclosingInterval(declAST, rng.Start, rng.End)
if path == nil {
return nil, fmt.Errorf("no path for range %v", rng)
}
// TODO(rstambler): Support other node types.
// For now, we only associate an ast.Node for type declarations.
switch obj.Type().(type) {
case *types.Named, *types.Struct, *types.Interface:
for _, node := range path {
if node, ok := node.(*ast.GenDecl); ok && node.Tok == token.TYPE {
return node, nil
}
}
}
return nil, nil // didn't find a node, but no error
}

View File

@ -34,6 +34,7 @@ type View interface {
// the loading of packages into their own caching systems.
type File interface {
URI() span.URI
View() View
GetAST(ctx context.Context) *ast.File
GetFileSet(ctx context.Context) *token.FileSet
GetPackage(ctx context.Context) Package