[clangd] Support returning a limited number of completion results.

Summary:
All results are scored, we only process CodeCompletionStrings for the winners.
We now return CompletionList rather than CompletionItem[] (both are valid).
sortText is now based on CodeCompletionResult::orderedName (mostly the same).

This is the first clangd-only completion option, so plumbing changed.
It requires a small clangd patch (exposing CodeCompletionResult::orderedName).

(This can't usefully be enabled yet: we don't support server-side filtering)

Reviewers: ilya-biryukov

Subscribers: cfe-commits

Differential Revision: https://reviews.llvm.org/D39852

llvm-svn: 318287
This commit is contained in:
Sam McCall 2017-11-15 09:16:29 +00:00
parent 1102a861f5
commit a40371bcb6
15 changed files with 430 additions and 311 deletions

View File

@ -195,15 +195,15 @@ void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
}
void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
auto Items = Server
.codeComplete(Params.textDocument.uri.file,
Position{Params.position.line,
Params.position.character})
.get() // FIXME(ibiryukov): This could be made async if we
// had an API that would allow to attach callbacks to
// futures returned by ClangdServer.
.Value;
C.reply(json::ary(Items));
auto List = Server
.codeComplete(
Params.textDocument.uri.file,
Position{Params.position.line, Params.position.character})
.get() // FIXME(ibiryukov): This could be made async if we
// had an API that would allow to attach callbacks to
// futures returned by ClangdServer.
.Value;
C.reply(List);
}
void ClangdLSPServer::onSignatureHelp(Ctx C,

View File

@ -222,11 +222,11 @@ std::future<void> ClangdServer::forceReparse(PathRef File) {
std::move(TaggedFS));
}
std::future<Tagged<std::vector<CompletionItem>>>
std::future<Tagged<CompletionList>>
ClangdServer::codeComplete(PathRef File, Position Pos,
llvm::Optional<StringRef> OverridenContents,
IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS) {
using ResultType = Tagged<std::vector<CompletionItem>>;
using ResultType = Tagged<CompletionList>;
std::promise<ResultType> ResultPromise;
@ -242,11 +242,10 @@ ClangdServer::codeComplete(PathRef File, Position Pos,
}
void ClangdServer::codeComplete(
UniqueFunction<void(Tagged<std::vector<CompletionItem>>)> Callback,
PathRef File, Position Pos, llvm::Optional<StringRef> OverridenContents,
UniqueFunction<void(Tagged<CompletionList>)> Callback, PathRef File,
Position Pos, llvm::Optional<StringRef> OverridenContents,
IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS) {
using CallbackType =
UniqueFunction<void(Tagged<std::vector<CompletionItem>>)>;
using CallbackType = UniqueFunction<void(Tagged<CompletionList>)>;
std::string Contents;
if (OverridenContents) {
@ -283,7 +282,7 @@ void ClangdServer::codeComplete(
// FIXME(ibiryukov): even if Preamble is non-null, we may want to check
// both the old and the new version in case only one of them matches.
std::vector<CompletionItem> Result = clangd::codeComplete(
CompletionList Result = clangd::codeComplete(
File, Resources->getCompileCommand(),
Preamble ? &Preamble->Preamble : nullptr, Contents, Pos,
TaggedFS.Value, PCHs, CodeCompleteOpts, Logger);

View File

@ -251,18 +251,17 @@ public:
/// This method should only be called for currently tracked files. However, it
/// is safe to call removeDocument for \p File after this method returns, even
/// while returned future is not yet ready.
std::future<Tagged<std::vector<CompletionItem>>>
std::future<Tagged<CompletionList>>
codeComplete(PathRef File, Position Pos,
llvm::Optional<StringRef> OverridenContents = llvm::None,
IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr);
/// A version of `codeComplete` that runs \p Callback on the processing thread
/// when codeComplete results become available.
void codeComplete(
UniqueFunction<void(Tagged<std::vector<CompletionItem>>)> Callback,
PathRef File, Position Pos,
llvm::Optional<StringRef> OverridenContents = llvm::None,
IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr);
void codeComplete(UniqueFunction<void(Tagged<CompletionList>)> Callback,
PathRef File, Position Pos,
llvm::Optional<StringRef> OverridenContents = llvm::None,
IntrusiveRefCntPtr<vfs::FileSystem> *UsedFS = nullptr);
/// Provide signature help for \p File at \p Pos. If \p OverridenContents is
/// not None, they will used only for signature help, i.e. no diagnostics

View File

@ -368,69 +368,42 @@ std::string getDocumentation(const CodeCompletionString &CCS) {
return Result;
}
class CompletionItemsCollector : public CodeCompleteConsumer {
public:
CompletionItemsCollector(const clang::CodeCompleteOptions &CodeCompleteOpts,
std::vector<CompletionItem> &Items)
: CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false),
Items(Items),
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Allocator) {}
/// A scored code completion result.
/// It may be promoted to a CompletionItem if it's among the top-ranked results.
struct CompletionCandidate {
CompletionCandidate(CodeCompletionResult &Result)
: Result(&Result), Score(score(Result)) {}
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
CodeCompletionResult *Results,
unsigned NumResults) override final {
Items.reserve(NumResults);
for (unsigned I = 0; I < NumResults; ++I) {
auto &Result = Results[I];
const auto *CCS = Result.CreateCodeCompletionString(
S, Context, *Allocator, CCTUInfo,
CodeCompleteOpts.IncludeBriefComments);
assert(CCS && "Expected the CodeCompletionString to be non-null");
Items.push_back(ProcessCodeCompleteResult(Result, *CCS));
}
std::sort(Items.begin(), Items.end());
CodeCompletionResult *Result;
// Higher score is worse. FIXME: use a more natural scale!
int Score;
// Comparison reflects rank: better candidates are smaller.
bool operator<(const CompletionCandidate &C) const {
if (Score != C.Score)
return Score < C.Score;
return *Result < *C.Result;
}
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
std::string sortText() const {
// Fill in the sortText of the CompletionItem.
assert(Score <= 999999 && "Expecting score to have at most 6-digits");
std::string S, NameStorage;
StringRef Name = Result->getOrderedName(NameStorage);
llvm::raw_string_ostream(S)
<< llvm::format("%06d%.*s", Score, Name.size(), Name.data());
return S;
}
private:
CompletionItem
ProcessCodeCompleteResult(const CodeCompletionResult &Result,
const CodeCompletionString &CCS) const {
// Adjust this to InsertTextFormat::Snippet iff we encounter a
// CK_Placeholder chunk in SnippetCompletionItemsCollector.
CompletionItem Item;
Item.insertTextFormat = InsertTextFormat::PlainText;
Item.documentation = getDocumentation(CCS);
// Fill in the label, detail, insertText and filterText fields of the
// CompletionItem.
ProcessChunks(CCS, Item);
// Fill in the kind field of the CompletionItem.
Item.kind = getKind(Result.Kind, Result.CursorKind);
FillSortText(CCS, Item);
return Item;
}
virtual void ProcessChunks(const CodeCompletionString &CCS,
CompletionItem &Item) const = 0;
static int GetSortPriority(const CodeCompletionString &CCS) {
int Score = CCS.getPriority();
static int score(const CodeCompletionResult &Result) {
int Score = Result.Priority;
// Fill in the sortText of the CompletionItem.
assert(Score <= 99999 && "Expecting code completion result "
"priority to have at most 5-digits");
const int Penalty = 100000;
switch (static_cast<CXAvailabilityKind>(CCS.getAvailability())) {
switch (static_cast<CXAvailabilityKind>(Result.Availability)) {
case CXAvailability_Available:
// No penalty.
break;
@ -444,21 +417,75 @@ private:
Score += 3 * Penalty;
break;
}
return Score;
}
};
static void FillSortText(const CodeCompletionString &CCS,
CompletionItem &Item) {
int Priority = GetSortPriority(CCS);
// Fill in the sortText of the CompletionItem.
assert(Priority <= 999999 &&
"Expecting sort priority to have at most 6-digits");
llvm::raw_string_ostream(Item.sortText)
<< llvm::format("%06d%s", Priority, Item.filterText.c_str());
class CompletionItemsCollector : public CodeCompleteConsumer {
public:
CompletionItemsCollector(const clangd::CodeCompleteOptions &CodeCompleteOpts,
CompletionList &Items)
: CodeCompleteConsumer(CodeCompleteOpts.getClangCompleteOpts(),
/*OutputIsBinary=*/false),
ClangdOpts(CodeCompleteOpts), Items(Items),
Allocator(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Allocator) {}
void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context,
CodeCompletionResult *Results,
unsigned NumResults) override final {
std::priority_queue<CompletionCandidate> Candidates;
for (unsigned I = 0; I < NumResults; ++I) {
Candidates.emplace(Results[I]);
if (ClangdOpts.Limit && Candidates.size() > ClangdOpts.Limit) {
Candidates.pop();
Items.isIncomplete = true;
}
}
while (!Candidates.empty()) {
auto &Candidate = Candidates.top();
const auto *CCS = Candidate.Result->CreateCodeCompletionString(
S, Context, *Allocator, CCTUInfo,
CodeCompleteOpts.IncludeBriefComments);
assert(CCS && "Expected the CodeCompletionString to be non-null");
Items.items.push_back(ProcessCodeCompleteResult(Candidate, *CCS));
Candidates.pop();
}
std::reverse(Items.items.begin(), Items.items.end());
}
std::vector<CompletionItem> &Items;
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
private:
CompletionItem
ProcessCodeCompleteResult(const CompletionCandidate &Candidate,
const CodeCompletionString &CCS) const {
// Adjust this to InsertTextFormat::Snippet iff we encounter a
// CK_Placeholder chunk in SnippetCompletionItemsCollector.
CompletionItem Item;
Item.insertTextFormat = InsertTextFormat::PlainText;
Item.documentation = getDocumentation(CCS);
Item.sortText = Candidate.sortText();
// Fill in the label, detail, insertText and filterText fields of the
// CompletionItem.
ProcessChunks(CCS, Item);
// Fill in the kind field of the CompletionItem.
Item.kind = getKind(Candidate.Result->Kind, Candidate.Result->CursorKind);
return Item;
}
virtual void ProcessChunks(const CodeCompletionString &CCS,
CompletionItem &Item) const = 0;
clangd::CodeCompleteOptions ClangdOpts;
CompletionList &Items;
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Allocator;
CodeCompletionTUInfo CCTUInfo;
@ -474,8 +501,8 @@ class PlainTextCompletionItemsCollector final
public:
PlainTextCompletionItemsCollector(
const clang::CodeCompleteOptions &CodeCompleteOpts,
std::vector<CompletionItem> &Items)
const clangd::CodeCompleteOptions &CodeCompleteOpts,
CompletionList &Items)
: CompletionItemsCollector(CodeCompleteOpts, Items) {}
private:
@ -511,8 +538,8 @@ class SnippetCompletionItemsCollector final : public CompletionItemsCollector {
public:
SnippetCompletionItemsCollector(
const clang::CodeCompleteOptions &CodeCompleteOpts,
std::vector<CompletionItem> &Items)
const clangd::CodeCompleteOptions &CodeCompleteOpts,
CompletionList &Items)
: CompletionItemsCollector(CodeCompleteOpts, Items) {}
private:
@ -795,7 +822,8 @@ clangd::CodeCompleteOptions::CodeCompleteOptions(bool EnableSnippets,
IncludeMacros(IncludeMacros), IncludeGlobals(IncludeGlobals),
IncludeBriefComments(IncludeBriefComments) {}
clang::CodeCompleteOptions clangd::CodeCompleteOptions::getClangCompleteOpts() {
clang::CodeCompleteOptions
clangd::CodeCompleteOptions::getClangCompleteOpts() const {
clang::CodeCompleteOptions Result;
Result.IncludeCodePatterns = EnableSnippets && IncludeCodePatterns;
Result.IncludeMacros = IncludeMacros;
@ -805,25 +833,24 @@ clang::CodeCompleteOptions clangd::CodeCompleteOptions::getClangCompleteOpts() {
return Result;
}
std::vector<CompletionItem>
CompletionList
clangd::codeComplete(PathRef FileName, const tooling::CompileCommand &Command,
PrecompiledPreamble const *Preamble, StringRef Contents,
Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,
std::shared_ptr<PCHContainerOperations> PCHs,
clangd::CodeCompleteOptions Opts, clangd::Logger &Logger) {
std::vector<CompletionItem> Results;
CompletionList Results;
std::unique_ptr<CodeCompleteConsumer> Consumer;
clang::CodeCompleteOptions ClangCompleteOpts = Opts.getClangCompleteOpts();
if (Opts.EnableSnippets) {
Consumer = llvm::make_unique<SnippetCompletionItemsCollector>(
ClangCompleteOpts, Results);
Consumer =
llvm::make_unique<SnippetCompletionItemsCollector>(Opts, Results);
} else {
Consumer = llvm::make_unique<PlainTextCompletionItemsCollector>(
ClangCompleteOpts, Results);
Consumer =
llvm::make_unique<PlainTextCompletionItemsCollector>(Opts, Results);
}
invokeCodeComplete(std::move(Consumer), ClangCompleteOpts, FileName, Command,
Preamble, Contents, Pos, std::move(VFS), std::move(PCHs),
Logger);
invokeCodeComplete(std::move(Consumer), Opts.getClangCompleteOpts(), FileName,
Command, Preamble, Contents, Pos, std::move(VFS),
std::move(PCHs), Logger);
return Results;
}

View File

@ -263,7 +263,7 @@ struct CodeCompleteOptions {
bool IncludeBriefComments);
/// Returns options that can be passed to clang's completion engine.
clang::CodeCompleteOptions getClangCompleteOpts();
clang::CodeCompleteOptions getClangCompleteOpts() const;
/// When true, completion items will contain expandable code snippets in
/// completion (e.g. `return ${1:expression}` or `foo(${1:int a}, ${2:int
@ -285,10 +285,14 @@ struct CodeCompleteOptions {
/// FIXME(ibiryukov): it looks like turning this option on significantly slows
/// down completion, investigate if it can be made faster.
bool IncludeBriefComments = true;
/// Limit the number of results returned (0 means no limit).
/// If more results are available, we set CompletionList.isIncomplete.
size_t Limit = 0;
};
/// Get code completions at a specified \p Pos in \p FileName.
std::vector<CompletionItem>
CompletionList
codeComplete(PathRef FileName, const tooling::CompileCommand &Command,
PrecompiledPreamble const *Preamble, StringRef Contents,
Position Pos, IntrusiveRefCntPtr<vfs::FileSystem> VFS,

View File

@ -1043,6 +1043,13 @@ bool clangd::operator<(const CompletionItem &L, const CompletionItem &R) {
(R.sortText.empty() ? R.label : R.sortText);
}
json::Expr CompletionList::unparse(const CompletionList &L) {
return json::obj{
{"isIncomplete", L.isIncomplete},
{"items", json::ary(L.items)},
};
}
json::Expr ParameterInformation::unparse(const ParameterInformation &PI) {
assert(!PI.label.empty() && "parameter information label is required");
json::obj Result{{"label", PI.label}};

View File

@ -547,6 +547,18 @@ struct CompletionItem {
bool operator<(const CompletionItem &, const CompletionItem &);
/// Represents a collection of completion items to be presented in the editor.
struct CompletionList {
/// The list is not complete. Further typing should result in recomputing the
/// list.
bool isIncomplete = false;
/// The completion items.
std::vector<CompletionItem> items;
static json::Expr unparse(const CompletionList &);
};
/// A single parameter of a particular signature.
struct ParameterInformation {

View File

@ -17,14 +17,17 @@ Content-Length: 146
#
# CHECK: "id": 1,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK: ]
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK: ]
# CHECK-NEXT: }
Content-Length: 172
{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"uri":"file:///main.cpp","position":{"line":3,"character":5}}}
@ -32,14 +35,17 @@ Content-Length: 172
#
# CHECK: "id": 2,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK: ]
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK: ]
# CHECK-NEXT: }
Content-Length: 44
{"jsonrpc":"2.0","id":3,"method":"shutdown"}

View File

@ -11,7 +11,7 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":7}}}
Content-Length: 58
# CHECK: {"id":1,"jsonrpc":"2.0","result":[
# CHECK: {"id":1,"jsonrpc":"2.0","result":{"isIncomplete":false,"items":
#
# Keyword
# CHECK-DAG: {"filterText":"int","insertText":"int","insertTextFormat":1,"kind":14,"label":"int","sortText":"000050int"}
@ -31,6 +31,6 @@ Content-Length: 58
# Function
# CHECK-DAG: {"detail":"int","filterText":"function","insertText":"function()","insertTextFormat":1,"kind":3,"label":"function()","sortText":"000012function"}
#
# CHECK-SAME: ]}
# CHECK-SAME: ]}}
{"jsonrpc":"2.0","id":3,"method":"shutdown","params":null}

View File

@ -15,69 +15,74 @@ Content-Length: 151
{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":12,"character":8}}}
# CHECK: "id": 2,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "void",
# CHECK-NEXT: "filterText": "priv",
# CHECK-NEXT: "insertText": "priv",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "priv()",
# CHECK-NEXT: "sortText": "000034priv"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "void",
# CHECK-NEXT: "filterText": "prot",
# CHECK-NEXT: "insertText": "prot",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "prot()",
# CHECK-NEXT: "sortText": "000034prot"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "void",
# CHECK-NEXT: "filterText": "pub",
# CHECK-NEXT: "insertText": "pub",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "pub()",
# CHECK-NEXT: "sortText": "000034pub"
# CHECK-NEXT: },
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "void",
# CHECK-NEXT: "filterText": "priv",
# CHECK-NEXT: "insertText": "priv",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "priv()",
# CHECK-NEXT: "sortText": "000034priv"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "void",
# CHECK-NEXT: "filterText": "prot",
# CHECK-NEXT: "insertText": "prot",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "prot()",
# CHECK-NEXT: "sortText": "000034prot"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "void",
# CHECK-NEXT: "filterText": "pub",
# CHECK-NEXT: "insertText": "pub",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "pub()",
# CHECK-NEXT: "sortText": "000034pub"
# CHECK-NEXT: },
Content-Length: 151
{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":17,"character":4}}}
# CHECK: "id": 3,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "void",
# CHECK-NEXT: "filterText": "pub",
# CHECK-NEXT: "insertText": "pub",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "pub()",
# CHECK-NEXT: "sortText": "000034pub"
# CHECK-NEXT: },
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "void",
# CHECK-NEXT: "filterText": "pub",
# CHECK-NEXT: "insertText": "pub",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "pub()",
# CHECK-NEXT: "sortText": "000034pub"
# CHECK-NEXT: },
# priv() and prot() are at the end of the list
# CHECK-NEXT: {
# CHECK: "detail": "void",
# CHECK: "filterText": "priv",
# CHECK-NEXT: "insertText": "priv",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "priv()",
# CHECK-NEXT: "sortText": "200034priv"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "void",
# CHECK-NEXT: "filterText": "prot",
# CHECK-NEXT: "insertText": "prot",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "prot()",
# CHECK-NEXT: "sortText": "200034prot"
# CHECK-NEXT: }
# CHECK-NEXT: ]
# CHECK-NEXT: {
# CHECK: "detail": "void",
# CHECK: "filterText": "priv",
# CHECK-NEXT: "insertText": "priv",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "priv()",
# CHECK-NEXT: "sortText": "200034priv"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "void",
# CHECK-NEXT: "filterText": "prot",
# CHECK-NEXT: "insertText": "prot",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "prot()",
# CHECK-NEXT: "sortText": "200034prot"
# CHECK-NEXT: }
# CHECK-NEXT: ]
# CHECK-NEXT: }
Content-Length: 58
{"jsonrpc":"2.0","id":4,"method":"shutdown","params":null}

View File

@ -10,37 +10,40 @@ Content-Length: 151
{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":11,"character":8}}}
# CHECK: "id": 2,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# Eligible const functions are at the top of the list.
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "bar",
# CHECK-NEXT: "insertText": "bar",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "bar() const",
# CHECK-NEXT: "sortText": "000037bar"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "foo",
# CHECK-NEXT: "insertText": "foo",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "Foo::foo() const",
# CHECK-NEXT: "sortText": "000037foo"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "bar",
# CHECK-NEXT: "insertText": "bar",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "bar() const",
# CHECK-NEXT: "sortText": "000037bar"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "foo",
# CHECK-NEXT: "insertText": "foo",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "Foo::foo() const",
# CHECK-NEXT: "sortText": "000037foo"
# CHECK-NEXT: },
# Ineligible non-const function is at the bottom of the list.
# CHECK-NEXT: {
# CHECK: "detail": "int",
# CHECK: "filterText": "foo",
# CHECK-NEXT: "insertText": "foo",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "foo() const",
# CHECK-NEXT: "sortText": "200035foo"
# CHECK-NEXT: }
# CHECK-NEXT: ]
# CHECK-NEXT: {
# CHECK: "detail": "int",
# CHECK: "filterText": "foo",
# CHECK-NEXT: "insertText": "foo",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "foo() const",
# CHECK-NEXT: "sortText": "200035foo"
# CHECK-NEXT: }
# CHECK-NEXT: ]
# CHECK-NEXT: }
Content-Length: 44
{"jsonrpc":"2.0","id":4,"method":"shutdown"}

View File

@ -14,71 +14,74 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
# CHECK: "id": 1,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "a",
# CHECK-NEXT: "insertText": "a",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 5,
# CHECK-NEXT: "label": "a",
# CHECK-NEXT: "sortText": "000035a"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "bb",
# CHECK-NEXT: "insertText": "bb",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 5,
# CHECK-NEXT: "label": "bb",
# CHECK-NEXT: "sortText": "000035bb"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "ccc",
# CHECK-NEXT: "insertText": "ccc",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 5,
# CHECK-NEXT: "label": "ccc",
# CHECK-NEXT: "sortText": "000035ccc"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "f",
# CHECK-NEXT: "insertText": "f(${1:int i}, ${2:const float f})",
# CHECK-NEXT: "insertTextFormat": 2,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "f(int i, const float f) const",
# CHECK-NEXT: "sortText": "000035f"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake::",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "fake &",
# CHECK-NEXT: "filterText": "operator=",
# CHECK-NEXT: "insertText": "operator=(${1:const fake &})",
# CHECK-NEXT: "insertTextFormat": 2,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "operator=(const fake &)",
# CHECK-NEXT: "sortText": "000079operator="
# CHECK-NEXT: },
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "a",
# CHECK-NEXT: "insertText": "a",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 5,
# CHECK-NEXT: "label": "a",
# CHECK-NEXT: "sortText": "000035a"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "bb",
# CHECK-NEXT: "insertText": "bb",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 5,
# CHECK-NEXT: "label": "bb",
# CHECK-NEXT: "sortText": "000035bb"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "ccc",
# CHECK-NEXT: "insertText": "ccc",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 5,
# CHECK-NEXT: "label": "ccc",
# CHECK-NEXT: "sortText": "000035ccc"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "f",
# CHECK-NEXT: "insertText": "f(${1:int i}, ${2:const float f})",
# CHECK-NEXT: "insertTextFormat": 2,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "f(int i, const float f) const",
# CHECK-NEXT: "sortText": "000035f"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake::",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK-NEXT: },
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "fake &",
# CHECK-NEXT: "filterText": "operator=",
# CHECK-NEXT: "insertText": "operator=(${1:const fake &})",
# CHECK-NEXT: "insertTextFormat": 2,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "operator=(const fake &)",
# CHECK-NEXT: "sortText": "000079operator="
# CHECK-NEXT: },
# FIXME: Why do some buildbots show an extra operator==(fake&&) here?
# CHECK: {
# CHECK: "detail": "void",
# CHECK-NEXT: "filterText": "~fake",
# CHECK-NEXT: "insertText": "~fake()",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 4,
# CHECK-NEXT: "label": "~fake()",
# CHECK-NEXT: "sortText": "000079~fake"
# CHECK-NEXT: }
# CHECK-NEXT: ]
# CHECK: {
# CHECK: "detail": "void",
# CHECK-NEXT: "filterText": "~fake",
# CHECK-NEXT: "insertText": "~fake()",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 4,
# CHECK-NEXT: "label": "~fake()",
# CHECK-NEXT: "sortText": "000079~fake"
# CHECK-NEXT: }
# CHECK-NEXT: ]
# CHECK-NEXT: }
# Update the source file and check for completions again.
Content-Length: 226
@ -89,16 +92,18 @@ Content-Length: 148
{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
# CHECK: "id": 3,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int (*)(int, int)",
# CHECK-NEXT: "filterText": "func",
# CHECK-NEXT: "insertText": "func()",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "func()",
# CHECK-NEXT: "sortText": "000034func"
# CHECK-NEXT: },
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int (*)(int, int)",
# CHECK-NEXT: "filterText": "func",
# CHECK-NEXT: "insertText": "func()",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 2,
# CHECK-NEXT: "label": "func()",
# CHECK-NEXT: "sortText": "000034func"
# CHECK-NEXT: },
Content-Length: 44
{"jsonrpc":"2.0","id":4,"method":"shutdown"}

View File

@ -14,7 +14,9 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
# CHECK: "id": 1
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "a",
@ -84,7 +86,9 @@ Content-Length: 148
{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
# CHECK: "id": 2
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int",
# CHECK-NEXT: "filterText": "a",
@ -158,7 +162,9 @@ Content-Length: 148
{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
# CHECK: "id": 3,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK-NEXT: {
# CHECK-NEXT: "detail": "int (*)(int, int)",
# CHECK-NEXT: "filterText": "func",

View File

@ -31,14 +31,17 @@ Content-Length: 146
#
# CHECK: "id": 1,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK: ]
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK: ]
# CHECK-NEXT: }
X-Test: Testing
Content-Type: application/vscode-jsonrpc; charset-utf-8
@ -57,14 +60,17 @@ Content-Length: 146
#
# CHECK: "id": 3,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK: ]
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK: ]
# CHECK-NEXT: }
# STDERR: Warning: Duplicate Content-Length header received. The previous value for this message (10) was ignored.
Content-Type: application/vscode-jsonrpc; charset-utf-8
@ -83,14 +89,17 @@ Content-Length: 146
#
# CHECK: "id": 5,
# CHECK-NEXT: "jsonrpc": "2.0",
# CHECK-NEXT: "result": [
# CHECK: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK: ]
# CHECK-NEXT: "result": {
# CHECK-NEXT: "isIncomplete": false,
# CHECK-NEXT: "items": [
# CHECK: "filterText": "fake",
# CHECK-NEXT: "insertText": "fake",
# CHECK-NEXT: "insertTextFormat": 1,
# CHECK-NEXT: "kind": 7,
# CHECK-NEXT: "label": "fake::",
# CHECK-NEXT: "sortText": "000075fake"
# CHECK: ]
# CHECK-NEXT: }
Content-Length: 1024
{"jsonrpc":"2.0","id":5,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}}

View File

@ -619,16 +619,15 @@ struct bar { T x; };
class ClangdCompletionTest : public ClangdVFSTest {
protected:
template <class Predicate>
bool ContainsItemPred(std::vector<CompletionItem> const &Items,
Predicate Pred) {
for (const auto &Item : Items) {
bool ContainsItemPred(CompletionList const &Items, Predicate Pred) {
for (const auto &Item : Items.items) {
if (Pred(Item))
return true;
}
return false;
}
bool ContainsItem(std::vector<CompletionItem> const &Items, StringRef Name) {
bool ContainsItem(CompletionList const &Items, StringRef Name) {
return ContainsItemPred(Items, [Name](clangd::CompletionItem Item) {
return Item.insertText == Name;
});
@ -694,6 +693,44 @@ int b = ;
}
}
TEST_F(ClangdCompletionTest, Limit) {
MockFSProvider FS;
MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
CDB.ExtraClangFlags.push_back("-xc++");
ErrorCheckingDiagConsumer DiagConsumer;
clangd::CodeCompleteOptions Opts;
Opts.Limit = 2;
ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
Opts, EmptyLogger::getInstance());
auto FooCpp = getVirtualTestFilePath("foo.cpp");
FS.Files[FooCpp] = "";
FS.ExpectedFile = FooCpp;
StringWithPos Completion = parseTextMarker(R"cpp(
struct ClassWithMembers {
int AAA();
int BBB();
int CCC();
}
int main() { ClassWithMembers().{complete} }
)cpp",
"complete");
Server.addDocument(FooCpp, Completion.Text);
/// For after-dot completion we must always get consistent results.
auto Results = Server
.codeComplete(FooCpp, Completion.MarkerPos,
StringRef(Completion.Text))
.get()
.Value;
EXPECT_TRUE(Results.isIncomplete);
EXPECT_EQ(Opts.Limit, Results.items.size());
EXPECT_TRUE(ContainsItem(Results, "AAA"));
EXPECT_TRUE(ContainsItem(Results, "BBB"));
EXPECT_FALSE(ContainsItem(Results, "CCC"));
}
TEST_F(ClangdCompletionTest, CompletionOptions) {
MockFSProvider FS;
ErrorCheckingDiagConsumer DiagConsumer;