// 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 ( "bytes" "fmt" "sort" "strings" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" ) func toProtocolCompletionItems(candidates []source.CompletionItem, prefix string, pos protocol.Position, snippetsSupported, signatureHelpEnabled bool) []protocol.CompletionItem { insertTextFormat := protocol.PlainTextTextFormat if snippetsSupported { insertTextFormat = protocol.SnippetTextFormat } sort.SliceStable(candidates, func(i, j int) bool { return candidates[i].Score > candidates[j].Score }) items := []protocol.CompletionItem{} for i, candidate := range candidates { // Matching against the label. if !strings.HasPrefix(candidate.Label, prefix) { continue } // InsertText is deprecated in favor of TextEdits. // TODO(rstambler): Remove this logic when we are confident that we no // longer need to support it. insertText, triggerSignatureHelp := labelToProtocolSnippets(candidate.Label, candidate.Kind, insertTextFormat, signatureHelpEnabled) if strings.HasPrefix(insertText, prefix) { insertText = insertText[len(prefix):] } item := protocol.CompletionItem{ Label: candidate.Label, Detail: candidate.Detail, Kind: toProtocolCompletionItemKind(candidate.Kind), TextEdit: &protocol.TextEdit{ NewText: insertText, Range: protocol.Range{ Start: pos, End: pos, }, }, // 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: insertText, Preselect: i == 0, } // If we are completing a function, we should trigger signature help if possible. if triggerSignatureHelp && signatureHelpEnabled { 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 } } func labelToProtocolSnippets(label string, kind source.CompletionItemKind, insertTextFormat protocol.InsertTextFormat, signatureHelpEnabled bool) (string, bool) { switch kind { case source.ConstantCompletionItem: // The label for constants is of the format " = ". // We should not insert the " = " part of the label. if i := strings.Index(label, " ="); i >= 0 { return label[:i], false } case source.FunctionCompletionItem, source.MethodCompletionItem: var trimmed, params string if i := strings.Index(label, "("); i >= 0 { trimmed = label[:i] params = strings.Trim(label[i:], "()") } if params == "" || trimmed == "" { return label, true } // Don't add parameters or parens for the plaintext insert format. if insertTextFormat == protocol.PlainTextTextFormat { return trimmed, true } // If we do have signature help enabled, the user can see parameters as // they type in the function, so we just return empty parentheses. if signatureHelpEnabled { return trimmed + "($1)", true } // If signature help is not enabled, we should give the user parameters // that they can tab through. The insert text format follows the // specification defined by Microsoft for LSP. The "$", "}, and "\" // characters should be escaped. r := strings.NewReplacer( `\`, `\\`, `}`, `\}`, `$`, `\$`, ) b := bytes.NewBufferString(trimmed) b.WriteByte('(') for i, p := range strings.Split(params, ",") { if i != 0 { b.WriteString(", ") } fmt.Fprintf(b, "${%v:%v}", i+1, r.Replace(strings.Trim(p, " "))) } b.WriteByte(')') return b.String(), false } return label, false }