mirror of
https://github.com/golang/go
synced 2024-11-18 14:14:46 -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:
parent
6732636ccd
commit
4796d4bd3d
5
internal/lsp/cache/file.go
vendored
5
internal/lsp/cache/file.go
vendored
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
69
internal/lsp/source/hover.go
Normal file
69
internal/lsp/source/hover.go
Normal 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
|
||||
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user