Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -164,6 +164,8 @@ ClangdDiagnosticOptions DiagOpts; /// The supported kinds of the client. SymbolKindBitset SupportedSymbolKinds; + /// The supported completion item kinds of the client. + CompletionItemKindBitset SupportedCompletionItemKinds; // Store of the current versions of the open documents. DraftStore DraftMgr; Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -70,6 +70,14 @@ return Defaults; } +CompletionItemKindBitset defaultCompletionItemKinds() { + CompletionItemKindBitset Defaults; + for (size_t I = CompletionItemKindMin; + I <= static_cast(CompletionItemKind::Reference); ++I) + Defaults.set(I); + return Defaults; +} + } // namespace void ClangdLSPServer::onInitialize(InitializeParams &Params) { @@ -96,6 +104,11 @@ } } + if (Params.capabilities.textDocument.completion.completionItemKind) + for (CompletionItemKind Kind : *Params.capabilities.textDocument.completion + .completionItemKind->valueSet) + SupportedCompletionItemKinds.set(static_cast(Kind)); + reply(json::Object{ {{"capabilities", json::Object{ @@ -347,8 +360,12 @@ return replyError(List.takeError()); CompletionList LSPList; LSPList.isIncomplete = List->HasMore; - for (const auto &R : List->Completions) - LSPList.items.push_back(R.render(CCOpts)); + for (const auto &R : List->Completions) { + CompletionItem C = R.render(CCOpts); + C.kind = adjustKindToCapability( + C.kind, SupportedCompletionItemKinds); + LSPList.items.push_back(std::move(C)); + } return reply(std::move(LSPList)); }); } @@ -459,6 +476,7 @@ : CompilationDB::makeDirectoryBased( std::move(CompileCommandsDir))), CCOpts(CCOpts), SupportedSymbolKinds(defaultSymbolKinds()), + SupportedCompletionItemKinds(defaultCompletionItemKinds()), Server(new ClangdServer(CDB.getCDB(), FSProvider, /*DiagConsumer=*/*this, Opts)) {} Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -233,13 +233,65 @@ }; bool fromJSON(const llvm::json::Value &, CompletionItemClientCapabilities &); +/// The kind of a completion entry. +enum class CompletionItemKind { + Missing = 0, + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, +}; + +bool fromJSON(const llvm::json::Value &, CompletionItemKind &); + +struct CompletionItemKindCapabilities { + /// The CompletionItemKinds that the client supports. If not set, the client + /// only supports <= CompletionItemKind::Reference and will not fall back to a + /// valid default value. + llvm::Optional> valueSet; +}; +bool fromJSON(const llvm::json::Value &, std::vector &); +bool fromJSON(const llvm::json::Value &, CompletionItemKindCapabilities &); + +constexpr auto CompletionItemKindMin = + static_cast(CompletionItemKind::Text); +constexpr auto CompletionItemKindMax = + static_cast(CompletionItemKind::TypeParameter); +using CompletionItemKindBitset = std::bitset; +CompletionItemKind +adjustKindToCapability(CompletionItemKind Kind, + CompletionItemKindBitset &supportedCompletionItemKinds); + struct CompletionClientCapabilities { /// Whether completion supports dynamic registration. bool dynamicRegistration = false; /// The client supports the following `CompletionItem` specific capabilities. CompletionItemClientCapabilities completionItem; - // NOTE: not used by clangd at the moment. - // llvm::Optional completionItemKind; + /// The CompletionItemKinds that the client supports. If not set, the client + /// only supports <= CompletionItemKind::Reference and will not fall back to a + /// valid default value. + llvm::Optional completionItemKind; /// The client supports to send additional context information for a /// `textDocument/completion` request. @@ -683,36 +735,6 @@ }; llvm::json::Value toJSON(const Hover &H); -/// The kind of a completion entry. -enum class CompletionItemKind { - Missing = 0, - Text = 1, - Method = 2, - Function = 3, - Constructor = 4, - Field = 5, - Variable = 6, - Class = 7, - Interface = 8, - Module = 9, - Property = 10, - Unit = 11, - Value = 12, - Enum = 13, - Keyword = 14, - Snippet = 15, - Color = 16, - File = 17, - Reference = 18, - Folder = 19, - EnumMember = 20, - Constant = 21, - Struct = 22, - Event = 23, - Operator = 24, - TypeParameter = 25, -}; - /// Defines whether the insert text in a completion item should be interpreted /// as plain text or a snippet. enum class InsertTextFormat { Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -496,6 +496,57 @@ return std::move(Result); } +bool fromJSON(const json::Value &E, CompletionItemKind &Out) { + if (auto T = E.getAsInteger()) { + if (*T < static_cast(CompletionItemKind::File) || + *T > static_cast(CompletionItemKind::TypeParameter)) + return false; + Out = static_cast(*T); + return true; + } + return false; +} + +CompletionItemKind +adjustKindToCapability(CompletionItemKind Kind, + CompletionItemKindBitset &supportedCompletionItemKinds) { + auto KindVal = static_cast(Kind); + if (KindVal >= CompletionItemKindMin && + KindVal <= supportedCompletionItemKinds.size() && + supportedCompletionItemKinds[KindVal]) + return Kind; + + switch (Kind) { + // Provide some fall backs for common kinds that are close enough. + case CompletionItemKind::Folder: + return CompletionItemKind::File; + case CompletionItemKind::EnumMember: + return CompletionItemKind::Enum; + case CompletionItemKind::Struct: + return CompletionItemKind::Class; + default: + return CompletionItemKind::Text; + } +} + +bool fromJSON(const json::Value &E, std::vector &Out) { + if (auto *A = E.getAsArray()) { + Out.clear(); + for (size_t I = 0; I < A->size(); ++I) { + CompletionItemKind KindOut; + if (fromJSON((*A)[I], KindOut)) + Out.push_back(KindOut); + } + return true; + } + return false; +} + +bool fromJSON(const json::Value &Params, CompletionItemKindCapabilities &R) { + json::ObjectMapper O(Params); + return O && O.map("valueSet", R.valueSet); +} + json::Value toJSON(const CompletionItem &CI) { assert(!CI.label.empty() && "completion item label is required"); json::Object Result{{"label", CI.label}};