diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index b751c91eb519..465bbde67e0c 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -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, diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index df85bca9a22f..3dece2491d6c 100644 --- a/clang-tools-extra/clangd/ClangdServer.cpp +++ b/clang-tools-extra/clangd/ClangdServer.cpp @@ -222,11 +222,11 @@ std::future ClangdServer::forceReparse(PathRef File) { std::move(TaggedFS)); } -std::future>> +std::future> ClangdServer::codeComplete(PathRef File, Position Pos, llvm::Optional OverridenContents, IntrusiveRefCntPtr *UsedFS) { - using ResultType = Tagged>; + using ResultType = Tagged; std::promise ResultPromise; @@ -242,11 +242,10 @@ ClangdServer::codeComplete(PathRef File, Position Pos, } void ClangdServer::codeComplete( - UniqueFunction>)> Callback, - PathRef File, Position Pos, llvm::Optional OverridenContents, + UniqueFunction)> Callback, PathRef File, + Position Pos, llvm::Optional OverridenContents, IntrusiveRefCntPtr *UsedFS) { - using CallbackType = - UniqueFunction>)>; + using CallbackType = UniqueFunction)>; 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 Result = clangd::codeComplete( + CompletionList Result = clangd::codeComplete( File, Resources->getCompileCommand(), Preamble ? &Preamble->Preamble : nullptr, Contents, Pos, TaggedFS.Value, PCHs, CodeCompleteOpts, Logger); diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h index eb8964bf5957..247fc64cf8e7 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -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>> + std::future> codeComplete(PathRef File, Position Pos, llvm::Optional OverridenContents = llvm::None, IntrusiveRefCntPtr *UsedFS = nullptr); /// A version of `codeComplete` that runs \p Callback on the processing thread /// when codeComplete results become available. - void codeComplete( - UniqueFunction>)> Callback, - PathRef File, Position Pos, - llvm::Optional OverridenContents = llvm::None, - IntrusiveRefCntPtr *UsedFS = nullptr); + void codeComplete(UniqueFunction)> Callback, + PathRef File, Position Pos, + llvm::Optional OverridenContents = llvm::None, + IntrusiveRefCntPtr *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 diff --git a/clang-tools-extra/clangd/ClangdUnit.cpp b/clang-tools-extra/clangd/ClangdUnit.cpp index fa261654450e..2fbc7a2d4d24 100644 --- a/clang-tools-extra/clangd/ClangdUnit.cpp +++ b/clang-tools-extra/clangd/ClangdUnit.cpp @@ -368,69 +368,42 @@ std::string getDocumentation(const CodeCompletionString &CCS) { return Result; } -class CompletionItemsCollector : public CodeCompleteConsumer { -public: - CompletionItemsCollector(const clang::CodeCompleteOptions &CodeCompleteOpts, - std::vector &Items) - : CodeCompleteConsumer(CodeCompleteOpts, /*OutputIsBinary=*/false), - Items(Items), - Allocator(std::make_shared()), - 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(CCS.getAvailability())) { + switch (static_cast(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()), + CCTUInfo(Allocator) {} + + void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, + CodeCompletionResult *Results, + unsigned NumResults) override final { + std::priority_queue 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 &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 Allocator; CodeCompletionTUInfo CCTUInfo; @@ -474,8 +501,8 @@ class PlainTextCompletionItemsCollector final public: PlainTextCompletionItemsCollector( - const clang::CodeCompleteOptions &CodeCompleteOpts, - std::vector &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 &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 +CompletionList clangd::codeComplete(PathRef FileName, const tooling::CompileCommand &Command, PrecompiledPreamble const *Preamble, StringRef Contents, Position Pos, IntrusiveRefCntPtr VFS, std::shared_ptr PCHs, clangd::CodeCompleteOptions Opts, clangd::Logger &Logger) { - std::vector Results; + CompletionList Results; std::unique_ptr Consumer; - clang::CodeCompleteOptions ClangCompleteOpts = Opts.getClangCompleteOpts(); if (Opts.EnableSnippets) { - Consumer = llvm::make_unique( - ClangCompleteOpts, Results); + Consumer = + llvm::make_unique(Opts, Results); } else { - Consumer = llvm::make_unique( - ClangCompleteOpts, Results); + Consumer = + llvm::make_unique(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; } diff --git a/clang-tools-extra/clangd/ClangdUnit.h b/clang-tools-extra/clangd/ClangdUnit.h index 55085d855606..9ce3bbc1347f 100644 --- a/clang-tools-extra/clangd/ClangdUnit.h +++ b/clang-tools-extra/clangd/ClangdUnit.h @@ -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 +CompletionList codeComplete(PathRef FileName, const tooling::CompileCommand &Command, PrecompiledPreamble const *Preamble, StringRef Contents, Position Pos, IntrusiveRefCntPtr VFS, diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index 7d5313417591..5ef8c9bfe8eb 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -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}}; diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 8c1f1a593070..1569e0222bcd 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -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 items; + + static json::Expr unparse(const CompletionList &); +}; + /// A single parameter of a particular signature. struct ParameterInformation { diff --git a/clang-tools-extra/test/clangd/authority-less-uri.test b/clang-tools-extra/test/clangd/authority-less-uri.test index d33a1c278f02..9042f05b75bc 100644 --- a/clang-tools-extra/test/clangd/authority-less-uri.test +++ b/clang-tools-extra/test/clangd/authority-less-uri.test @@ -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"} diff --git a/clang-tools-extra/test/clangd/completion-items-kinds.test b/clang-tools-extra/test/clangd/completion-items-kinds.test index 83d2c7b96fa2..03bd96f1190c 100644 --- a/clang-tools-extra/test/clangd/completion-items-kinds.test +++ b/clang-tools-extra/test/clangd/completion-items-kinds.test @@ -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} diff --git a/clang-tools-extra/test/clangd/completion-priorities.test b/clang-tools-extra/test/clangd/completion-priorities.test index 45501dc448ba..349c1fbc43cd 100644 --- a/clang-tools-extra/test/clangd/completion-priorities.test +++ b/clang-tools-extra/test/clangd/completion-priorities.test @@ -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} diff --git a/clang-tools-extra/test/clangd/completion-qualifiers.test b/clang-tools-extra/test/clangd/completion-qualifiers.test index 982d7d93158b..d6844010ac72 100644 --- a/clang-tools-extra/test/clangd/completion-qualifiers.test +++ b/clang-tools-extra/test/clangd/completion-qualifiers.test @@ -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"} diff --git a/clang-tools-extra/test/clangd/completion-snippet.test b/clang-tools-extra/test/clangd/completion-snippet.test index ec1ac355bf16..23305c1567d2 100644 --- a/clang-tools-extra/test/clangd/completion-snippet.test +++ b/clang-tools-extra/test/clangd/completion-snippet.test @@ -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"} diff --git a/clang-tools-extra/test/clangd/completion.test b/clang-tools-extra/test/clangd/completion.test index 7ff06523123a..0d0c7cf474a7 100644 --- a/clang-tools-extra/test/clangd/completion.test +++ b/clang-tools-extra/test/clangd/completion.test @@ -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", diff --git a/clang-tools-extra/test/clangd/protocol.test b/clang-tools-extra/test/clangd/protocol.test index 7837410de8a1..14d27ba600fd 100644 --- a/clang-tools-extra/test/clangd/protocol.test +++ b/clang-tools-extra/test/clangd/protocol.test @@ -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}}} diff --git a/clang-tools-extra/unittests/clangd/ClangdTests.cpp b/clang-tools-extra/unittests/clangd/ClangdTests.cpp index 0650155321b4..aac5fabdd640 100644 --- a/clang-tools-extra/unittests/clangd/ClangdTests.cpp +++ b/clang-tools-extra/unittests/clangd/ClangdTests.cpp @@ -619,16 +619,15 @@ struct bar { T x; }; class ClangdCompletionTest : public ClangdVFSTest { protected: template - bool ContainsItemPred(std::vector 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 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;