// 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) } } // 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 ") // 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 }