mirror of
https://github.com/golang/go
synced 2024-11-18 20:54:40 -07:00
eac381796e
Our loop to make all candidates use the same "filterText" wasn't including the final candidate. This was causing strange ordering of candidates in VSCode when the "worst" candidate happened to match the prefix exactly (causing VSCode to reorder it to the top). Fixes golang/go#36519. Change-Id: Ie2119ca94d13f337edd241fbde862706bdeee191 Reviewed-on: https://go-review.googlesource.com/c/tools/+/214184 Run-TryBot: Muir Manders <muir@mnd.rs> 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 {
|
|
for i := 1; i < len(items); i++ {
|
|
// Give all the candidates 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
|
|
}
|