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"
|
"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
|
var results []protocol.CompletionItem
|
||||||
sort.Slice(items, func(i, j int) bool {
|
sort.Slice(items, func(i, j int) bool {
|
||||||
return items[i].Score > items[j].Score
|
return items[i].Score > items[j].Score
|
||||||
@ -22,14 +22,33 @@ func toProtocolCompletionItems(items []source.CompletionItem, snippetsSupported,
|
|||||||
if snippetsSupported {
|
if snippetsSupported {
|
||||||
insertTextFormat = protocol.SnippetTextFormat
|
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)
|
insertText, triggerSignatureHelp := labelToProtocolSnippets(item.Label, item.Kind, insertTextFormat, signatureHelpEnabled)
|
||||||
|
if prefix != "" {
|
||||||
|
insertText = insertText[len(prefix):]
|
||||||
|
}
|
||||||
i := protocol.CompletionItem{
|
i := protocol.CompletionItem{
|
||||||
Label: item.Label,
|
Label: item.Label,
|
||||||
InsertText: insertText,
|
|
||||||
Detail: item.Detail,
|
Detail: item.Detail,
|
||||||
Kind: float64(toProtocolCompletionItemKind(item.Kind)),
|
Kind: float64(toProtocolCompletionItemKind(item.Kind)),
|
||||||
InsertTextFormat: insertTextFormat,
|
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 we are completing a function, we should trigger signature help if possible.
|
||||||
if triggerSignatureHelp && signatureHelpEnabled {
|
if triggerSignatureHelp && signatureHelpEnabled {
|
||||||
@ -72,7 +91,9 @@ func labelToProtocolSnippets(label string, kind source.CompletionItemKind, inser
|
|||||||
case source.ConstantCompletionItem:
|
case source.ConstantCompletionItem:
|
||||||
// The label for constants is of the format "<identifier> = <value>".
|
// The label for constants is of the format "<identifier> = <value>".
|
||||||
// We should now insert the " = <value>" part of the label.
|
// 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:
|
case source.FunctionCompletionItem, source.MethodCompletionItem:
|
||||||
trimmed := label[:strings.Index(label, "(")]
|
trimmed := label[:strings.Index(label, "(")]
|
||||||
params := strings.Trim(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 {
|
if err != nil {
|
||||||
t.Fatalf("completion failed for %s:%v:%v: %v", filepath.Base(src.Filename), src.Line, src.Column, err)
|
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 != "" {
|
if diff := diffC(src, want, got); diff != "" {
|
||||||
t.Errorf(diff)
|
t.Errorf(diff)
|
||||||
}
|
}
|
||||||
|
@ -169,13 +169,13 @@ func (s *server) Completion(ctx context.Context, params *protocol.CompletionPara
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pos := fromProtocolPosition(tok, params.Position)
|
pos := fromProtocolPosition(tok, params.Position)
|
||||||
items, err := source.Completion(ctx, f, pos)
|
items, prefix, err := source.Completion(ctx, f, pos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &protocol.CompletionList{
|
return &protocol.CompletionList{
|
||||||
IsIncomplete: false,
|
IsIncomplete: false,
|
||||||
Items: toProtocolCompletionItems(items, s.snippetsSupported, s.signatureHelpEnabled),
|
Items: toProtocolCompletionItems(items, prefix, params.Position, s.snippetsSupported, s.signatureHelpEnabled),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,17 +34,16 @@ const (
|
|||||||
PackageCompletionItem
|
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()
|
file, err := f.GetAST()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
pkg, err := f.GetPackage()
|
pkg, err := f.GetPackage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
items, _, err := completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo)
|
return completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo)
|
||||||
return items, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const stdScore float64 = 1.0
|
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()) {
|
if typ != nil && matchingTypes(typ, obj.Type()) {
|
||||||
weight *= 10.0
|
weight *= 10.0
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(obj.Name(), prefix) {
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
|
item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
|
||||||
return isParameter(sig, v)
|
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?
|
// Is this the Sel part of a selector?
|
||||||
if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
|
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
|
// reject defining identifiers
|
||||||
if obj, ok := info.Defs[n]; ok {
|
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)
|
// recv.‸(arg)
|
||||||
case *ast.TypeAssertExpr:
|
case *ast.TypeAssertExpr:
|
||||||
// Create a fake selector expression.
|
// 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:
|
case *ast.SelectorExpr:
|
||||||
return selector(n, info, found)
|
items, err = selector(n, pos, info, found)
|
||||||
|
return items, prefix, err
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// fallback to lexical completions
|
// 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
|
// selector finds completions for
|
||||||
// the specified selector expression.
|
// the specified selector expression.
|
||||||
// TODO(rstambler): Set the prefix filter correctly for selectors.
|
// 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?
|
// Is sel a qualified identifier?
|
||||||
if id, ok := sel.X.(*ast.Ident); ok {
|
if id, ok := sel.X.(*ast.Ident); ok {
|
||||||
if pkgname, ok := info.Uses[id].(*types.PkgName); 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() {
|
for _, name := range scope.Names() {
|
||||||
items = found(scope.Lookup(name), stdScore, items)
|
items = found(scope.Lookup(name), stdScore, items)
|
||||||
}
|
}
|
||||||
return items, prefix, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inv: sel is a true selector.
|
// Inv: sel is a true selector.
|
||||||
tv, ok := info.Types[sel.X]
|
tv, ok := info.Types[sel.X]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, "", fmt.Errorf("cannot resolve %s", sel.X)
|
return nil, fmt.Errorf("cannot resolve %s", sel.X)
|
||||||
}
|
}
|
||||||
|
|
||||||
// methods of T
|
// methods of T
|
||||||
@ -193,7 +192,7 @@ func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []Co
|
|||||||
items = found(f, stdScore, items)
|
items = found(f, stdScore, items)
|
||||||
}
|
}
|
||||||
|
|
||||||
return items, prefix, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// lexical finds completions in the lexical environment.
|
// 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, info.Scopes[n])
|
||||||
}
|
}
|
||||||
scopes = append(scopes, pkg.Scope())
|
scopes = append(scopes, pkg.Scope(), types.Universe)
|
||||||
|
|
||||||
// Process scopes innermost first.
|
// Process scopes innermost first.
|
||||||
for i, scope := range scopes {
|
for i, scope := range scopes {
|
||||||
|
Loading…
Reference in New Issue
Block a user