1
0
mirror of https://github.com/golang/go synced 2024-11-18 22:44:48 -07:00
go/internal/lsp/completion.go
Heschi Kreinick 3f7dfa39cf internal/lsp: sort by label after score
I want to stop sorting unimported completions. We still want to show
users something reasonable, so use label as a tiebreaker for score in
the higher level completion function.

To maintain the current sorting, we need to adjust scores by search
depth (height?) for lexical completions. A few tests are really ties,
and need sorting in the test case.

Change-Id: Ie2d09fdcbebf6fda4ab33a2f16c579d12b0f26ad
Reviewed-on: https://go-review.googlesource.com/c/tools/+/212633
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-12-30 21:11:21 +00:00

151 lines
4.7 KiB
Go

// Copyright 2018 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 lsp
import (
"context"
"fmt"
"sort"
"strings"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/telemetry/log"
"golang.org/x/tools/internal/telemetry/tag"
)
func (s *Server) completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
uri := span.NewURI(params.TextDocument.URI)
view, err := s.session.ViewOf(uri)
if err != nil {
return nil, err
}
snapshot := view.Snapshot()
options := view.Options()
fh, err := snapshot.GetFile(ctx, uri)
if err != nil {
return nil, err
}
var candidates []source.CompletionItem
var surrounding *source.Selection
switch fh.Identity().Kind {
case source.Go:
options.Completion.FullDocumentation = options.HoverKind == source.FullDocumentation
candidates, surrounding, err = source.Completion(ctx, snapshot, fh, params.Position, options.Completion)
case source.Mod:
candidates, surrounding = nil, nil
}
if err != nil {
log.Print(ctx, "no completions found", tag.Of("At", params.Position), tag.Of("Failure", err))
}
if candidates == nil {
return &protocol.CompletionList{
Items: []protocol.CompletionItem{},
}, nil
}
// We might need to adjust the position to account for the prefix.
rng, err := surrounding.Range()
if err != nil {
return nil, err
}
// Sort the candidates by score, then label, since that is not supported by LSP yet.
sort.SliceStable(candidates, func(i, j int) bool {
if candidates[i].Score != candidates[j].Score {
return candidates[i].Score > candidates[j].Score
}
return candidates[i].Label < candidates[j].Label
})
// When using deep completions/fuzzy matching, report results as incomplete so
// client fetches updated completions after every key stroke.
incompleteResults := options.Completion.Deep || options.Completion.FuzzyMatching
items := toProtocolCompletionItems(candidates, rng, options)
if incompleteResults && len(items) > 1 {
for i := range items[1:] {
// Give all the candidaites the same filterText to trick VSCode
// into not reordering our candidates. All the candidates will
// appear to be equally good matches, so VSCode's fuzzy
// matching/ranking just maintains the natural "sortText"
// ordering. We can only do this in tandem with
// "incompleteResults" since otherwise client side filtering is
// important.
items[i].FilterText = items[0].FilterText
}
}
return &protocol.CompletionList{
IsIncomplete: incompleteResults,
Items: items,
}, nil
}
func toProtocolCompletionItems(candidates []source.CompletionItem, rng protocol.Range, options source.Options) []protocol.CompletionItem {
var (
items = make([]protocol.CompletionItem, 0, len(candidates))
numDeepCompletionsSeen int
)
for i, candidate := range candidates {
// Limit the number of deep completions to not overwhelm the user in cases
// with dozens of deep completion matches.
if candidate.Depth > 0 {
if !options.Completion.Deep {
continue
}
if numDeepCompletionsSeen >= source.MaxDeepCompletions {
continue
}
numDeepCompletionsSeen++
}
insertText := candidate.InsertText
if options.InsertTextFormat == protocol.SnippetTextFormat {
insertText = candidate.Snippet()
}
// This can happen if the client has snippets disabled but the
// candidate only supports snippet insertion.
if insertText == "" {
continue
}
item := protocol.CompletionItem{
Label: candidate.Label,
Detail: candidate.Detail,
Kind: candidate.Kind,
TextEdit: &protocol.TextEdit{
NewText: insertText,
Range: rng,
},
InsertTextFormat: options.InsertTextFormat,
AdditionalTextEdits: candidate.AdditionalTextEdits,
// 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),
// Trim address operator (VSCode doesn't like weird characters
// in filterText).
FilterText: strings.TrimLeft(candidate.InsertText, "&"),
Preselect: i == 0,
Documentation: candidate.Documentation,
}
// Trigger signature help for any function or method completion.
// This is helpful even if a function does not have parameters,
// since we show return types as well.
switch item.Kind {
case protocol.FunctionCompletion, protocol.MethodCompletion:
item.Command = &protocol.Command{
Command: "editor.action.triggerParameterHints",
}
}
items = append(items, item)
}
return items
}