Index: clangd/ClangdUnit.h =================================================================== --- clangd/ClangdUnit.h +++ clangd/ClangdUnit.h @@ -79,6 +79,7 @@ const ASTContext &getASTContext() const; Preprocessor &getPreprocessor(); + std::shared_ptr getPreprocessorPtr(); const Preprocessor &getPreprocessor() const; /// This function returns all top-level decls, including those that come Index: clangd/ClangdUnit.cpp =================================================================== --- clangd/ClangdUnit.cpp +++ clangd/ClangdUnit.cpp @@ -569,6 +569,10 @@ Preprocessor &ParsedAST::getPreprocessor() { return Clang->getPreprocessor(); } +std::shared_ptr ParsedAST::getPreprocessorPtr() { + return Clang->getPreprocessorPtr(); +} + const Preprocessor &ParsedAST::getPreprocessor() const { return Clang->getPreprocessor(); } Index: clangd/index/FileIndex.cpp =================================================================== --- clangd/index/FileIndex.cpp +++ clangd/index/FileIndex.cpp @@ -17,8 +17,10 @@ /// Retrieves namespace and class level symbols in \p Decls. std::unique_ptr indexAST(ASTContext &Ctx, + std::shared_ptr PP, llvm::ArrayRef Decls) { auto Collector = std::make_shared(); + Collector->setPreprocessor(std::move(PP)); index::IndexingOptions IndexOpts; IndexOpts.SystemSymbolFilter = index::IndexingOptions::SystemSymbolFilterKind::All; @@ -67,7 +69,8 @@ if (!AST) { FSymbols.update(Path, nullptr); } else { - auto Slab = indexAST(AST->getASTContext(), AST->getTopLevelDecls()); + auto Slab = indexAST(AST->getASTContext(), AST->getPreprocessorPtr(), + AST->getTopLevelDecls()); FSymbols.update(Path, std::move(Slab)); } auto Symbols = FSymbols.allSymbols(); Index: clangd/index/Index.h =================================================================== --- clangd/index/Index.h +++ clangd/index/Index.h @@ -89,10 +89,20 @@ // (not a definition), which is usually declared in ".h" file. SymbolLocation CanonicalDeclaration; + // Documentation including comment for the symbol declaration. + std::string Documentation; + // A brief description of the symbol that can be displayed in the completion + // candidate list. For example, "a::b::Foo(X x, Y y) const" is a labal for + // a function. + std::string CompletionLabel; + // Detail about the symbol. For example, the result type of a function. + std::string CompletionDetail; + // The placeholder text for function parameters in order. + std::vector Params; + // FIXME: add definition location of the symbol. // FIXME: add all occurrences support. // FIXME: add extra fields for index scoring signals. - // FIXME: add code completion information. }; // A symbol container that stores a set of symbols. The container will maintain Index: clangd/index/SymbolCollector.h =================================================================== --- clangd/index/SymbolCollector.h +++ clangd/index/SymbolCollector.h @@ -23,6 +23,12 @@ public: SymbolCollector() = default; + void initialize(ASTContext &Ctx) override { ASTCtx = &Ctx; } + + void setPreprocessor(std::shared_ptr PP) override { + this->PP = std::move(PP); + } + bool handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, ArrayRef Relations, FileID FID, @@ -36,6 +42,8 @@ private: // All Symbols collected from the AST. SymbolSlab Symbols; + ASTContext *ASTCtx; + std::shared_ptr PP; }; } // namespace clangd Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -14,6 +14,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Index/IndexSymbol.h" #include "clang/Index/USRGeneration.h" +#include "clang/Sema/CodeCompleteConsumer.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" @@ -56,6 +57,79 @@ } return AbsolutePath.str(); } + +std::string getDocumentation(const CodeCompletionString &CCS) { + // Things like __attribute__((nonnull(1,3))) and [[noreturn]]. Present this + // information in the documentation field. + std::string Result; + const unsigned AnnotationCount = CCS.getAnnotationCount(); + if (AnnotationCount > 0) { + Result += "Annotation"; + if (AnnotationCount == 1) { + Result += ": "; + } else /* AnnotationCount > 1 */ { + Result += "s: "; + } + for (unsigned I = 0; I < AnnotationCount; ++I) { + Result += CCS.getAnnotation(I); + Result.push_back(I == AnnotationCount - 1 ? '\n' : ' '); + } + } + // Add brief documentation (if there is any). + if (CCS.getBriefComment() != nullptr) { + if (!Result.empty()) { + // This means we previously added annotations. Add an extra newline + // character to make the annotations stand out. + Result.push_back('\n'); + } + Result += CCS.getBriefComment(); + } + return Result; +} + +void ProcessChunks(const CodeCompletionString &CCS, Symbol *Sym) { + for (const auto &Chunk : CCS) { + // Informative qualifier chunks only clutter completion results, skip + // them. + if (Chunk.Kind == CodeCompletionString::CK_Informative && + StringRef(Chunk.Text).endswith("::")) + continue; + + switch (Chunk.Kind) { + case CodeCompletionString::CK_TypedText: + // There's always exactly one CK_TypedText chunk. + Sym->CompletionLabel += Chunk.Text; + break; + case CodeCompletionString::CK_ResultType: + Sym->CompletionDetail = Chunk.Text; + break; + case CodeCompletionString::CK_Optional: + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + Sym->Params.push_back(Chunk.Text); + LLVM_FALLTHROUGH; + default: + Sym->CompletionLabel += Chunk.Text; + break; + } + } +} + +void addSymbolCompletionInfo(ASTContext &Ctx, Preprocessor &PP, + const NamedDecl *ND, Symbol *Sym) { + CodeCompletionResult SymbolCompletion(ND, 0); + auto Allocator = std::make_shared(); + CodeCompletionTUInfo TUInfo(Allocator); + const auto *CCS = SymbolCompletion.CreateCodeCompletionString( + Ctx, PP, CodeCompletionContext::CCC_Name, *Allocator, TUInfo, + /*IncludeBriefComments*/ true); + Sym->Documentation = getDocumentation(*CCS); + + ProcessChunks(*CCS, Sym); +} + } // namespace // Always return true to continue indexing. @@ -86,8 +160,14 @@ SymbolLocation Location = { makeAbsolutePath(SM, SM.getFilename(D->getLocation())), SM.getFileOffset(D->getLocStart()), SM.getFileOffset(D->getLocEnd())}; - Symbols.insert({std::move(ID), ND->getQualifiedNameAsString(), - index::getSymbolInfo(D), std::move(Location)}); + Symbol Sym; + Sym.ID = std::move(ID); + Sym.QualifiedName = ND->getQualifiedNameAsString(); + Sym.SymInfo = index::getSymbolInfo(D); + Sym.CanonicalDeclaration = std::move(Location); + assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set."); + addSymbolCompletionInfo(*ASTCtx, *PP, ND, &Sym); + Symbols.insert(std::move(Sym)); } return true; Index: unittests/clangd/SymbolCollectorTests.cpp =================================================================== --- unittests/clangd/SymbolCollectorTests.cpp +++ unittests/clangd/SymbolCollectorTests.cpp @@ -9,7 +9,6 @@ #include "index/SymbolCollector.h" #include "index/SymbolYAML.h" -#include "clang/Index/IndexingAction.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/FileSystemOptions.h" #include "clang/Basic/VirtualFileSystem.h" @@ -26,12 +25,17 @@ #include #include +using testing::AllOf; using testing::Eq; using testing::Field; using testing::UnorderedElementsAre; // GMock helpers for matching Symbol. MATCHER_P(QName, Name, "") { return arg.second.QualifiedName == Name; } +MATCHER_P(Labeled, Label, "") { return arg.second.CompletionLabel == Label; } +MATCHER_P(Detail, D, "") { return arg.second.CompletionDetail == D; } +MATCHER_P(Doc, D, "") { return arg.second.Documentation == D; } +MATCHER_P(Params, P, "") { return arg.second.Params == P; } namespace clang { namespace clangd { @@ -107,6 +111,23 @@ QName("f1"), QName("f2"))); } +TEST_F(SymbolCollectorTest, SymbolWithDocumentation) { + const std::string Main = R"( + namespace nx { + /// Foo comment. + int ff(int x, double y) { return 0; } + } + )"; + runSymbolCollector(/*Header=*/"", Main); + EXPECT_THAT( + Symbols, + UnorderedElementsAre( + QName("nx"), + AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"), Detail("int"), + Doc("Foo comment."), + Params(std::vector({"int x", "double y"}))))); +} + TEST_F(SymbolCollectorTest, YAMLConversions) { const std::string YAML1 = R"( ---