1
0
mirror of https://github.com/golang/go synced 2024-11-18 18:54:42 -07:00
go/internal/lsp/completion.go

155 lines
5.1 KiB
Go
Raw Normal View History

// 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"
"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 := s.session.ViewOf(uri)
f, m, err := getGoFile(ctx, view, uri)
if err != nil {
return nil, err
}
spn, err := m.PointSpan(params.Position)
if err != nil {
return nil, err
}
rng, err := spn.Range(m.Converter)
if err != nil {
return nil, err
}
candidates, surrounding, err := source.Completion(ctx, view, f, rng.Start, source.CompletionOptions{
DeepComplete: s.useDeepCompletions,
WantDocumentaton: s.wantCompletionDocumentation,
WantFullDocumentation: s.hoverKind == fullDocumentation,
})
if err != nil {
log.Print(ctx, "no completions found", tag.Of("At", rng), tag.Of("Failure", err))
}
return &protocol.CompletionList{
internal/lsp: add fuzzy completion matching Make use of the existing fuzzy matcher to perform server side fuzzy completion matching. Previously the server did exact prefix matching for completion candidates and left fancy filtering to the client. Having the server do fuzzy matching has two main benefits: - Deep completions now update as you type. The completion candidates returned to the client are marked "incomplete", causing the client to refresh the candidates after every keystroke. This lets the server pick the most relevant set of deep completion candidates. - All editors get fuzzy matching for free. VSCode has fuzzy matching out of the box, but some editors either don't provide it, or it can be difficult to set up. I modified the fuzzy matcher to allow matches where the input doesn't match the final segment of the candidate. For example, previously "ab" would not match "abc.def" because the "b" in "ab" did not match the final segment "def". I can see how this is useful when the text matching happens in a vacuum and candidate's final segment is the most specific part. But, in our case, we have various other methods to order candidates, so we don't want to exclude them just because the final segment doesn't match. For example, if we know our candidate needs to be type "context.Context" and "foo.ctx" is of the right type, we want to suggest "foo.ctx" as soon as the user starts inputting "foo", even though "foo" doesn't match "ctx" at all. Note that fuzzy matching is behind the "useDeepCompletions" config flag for the time being. Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78 Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
// When using deep completions/fuzzy matching, report results as incomplete so
// client fetches updated completions after every key stroke.
IsIncomplete: s.useDeepCompletions,
Items: s.toProtocolCompletionItems(ctx, view, m, candidates, params.Position, surrounding),
}, nil
}
// Limit deep completion results because in some cases there are too many
// to be useful.
const maxDeepCompletions = 3
func (s *Server) toProtocolCompletionItems(ctx context.Context, view source.View, m *protocol.ColumnMapper, candidates []source.CompletionItem, pos protocol.Position, surrounding *source.Selection) []protocol.CompletionItem {
// Sort the candidates by score, since that is not supported by LSP yet.
sort.SliceStable(candidates, func(i, j int) bool {
return candidates[i].Score > candidates[j].Score
})
// We might need to adjust the position to account for the prefix.
insertionRange := protocol.Range{
Start: pos,
End: pos,
}
if surrounding != nil {
spn, err := surrounding.Range.Span()
if err != nil {
log.Print(ctx, "failed to get span for surrounding position: %s:%v:%v: %v", tag.Of("Position", pos), tag.Of("Failure", err))
} else {
rng, err := m.Range(spn)
if err != nil {
log.Print(ctx, "failed to convert surrounding position", tag.Of("Position", pos), tag.Of("Failure", err))
} else {
insertionRange = rng
}
}
}
internal/lsp: add fuzzy completion matching Make use of the existing fuzzy matcher to perform server side fuzzy completion matching. Previously the server did exact prefix matching for completion candidates and left fancy filtering to the client. Having the server do fuzzy matching has two main benefits: - Deep completions now update as you type. The completion candidates returned to the client are marked "incomplete", causing the client to refresh the candidates after every keystroke. This lets the server pick the most relevant set of deep completion candidates. - All editors get fuzzy matching for free. VSCode has fuzzy matching out of the box, but some editors either don't provide it, or it can be difficult to set up. I modified the fuzzy matcher to allow matches where the input doesn't match the final segment of the candidate. For example, previously "ab" would not match "abc.def" because the "b" in "ab" did not match the final segment "def". I can see how this is useful when the text matching happens in a vacuum and candidate's final segment is the most specific part. But, in our case, we have various other methods to order candidates, so we don't want to exclude them just because the final segment doesn't match. For example, if we know our candidate needs to be type "context.Context" and "foo.ctx" is of the right type, we want to suggest "foo.ctx" as soon as the user starts inputting "foo", even though "foo" doesn't match "ctx" at all. Note that fuzzy matching is behind the "useDeepCompletions" config flag for the time being. Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78 Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
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 !s.useDeepCompletions {
continue
}
if numDeepCompletionsSeen >= maxDeepCompletions {
continue
}
numDeepCompletionsSeen++
}
insertText := candidate.InsertText
if s.insertTextFormat == protocol.SnippetTextFormat {
insertText = candidate.Snippet(s.usePlaceholders)
}
internal/lsp: add fuzzy completion matching Make use of the existing fuzzy matcher to perform server side fuzzy completion matching. Previously the server did exact prefix matching for completion candidates and left fancy filtering to the client. Having the server do fuzzy matching has two main benefits: - Deep completions now update as you type. The completion candidates returned to the client are marked "incomplete", causing the client to refresh the candidates after every keystroke. This lets the server pick the most relevant set of deep completion candidates. - All editors get fuzzy matching for free. VSCode has fuzzy matching out of the box, but some editors either don't provide it, or it can be difficult to set up. I modified the fuzzy matcher to allow matches where the input doesn't match the final segment of the candidate. For example, previously "ab" would not match "abc.def" because the "b" in "ab" did not match the final segment "def". I can see how this is useful when the text matching happens in a vacuum and candidate's final segment is the most specific part. But, in our case, we have various other methods to order candidates, so we don't want to exclude them just because the final segment doesn't match. For example, if we know our candidate needs to be type "context.Context" and "foo.ctx" is of the right type, we want to suggest "foo.ctx" as soon as the user starts inputting "foo", even though "foo" doesn't match "ctx" at all. Note that fuzzy matching is behind the "useDeepCompletions" config flag for the time being. Change-Id: Ic7674f0cf885af770c30daef472f2e3c5ac4db78 Reviewed-on: https://go-review.googlesource.com/c/tools/+/190099 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-08-13 14:45:19 -06:00
item := protocol.CompletionItem{
Label: candidate.Label,
Detail: candidate.Detail,
Kind: toProtocolCompletionItemKind(candidate.Kind),
TextEdit: &protocol.TextEdit{
NewText: insertText,
Range: insertionRange,
},
InsertTextFormat: s.insertTextFormat,
// 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),
FilterText: 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
}
func toProtocolCompletionItemKind(kind source.CompletionItemKind) protocol.CompletionItemKind {
switch kind {
case source.InterfaceCompletionItem:
return protocol.InterfaceCompletion
case source.StructCompletionItem:
return protocol.StructCompletion
case source.TypeCompletionItem:
return protocol.TypeParameterCompletion // ??
case source.ConstantCompletionItem:
return protocol.ConstantCompletion
case source.FieldCompletionItem:
return protocol.FieldCompletion
case source.ParameterCompletionItem, source.VariableCompletionItem:
return protocol.VariableCompletion
case source.FunctionCompletionItem:
return protocol.FunctionCompletion
case source.MethodCompletionItem:
return protocol.MethodCompletion
case source.PackageCompletionItem:
return protocol.ModuleCompletion // ??
default:
return protocol.TextCompletion
}
}