internal/lsp: refactor completion and move into source directory
The completion function belongs in internal/lsp/source, so move it
there. Some small refactoring of completion, by moving each type of
completion into helper functions that append to the list of results.
Change-Id: I8599092906609591d499183657fe2d21d1f74df1
Reviewed-on: https://go-review.googlesource.com/c/148397
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index c068ecd..878793b 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -1,723 +1,49 @@
package lsp
import (
- "bytes"
- "fmt"
- "go/ast"
- "go/format"
- "go/token"
- "go/types"
- "strings"
+ "sort"
- "golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
-func completion(v *source.View, uri protocol.DocumentURI, pos protocol.Position) (items []protocol.CompletionItem, err error) {
- f := v.GetFile(source.URI(uri))
- if err != nil {
- return nil, err
- }
- tok, err := f.GetToken()
- if err != nil {
- return nil, err
- }
- p := fromProtocolPosition(tok, pos)
- file, err := f.GetAST() // Use p to prune the AST?
- if err != nil {
- return nil, err
- }
- pkg, err := f.GetPackage()
- if err != nil {
- return nil, err
- }
- items, _, err = completions(v.Config.Fset, file, p, pkg.Types, pkg.TypesInfo)
- return items, err
-}
-
-// Completions returns the map of possible candidates for completion,
-// given a position, a file AST, and type information. The prefix is
-// computed based on the preceding identifier and can be used by the
-// client to score the quality of the completion. For instance, some
-// clients may tolerate imperfect matches as valid completion results,
-// since users may make typos.
-func completions(fset *token.FileSet, file *ast.File, pos token.Pos, pkg *types.Package, info *types.Info) (completions []protocol.CompletionItem, prefix string, err error) {
- path, _ := astutil.PathEnclosingInterval(file, pos, pos)
- if path == nil {
- return nil, "", fmt.Errorf("cannot find node enclosing position")
- }
- // If the position is not an identifier but immediately follows
- // an identifier or selector period (as is common when
- // requesting a completion), use the path to the preceding node.
- if _, ok := path[0].(*ast.Ident); !ok {
- if p, _ := astutil.PathEnclosingInterval(file, pos-1, pos-1); p != nil {
- switch p[0].(type) {
- case *ast.Ident, *ast.SelectorExpr:
- path = p // use preceding ident/selector
- }
- }
- }
-
- expectedTyp := expectedType(path, pos, info)
- enclosing := enclosingFunc(path, pos, info)
- pkgStringer := qualifier(file, pkg, info)
-
- seen := make(map[types.Object]bool)
- const stdWeight = 1 // default rank for a completion result
-
- // found adds a candidate completion.
- // Only the first candidate of a given name is considered.
- found := func(obj types.Object, weight float32) {
- if obj.Pkg() != nil && obj.Pkg() != pkg && !obj.Exported() {
- return // inaccessible
- }
- if !seen[obj] {
- seen[obj] = true
- if expectedTyp != nil && matchingTypes(expectedTyp, obj.Type()) {
- weight *= 10
- }
- item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
- return isParam(enclosing, v)
- })
- completions = append(completions, item)
- }
- }
-
- // selector finds completions for
- // the specified selector expression.
- // TODO(rstambler): Set the prefix filter correctly for selectors.
- selector := func(sel *ast.SelectorExpr) error {
- // Is sel a qualified identifier?
- if id, ok := sel.X.(*ast.Ident); ok {
- if pkgname, ok := info.Uses[id].(*types.PkgName); ok {
- // Enumerate package members.
- // TODO(adonovan): can Imported() be nil?
- scope := pkgname.Imported().Scope()
- // TODO testcase: bad import
- for _, name := range scope.Names() {
- found(scope.Lookup(name), stdWeight)
- }
- return nil
- }
- }
-
- // Inv: sel is a true selector.
- tv, ok := info.Types[sel.X]
- if !ok {
- var buf bytes.Buffer
- format.Node(&buf, fset, sel.X) // TODO check for error
- return fmt.Errorf("cannot resolve %s", &buf)
- }
-
- // methods of T
- mset := types.NewMethodSet(tv.Type)
- for i := 0; i < mset.Len(); i++ {
- found(mset.At(i).Obj(), stdWeight)
- }
-
- // methods of *T
- if tv.Addressable() && !types.IsInterface(tv.Type) && !isPointer(tv.Type) {
- mset := types.NewMethodSet(types.NewPointer(tv.Type))
- for i := 0; i < mset.Len(); i++ {
- found(mset.At(i).Obj(), stdWeight)
- }
- }
-
- // fields of T
- for _, f := range fieldSelections(tv.Type) {
- found(f, stdWeight)
- }
-
- return nil
- }
-
- // lexical finds completions in the lexical environment.
- lexical := func(path []ast.Node) {
- var scopes []*types.Scope // scopes[i], where i<len(path), is the possibly nil Scope of path[i].
- for _, n := range path {
- switch node := n.(type) {
- case *ast.FuncDecl:
- n = node.Type
- case *ast.FuncLit:
- n = node.Type
- }
- scopes = append(scopes, info.Scopes[n])
- }
- scopes = append(scopes, pkg.Scope(), types.Universe)
-
- // Process scopes innermost first.
- for i, scope := range scopes {
- if scope == nil {
- continue
- }
- for _, name := range scope.Names() {
- declScope, obj := scope.LookupParent(name, pos)
- if declScope != scope {
- continue // Name was declared in some enclosing scope, or not at all.
- }
- // If obj's type is invalid, find the AST node that defines the lexical block
- // containing the declaration of obj. Don't resolve types for packages.
- if _, ok := obj.(*types.PkgName); !ok && obj.Type() == types.Typ[types.Invalid] {
- // Match the scope to its ast.Node. If the scope is the package scope,
- // use the *ast.File as the starting node.
- var node ast.Node
- if i < len(path) {
- node = path[i]
- } else if i == len(path) { // use the *ast.File for package scope
- node = path[i-1]
- }
- if node != nil {
- if resolved := resolveInvalid(obj, node, info); resolved != nil {
- obj = resolved
- }
- }
- }
-
- score := float32(stdWeight)
- // Rank builtins significantly lower than other results.
- if scope == types.Universe {
- score *= 0.1
- }
- found(obj, score)
- }
- }
- }
-
- // complit finds completions for field names inside a composite literal.
- // It reports whether the node was handled as part of a composite literal.
- complit := func(node ast.Node) bool {
- var lit *ast.CompositeLit
-
- switch n := node.(type) {
- case *ast.CompositeLit:
- // The enclosing node will be a composite literal if the user has just
- // opened the curly brace (e.g. &x{<>) or the completion request is triggered
- // from an already completed composite literal expression (e.g. &x{foo: 1, <>})
- //
- // If the cursor position is within a key-value expression inside the composite
- // literal, we try to determine if it is before or after the colon. If it is before
- // the colon, we return field completions. If the cursor does not belong to any
- // expression within the composite literal, we show composite literal completions.
- var expr ast.Expr
- for _, e := range n.Elts {
- if e.Pos() <= pos && pos < e.End() {
- expr = e
- break
- }
- }
- lit = n
- // If the position belongs to a key-value expression and is after the colon,
- // don't show composite literal completions.
- if kv, ok := expr.(*ast.KeyValueExpr); ok && pos > kv.Colon {
- lit = nil
- }
- case *ast.KeyValueExpr:
- // If the enclosing node is a key-value expression (e.g. &x{foo: <>}),
- // we show composite literal completions if the cursor position is before the colon.
- if len(path) > 1 && pos < n.Colon {
- if l, ok := path[1].(*ast.CompositeLit); ok {
- lit = l
- }
- }
- case *ast.Ident:
- // If the enclosing node is an identifier, it can either be an identifier that is
- // part of a composite literal (e.g. &x{fo<>}), or it can be an identifier that is
- // part of a key-value expression, which is part of a composite literal (e.g. &x{foo: ba<>).
- // We handle both of these cases, showing composite literal completions only if
- // the cursor position for the key-value expression is before the colon.
- if len(path) > 1 {
- if l, ok := path[1].(*ast.CompositeLit); ok {
- lit = l
- } else if len(path) > 2 {
- if l, ok := path[2].(*ast.CompositeLit); ok {
- // Confirm that cursor position is inside curly braces.
- if l.Lbrace <= pos && pos <= l.Rbrace {
- lit = l
- if kv, ok := path[1].(*ast.KeyValueExpr); ok {
- if pos > kv.Colon {
- lit = nil
- }
- }
- }
- }
- }
- }
- }
- if lit == nil {
- return false
- }
- // Mark fields that have already been set, apart from the current field.
- hasKeys := false // true if the composite literal already has key-value pairs
- addedFields := make(map[*types.Var]bool)
- for _, el := range lit.Elts {
- if kv, ok := el.(*ast.KeyValueExpr); ok {
- hasKeys = true
- if kv.Pos() <= pos && pos <= kv.End() {
- continue
- }
- if key, ok := kv.Key.(*ast.Ident); ok {
- if used, ok := info.Uses[key]; ok {
- if usedVar, ok := used.(*types.Var); ok {
- addedFields[usedVar] = true
- }
- }
- }
- }
- }
- // If the underlying type of the composite literal is a struct,
- // we show completions for the fields of this struct.
- if tv, ok := info.Types[lit]; ok {
- var structPkg *types.Package // package containing the struct type declaration
- if s, ok := tv.Type.Underlying().(*types.Struct); ok {
- for i := 0; i < s.NumFields(); i++ {
- field := s.Field(i)
- if i == 0 {
- structPkg = field.Pkg()
- }
- if !addedFields[field] {
- found(field, stdWeight*10)
- }
- }
- // Add lexical completions if the user hasn't typed a key value expression
- // and if the struct fields are defined in the same package as the user is in.
- if !hasKeys && structPkg == pkg {
- lexical(path)
- }
- return true
- }
- }
- return false
- }
-
- if complit(path[0]) {
- return completions, prefix, nil
- }
-
- switch n := path[0].(type) {
- case *ast.Ident:
- // Set the filter prefix.
- prefix = n.Name[:pos-n.Pos()]
-
- // Is this the Sel part of a selector?
- if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
- if err := selector(sel); err != nil {
- return nil, prefix, err
- }
- } else {
- // reject defining identifiers
- if obj, ok := info.Defs[n]; ok {
- if v, ok := obj.(*types.Var); ok && v.IsField() {
- // An anonymous field is also a reference to a type.
- } else {
- of := ""
- if obj != nil {
- qual := types.RelativeTo(pkg)
- of += ", of " + types.ObjectString(obj, qual)
- }
- return nil, "", fmt.Errorf("this is a definition%s", of)
- }
- }
-
- lexical(path)
- }
-
- // Support completions when no letters of the function name have been
- // typed yet, but the parens are there:
- // recv.‸(arg)
- case *ast.TypeAssertExpr:
- // Create a fake selector expression.
- if err := selector(&ast.SelectorExpr{X: n.X}); err != nil {
- return nil, prefix, err
- }
-
- case *ast.SelectorExpr:
- if err := selector(n); err != nil {
- return nil, prefix, err
- }
-
- default:
- // TODO(adonovan): a lexical query may not be what the
- // user expects when completing after the period of a
- // type assertion.
-
- lexical(path)
- }
-
- return completions, prefix, nil
-}
-
-// qualifier returns a function that appropriately formats a types.PkgName appearing in q.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(pkg *types.Package) string {
- if pkg == pkg {
- return ""
- }
- if name, ok := imports[pkg]; ok {
- return name
- }
- return pkg.Name()
- }
-}
-
-// enclosingFunc returns the signature of the function enclosing the position.
-func enclosingFunc(path []ast.Node, pos token.Pos, info *types.Info) *types.Signature {
- for _, node := range path {
- switch t := node.(type) {
- case *ast.FuncDecl:
- if obj, ok := info.Defs[t.Name]; ok {
- return obj.Type().(*types.Signature)
- }
- case *ast.FuncLit:
- if typ, ok := info.Types[t]; ok {
- return typ.Type.(*types.Signature)
- }
- }
- }
- return nil
-}
-
-// formatCompletion returns the label, details, and kind for a types.Object,
-// fitting the format of a LSP completion item.
-func formatCompletion(obj types.Object, qualifier types.Qualifier, score float32, isParam func(*types.Var) bool) protocol.CompletionItem {
- label := obj.Name()
- detail := types.TypeString(obj.Type(), qualifier)
-
- var kind protocol.CompletionItemKind
-
- switch o := obj.(type) {
- case *types.TypeName:
- detail, kind = formatType(o.Type(), qualifier)
- if obj.Parent() == types.Universe {
- detail = ""
- }
- case *types.Const:
- if obj.Parent() == types.Universe {
- detail = ""
- } else {
- val := o.Val().ExactString()
- if !strings.Contains(val, "\\n") { // skip any multiline constants
- label += " = " + o.Val().ExactString()
- }
- }
- kind = protocol.ConstantCompletion
- case *types.Var:
- if _, ok := o.Type().(*types.Struct); ok {
- detail = "struct{...}" // for anonymous structs
- }
- if o.IsField() {
- kind = protocol.FieldCompletion
- } else if isParam(o) {
- kind = protocol.TypeParameterCompletion
- } else {
- kind = protocol.VariableCompletion
- }
- case *types.Func:
- if sig, ok := o.Type().(*types.Signature); ok {
- label += formatParams(sig.Params(), sig.Variadic(), qualifier)
- detail = strings.Trim(types.TypeString(sig.Results(), qualifier), "()")
- kind = protocol.FunctionCompletion
- if sig.Recv() != nil {
- kind = protocol.MethodCompletion
- }
- }
- case *types.Builtin:
- item, ok := builtinDetails[obj.Name()]
- if !ok {
- break
- }
- label, detail = item.label, item.detail
- kind = protocol.FunctionCompletion
- case *types.PkgName:
- kind = protocol.ModuleCompletion // package??
- detail = fmt.Sprintf("\"%s\"", o.Imported().Path())
- case *types.Nil:
- kind = protocol.VariableCompletion
- detail = ""
- }
-
- detail = strings.TrimPrefix(detail, "untyped ")
-
- return protocol.CompletionItem{
- Label: label,
- Detail: detail,
- Kind: float64(kind),
- }
-}
-
-// formatType returns the detail and kind for an object of type *types.TypeName.
-func formatType(typ types.Type, qualifier types.Qualifier) (detail string, kind protocol.CompletionItemKind) {
- if types.IsInterface(typ) {
- detail = "interface{...}"
- kind = protocol.InterfaceCompletion
- } else if _, ok := typ.(*types.Struct); ok {
- detail = "struct{...}"
- kind = protocol.StructCompletion
- } else if typ != typ.Underlying() {
- detail, kind = formatType(typ.Underlying(), qualifier)
- } else {
- detail = types.TypeString(typ, qualifier)
- kind = protocol.TypeParameterCompletion // ???
- }
- return detail, kind
-}
-
-func formatParams(t *types.Tuple, variadic bool, qualifier types.Qualifier) string {
- var b strings.Builder
- b.WriteByte('(')
- for i := 0; i < t.Len(); i++ {
- if i > 0 {
- b.WriteString(", ")
- }
- el := t.At(i)
- typ := types.TypeString(el.Type(), qualifier)
- // Handle a variadic parameter (can only be the final parameter).
- if variadic && i == t.Len()-1 {
- typ = strings.Replace(typ, "[]", "...", 1)
- }
- fmt.Fprintf(&b, "%v %v", el.Name(), typ)
- }
- b.WriteByte(')')
- return b.String()
-}
-
-func isParam(sig *types.Signature, v *types.Var) bool {
- if sig == nil {
- return false
- }
- for i := 0; i < sig.Params().Len(); i++ {
- if sig.Params().At(i) == v {
- return true
- }
- }
- return false
-}
-
-// expectedType returns the expected type for an expression at the query position.
-func expectedType(path []ast.Node, pos token.Pos, info *types.Info) types.Type {
- for i, node := range path {
- if i == 2 {
- break
- }
- switch expr := node.(type) {
- case *ast.BinaryExpr:
- // Determine if query position comes from left or right of op.
- e := expr.X
- if pos < expr.OpPos {
- e = expr.Y
- }
- if tv, ok := info.Types[e]; ok {
- return tv.Type
- }
- case *ast.AssignStmt:
- // Only rank completions if you are on the right side of the token.
- if pos <= expr.TokPos {
- break
- }
- i := exprAtPos(pos, expr.Rhs)
- if i >= len(expr.Lhs) {
- i = len(expr.Lhs) - 1
- }
- if tv, ok := info.Types[expr.Lhs[i]]; ok {
- return tv.Type
- }
- case *ast.CallExpr:
- if tv, ok := info.Types[expr.Fun]; ok {
- if sig, ok := tv.Type.(*types.Signature); ok {
- if sig.Params().Len() == 0 {
- return nil
- }
- i := exprAtPos(pos, expr.Args)
- // Make sure not to run past the end of expected parameters.
- if i >= sig.Params().Len() {
- i = sig.Params().Len() - 1
- }
- return sig.Params().At(i).Type()
- }
- }
- }
- }
- return nil
-}
-
-// matchingTypes reports whether actual is a good candidate type
-// for a completion in a context of the expected type.
-func matchingTypes(expected, actual types.Type) bool {
- // Use a function's return type as its type.
- if sig, ok := actual.(*types.Signature); ok {
- if sig.Results().Len() == 1 {
- actual = sig.Results().At(0).Type()
- }
- }
- return types.Identical(types.Default(expected), types.Default(actual))
-}
-
-// exprAtPos returns the index of the expression containing pos.
-func exprAtPos(pos token.Pos, args []ast.Expr) int {
- for i, expr := range args {
- if expr.Pos() <= pos && pos <= expr.End() {
- return i
- }
- }
- return len(args)
-}
-
-// fieldSelections returns the set of fields that can
-// be selected from a value of type T.
-func fieldSelections(T types.Type) (fields []*types.Var) {
- // TODO(adonovan): this algorithm doesn't exclude ambiguous
- // selections that match more than one field/method.
- // types.NewSelectionSet should do that for us.
-
- seen := make(map[types.Type]bool) // for termination on recursive types
- var visit func(T types.Type)
- visit = func(T types.Type) {
- if !seen[T] {
- seen[T] = true
- if T, ok := deref(T).Underlying().(*types.Struct); ok {
- for i := 0; i < T.NumFields(); i++ {
- f := T.Field(i)
- fields = append(fields, f)
- if f.Anonymous() {
- visit(f.Type())
- }
- }
- }
- }
- }
- visit(T)
-
- return fields
-}
-
-func isPointer(T types.Type) bool {
- _, ok := T.(*types.Pointer)
- return ok
-}
-
-// deref returns a pointer's element type; otherwise it returns typ.
-func deref(typ types.Type) types.Type {
- if p, ok := typ.Underlying().(*types.Pointer); ok {
- return p.Elem()
- }
- return typ
-}
-
-// resolveInvalid traverses the node of the AST that defines the scope
-// containing the declaration of obj, and attempts to find a user-friendly
-// name for its invalid type. The resulting Object and its Type are fake.
-func resolveInvalid(obj types.Object, node ast.Node, info *types.Info) types.Object {
- // Construct a fake type for the object and return a fake object with this type.
- formatResult := func(expr ast.Expr) types.Object {
- var typename string
- switch t := expr.(type) {
- case *ast.SelectorExpr:
- typename = fmt.Sprintf("%s.%s", t.X, t.Sel)
- case *ast.Ident:
- typename = t.String()
- default:
- return nil
- }
- typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), nil, nil)
- return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
- }
- var resultExpr ast.Expr
- ast.Inspect(node, func(node ast.Node) bool {
- switch n := node.(type) {
- case *ast.ValueSpec:
- for _, name := range n.Names {
- if info.Defs[name] == obj {
- resultExpr = n.Type
- }
- }
- return false
- case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit.
- for _, name := range n.Names {
- if info.Defs[name] == obj {
- resultExpr = n.Type
- }
- }
- return false
- // TODO(rstambler): Handle range statements.
- default:
- return true
- }
+func toProtocolCompletionItems(items []source.CompletionItem) []protocol.CompletionItem {
+ var results []protocol.CompletionItem
+ sort.Slice(items, func(i, j int) bool {
+ return items[i].Score > items[j].Score
})
- return formatResult(resultExpr)
+ for _, item := range items {
+ results = append(results, protocol.CompletionItem{
+ Label: item.Label,
+ Detail: item.Detail,
+ Kind: float64(toProtocolCompletionItemKind(item.Kind)),
+ })
+ }
+ return results
}
-type itemDetails struct {
- label, detail string
-}
+func toProtocolCompletionItemKind(kind source.CompletionItemKind) protocol.CompletionItemKind {
+ switch kind {
+ case source.InterfaceCompletionItem:
+ return protocol.InterfaceCompletion
+ case source.StructCompletionItem:
+ return protocol.StructCompletion
+ case source.TypeCompletionItem:
+ return protocol.TypeParameterCompletion // ??
+ case source.ConstantCompletionItem:
+ return protocol.ConstantCompletion
+ case source.FieldCompletionItem:
+ return protocol.FieldCompletion
+ case source.ParameterCompletionItem, source.VariableCompletionItem:
+ return protocol.VariableCompletion
+ case source.FunctionCompletionItem:
+ return protocol.FunctionCompletion
+ case source.MethodCompletionItem:
+ return protocol.MethodCompletion
+ case source.PackageCompletionItem:
+ return protocol.ModuleCompletion // ??
+ default:
+ return protocol.TextCompletion
+ }
-var builtinDetails = map[string]itemDetails{
- "append": { // append(slice []T, elems ...T)
- label: "append(slice []T, elems ...T)",
- detail: "[]T",
- },
- "cap": { // cap(v []T) int
- label: "cap(v []T)",
- detail: "int",
- },
- "close": { // close(c chan<- T)
- label: "close(c chan<- T)",
- },
- "complex": { // complex(r, i float64) complex128
- label: "complex(real, imag float64)",
- detail: "complex128",
- },
- "copy": { // copy(dst, src []T) int
- label: "copy(dst, src []T)",
- detail: "int",
- },
- "delete": { // delete(m map[T]T1, key T)
- label: "delete(m map[K]V, key K)",
- },
- "imag": { // imag(c complex128) float64
- label: "imag(complex128)",
- detail: "float64",
- },
- "len": { // len(v T) int
- label: "len(T)",
- detail: "int",
- },
- "make": { // make(t T, size ...int) T
- label: "make(t T, size ...int)",
- detail: "T",
- },
- "new": { // new(T) *T
- label: "new(T)",
- detail: "*T",
- },
- "panic": { // panic(v interface{})
- label: "panic(interface{})",
- },
- "print": { // print(args ...T)
- label: "print(args ...T)",
- },
- "println": { // println(args ...T)
- label: "println(args ...T)",
- },
- "real": { // real(c complex128) float64
- label: "real(complex128)",
- detail: "float64",
- },
- "recover": { // recover() interface{}
- label: "recover()",
- detail: "interface{}",
- },
}
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 9ee806a..0818195 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -149,13 +149,19 @@
}
func (s *server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
- items, err := completion(s.view, params.TextDocument.URI, params.Position)
+ f := s.view.GetFile(source.URI(params.TextDocument.URI))
+ tok, err := f.GetToken()
+ if err != nil {
+ return nil, err
+ }
+ pos := fromProtocolPosition(tok, params.Position)
+ items, err := source.Completion(ctx, f, pos)
if err != nil {
return nil, err
}
return &protocol.CompletionList{
IsIncomplete: false,
- Items: items,
+ Items: toProtocolCompletionItems(items),
}, nil
}
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
new file mode 100644
index 0000000..5d1e29a
--- /dev/null
+++ b/internal/lsp/source/completion.go
@@ -0,0 +1,731 @@
+package source
+
+import (
+ "context"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+ "strings"
+
+ "golang.org/x/tools/go/ast/astutil"
+)
+
+type CompletionItem struct {
+ Label, Detail string
+ Kind CompletionItemKind
+ Score int
+}
+
+type CompletionItemKind int
+
+const (
+ Unknown CompletionItemKind = iota
+ InterfaceCompletionItem
+ StructCompletionItem
+ TypeCompletionItem
+ ConstantCompletionItem
+ FieldCompletionItem
+ ParameterCompletionItem
+ VariableCompletionItem
+ FunctionCompletionItem
+ MethodCompletionItem
+ PackageCompletionItem
+)
+
+func Completion(ctx context.Context, f *File, pos token.Pos) ([]CompletionItem, error) {
+ file, err := f.GetAST()
+ if err != nil {
+ return nil, err
+ }
+ pkg, err := f.GetPackage()
+ if err != nil {
+ return nil, err
+ }
+ items, _, err := completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo)
+ return items, err
+}
+
+type finder func(types.Object, float64, []CompletionItem) []CompletionItem
+
+// completions returns the map of possible candidates for completion, given a
+// position, a file AST, and type information. The prefix is computed based on
+// the preceding identifier and can be used by the client to score the quality
+// of the completion. For instance, some clients may tolerate imperfect matches
+// as valid completion results, since users may make typos.
+func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.Package, info *types.Info) (items []CompletionItem, prefix string, err error) {
+ path, _ := astutil.PathEnclosingInterval(file, pos, pos)
+ if path == nil {
+ return nil, "", fmt.Errorf("cannot find node enclosing position")
+ }
+ // If the position is not an identifier but immediately follows
+ // an identifier or selector period (as is common when
+ // requesting a completion), use the path to the preceding node.
+ if _, ok := path[0].(*ast.Ident); !ok {
+ if p, _ := astutil.PathEnclosingInterval(file, pos-1, pos-1); p != nil {
+ switch p[0].(type) {
+ case *ast.Ident, *ast.SelectorExpr:
+ path = p // use preceding ident/selector
+ }
+ }
+ }
+
+ // Save certain facts about the query position, including the expected type
+ // of the completion result, the signature of the function enclosing the
+ // position.
+ typ := expectedType(path, pos, info)
+ sig := enclosingFunction(path, pos, info)
+ pkgStringer := qualifier(file, pkg, info)
+
+ seen := make(map[types.Object]bool)
+
+ // found adds a candidate completion.
+ // Only the first candidate of a given name is considered.
+ found := func(obj types.Object, weight float64, items []CompletionItem) []CompletionItem {
+ if obj.Pkg() != nil && obj.Pkg() != pkg && !obj.Exported() {
+ return items // inaccessible
+ }
+ if !seen[obj] {
+ seen[obj] = true
+ if typ != nil && matchingTypes(typ, obj.Type()) {
+ weight *= 10
+ }
+ item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
+ return isParameter(sig, v)
+ })
+ items = append(items, item)
+ }
+ return items
+ }
+
+ // The position is within a composite literal.
+ if items, ok := complit(path, pos, pkg, info, found); ok {
+ return items, "", nil
+ }
+
+ switch n := path[0].(type) {
+ case *ast.Ident:
+ // Set the filter prefix.
+ prefix = n.Name[:pos-n.Pos()]
+
+ // Is this the Sel part of a selector?
+ if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
+ return selector(sel, info, found)
+ }
+ // reject defining identifiers
+ if obj, ok := info.Defs[n]; ok {
+ if v, ok := obj.(*types.Var); ok && v.IsField() {
+ // An anonymous field is also a reference to a type.
+ } else {
+ of := ""
+ if obj != nil {
+ qual := types.RelativeTo(pkg)
+ of += ", of " + types.ObjectString(obj, qual)
+ }
+ return nil, "", fmt.Errorf("this is a definition%s", of)
+ }
+ }
+
+ items = append(items, lexical(path, pos, pkg, info, found)...)
+
+ // The function name hasn't been typed yet, but the parens are there:
+ // recv.‸(arg)
+ case *ast.TypeAssertExpr:
+ // Create a fake selector expression.
+ return selector(&ast.SelectorExpr{X: n.X}, info, found)
+
+ case *ast.SelectorExpr:
+ return selector(n, info, found)
+
+ default:
+ // fallback to lexical completions
+ return lexical(path, pos, pkg, info, found), "", nil
+ }
+
+ return items, prefix, nil
+}
+
+// selector finds completions for
+// the specified selector expression.
+// TODO(rstambler): Set the prefix filter correctly for selectors.
+func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []CompletionItem, prefix string, err error) {
+ // Is sel a qualified identifier?
+ if id, ok := sel.X.(*ast.Ident); ok {
+ if pkgname, ok := info.Uses[id].(*types.PkgName); ok {
+ // Enumerate package members.
+ // TODO(adonovan): can Imported() be nil?
+ scope := pkgname.Imported().Scope()
+ // TODO testcase: bad import
+ for _, name := range scope.Names() {
+ items = found(scope.Lookup(name), 1, items)
+ }
+ return items, prefix, nil
+ }
+ }
+
+ // Inv: sel is a true selector.
+ tv, ok := info.Types[sel.X]
+ if !ok {
+ return nil, "", fmt.Errorf("cannot resolve %s", sel.X)
+ }
+
+ // methods of T
+ mset := types.NewMethodSet(tv.Type)
+ for i := 0; i < mset.Len(); i++ {
+ items = found(mset.At(i).Obj(), 1, items)
+ }
+
+ // methods of *T
+ if tv.Addressable() && !types.IsInterface(tv.Type) && !isPointer(tv.Type) {
+ mset := types.NewMethodSet(types.NewPointer(tv.Type))
+ for i := 0; i < mset.Len(); i++ {
+ items = found(mset.At(i).Obj(), 1, items)
+ }
+ }
+
+ // fields of T
+ for _, f := range fieldSelections(tv.Type) {
+ items = found(f, 1, items)
+ }
+
+ return items, prefix, nil
+}
+
+// lexical finds completions in the lexical environment.
+func lexical(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Info, found finder) (items []CompletionItem) {
+ var scopes []*types.Scope // scopes[i], where i<len(path), is the possibly nil Scope of path[i].
+ for _, n := range path {
+ switch node := n.(type) {
+ case *ast.FuncDecl:
+ n = node.Type
+ case *ast.FuncLit:
+ n = node.Type
+ }
+ scopes = append(scopes, info.Scopes[n])
+ }
+ scopes = append(scopes, pkg.Scope(), types.Universe)
+
+ // Process scopes innermost first.
+ for i, scope := range scopes {
+ if scope == nil {
+ continue
+ }
+ for _, name := range scope.Names() {
+ declScope, obj := scope.LookupParent(name, pos)
+ if declScope != scope {
+ continue // Name was declared in some enclosing scope, or not at all.
+ }
+ // If obj's type is invalid, find the AST node that defines the lexical block
+ // containing the declaration of obj. Don't resolve types for packages.
+ if _, ok := obj.(*types.PkgName); !ok && obj.Type() == types.Typ[types.Invalid] {
+ // Match the scope to its ast.Node. If the scope is the package scope,
+ // use the *ast.File as the starting node.
+ var node ast.Node
+ if i < len(path) {
+ node = path[i]
+ } else if i == len(path) { // use the *ast.File for package scope
+ node = path[i-1]
+ }
+ if node != nil {
+ if resolved := resolveInvalid(obj, node, info); resolved != nil {
+ obj = resolved
+ }
+ }
+ }
+
+ score := 1.0
+ // Rank builtins significantly lower than other results.
+ if scope == types.Universe {
+ score *= 0.1
+ }
+ items = found(obj, score, items)
+ }
+ }
+ return items
+}
+
+// complit finds completions for field names inside a composite literal.
+// It reports whether the node was handled as part of a composite literal.
+func complit(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Info, found finder) (items []CompletionItem, ok bool) {
+ var lit *ast.CompositeLit
+
+ // First, determine if the pos is within a composite literal.
+ switch n := path[0].(type) {
+ case *ast.CompositeLit:
+ // The enclosing node will be a composite literal if the user has just
+ // opened the curly brace (e.g. &x{<>) or the completion request is triggered
+ // from an already completed composite literal expression (e.g. &x{foo: 1, <>})
+ //
+ // If the cursor position is within a key-value expression inside the composite
+ // literal, we try to determine if it is before or after the colon. If it is before
+ // the colon, we return field completions. If the cursor does not belong to any
+ // expression within the composite literal, we show composite literal completions.
+ var expr ast.Expr
+ for _, e := range n.Elts {
+ if e.Pos() <= pos && pos < e.End() {
+ expr = e
+ break
+ }
+ }
+ lit = n
+ // If the position belongs to a key-value expression and is after the colon,
+ // don't show composite literal completions.
+ if kv, ok := expr.(*ast.KeyValueExpr); ok && pos > kv.Colon {
+ lit = nil
+ }
+ case *ast.KeyValueExpr:
+ // If the enclosing node is a key-value expression (e.g. &x{foo: <>}),
+ // we show composite literal completions if the cursor position is before the colon.
+ if len(path) > 1 && pos < n.Colon {
+ if l, ok := path[1].(*ast.CompositeLit); ok {
+ lit = l
+ }
+ }
+ case *ast.Ident:
+ // If the enclosing node is an identifier, it can either be an identifier that is
+ // part of a composite literal (e.g. &x{fo<>}), or it can be an identifier that is
+ // part of a key-value expression, which is part of a composite literal (e.g. &x{foo: ba<>).
+ // We handle both of these cases, showing composite literal completions only if
+ // the cursor position for the key-value expression is before the colon.
+ if len(path) > 1 {
+ if l, ok := path[1].(*ast.CompositeLit); ok {
+ lit = l
+ } else if len(path) > 2 {
+ if l, ok := path[2].(*ast.CompositeLit); ok {
+ // Confirm that cursor position is inside curly braces.
+ if l.Lbrace <= pos && pos <= l.Rbrace {
+ lit = l
+ if kv, ok := path[1].(*ast.KeyValueExpr); ok {
+ if pos > kv.Colon {
+ lit = nil
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ // We are not in a composite literal.
+ if lit == nil {
+ return nil, false
+ }
+ // Mark fields of the composite literal that have already been set,
+ // except for the current field.
+ hasKeys := false // true if the composite literal already has key-value pairs
+ addedFields := make(map[*types.Var]bool)
+ for _, el := range lit.Elts {
+ if kv, ok := el.(*ast.KeyValueExpr); ok {
+ hasKeys = true
+ if kv.Pos() <= pos && pos <= kv.End() {
+ continue
+ }
+ if key, ok := kv.Key.(*ast.Ident); ok {
+ if used, ok := info.Uses[key]; ok {
+ if usedVar, ok := used.(*types.Var); ok {
+ addedFields[usedVar] = true
+ }
+ }
+ }
+ }
+ }
+ // If the underlying type of the composite literal is a struct,
+ // collect completions for the fields of this struct.
+ if tv, ok := info.Types[lit]; ok {
+ var structPkg *types.Package // package containing the struct type declaration
+ if s, ok := tv.Type.Underlying().(*types.Struct); ok {
+ for i := 0; i < s.NumFields(); i++ {
+ field := s.Field(i)
+ if i == 0 {
+ structPkg = field.Pkg()
+ }
+ if !addedFields[field] {
+ items = found(field, 10, items)
+ }
+ }
+ // Add lexical completions if the user hasn't typed a key value expression
+ // and if the struct fields are defined in the same package as the user is in.
+ if !hasKeys && structPkg == pkg {
+ items = append(items, lexical(path, pos, pkg, info, found)...)
+ }
+ return items, true
+ }
+ }
+ return items, false
+}
+
+// formatCompletion creates a completion item for a given types.Object.
+func formatCompletion(obj types.Object, qualifier types.Qualifier, score float64, isParam func(*types.Var) bool) CompletionItem {
+ label := obj.Name()
+ detail := types.TypeString(obj.Type(), qualifier)
+ var kind CompletionItemKind
+
+ switch o := obj.(type) {
+ case *types.TypeName:
+ detail, kind = formatType(o.Type(), qualifier)
+ if obj.Parent() == types.Universe {
+ detail = ""
+ }
+ case *types.Const:
+ if obj.Parent() == types.Universe {
+ detail = ""
+ } else {
+ val := o.Val().ExactString()
+ if !strings.Contains(val, "\\n") { // skip any multiline constants
+ label += " = " + o.Val().ExactString()
+ }
+ }
+ kind = ConstantCompletionItem
+ case *types.Var:
+ if _, ok := o.Type().(*types.Struct); ok {
+ detail = "struct{...}" // for anonymous structs
+ }
+ if o.IsField() {
+ kind = FieldCompletionItem
+ } else if isParam(o) {
+ kind = ParameterCompletionItem
+ } else {
+ kind = VariableCompletionItem
+ }
+ case *types.Func:
+ if sig, ok := o.Type().(*types.Signature); ok {
+ label += formatParams(sig.Params(), sig.Variadic(), qualifier)
+ detail = strings.Trim(types.TypeString(sig.Results(), qualifier), "()")
+ kind = FunctionCompletionItem
+ if sig.Recv() != nil {
+ kind = MethodCompletionItem
+ }
+ }
+ case *types.Builtin:
+ item, ok := builtinDetails[obj.Name()]
+ if !ok {
+ break
+ }
+ label, detail = item.label, item.detail
+ kind = FunctionCompletionItem
+ case *types.PkgName:
+ kind = PackageCompletionItem
+ detail = fmt.Sprintf("\"%s\"", o.Imported().Path())
+ case *types.Nil:
+ kind = VariableCompletionItem
+ detail = ""
+ }
+ detail = strings.TrimPrefix(detail, "untyped ")
+
+ return CompletionItem{
+ Label: label,
+ Detail: detail,
+ Kind: kind,
+ }
+}
+
+// formatType returns the detail and kind for an object of type *types.TypeName.
+func formatType(typ types.Type, qualifier types.Qualifier) (detail string, kind CompletionItemKind) {
+ if types.IsInterface(typ) {
+ detail = "interface{...}"
+ kind = InterfaceCompletionItem
+ } else if _, ok := typ.(*types.Struct); ok {
+ detail = "struct{...}"
+ kind = StructCompletionItem
+ } else if typ != typ.Underlying() {
+ detail, kind = formatType(typ.Underlying(), qualifier)
+ } else {
+ detail = types.TypeString(typ, qualifier)
+ kind = TypeCompletionItem
+ }
+ return detail, kind
+}
+
+// formatParams correctly format the parameters of a function.
+func formatParams(t *types.Tuple, variadic bool, qualifier types.Qualifier) string {
+ var b strings.Builder
+ b.WriteByte('(')
+ for i := 0; i < t.Len(); i++ {
+ if i > 0 {
+ b.WriteString(", ")
+ }
+ el := t.At(i)
+ typ := types.TypeString(el.Type(), qualifier)
+ // Handle a variadic parameter (can only be the final parameter).
+ if variadic && i == t.Len()-1 {
+ typ = strings.Replace(typ, "[]", "...", 1)
+ }
+ fmt.Fprintf(&b, "%v %v", el.Name(), typ)
+ }
+ b.WriteByte(')')
+ return b.String()
+}
+
+// isParameter returns true if the given *types.Var is a parameter to the given
+// *types.Signature.
+func isParameter(sig *types.Signature, v *types.Var) bool {
+ if sig == nil {
+ return false
+ }
+ for i := 0; i < sig.Params().Len(); i++ {
+ if sig.Params().At(i) == v {
+ return true
+ }
+ }
+ return false
+}
+
+// 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(pkg *types.Package) string {
+ if pkg == pkg {
+ return ""
+ }
+ if name, ok := imports[pkg]; ok {
+ return name
+ }
+ return pkg.Name()
+ }
+}
+
+// enclosingFunction returns the signature of the function enclosing the given
+// position.
+func enclosingFunction(path []ast.Node, pos token.Pos, info *types.Info) *types.Signature {
+ for _, node := range path {
+ switch t := node.(type) {
+ case *ast.FuncDecl:
+ if obj, ok := info.Defs[t.Name]; ok {
+ return obj.Type().(*types.Signature)
+ }
+ case *ast.FuncLit:
+ if typ, ok := info.Types[t]; ok {
+ return typ.Type.(*types.Signature)
+ }
+ }
+ }
+ return nil
+}
+
+// expectedType returns the expected type for an expression at the query position.
+func expectedType(path []ast.Node, pos token.Pos, info *types.Info) types.Type {
+ for i, node := range path {
+ if i == 2 {
+ break
+ }
+ switch expr := node.(type) {
+ case *ast.BinaryExpr:
+ // Determine if query position comes from left or right of op.
+ e := expr.X
+ if pos < expr.OpPos {
+ e = expr.Y
+ }
+ if tv, ok := info.Types[e]; ok {
+ return tv.Type
+ }
+ case *ast.AssignStmt:
+ // Only rank completions if you are on the right side of the token.
+ if pos <= expr.TokPos {
+ break
+ }
+ i := exprAtPos(pos, expr.Rhs)
+ if i >= len(expr.Lhs) {
+ i = len(expr.Lhs) - 1
+ }
+ if tv, ok := info.Types[expr.Lhs[i]]; ok {
+ return tv.Type
+ }
+ case *ast.CallExpr:
+ if tv, ok := info.Types[expr.Fun]; ok {
+ if sig, ok := tv.Type.(*types.Signature); ok {
+ if sig.Params().Len() == 0 {
+ return nil
+ }
+ i := exprAtPos(pos, expr.Args)
+ // Make sure not to run past the end of expected parameters.
+ if i >= sig.Params().Len() {
+ i = sig.Params().Len() - 1
+ }
+ return sig.Params().At(i).Type()
+ }
+ }
+ }
+ }
+ return nil
+}
+
+// matchingTypes reports whether actual is a good candidate type
+// for a completion in a context of the expected type.
+func matchingTypes(expected, actual types.Type) bool {
+ // Use a function's return type as its type.
+ if sig, ok := actual.(*types.Signature); ok {
+ if sig.Results().Len() == 1 {
+ actual = sig.Results().At(0).Type()
+ }
+ }
+ return types.Identical(types.Default(expected), types.Default(actual))
+}
+
+// exprAtPos returns the index of the expression containing pos.
+func exprAtPos(pos token.Pos, args []ast.Expr) int {
+ for i, expr := range args {
+ if expr.Pos() <= pos && pos <= expr.End() {
+ return i
+ }
+ }
+ return len(args)
+}
+
+// fieldSelections returns the set of fields that can
+// be selected from a value of type T.
+func fieldSelections(T types.Type) (fields []*types.Var) {
+ // TODO(adonovan): this algorithm doesn't exclude ambiguous
+ // selections that match more than one field/method.
+ // types.NewSelectionSet should do that for us.
+
+ seen := make(map[types.Type]bool) // for termination on recursive types
+ var visit func(T types.Type)
+ visit = func(T types.Type) {
+ if !seen[T] {
+ seen[T] = true
+ if T, ok := deref(T).Underlying().(*types.Struct); ok {
+ for i := 0; i < T.NumFields(); i++ {
+ f := T.Field(i)
+ fields = append(fields, f)
+ if f.Anonymous() {
+ visit(f.Type())
+ }
+ }
+ }
+ }
+ }
+ visit(T)
+
+ return fields
+}
+
+func isPointer(T types.Type) bool {
+ _, ok := T.(*types.Pointer)
+ return ok
+}
+
+// deref returns a pointer's element type; otherwise it returns typ.
+func deref(typ types.Type) types.Type {
+ if p, ok := typ.Underlying().(*types.Pointer); ok {
+ return p.Elem()
+ }
+ return typ
+}
+
+// resolveInvalid traverses the node of the AST that defines the scope
+// containing the declaration of obj, and attempts to find a user-friendly
+// name for its invalid type. The resulting Object and its Type are fake.
+func resolveInvalid(obj types.Object, node ast.Node, info *types.Info) types.Object {
+ // Construct a fake type for the object and return a fake object with this type.
+ formatResult := func(expr ast.Expr) types.Object {
+ var typename string
+ switch t := expr.(type) {
+ case *ast.SelectorExpr:
+ typename = fmt.Sprintf("%s.%s", t.X, t.Sel)
+ case *ast.Ident:
+ typename = t.String()
+ default:
+ return nil
+ }
+ typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), nil, nil)
+ return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
+ }
+ var resultExpr ast.Expr
+ ast.Inspect(node, func(node ast.Node) bool {
+ switch n := node.(type) {
+ case *ast.ValueSpec:
+ for _, name := range n.Names {
+ if info.Defs[name] == obj {
+ resultExpr = n.Type
+ }
+ }
+ return false
+ case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit.
+ for _, name := range n.Names {
+ if info.Defs[name] == obj {
+ resultExpr = n.Type
+ }
+ }
+ return false
+ // TODO(rstambler): Handle range statements.
+ default:
+ return true
+ }
+ })
+ return formatResult(resultExpr)
+}
+
+type itemDetails struct {
+ label, detail string
+}
+
+var builtinDetails = map[string]itemDetails{
+ "append": { // append(slice []T, elems ...T)
+ label: "append(slice []T, elems ...T)",
+ detail: "[]T",
+ },
+ "cap": { // cap(v []T) int
+ label: "cap(v []T)",
+ detail: "int",
+ },
+ "close": { // close(c chan<- T)
+ label: "close(c chan<- T)",
+ },
+ "complex": { // complex(r, i float64) complex128
+ label: "complex(real, imag float64)",
+ detail: "complex128",
+ },
+ "copy": { // copy(dst, src []T) int
+ label: "copy(dst, src []T)",
+ detail: "int",
+ },
+ "delete": { // delete(m map[T]T1, key T)
+ label: "delete(m map[K]V, key K)",
+ },
+ "imag": { // imag(c complex128) float64
+ label: "imag(complex128)",
+ detail: "float64",
+ },
+ "len": { // len(v T) int
+ label: "len(T)",
+ detail: "int",
+ },
+ "make": { // make(t T, size ...int) T
+ label: "make(t T, size ...int)",
+ detail: "T",
+ },
+ "new": { // new(T) *T
+ label: "new(T)",
+ detail: "*T",
+ },
+ "panic": { // panic(v interface{})
+ label: "panic(interface{})",
+ },
+ "print": { // print(args ...T)
+ label: "print(args ...T)",
+ },
+ "println": { // println(args ...T)
+ label: "println(args ...T)",
+ },
+ "real": { // real(c complex128) float64
+ label: "real(complex128)",
+ detail: "float64",
+ },
+ "recover": { // recover() interface{}
+ label: "recover()",
+ detail: "interface{}",
+ },
+}