mirror of
https://github.com/golang/go
synced 2024-11-19 02:44:44 -07:00
a1c92a4ac6
Change-Id: I1a084a47d2f171c6b94bde6fece008cddd547833 Reviewed-on: https://go-review.googlesource.com/c/tools/+/175937 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
210 lines
5.5 KiB
Go
210 lines
5.5 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 (
|
|
"bytes"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/printer"
|
|
"go/types"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/internal/lsp/snippet"
|
|
)
|
|
|
|
// formatCompletion creates a completion item for a given types.Object.
|
|
func (c *completer) item(obj types.Object, score float64) CompletionItem {
|
|
// Handle builtin types separately.
|
|
if obj.Parent() == types.Universe {
|
|
return c.formatBuiltin(obj, score)
|
|
}
|
|
|
|
var (
|
|
label = obj.Name()
|
|
detail = types.TypeString(obj.Type(), c.qf)
|
|
insert = label
|
|
kind CompletionItemKind
|
|
plainSnippet *snippet.Builder
|
|
placeholderSnippet *snippet.Builder
|
|
)
|
|
|
|
switch o := obj.(type) {
|
|
case *types.TypeName:
|
|
detail, kind = formatType(o.Type(), c.qf)
|
|
case *types.Const:
|
|
if obj.Parent() == types.Universe {
|
|
detail = ""
|
|
} else {
|
|
val := o.Val().ExactString()
|
|
if !strings.ContainsRune(val, '\n') { // skip any multiline constants
|
|
label += " = " + val
|
|
}
|
|
}
|
|
kind = ConstantCompletionItem
|
|
case *types.Var:
|
|
if _, ok := o.Type().(*types.Struct); ok {
|
|
detail = "struct{...}" // for anonymous structs
|
|
}
|
|
if o.IsField() {
|
|
kind = FieldCompletionItem
|
|
plainSnippet, placeholderSnippet = c.structFieldSnippets(label, detail)
|
|
} else if c.isParameter(o) {
|
|
kind = ParameterCompletionItem
|
|
} else {
|
|
kind = VariableCompletionItem
|
|
}
|
|
case *types.Func:
|
|
sig, ok := o.Type().(*types.Signature)
|
|
if !ok {
|
|
break
|
|
}
|
|
params := formatParams(sig.Params(), sig.Variadic(), c.qf)
|
|
results, writeParens := formatResults(sig.Results(), c.qf)
|
|
label, detail = formatFunction(obj.Name(), params, results, writeParens)
|
|
plainSnippet, placeholderSnippet = c.functionCallSnippets(obj.Name(), params)
|
|
kind = FunctionCompletionItem
|
|
if sig.Recv() != nil {
|
|
kind = MethodCompletionItem
|
|
}
|
|
case *types.PkgName:
|
|
kind = PackageCompletionItem
|
|
detail = fmt.Sprintf("\"%s\"", o.Imported().Path())
|
|
}
|
|
detail = strings.TrimPrefix(detail, "untyped ")
|
|
|
|
return CompletionItem{
|
|
Label: label,
|
|
InsertText: insert,
|
|
Detail: detail,
|
|
Kind: kind,
|
|
Score: score,
|
|
Snippet: plainSnippet,
|
|
PlaceholderSnippet: placeholderSnippet,
|
|
}
|
|
}
|
|
|
|
// isParameter returns true if the given *types.Var is a parameter
|
|
// of the enclosingFunction.
|
|
func (c *completer) isParameter(v *types.Var) bool {
|
|
if c.enclosingFunction == nil {
|
|
return false
|
|
}
|
|
for i := 0; i < c.enclosingFunction.Params().Len(); i++ {
|
|
if c.enclosingFunction.Params().At(i) == v {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *completer) formatBuiltin(obj types.Object, score float64) CompletionItem {
|
|
item := CompletionItem{
|
|
Label: obj.Name(),
|
|
InsertText: obj.Name(),
|
|
Score: score,
|
|
}
|
|
switch obj.(type) {
|
|
case *types.Const:
|
|
item.Kind = ConstantCompletionItem
|
|
case *types.Builtin:
|
|
item.Kind = FunctionCompletionItem
|
|
builtinPkg := c.view.BuiltinPackage()
|
|
if builtinPkg == nil || builtinPkg.Scope == nil {
|
|
break
|
|
}
|
|
fn := builtinPkg.Scope.Lookup(obj.Name())
|
|
if fn == nil {
|
|
break
|
|
}
|
|
decl, ok := fn.Decl.(*ast.FuncDecl)
|
|
if !ok {
|
|
break
|
|
}
|
|
params, _ := c.formatFieldList(decl.Type.Params)
|
|
results, writeResultParens := c.formatFieldList(decl.Type.Results)
|
|
item.Label, item.Detail = formatFunction(obj.Name(), params, results, writeResultParens)
|
|
item.Snippet, item.PlaceholderSnippet = c.functionCallSnippets(obj.Name(), params)
|
|
case *types.TypeName:
|
|
if types.IsInterface(obj.Type()) {
|
|
item.Kind = InterfaceCompletionItem
|
|
} else {
|
|
item.Kind = TypeCompletionItem
|
|
}
|
|
case *types.Nil:
|
|
item.Kind = VariableCompletionItem
|
|
}
|
|
return item
|
|
}
|
|
|
|
var replacer = strings.NewReplacer(
|
|
`ComplexType`, `complex128`,
|
|
`FloatType`, `float64`,
|
|
`IntegerType`, `int`,
|
|
)
|
|
|
|
func (c *completer) formatFieldList(list *ast.FieldList) ([]string, bool) {
|
|
if list == nil {
|
|
return nil, false
|
|
}
|
|
var writeResultParens bool
|
|
var result []string
|
|
for i := 0; i < len(list.List); i++ {
|
|
if i >= 1 {
|
|
writeResultParens = true
|
|
}
|
|
p := list.List[i]
|
|
cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 4}
|
|
b := &bytes.Buffer{}
|
|
if err := cfg.Fprint(b, c.view.FileSet(), p.Type); err != nil {
|
|
c.view.Logger().Errorf(c.ctx, "unable to print type %v", p.Type)
|
|
continue
|
|
}
|
|
typ := replacer.Replace(b.String())
|
|
if len(p.Names) == 0 {
|
|
result = append(result, fmt.Sprintf("%s", typ))
|
|
}
|
|
for _, name := range p.Names {
|
|
if name.Name != "" {
|
|
if i == 0 {
|
|
writeResultParens = true
|
|
}
|
|
result = append(result, fmt.Sprintf("%s %s", name.Name, typ))
|
|
} else {
|
|
result = append(result, fmt.Sprintf("%s", typ))
|
|
}
|
|
}
|
|
}
|
|
return result, writeResultParens
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
}
|