internal/lsp: attach documentation to signature help
This change refactors hover to generate documentation for just the declaration portion of an identifier. Updates golang/go#29151 Change-Id: I16d48a99b56c36132e49cc87e2736f85c88ed14a Reviewed-on: https://go-review.googlesource.com/c/tools/+/180657 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
d0a3d01286
commit
0139d5756a
|
@ -31,7 +31,7 @@ func (s *Server) definition(ctx context.Context, params *protocol.TextDocumentPo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
decSpan, err := ident.Declaration.Range.Span()
|
decSpan, err := ident.DeclarationRange().Span()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ func (s *Server) hover(ctx context.Context, params *protocol.TextDocumentPositio
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hover, err := ident.Hover(ctx, nil, s.preferredContentFormat == protocol.Markdown, !s.noDocsOnHover)
|
hover, err := ident.Hover(ctx, s.preferredContentFormat == protocol.Markdown, !s.noDocsOnHover)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ func toProtocolSignatureHelp(info *source.SignatureInformation) *protocol.Signat
|
||||||
Signatures: []protocol.SignatureInformation{
|
Signatures: []protocol.SignatureInformation{
|
||||||
{
|
{
|
||||||
Label: info.Label,
|
Label: info.Label,
|
||||||
|
Documentation: info.Documentation,
|
||||||
Parameters: toProtocolParameterInformation(info.Parameters),
|
Parameters: toProtocolParameterInformation(info.Parameters),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,52 +15,55 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// formatter returns the a hover value formatted with its documentation.
|
type documentation struct {
|
||||||
type formatter func(interface{}, *ast.CommentGroup) (string, error)
|
source interface{}
|
||||||
|
comment *ast.CommentGroup
|
||||||
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) {
|
func (i *IdentifierInfo) Hover(ctx context.Context, markdownSupported, wantComments bool) (string, error) {
|
||||||
|
h, err := i.decl.hover(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
c := h.comment
|
||||||
if !wantComments {
|
if !wantComments {
|
||||||
c = nil
|
c = nil
|
||||||
}
|
}
|
||||||
return writeHover(x, i.File.FileSet(), &b, c, markdownSupported, qf)
|
var b strings.Builder
|
||||||
|
return writeHover(h.source, i.File.FileSet(), &b, c, markdownSupported, i.qf)
|
||||||
}
|
}
|
||||||
obj := i.Declaration.Object
|
|
||||||
switch node := i.Declaration.Node.(type) {
|
func (d declaration) hover(ctx context.Context) (*documentation, error) {
|
||||||
|
obj := d.obj
|
||||||
|
switch node := d.node.(type) {
|
||||||
case *ast.GenDecl:
|
case *ast.GenDecl:
|
||||||
switch obj := obj.(type) {
|
switch obj := obj.(type) {
|
||||||
case *types.TypeName, *types.Var, *types.Const, *types.Func:
|
case *types.TypeName, *types.Var, *types.Const, *types.Func:
|
||||||
return formatGenDecl(node, obj, obj.Type(), f)
|
return formatGenDecl(node, obj, obj.Type())
|
||||||
}
|
}
|
||||||
case *ast.TypeSpec:
|
case *ast.TypeSpec:
|
||||||
if obj.Parent() == types.Universe {
|
if obj.Parent() == types.Universe {
|
||||||
if obj.Name() == "error" {
|
if obj.Name() == "error" {
|
||||||
return f(node, nil)
|
return &documentation{node, nil}, nil
|
||||||
}
|
}
|
||||||
return f(node.Name, nil) // comments not needed for builtins
|
return &documentation{node.Name, nil}, nil // comments not needed for builtins
|
||||||
}
|
}
|
||||||
case *ast.FuncDecl:
|
case *ast.FuncDecl:
|
||||||
switch obj.(type) {
|
switch obj.(type) {
|
||||||
case *types.Func:
|
case *types.Func:
|
||||||
return f(obj, node.Doc)
|
return &documentation{obj, node.Doc}, nil
|
||||||
case *types.Builtin:
|
case *types.Builtin:
|
||||||
return f(node.Type, node.Doc)
|
return &documentation{node.Type, node.Doc}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return f(obj, nil)
|
return &documentation{obj, nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type, f formatter) (string, error) {
|
func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*documentation, error) {
|
||||||
if _, ok := typ.(*types.Named); ok {
|
if _, ok := typ.(*types.Named); ok {
|
||||||
switch typ.Underlying().(type) {
|
switch typ.Underlying().(type) {
|
||||||
case *types.Interface, *types.Struct:
|
case *types.Interface, *types.Struct:
|
||||||
return formatGenDecl(node, obj, typ.Underlying(), f)
|
return formatGenDecl(node, obj, typ.Underlying())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var spec ast.Spec
|
var spec ast.Spec
|
||||||
|
@ -71,31 +74,31 @@ func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type, f format
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if spec == nil {
|
if spec == nil {
|
||||||
return "", fmt.Errorf("no spec for node %v at position %v", node, obj.Pos())
|
return nil, fmt.Errorf("no spec for node %v at position %v", node, obj.Pos())
|
||||||
}
|
}
|
||||||
// If we have a field or method.
|
// If we have a field or method.
|
||||||
switch obj.(type) {
|
switch obj.(type) {
|
||||||
case *types.Var, *types.Const, *types.Func:
|
case *types.Var, *types.Const, *types.Func:
|
||||||
return formatVar(spec, obj, f)
|
return formatVar(spec, obj)
|
||||||
}
|
}
|
||||||
// Handle types.
|
// Handle types.
|
||||||
switch spec := spec.(type) {
|
switch spec := spec.(type) {
|
||||||
case *ast.TypeSpec:
|
case *ast.TypeSpec:
|
||||||
if len(node.Specs) > 1 {
|
if len(node.Specs) > 1 {
|
||||||
// If multiple types are declared in the same block.
|
// If multiple types are declared in the same block.
|
||||||
return f(spec.Type, spec.Doc)
|
return &documentation{spec.Type, spec.Doc}, nil
|
||||||
} else {
|
} else {
|
||||||
return f(spec, node.Doc)
|
return &documentation{spec, node.Doc}, nil
|
||||||
}
|
}
|
||||||
case *ast.ValueSpec:
|
case *ast.ValueSpec:
|
||||||
return f(spec, spec.Doc)
|
return &documentation{spec, spec.Doc}, nil
|
||||||
case *ast.ImportSpec:
|
case *ast.ImportSpec:
|
||||||
return f(spec, spec.Doc)
|
return &documentation{spec, spec.Doc}, nil
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("unable to format spec %v (%T)", spec, spec)
|
return nil, fmt.Errorf("unable to format spec %v (%T)", spec, spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatVar(node ast.Spec, obj types.Object, f formatter) (string, error) {
|
func formatVar(node ast.Spec, obj types.Object) (*documentation, error) {
|
||||||
var fieldList *ast.FieldList
|
var fieldList *ast.FieldList
|
||||||
if spec, ok := node.(*ast.TypeSpec); ok {
|
if spec, ok := node.(*ast.TypeSpec); ok {
|
||||||
switch t := spec.Type.(type) {
|
switch t := spec.Type.(type) {
|
||||||
|
@ -112,22 +115,22 @@ func formatVar(node ast.Spec, obj types.Object, f formatter) (string, error) {
|
||||||
field := fieldList.List[i]
|
field := fieldList.List[i]
|
||||||
if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() {
|
if field.Pos() <= obj.Pos() && obj.Pos() <= field.End() {
|
||||||
if field.Doc.Text() != "" {
|
if field.Doc.Text() != "" {
|
||||||
return f(obj, field.Doc)
|
return &documentation{obj, field.Doc}, nil
|
||||||
} else if field.Comment.Text() != "" {
|
} else if field.Comment.Text() != "" {
|
||||||
return f(obj, field.Comment)
|
return &documentation{obj, field.Comment}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we weren't able to find documentation for the object.
|
// If we weren't able to find documentation for the object.
|
||||||
return f(obj, nil)
|
return &documentation{obj, nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeHover writes the hover for a given node and its documentation.
|
// 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) {
|
func writeHover(x interface{}, fset *token.FileSet, b *strings.Builder, c *ast.CommentGroup, markdownSupported bool, qf types.Qualifier) (string, error) {
|
||||||
if c != nil {
|
if c != nil {
|
||||||
// TODO(rstambler): Improve conversion from Go docs to markdown.
|
// TODO(rstambler): Improve conversion from Go docs to markdown.
|
||||||
b.WriteString(doc.Synopsis(c.Text()))
|
b.WriteString(formatDocumentation(c))
|
||||||
b.WriteRune('\n')
|
b.WriteRune('\n')
|
||||||
}
|
}
|
||||||
if markdownSupported {
|
if markdownSupported {
|
||||||
|
@ -146,3 +149,10 @@ func writeHover(x interface{}, fset *token.FileSet, b *strings.Builder, c *ast.C
|
||||||
}
|
}
|
||||||
return b.String(), nil
|
return b.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatDocumentation(c *ast.CommentGroup) string {
|
||||||
|
if c == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return doc.Synopsis(c.Text())
|
||||||
|
}
|
||||||
|
|
|
@ -25,14 +25,21 @@ type IdentifierInfo struct {
|
||||||
Range span.Range
|
Range span.Range
|
||||||
Object types.Object
|
Object types.Object
|
||||||
}
|
}
|
||||||
Declaration struct {
|
decl declaration
|
||||||
Range span.Range
|
|
||||||
Node ast.Node
|
|
||||||
Object types.Object
|
|
||||||
}
|
|
||||||
|
|
||||||
ident *ast.Ident
|
ident *ast.Ident
|
||||||
wasEmbeddedField bool
|
wasEmbeddedField bool
|
||||||
|
qf types.Qualifier
|
||||||
|
}
|
||||||
|
|
||||||
|
type declaration struct {
|
||||||
|
rng span.Range
|
||||||
|
node ast.Node
|
||||||
|
obj types.Object
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IdentifierInfo) DeclarationRange() span.Range {
|
||||||
|
return i.decl.rng
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identifier returns identifier information for a position
|
// Identifier returns identifier information for a position
|
||||||
|
@ -72,7 +79,10 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := &IdentifierInfo{File: f}
|
result := &IdentifierInfo{
|
||||||
|
File: f,
|
||||||
|
qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
|
||||||
|
}
|
||||||
|
|
||||||
switch node := path[0].(type) {
|
switch node := path[0].(type) {
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
|
@ -91,21 +101,21 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
|
||||||
}
|
}
|
||||||
result.Name = result.ident.Name
|
result.Name = result.ident.Name
|
||||||
result.Range = span.NewRange(f.FileSet(), result.ident.Pos(), result.ident.End())
|
result.Range = span.NewRange(f.FileSet(), result.ident.Pos(), result.ident.End())
|
||||||
result.Declaration.Object = pkg.GetTypesInfo().ObjectOf(result.ident)
|
result.decl.obj = pkg.GetTypesInfo().ObjectOf(result.ident)
|
||||||
if result.Declaration.Object == nil {
|
if result.decl.obj == nil {
|
||||||
return nil, fmt.Errorf("no object for ident %v", result.Name)
|
return nil, fmt.Errorf("no object for ident %v", result.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Handle builtins separately.
|
// Handle builtins separately.
|
||||||
if result.Declaration.Object.Parent() == types.Universe {
|
if result.decl.obj.Parent() == types.Universe {
|
||||||
decl, ok := lookupBuiltinDecl(f.View(), result.Name).(ast.Node)
|
decl, ok := lookupBuiltinDecl(f.View(), result.Name).(ast.Node)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no declaration for %s", result.Name)
|
return nil, fmt.Errorf("no declaration for %s", result.Name)
|
||||||
}
|
}
|
||||||
result.Declaration.Node = decl
|
result.decl.node = decl
|
||||||
if result.Declaration.Range, err = posToRange(ctx, f.FileSet(), result.Name, decl.Pos()); err != nil {
|
if result.decl.rng, err = posToRange(ctx, f.FileSet(), result.Name, decl.Pos()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
|
@ -114,17 +124,17 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
|
||||||
if result.wasEmbeddedField {
|
if result.wasEmbeddedField {
|
||||||
// The original position was on the embedded field declaration, so we
|
// The original position was on the embedded field declaration, so we
|
||||||
// try to dig out the type and jump to that instead.
|
// try to dig out the type and jump to that instead.
|
||||||
if v, ok := result.Declaration.Object.(*types.Var); ok {
|
if v, ok := result.decl.obj.(*types.Var); ok {
|
||||||
if typObj := typeToObject(v.Type()); typObj != nil {
|
if typObj := typeToObject(v.Type()); typObj != nil {
|
||||||
result.Declaration.Object = typObj
|
result.decl.obj = typObj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if result.Declaration.Range, err = objToRange(ctx, f.FileSet(), result.Declaration.Object); err != nil {
|
if result.decl.rng, err = objToRange(ctx, f.FileSet(), result.decl.obj); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if result.Declaration.Node, err = objToNode(ctx, v, result.Declaration.Object, result.Declaration.Range); err != nil {
|
if result.decl.node, err = objToNode(ctx, v, result.decl.obj, result.decl.rng); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
typ := pkg.GetTypesInfo().TypeOf(result.ident)
|
typ := pkg.GetTypesInfo().TypeOf(result.ident)
|
||||||
|
@ -245,7 +255,7 @@ func importSpec(f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*Identifi
|
||||||
if dest == nil {
|
if dest == nil {
|
||||||
return nil, fmt.Errorf("package %q has no files", importPath)
|
return nil, fmt.Errorf("package %q has no files", importPath)
|
||||||
}
|
}
|
||||||
result.Declaration.Range = span.NewRange(f.FileSet(), dest.Name.Pos(), dest.Name.End())
|
result.decl.rng = span.NewRange(f.FileSet(), dest.Name.Pos(), dest.Name.End())
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type SignatureInformation struct {
|
type SignatureInformation struct {
|
||||||
Label string
|
Label, Documentation string
|
||||||
Parameters []ParameterInformation
|
Parameters []ParameterInformation
|
||||||
ActiveParameter int
|
ActiveParameter int
|
||||||
}
|
}
|
||||||
|
@ -82,13 +82,34 @@ func SignatureHelp(ctx context.Context, f GoFile, pos token.Pos) (*SignatureInfo
|
||||||
results, writeResultParens := formatResults(sig.Results(), qf)
|
results, writeResultParens := formatResults(sig.Results(), qf)
|
||||||
activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), pos)
|
activeParam := activeParameter(callExpr, sig.Params().Len(), sig.Variadic(), pos)
|
||||||
|
|
||||||
var name string
|
var (
|
||||||
|
name string
|
||||||
|
comment *ast.CommentGroup
|
||||||
|
)
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
|
rng, err := objToRange(ctx, f.FileSet(), obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
node, err := objToNode(ctx, f.View(), obj, rng)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decl := &declaration{
|
||||||
|
obj: obj,
|
||||||
|
rng: rng,
|
||||||
|
node: node,
|
||||||
|
}
|
||||||
|
d, err := decl.hover(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
name = obj.Name()
|
name = obj.Name()
|
||||||
|
comment = d.comment
|
||||||
} else {
|
} else {
|
||||||
name = "func"
|
name = "func"
|
||||||
}
|
}
|
||||||
return signatureInformation(name, params, results, writeResultParens, activeParam), nil
|
return signatureInformation(name, comment, params, results, writeResultParens, activeParam), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func builtinSignature(ctx context.Context, v View, callExpr *ast.CallExpr, name string, pos token.Pos) (*SignatureInformation, error) {
|
func builtinSignature(ctx context.Context, v View, callExpr *ast.CallExpr, name string, pos token.Pos) (*SignatureInformation, error) {
|
||||||
|
@ -111,10 +132,10 @@ func builtinSignature(ctx context.Context, v View, callExpr *ast.CallExpr, name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activeParam := activeParameter(callExpr, numParams, variadic, pos)
|
activeParam := activeParameter(callExpr, numParams, variadic, pos)
|
||||||
return signatureInformation(name, params, results, writeResultParens, activeParam), nil
|
return signatureInformation(name, nil, params, results, writeResultParens, activeParam), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func signatureInformation(name string, params, results []string, writeResultParens bool, activeParam int) *SignatureInformation {
|
func signatureInformation(name string, comment *ast.CommentGroup, params, results []string, writeResultParens bool, activeParam int) *SignatureInformation {
|
||||||
paramInfo := make([]ParameterInformation, 0, len(params))
|
paramInfo := make([]ParameterInformation, 0, len(params))
|
||||||
for _, p := range params {
|
for _, p := range params {
|
||||||
paramInfo = append(paramInfo, ParameterInformation{Label: p})
|
paramInfo = append(paramInfo, ParameterInformation{Label: p})
|
||||||
|
@ -126,6 +147,7 @@ func signatureInformation(name string, params, results []string, writeResultPare
|
||||||
}
|
}
|
||||||
return &SignatureInformation{
|
return &SignatureInformation{
|
||||||
Label: label,
|
Label: label,
|
||||||
|
Documentation: formatDocumentation(comment),
|
||||||
Parameters: paramInfo,
|
Parameters: paramInfo,
|
||||||
ActiveParameter: activeParam,
|
ActiveParameter: activeParam,
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,11 +362,11 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed for %v: %v", d.Src, err)
|
t.Fatalf("failed for %v: %v", d.Src, err)
|
||||||
}
|
}
|
||||||
hover, err := ident.Hover(ctx, nil, false, true)
|
hover, err := ident.Hover(ctx, false, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed for %v: %v", d.Src, err)
|
t.Fatalf("failed for %v: %v", d.Src, err)
|
||||||
}
|
}
|
||||||
rng := ident.Declaration.Range
|
rng := ident.DeclarationRange()
|
||||||
if d.IsType {
|
if d.IsType {
|
||||||
rng = ident.Type.Range
|
rng = ident.Type.Range
|
||||||
hover = ""
|
hover = ""
|
||||||
|
|
Loading…
Reference in New Issue