1
0
mirror of https://github.com/golang/go synced 2024-09-30 18:18:32 -06:00
go/internal/lsp/source/completion_format.go
Muir Manders 90abf76919 internal/lsp/source: fix a couple issues completing append() args
In this example:

     p := &[]int{}
     append([]int{}, *<>)

At <> we completed to "**p" instead of "*p...". There were two fixes:

1. builtinArgType() wasn't propagating the "modifiers", so we were
   forgetting about the preceding "*" pointer indirection and
   inserting it again with the completion. Fix by propagating
   modifiers.
2. The candidate formatting responsible for adding "..." had over
   simplified logic to determine if we are completing the variadic
   param. Now instead the candidate evaluation code marks the
   candidate as "variadic" so the formatting doesn't have to think at
   all.

Change-Id: Ib71ee8ecfafb915df331f1d2e55b76f76a530243
Reviewed-on: https://go-review.googlesource.com/c/tools/+/248018
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-08-15 16:56:00 +00:00

291 lines
7.6 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/types"
"strings"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/snippet"
"golang.org/x/tools/internal/span"
)
// formatCompletion creates a completion item for a given candidate.
func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, error) {
obj := cand.obj
// Handle builtin types separately.
if obj.Parent() == types.Universe {
return c.formatBuiltin(ctx, cand)
}
var (
label = cand.name
detail = types.TypeString(obj.Type(), c.qf)
insert = label
kind = protocol.TextCompletion
snip *snippet.Builder
protocolEdits []protocol.TextEdit
)
if obj.Type() == nil {
detail = ""
}
// expandFuncCall mutates the completion label, detail, and snippet
// to that of an invocation of sig.
expandFuncCall := func(sig *types.Signature) error {
s, err := newSignature(ctx, c.snapshot, c.pkg, c.file, "", sig, nil, c.qf)
if err != nil {
return err
}
snip = c.functionCallSnippet(label, s.params)
detail = "func" + s.format()
return nil
}
switch obj := obj.(type) {
case *types.TypeName:
detail, kind = formatType(obj.Type(), c.qf)
case *types.Const:
kind = protocol.ConstantCompletion
case *types.Var:
if _, ok := obj.Type().(*types.Struct); ok {
detail = "struct{...}" // for anonymous structs
} else if obj.IsField() {
detail = formatVarType(ctx, c.snapshot, c.pkg, c.file, obj, c.qf)
}
if obj.IsField() {
kind = protocol.FieldCompletion
snip = c.structFieldSnippet(label, detail)
} else {
kind = protocol.VariableCompletion
}
if obj.Type() == nil {
break
}
if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall {
if err := expandFuncCall(sig); err != nil {
return CompletionItem{}, err
}
}
case *types.Func:
sig, ok := obj.Type().Underlying().(*types.Signature)
if !ok {
break
}
kind = protocol.FunctionCompletion
if sig != nil && sig.Recv() != nil {
kind = protocol.MethodCompletion
}
if cand.expandFuncCall {
if err := expandFuncCall(sig); err != nil {
return CompletionItem{}, err
}
}
case *types.PkgName:
kind = protocol.ModuleCompletion
detail = fmt.Sprintf("%q", obj.Imported().Path())
case *types.Label:
kind = protocol.ConstantCompletion
detail = "label"
}
// If this candidate needs an additional import statement,
// add the additional text edits needed.
if cand.imp != nil {
addlEdits, err := c.importEdits(ctx, cand.imp)
if err != nil {
return CompletionItem{}, err
}
protocolEdits = append(protocolEdits, addlEdits...)
if kind != protocol.ModuleCompletion {
if detail != "" {
detail += " "
}
detail += fmt.Sprintf("(from %q)", cand.imp.importPath)
}
}
// Prepend "&" or "*" operator as appropriate.
var prefixOp string
if cand.takeAddress {
prefixOp = "&"
} else if cand.makePointer {
prefixOp = "*"
} else if cand.dereference > 0 {
prefixOp = strings.Repeat("*", cand.dereference)
}
if prefixOp != "" {
// If we are in a selector, add an edit to place prefix before selector.
if sel := enclosingSelector(c.path, c.pos); sel != nil {
edits, err := prependEdit(c.snapshot.FileSet(), c.mapper, sel, prefixOp)
if err != nil {
return CompletionItem{}, err
}
protocolEdits = append(protocolEdits, edits...)
} else {
// If there is no selector, just stick the prefix at the start.
insert = prefixOp + insert
}
label = prefixOp + label
}
// Add variadic "..." if we are filling in a variadic param.
if cand.variadic {
insert += "..."
if snip != nil {
snip.WriteText("...")
}
}
detail = strings.TrimPrefix(detail, "untyped ")
item := CompletionItem{
Label: label,
InsertText: insert,
AdditionalTextEdits: protocolEdits,
Detail: detail,
Kind: kind,
Score: cand.score,
Depth: len(c.deepState.chain),
snippet: snip,
obj: obj,
}
// If the user doesn't want documentation for completion items.
if !c.opts.documentation {
return item, nil
}
pos := c.snapshot.FileSet().Position(obj.Pos())
// We ignore errors here, because some types, like "unsafe" or "error",
// may not have valid positions that we can use to get documentation.
if !pos.IsValid() {
return item, nil
}
uri := span.URIFromPath(pos.Filename)
// Find the source file of the candidate, starting from a package
// that should have it in its dependencies.
searchPkg := c.pkg
if cand.imp != nil && cand.imp.pkg != nil {
searchPkg = cand.imp.pkg
}
pgf, pkg, err := findPosInPackage(c.snapshot, searchPkg, obj.Pos())
if err != nil {
return item, nil
}
posToDecl, err := c.snapshot.PosToDecl(ctx, pgf)
if err != nil {
return CompletionItem{}, err
}
decl := posToDecl[obj.Pos()]
if decl == nil {
return item, nil
}
hover, err := hoverInfo(pkg, obj, decl)
if err != nil {
event.Error(ctx, "failed to find Hover", err, tag.URI.Of(uri))
return item, nil
}
item.Documentation = hover.Synopsis
if c.opts.fullDocumentation {
item.Documentation = hover.FullDocumentation
}
return item, nil
}
// importEdits produces the text edits necessary to add the given import to the current file.
func (c *completer) importEdits(ctx context.Context, imp *importInfo) ([]protocol.TextEdit, error) {
if imp == nil {
return nil, nil
}
pgf, err := c.pkg.File(span.URIFromPath(c.filename))
if err != nil {
return nil, err
}
return computeOneImportFixEdits(ctx, c.snapshot, pgf, &imports.ImportFix{
StmtInfo: imports.ImportInfo{
ImportPath: imp.importPath,
Name: imp.name,
},
// IdentName is unused on this path and is difficult to get.
FixType: imports.AddImport,
})
}
func (c *completer) formatBuiltin(ctx context.Context, cand candidate) (CompletionItem, error) {
obj := cand.obj
item := CompletionItem{
Label: obj.Name(),
InsertText: obj.Name(),
Score: cand.score,
}
switch obj.(type) {
case *types.Const:
item.Kind = protocol.ConstantCompletion
case *types.Builtin:
item.Kind = protocol.FunctionCompletion
sig, err := newBuiltinSignature(ctx, c.snapshot, obj.Name())
if err != nil {
return CompletionItem{}, err
}
item.Detail = "func" + sig.format()
item.snippet = c.functionCallSnippet(obj.Name(), sig.params)
case *types.TypeName:
if types.IsInterface(obj.Type()) {
item.Kind = protocol.InterfaceCompletion
} else {
item.Kind = protocol.ClassCompletion
}
case *types.Nil:
item.Kind = protocol.VariableCompletion
}
return item, nil
}
// qualifier returns a function that appropriately formats a types.PkgName
// appearing in a *ast.File.
func qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier {
// Construct mapping of import paths to their defined or implicit names.
imports := make(map[*types.Package]string)
for _, imp := range f.Imports {
var obj types.Object
if imp.Name != nil {
obj = info.Defs[imp.Name]
} else {
obj = info.Implicits[imp]
}
if pkgname, ok := obj.(*types.PkgName); ok {
imports[pkgname.Imported()] = pkgname.Name()
}
}
// Define qualifier to replace full package paths with names of the imports.
return func(p *types.Package) string {
if p == pkg {
return ""
}
if name, ok := imports[p]; ok {
return name
}
return p.Name()
}
}