mirror of
https://github.com/golang/go
synced 2024-11-18 16:04:44 -07:00
internal/lsp: sort completions according to rank
The LSP specification doesn't have a Score field, so we must provide sortText to the protocol in order to maintain the correct order. Change-Id: I075849f520c21a0465dfb2060c598d8bae5f876b Reviewed-on: https://go-review.googlesource.com/c/151237 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
04b5d21e00
commit
36a8f0a386
@ -13,7 +13,7 @@ import (
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
)
|
||||
|
||||
func toProtocolCompletionItems(items []source.CompletionItem, snippetsSupported, signatureHelpEnabled bool) []protocol.CompletionItem {
|
||||
func toProtocolCompletionItems(items []source.CompletionItem, prefix string, pos protocol.Position, snippetsSupported, signatureHelpEnabled bool) []protocol.CompletionItem {
|
||||
var results []protocol.CompletionItem
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].Score > items[j].Score
|
||||
@ -22,14 +22,33 @@ func toProtocolCompletionItems(items []source.CompletionItem, snippetsSupported,
|
||||
if snippetsSupported {
|
||||
insertTextFormat = protocol.SnippetTextFormat
|
||||
}
|
||||
for _, item := range items {
|
||||
for i, item := range items {
|
||||
// Matching against the label.
|
||||
if !strings.HasPrefix(item.Label, prefix) {
|
||||
continue
|
||||
}
|
||||
insertText, triggerSignatureHelp := labelToProtocolSnippets(item.Label, item.Kind, insertTextFormat, signatureHelpEnabled)
|
||||
if prefix != "" {
|
||||
insertText = insertText[len(prefix):]
|
||||
}
|
||||
i := protocol.CompletionItem{
|
||||
Label: item.Label,
|
||||
InsertText: insertText,
|
||||
Detail: item.Detail,
|
||||
Kind: float64(toProtocolCompletionItemKind(item.Kind)),
|
||||
InsertTextFormat: insertTextFormat,
|
||||
TextEdit: &protocol.TextEdit{
|
||||
NewText: insertText,
|
||||
Range: protocol.Range{
|
||||
Start: pos,
|
||||
End: pos,
|
||||
},
|
||||
},
|
||||
// InsertText is deprecated in favor of TextEdit.
|
||||
InsertText: insertText,
|
||||
// This is a hack so that the client sorts completion results in the order
|
||||
// according to their score. This can be removed upon the resolution of
|
||||
// https://github.com/Microsoft/language-server-protocol/issues/348.
|
||||
SortText: fmt.Sprintf("%05d", i),
|
||||
}
|
||||
// If we are completing a function, we should trigger signature help if possible.
|
||||
if triggerSignatureHelp && signatureHelpEnabled {
|
||||
@ -72,7 +91,9 @@ func labelToProtocolSnippets(label string, kind source.CompletionItemKind, inser
|
||||
case source.ConstantCompletionItem:
|
||||
// The label for constants is of the format "<identifier> = <value>".
|
||||
// We should now insert the " = <value>" part of the label.
|
||||
return label[:strings.Index(label, " =")], false
|
||||
if i := strings.Index(label, " ="); i >= 0 {
|
||||
return label[:i], false
|
||||
}
|
||||
case source.FunctionCompletionItem, source.MethodCompletionItem:
|
||||
trimmed := label[:strings.Index(label, "(")]
|
||||
params := strings.Trim(label[strings.Index(label, "("):], "()")
|
||||
|
@ -201,10 +201,28 @@ func (c completions) test(t *testing.T, exported *packagestest.Exported, s *serv
|
||||
},
|
||||
},
|
||||
})
|
||||
var got []protocol.CompletionItem
|
||||
for _, item := range list.Items {
|
||||
// Skip all types with no details (builtin types).
|
||||
if item.Detail == "" && item.Kind == float64(protocol.TypeParameterCompletion) {
|
||||
continue
|
||||
}
|
||||
// Skip remaining builtin types.
|
||||
trimmed := item.Label
|
||||
if i := strings.Index(trimmed, "("); i >= 0 {
|
||||
trimmed = trimmed[:i]
|
||||
}
|
||||
switch trimmed {
|
||||
case "append", "cap", "close", "complex", "copy", "delete",
|
||||
"error", "false", "imag", "iota", "len", "make", "new",
|
||||
"nil", "panic", "print", "println", "real", "recover", "true":
|
||||
continue
|
||||
}
|
||||
got = append(got, item)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("completion failed for %s:%v:%v: %v", filepath.Base(src.Filename), src.Line, src.Column, err)
|
||||
}
|
||||
got := list.Items
|
||||
if diff := diffC(src, want, got); diff != "" {
|
||||
t.Errorf(diff)
|
||||
}
|
||||
|
@ -169,13 +169,13 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara
|
||||
return nil, err
|
||||
}
|
||||
pos := fromProtocolPosition(tok, params.Position)
|
||||
items, err := source.Completion(ctx, f, pos)
|
||||
items, prefix, err := source.Completion(ctx, f, pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &protocol.CompletionList{
|
||||
IsIncomplete: false,
|
||||
Items: toProtocolCompletionItems(items, s.snippetsSupported, s.signatureHelpEnabled),
|
||||
Items: toProtocolCompletionItems(items, prefix, params.Position, s.snippetsSupported, s.signatureHelpEnabled),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -34,17 +34,16 @@ const (
|
||||
PackageCompletionItem
|
||||
)
|
||||
|
||||
func Completion(ctx context.Context, f *File, pos token.Pos) ([]CompletionItem, error) {
|
||||
func Completion(ctx context.Context, f *File, pos token.Pos) ([]CompletionItem, string, error) {
|
||||
file, err := f.GetAST()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
pkg, err := f.GetPackage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
items, _, err := completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo)
|
||||
return items, err
|
||||
return completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo)
|
||||
}
|
||||
|
||||
const stdScore float64 = 1.0
|
||||
@ -93,9 +92,6 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.
|
||||
if typ != nil && matchingTypes(typ, obj.Type()) {
|
||||
weight *= 10.0
|
||||
}
|
||||
if !strings.HasPrefix(obj.Name(), prefix) {
|
||||
return items
|
||||
}
|
||||
item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
|
||||
return isParameter(sig, v)
|
||||
})
|
||||
@ -115,7 +111,8 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.
|
||||
|
||||
// Is this the Sel part of a selector?
|
||||
if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
|
||||
return selector(sel, info, found)
|
||||
items, err = selector(sel, pos, info, found)
|
||||
return items, prefix, err
|
||||
}
|
||||
// reject defining identifiers
|
||||
if obj, ok := info.Defs[n]; ok {
|
||||
@ -137,10 +134,12 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.
|
||||
// recv.‸(arg)
|
||||
case *ast.TypeAssertExpr:
|
||||
// Create a fake selector expression.
|
||||
return selector(&ast.SelectorExpr{X: n.X}, info, found)
|
||||
items, err = selector(&ast.SelectorExpr{X: n.X}, pos, info, found)
|
||||
return items, prefix, err
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
return selector(n, info, found)
|
||||
items, err = selector(n, pos, info, found)
|
||||
return items, prefix, err
|
||||
|
||||
default:
|
||||
// fallback to lexical completions
|
||||
@ -153,7 +152,7 @@ func completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.
|
||||
// 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) {
|
||||
func selector(sel *ast.SelectorExpr, pos token.Pos, info *types.Info, found finder) (items []CompletionItem, err error) {
|
||||
// Is sel a qualified identifier?
|
||||
if id, ok := sel.X.(*ast.Ident); ok {
|
||||
if pkgname, ok := info.Uses[id].(*types.PkgName); ok {
|
||||
@ -164,14 +163,14 @@ func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []Co
|
||||
for _, name := range scope.Names() {
|
||||
items = found(scope.Lookup(name), stdScore, items)
|
||||
}
|
||||
return items, prefix, nil
|
||||
return items, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Inv: sel is a true selector.
|
||||
tv, ok := info.Types[sel.X]
|
||||
if !ok {
|
||||
return nil, "", fmt.Errorf("cannot resolve %s", sel.X)
|
||||
return nil, fmt.Errorf("cannot resolve %s", sel.X)
|
||||
}
|
||||
|
||||
// methods of T
|
||||
@ -193,7 +192,7 @@ func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []Co
|
||||
items = found(f, stdScore, items)
|
||||
}
|
||||
|
||||
return items, prefix, nil
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// lexical finds completions in the lexical environment.
|
||||
@ -208,7 +207,7 @@ func lexical(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Inf
|
||||
}
|
||||
scopes = append(scopes, info.Scopes[n])
|
||||
}
|
||||
scopes = append(scopes, pkg.Scope())
|
||||
scopes = append(scopes, pkg.Scope(), types.Universe)
|
||||
|
||||
// Process scopes innermost first.
|
||||
for i, scope := range scopes {
|
||||
|
Loading…
Reference in New Issue
Block a user