go/internal/lsp/source/hover.go
Rebecca Stambler abbb706b23 internal/lsp: enable enhanced hover by default
This change removes the explicit configuration for improved
documentation on hover. We use a comment's synopsis rather than the full
comment.

However, we also add a "noDocsOnHover" setting that is used by the cmd
tests. Ultimately, no one should use this setting and we should remove
it. We leave it temporarily because the cmd tests still need work.

Change-Id: I5488eca96a729ed7edad8f59b95af163903740d6
Reviewed-on: https://go-review.googlesource.com/c/tools/+/174378
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-05-16 17:30:22 +00:00

139 lines
3.7 KiB
Go

// 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 (
"context"
"fmt"
"go/ast"
"go/doc"
"go/format"
"go/token"
"go/types"
"strings"
)
// formatter returns the a hover value formatted with its documentation.
type formatter func(interface{}, *ast.CommentGroup) (string, error)
func (i *IdentifierInfo) Hover(ctx context.Context, qf types.Qualifier, markdownSupported, wantComments bool) (string, error) {
file := i.File.GetAST(ctx)
if qf == nil {
pkg := i.File.GetPackage(ctx)
qf = qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
}
var b strings.Builder
f := func(x interface{}, c *ast.CommentGroup) (string, error) {
if !wantComments {
c = nil
}
return writeHover(x, i.File.GetFileSet(ctx), &b, c, markdownSupported, qf)
}
obj := i.Declaration.Object
switch node := i.Declaration.Node.(type) {
case *ast.GenDecl:
switch obj := obj.(type) {
case *types.TypeName, *types.Var, *types.Const, *types.Func:
return formatGenDecl(node, obj, obj.Type(), f)
}
case *ast.FuncDecl:
if _, ok := obj.(*types.Func); ok {
return f(obj, node.Doc)
}
}
return f(obj, nil)
}
func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type, f formatter) (string, error) {
if _, ok := typ.(*types.Named); ok {
switch typ.Underlying().(type) {
case *types.Interface, *types.Struct:
return formatGenDecl(node, obj, typ.Underlying(), f)
}
}
var spec ast.Spec
for _, s := range node.Specs {
if s.Pos() <= obj.Pos() && obj.Pos() <= s.End() {
spec = s
break
}
}
if spec == nil {
return "", fmt.Errorf("no spec for node %v at position %v", node, obj.Pos())
}
// If we have a field or method.
switch obj.(type) {
case *types.Var, *types.Const, *types.Func:
return formatVar(spec, obj, f)
}
// Handle types.
switch spec := spec.(type) {
case *ast.TypeSpec:
if len(node.Specs) > 1 {
// If multiple types are declared in the same block.
return f(spec.Type, spec.Doc)
} else {
return f(spec, node.Doc)
}
case *ast.ValueSpec:
return f(spec, spec.Doc)
case *ast.ImportSpec:
return f(spec, spec.Doc)
}
return "", fmt.Errorf("unable to format spec %v (%T)", spec, spec)
}
func formatVar(node ast.Spec, obj types.Object, f formatter) (string, error) {
var fieldList *ast.FieldList
if spec, ok := node.(*ast.TypeSpec); ok {
switch t := spec.Type.(type) {
case *ast.StructType:
fieldList = t.Fields
case *ast.InterfaceType:
fieldList = t.Methods
}
}
// If we have a struct or interface declaration,
// we need to match the object to the corresponding field or method.
if fieldList != nil {
for i := 0; i < len(fieldList.List); i++ {
field := fieldList.List[i]
if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() {
if field.Doc.Text() != "" {
return f(obj, field.Doc)
} else if field.Comment.Text() != "" {
return f(obj, field.Comment)
}
}
}
}
// If we weren't able to find documentation for the object.
return f(obj, nil)
}
// writeHover writes the hover for a given node and its documentation.
func writeHover(x interface{}, fset *token.FileSet, b *strings.Builder, c *ast.CommentGroup, markdownSupported bool, qf types.Qualifier) (string, error) {
if c != nil {
// TODO(rstambler): Improve conversion from Go docs to markdown.
b.WriteString(doc.Synopsis(c.Text()))
b.WriteRune('\n')
}
if markdownSupported {
b.WriteString("```go\n")
}
switch x := x.(type) {
case ast.Node:
if err := format.Node(b, fset, x); err != nil {
return "", err
}
case types.Object:
b.WriteString(types.ObjectString(x, qf))
}
if markdownSupported {
b.WriteString("\n```")
}
return b.String(), nil
}