go/internal/lsp/source/hover.go
Ian Cottrell 4457e4cfd4 internal/lsp: add some trace spans to important functions
This uses the new opencensus compatability layer to add telementry to some of
the functions in the lsp, in order to allow us to understand their costs and
call patterns.

Change-Id: I7df820cd4eace7a4840ac6397d5df402369bf0a7
Reviewed-on: https://go-review.googlesource.com/c/tools/+/183419
Run-TryBot: Ian Cottrell <iancottrell@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-07-03 19:14:57 +00:00

168 lines
4.2 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/types"
"strings"
"golang.org/x/tools/internal/lsp/telemetry/trace"
)
type documentation struct {
source interface{}
comment *ast.CommentGroup
}
type HoverKind int
const (
NoDocumentation = HoverKind(iota)
SynopsisDocumentation
FullDocumentation
// TODO: Support a single-line hover mode for clients like Vim.
singleLine
)
func (i *IdentifierInfo) Hover(ctx context.Context, markdownSupported bool, hoverKind HoverKind) (string, error) {
ctx, ts := trace.StartSpan(ctx, "source.Hover")
defer ts.End()
h, err := i.decl.hover(ctx)
if err != nil {
return "", err
}
var b strings.Builder
if comment := formatDocumentation(hoverKind, h.comment); comment != "" {
b.WriteString(comment)
b.WriteRune('\n')
}
if markdownSupported {
b.WriteString("```go\n")
}
switch x := h.source.(type) {
case ast.Node:
if err := format.Node(&b, i.File.FileSet(), x); err != nil {
return "", err
}
case types.Object:
b.WriteString(types.ObjectString(x, i.qf))
}
if markdownSupported {
b.WriteString("\n```")
}
return b.String(), nil
}
func formatDocumentation(hoverKind HoverKind, c *ast.CommentGroup) string {
switch hoverKind {
case SynopsisDocumentation:
return doc.Synopsis((c.Text()))
case FullDocumentation:
return c.Text()
}
return ""
}
func (d declaration) hover(ctx context.Context) (*documentation, error) {
ctx, ts := trace.StartSpan(ctx, "source.hover")
defer ts.End()
obj := d.obj
switch node := d.node.(type) {
case *ast.GenDecl:
switch obj := obj.(type) {
case *types.TypeName, *types.Var, *types.Const, *types.Func:
return formatGenDecl(node, obj, obj.Type())
}
case *ast.TypeSpec:
if obj.Parent() == types.Universe {
if obj.Name() == "error" {
return &documentation{node, nil}, nil
}
return &documentation{node.Name, nil}, nil // comments not needed for builtins
}
case *ast.FuncDecl:
switch obj.(type) {
case *types.Func:
return &documentation{obj, node.Doc}, nil
case *types.Builtin:
return &documentation{node.Type, node.Doc}, nil
}
}
return &documentation{obj, nil}, nil
}
func formatGenDecl(node *ast.GenDecl, obj types.Object, typ types.Type) (*documentation, error) {
if _, ok := typ.(*types.Named); ok {
switch typ.Underlying().(type) {
case *types.Interface, *types.Struct:
return formatGenDecl(node, obj, typ.Underlying())
}
}
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 nil, 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)
}
// Handle types.
switch spec := spec.(type) {
case *ast.TypeSpec:
if len(node.Specs) > 1 {
// If multiple types are declared in the same block.
return &documentation{spec.Type, spec.Doc}, nil
} else {
return &documentation{spec, node.Doc}, nil
}
case *ast.ValueSpec:
return &documentation{spec, spec.Doc}, nil
case *ast.ImportSpec:
return &documentation{spec, spec.Doc}, nil
}
return nil, fmt.Errorf("unable to format spec %v (%T)", spec, spec)
}
func formatVar(node ast.Spec, obj types.Object) (*documentation, 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 &documentation{obj, field.Doc}, nil
} else if field.Comment.Text() != "" {
return &documentation{obj, field.Comment}, nil
}
}
}
}
// If we weren't able to find documentation for the object.
return &documentation{obj, nil}, nil
}