1
0
mirror of https://github.com/golang/go synced 2024-11-18 19:44:46 -07:00
go/internal/lsp/completion.go
Muir Manders f217c98fae internal/lsp: fix completion insertion
The insertion range for completion items was not right. The range's
end was 1 before the start. Fix by taking into account the length of
the prefix when generating the range start and end.

Now instead of a "prefix", we track the completion's
"surrounding". This is basically the start and end of the abutting
identifier along with the cursor position. When we insert the
completion text, we overwrite the entire identifier, not just the
prefix. This fixes postfix completion like completing "foo.<>Bar" to
"foo.BarBaz".

Fixes golang/go#32078
Fixes golang/go#32057

Change-Id: I9d065a413ff9a6e20ae662ff93ad0092c2007c1d
GitHub-Last-Rev: af5ab4d60566bf0589d9a712c80d75280178cba9
GitHub-Pull-Request: golang/tools#103
Reviewed-on: https://go-review.googlesource.com/c/tools/+/177757
Run-TryBot: Ian Cottrell <iancottrell@google.com>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-05-17 18:32:58 +00:00

131 lines
4.3 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"
)
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
}
items, surrounding, err := source.Completion(ctx, f, rng.Start)
if err != nil {
s.session.Logger().Infof(ctx, "no completions found for %s:%v:%v: %v", uri, int(params.Position.Line), int(params.Position.Character), err)
}
// We might need to adjust the position to account for the prefix.
insertionRng := protocol.Range{
Start: params.Position,
End: params.Position,
}
var prefix string
if surrounding != nil {
prefix = surrounding.Prefix()
spn, err := surrounding.Range.Span()
if err != nil {
s.session.Logger().Infof(ctx, "failed to get span for surrounding position: %s:%v:%v: %v", uri, int(params.Position.Line), int(params.Position.Character), err)
} else {
rng, err := m.Range(spn)
if err != nil {
s.session.Logger().Infof(ctx, "failed to convert surrounding position: %s:%v:%v: %v", uri, int(params.Position.Line), int(params.Position.Character), err)
} else {
insertionRng = rng
}
}
}
return &protocol.CompletionList{
IsIncomplete: false,
Items: toProtocolCompletionItems(items, prefix, insertionRng, s.insertTextFormat, s.usePlaceholders),
}, nil
}
func toProtocolCompletionItems(candidates []source.CompletionItem, prefix string, rng protocol.Range, insertTextFormat protocol.InsertTextFormat, usePlaceholders bool) []protocol.CompletionItem {
sort.SliceStable(candidates, func(i, j int) bool {
return candidates[i].Score > candidates[j].Score
})
items := make([]protocol.CompletionItem, 0, len(candidates))
for i, candidate := range candidates {
// Match against the label.
if !strings.HasPrefix(candidate.Label, prefix) {
continue
}
insertText := candidate.InsertText
if insertTextFormat == protocol.SnippetTextFormat {
insertText = candidate.Snippet(usePlaceholders)
}
item := protocol.CompletionItem{
Label: candidate.Label,
Detail: candidate.Detail,
Kind: toProtocolCompletionItemKind(candidate.Kind),
TextEdit: &protocol.TextEdit{
NewText: insertText,
Range: rng,
},
InsertTextFormat: 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,
}
// 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
}
}