mirror of
https://github.com/golang/go
synced 2024-11-18 23:34:45 -07:00
3f7dfa39cf
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>
151 lines
4.7 KiB
Go
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
|
|
}
|