diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 26e239fa7bed6..396b903a9a762 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.cpp +++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -513,6 +513,7 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params, SupportedSymbolKinds |= *Params.capabilities.WorkspaceSymbolKinds; if (Params.capabilities.CompletionItemKinds) SupportedCompletionItemKinds |= *Params.capabilities.CompletionItemKinds; + SupportsCompletionLabelDetails = Params.capabilities.CompletionLabelDetail; SupportsCodeAction = Params.capabilities.CodeActionStructure; SupportsHierarchicalDocumentSymbol = Params.capabilities.HierarchicalDocumentSymbol; @@ -1100,6 +1101,8 @@ void ClangdLSPServer::onCompletion(const CompletionParams &Params, CompletionItem C = R.render(Opts); C.kind = adjustKindToCapability( C.kind, SupportedCompletionItemKinds); + if (!SupportsCompletionLabelDetails) + removeCompletionLabelDetails(C); LSPList.items.push_back(std::move(C)); } return Reply(std::move(LSPList)); diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h index 332ff680438ad..a483d1110f64c 100644 --- a/clang-tools-extra/clangd/ClangdLSPServer.h +++ b/clang-tools-extra/clangd/ClangdLSPServer.h @@ -260,6 +260,8 @@ class ClangdLSPServer : private ClangdServer::Callbacks, SymbolKindBitset SupportedSymbolKinds; /// The supported completion item kinds of the client. CompletionItemKindBitset SupportedCompletionItemKinds; + // Whether the client supports CompletionItem.labelDetails. + bool SupportsCompletionLabelDetails = false; /// Whether the client supports CodeAction response objects. bool SupportsCodeAction = false; /// From capabilities of textDocument/documentSymbol. diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 3da923c6dd929..f4f93a01ec90e 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -2219,11 +2219,15 @@ bool isIndexedForCodeCompletion(const NamedDecl &ND, ASTContext &ASTCtx) { CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const { CompletionItem LSP; const auto *InsertInclude = Includes.empty() ? nullptr : &Includes[0]; + // We could move our indicators from label into labelDetails->description. + // In VSCode there are rendering issues that prevent these being aligned. LSP.label = ((InsertInclude && InsertInclude->Insertion) ? Opts.IncludeIndicator.Insert : Opts.IncludeIndicator.NoInsert) + (Opts.ShowOrigins ? "[" + llvm::to_string(Origin) + "]" : "") + - RequiredQualifier + Name + Signature; + RequiredQualifier + Name; + LSP.labelDetails.emplace(); + LSP.labelDetails->detail = Signature; LSP.kind = Kind; LSP.detail = BundleSize > 1 diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index f613624cd25d8..269e77372ad3b 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -393,6 +393,8 @@ bool fromJSON(const llvm::json::Value &Params, ClientCapabilities &R, if (auto *Item = Completion->getObject("completionItem")) { if (auto SnippetSupport = Item->getBoolean("snippetSupport")) R.CompletionSnippets = *SnippetSupport; + if (auto LabelDetailsSupport = Item->getBoolean("labelDetailsSupport")) + R.CompletionLabelDetail = *LabelDetailsSupport; if (const auto *DocumentationFormat = Item->getArray("documentationFormat")) { for (const auto &Format : *DocumentationFormat) { @@ -1069,6 +1071,25 @@ bool fromJSON(const llvm::json::Value &E, CompletionItemKindBitset &Out, return false; } +llvm::json::Value toJSON(const CompletionItemLabelDetails &CD) { + llvm::json::Object Result; + if (!CD.detail.empty()) + Result["detail"] = CD.detail; + if (!CD.description.empty()) + Result["description"] = CD.description; + return Result; +} + +void removeCompletionLabelDetails(CompletionItem &C) { + if (!C.labelDetails) + return; + if (!C.labelDetails->detail.empty()) + C.label += C.labelDetails->detail; + if (!C.labelDetails->description.empty()) + C.label = C.labelDetails->description + C.label; + C.labelDetails.reset(); +} + llvm::json::Value toJSON(const CompletionItem &CI) { assert(!CI.label.empty() && "completion item label is required"); llvm::json::Object Result{{"label", CI.label}}; @@ -1076,6 +1097,8 @@ llvm::json::Value toJSON(const CompletionItem &CI) { Result["kind"] = static_cast(CI.kind); if (!CI.detail.empty()) Result["detail"] = CI.detail; + if (CI.labelDetails) + Result["labelDetails"] = *CI.labelDetails; if (CI.documentation) Result["documentation"] = CI.documentation; if (!CI.sortText.empty()) diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 511eae1940c6e..cc2ad99451a2f 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -508,6 +508,10 @@ struct ClientCapabilities { /// textDocument.completion.completionItem.documentationFormat MarkupKind CompletionDocumentationFormat = MarkupKind::PlainText; + /// The client has support for completion item label details. + /// textDocument.completion.completionItem.labelDetailsSupport. + bool CompletionLabelDetail = false; + /// Client supports CodeAction return value for textDocument/codeAction. /// textDocument.codeAction.codeActionLiteralSupport. bool CodeActionStructure = false; @@ -1277,11 +1281,28 @@ enum class InsertTextFormat { Snippet = 2, }; +/// Additional details for a completion item label. +struct CompletionItemLabelDetails { + /// An optional string which is rendered less prominently directly after label + /// without any spacing. Should be used for function signatures or type + /// annotations. + std::string detail; + + /// An optional string which is rendered less prominently after + /// CompletionItemLabelDetails.detail. Should be used for fully qualified + /// names or file path. + std::string description; +}; +llvm::json::Value toJSON(const CompletionItemLabelDetails &); + struct CompletionItem { /// The label of this completion item. By default also the text that is /// inserted when selecting this completion. std::string label; + /// Additional details for the label. + std::optional labelDetails; + /// The kind of this completion item. Based of the kind an icon is chosen by /// the editor. CompletionItemKind kind = CompletionItemKind::Missing; @@ -1342,6 +1363,10 @@ struct CompletionItem { llvm::json::Value toJSON(const CompletionItem &); llvm::raw_ostream &operator<<(llvm::raw_ostream &, const CompletionItem &); +/// Remove the labelDetails field (for clients that don't support it). +/// Places the information into other fields of the completion item. +void removeCompletionLabelDetails(CompletionItem &); + bool operator<(const CompletionItem &, const CompletionItem &); /// Represents a collection of completion items to be presented in the editor. diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 1975488fd035a..fc330a4b8fd3f 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -2093,7 +2093,8 @@ TEST(CompletionTest, Render) { Opts.EnableSnippets = false; auto R = C.render(Opts); - EXPECT_EQ(R.label, "Foo::x(bool) const"); + EXPECT_EQ(R.label, "Foo::x"); + EXPECT_EQ(R.labelDetails->detail, "(bool) const"); EXPECT_EQ(R.insertText, "Foo::x"); EXPECT_EQ(R.insertTextFormat, InsertTextFormat::PlainText); EXPECT_EQ(R.filterText, "x"); @@ -2121,12 +2122,14 @@ TEST(CompletionTest, Render) { Include.Insertion.emplace(); R = C.render(Opts); - EXPECT_EQ(R.label, "^Foo::x(bool) const"); + EXPECT_EQ(R.label, "^Foo::x"); + EXPECT_EQ(R.labelDetails->detail, "(bool) const"); EXPECT_THAT(R.additionalTextEdits, Not(IsEmpty())); Opts.ShowOrigins = true; R = C.render(Opts); - EXPECT_EQ(R.label, "^[AS]Foo::x(bool) const"); + EXPECT_EQ(R.label, "^[AS]Foo::x"); + EXPECT_EQ(R.labelDetails->detail, "(bool) const"); C.BundleSize = 2; R = C.render(Opts);