Index: clangd/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -1366,11 +1366,45 @@ auto Top = mergeResults(Recorder->Results, IndexResults); CodeCompleteResult Output; + // Keys are indices into Output vector. + llvm::DenseMap OutputIndex; + LookupRequest DocIndexRequest; // Convert the results to final form, assembling the expensive strings. - for (auto &C : Top) { - Output.Completions.push_back(toCodeCompletion(C.first)); - Output.Completions.back().Score = C.second; + for (size_t i = 0; i < Top.size(); ++i) { + const ScoredBundle &BundleAndScope = Top[i]; + const CompletionCandidate::Bundle &CandidateBundle = BundleAndScope.first; + Output.Completions.push_back(toCodeCompletion(CandidateBundle)); + Output.Completions.back().Score = BundleAndScope.second; Output.Completions.back().CompletionTokenRange = TextEditRange; + + if (Opts.IncludeComments && + Output.Completions.back().Documentation.empty()) { + if (CandidateBundle.size() == 1) { + if (const CodeCompletionResult *SemaR = + CandidateBundle.front().SemaResult) { + if (auto ID = + getSymbolID(*SemaR, Recorder->CCSema->getSourceManager())) { + OutputIndex[i] = *ID; + DocIndexRequest.IDs.insert(*ID); + } + } + } + } + } + // Sema doesn't load docs from the preamble, so we get the docs from the + // index and assemble them for the final results. + if (!DocIndexRequest.IDs.empty() && Opts.Index) { + llvm::DenseMap FetchedDocs; + Opts.Index->lookup(DocIndexRequest, [&](const Symbol &S) { + if (!S.Documentation.empty()) + FetchedDocs[S.ID] = S.Documentation; + }); + for (auto IndexAndID : OutputIndex) { + auto FetchDocsIt = FetchedDocs.find(IndexAndID.second); + if (FetchDocsIt != FetchedDocs.end()) + Output.Completions[IndexAndID.first].Documentation = + FetchDocsIt->second; + } } Output.HasMore = Incomplete; Output.Context = Recorder->CCContext.getKind(); Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -729,6 +729,62 @@ Doc("Doooc"), ReturnType("void")))); } +TEST(CompletionTest, DocumentationFromIndex) { + MockFSProvider FS; + MockCompilationDatabase CDB; + IgnoreDiagnostics DiagConsumer; + FS.Files[testPath("foo.h")] = R"cpp( + class Foo { + public: + // Doc for foo + int foo(); + + // Doc for foo int + int foo2(int); + // Doc for foo bool + int foo2(bool); + }; + )cpp"; + + auto File = testPath("bar.cpp"); + Annotations Test(R"cpp( + #include "foo.h" + void test() { + Foo f; + f.^ + } + )cpp"); + auto Opts = ClangdServer::optsForTest(); + { + Opts.BuildDynamicSymbolIndex = false; + ClangdServer Server(CDB, FS, DiagConsumer, Opts); + runAddDocument(Server, File, Test.code()); + auto Results = cantFail(runCodeComplete(Server, File, Test.point(), {})); + EXPECT_THAT( + Results.Completions, + Contains((Named("foo"), Kind(CompletionItemKind::Method), Doc("")))); + EXPECT_THAT( + Results.Completions, + Contains((Named("foo2"), Kind(CompletionItemKind::Method), Doc("")))); + } + { + Opts.BuildDynamicSymbolIndex = true; + ClangdServer Server(CDB, FS, DiagConsumer, Opts); + runAddDocument(Server, File, Test.code()); + clangd::CodeCompleteOptions CCOpts; + CCOpts.BundleOverloads = true; + auto Results = + cantFail(runCodeComplete(Server, File, Test.point(), CCOpts)); + EXPECT_THAT(Results.Completions, + Contains((Named("foo"), Kind(CompletionItemKind::Method), + Doc("Doc for foo")))); + // No doc for overload bundle. + EXPECT_THAT( + Results.Completions, + Contains((Named("foo2"), Kind(CompletionItemKind::Method), Doc("")))); + } +} + TEST(CompletionTest, Documentation) { auto Results = completions( R"cpp(