--- /dev/null
+// 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 completion
+
+import (
+ "context"
+ "fmt"
+ "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/lsp/source"
+ "golang.org/x/tools/internal/span"
+ errors "golang.org/x/xerrors"
+)
+
+// item formats a candidate to a CompletionItem.
+func (c *completer) item(ctx context.Context, cand candidate) (CompletionItem, error) {
+ obj := cand.obj
+
+ // if the object isn't a valid match against the surrounding, return early.
+ matchScore := c.matcher.Score(cand.name)
+ if matchScore <= 0 {
+ return CompletionItem{}, errors.New("not a surrounding match")
+ }
+ cand.score *= float64(matchScore)
+
+ // Ignore deep candidates that wont be in the MaxDeepCompletions anyway.
+ if len(cand.path) != 0 && !c.deepState.isHighScore(cand.score) {
+ return CompletionItem{}, errors.New("not a high scoring candidate")
+ }
+
+ // 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) {
+ s := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf)
+ snip = c.functionCallSnippet(label, s.Params())
+ detail = "func" + s.Format()
+ }
+
+ switch obj := obj.(type) {
+ case *types.TypeName:
+ detail, kind = source.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 = source.FormatVarType(ctx, c.snapshot, c.pkg, obj, c.qf)
+ }
+ if obj.IsField() {
+ kind = protocol.FieldCompletion
+ snip = c.structFieldSnippet(cand, label, detail)
+ } else {
+ kind = protocol.VariableCompletion
+ }
+ if obj.Type() == nil {
+ break
+ }
+
+ if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall {
+ expandFuncCall(sig)
+ }
+ 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 {
+ expandFuncCall(sig)
+ }
+ 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(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)
+ }
+ }
+
+ var prefix, suffix string
+
+ // Prepend "&" or "*" operator as appropriate.
+ if cand.takeAddress {
+ prefix = "&"
+ } else if cand.makePointer {
+ prefix = "*"
+ } else if cand.dereference > 0 {
+ prefix = strings.Repeat("*", cand.dereference)
+ }
+
+ // Include "*" and "&" prefixes in the label.
+ label = prefix + label
+
+ if cand.convertTo != nil {
+ typeName := types.TypeString(cand.convertTo, c.qf)
+
+ switch cand.convertTo.(type) {
+ // We need extra parens when casting to these types. For example,
+ // we need "(*int)(foo)", not "*int(foo)".
+ case *types.Pointer, *types.Signature:
+ typeName = "(" + typeName + ")"
+ }
+
+ prefix = typeName + "(" + prefix
+ suffix = ")"
+ }
+
+ // Add variadic "..." if we are filling in a variadic param.
+ if cand.variadic {
+ suffix += "..."
+ }
+
+ if prefix != "" {
+ // 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, prefix)
+ if err != nil {
+ return CompletionItem{}, err
+ }
+ protocolEdits = append(protocolEdits, edits...)
+ } else {
+ // If there is no selector, just stick the prefix at the start.
+ insert = prefix + insert
+ if snip != nil {
+ snip.PrependText(prefix)
+ }
+ }
+ }
+
+ if suffix != "" {
+ insert += suffix
+ if snip != nil {
+ snip.WriteText(suffix)
+ }
+ }
+
+ detail = strings.TrimPrefix(detail, "untyped ")
+ // override computed detail with provided detail, if something is provided.
+ if cand.detail != "" {
+ detail = cand.detail
+ }
+ item := CompletionItem{
+ Label: label,
+ InsertText: insert,
+ AdditionalTextEdits: protocolEdits,
+ Detail: detail,
+ Kind: kind,
+ Score: cand.score,
+ Depth: len(cand.path),
+ 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 := source.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 := source.HoverInfo(ctx, 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(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 source.ComputeOneImportFixEdits(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 := source.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
+}