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 @@ -316,6 +316,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/CodeComplete.cpp =================================================================== --- clangd/CodeComplete.cpp +++ clangd/CodeComplete.cpp @@ -555,16 +555,21 @@ Item.kind = toCompletionItemKind(Sym.SymInfo.Kind); Item.label = Sym.Name; // FIXME(ioeric): support inserting/replacing scope qualifiers. - Item.insertText = Sym.Name; + // FIXME(ioeric): support snippets. + Item.insertText = Sym.CompletionPlainInsertText; Item.insertTextFormat = InsertTextFormat::PlainText; - Item.filterText = Filter; + Item.filterText = Sym.CompletionFilterText; // FIXME(ioeric): sort symbols appropriately. Item.sortText = ""; // FIXME(ioeric): use more symbol information (e.g. documentation, label) to // populate the completion item. + if (Sym.Detail) { + Item.documentation = Sym.Detail->Documentation; + Item.detail = Sym.Detail->CompletionDetail; + } return Item; } 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 @@ -14,6 +14,7 @@ #include "clang/Index/IndexSymbol.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/Hashing.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/StringExtras.h" #include #include @@ -92,10 +93,38 @@ // (not a definition), which is usually declared in ".h" file. SymbolLocation CanonicalDeclaration; + /// A brief description of the symbol that can be displayed in the completion + /// candidate list. For example, "Foo(X x, Y y) const" is a labal for a + /// function. + std::string CompletionLabel; + /// The piece of text that the user is expected to type to match the + /// code-completion string, typically a keyword or the name of a declarator or + /// macro. + std::string CompletionFilterText; + /// What to insert when completing this symbol (plain text version). + std::string CompletionPlainInsertText; + /// What to insert when completing this symbol (snippet version). This is + /// empty if it is the same as the plain insert text above. + std::string CompletionSnippetInsertText; + + /// Optional symbol details that are not required to be set. For example, an + /// index fuzzy match can return a large number of symbol candidates, and it + /// is preferable to send only core symbol information in the batched results + /// and have clients resolve full symbol information for a specific candidate + /// if needed. + struct Details { + // Documentation including comment for the symbol declaration. + std::string Documentation; + // This is what goes into the LSP detail field in a completion item. For + // example, the result type of a function. + std::string CompletionDetail; + }; + + llvm::Optional
Detail; + // 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 @@ -8,8 +8,11 @@ //===----------------------------------------------------------------------===// #include "Index.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" +#include "clang/Sema/CodeCompleteConsumer.h" namespace clang { namespace clangd { @@ -21,7 +24,13 @@ // changed. class SymbolCollector : public index::IndexDataConsumer { public: - SymbolCollector() = default; + SymbolCollector(); + + 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, @@ -34,8 +43,14 @@ SymbolSlab takeSymbols() { return std::move(Symbols); } private: + void addSymbolCompletionInfo(const NamedDecl *ND, Symbol *Sym); + // All Symbols collected from the AST. SymbolSlab Symbols; + ASTContext *ASTCtx; + std::shared_ptr PP; + std::shared_ptr CompletionAllocator; + CodeCompletionTUInfo CompletionTUInfo; }; } // namespace clangd Index: clangd/index/SymbolCollector.cpp =================================================================== --- clangd/index/SymbolCollector.cpp +++ clangd/index/SymbolCollector.cpp @@ -8,8 +8,7 @@ //===----------------------------------------------------------------------===// #include "SymbolCollector.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/Decl.h" +#include "../CodeCompletionStrings.h" #include "clang/AST/DeclCXX.h" #include "clang/Basic/SourceManager.h" #include "clang/Index/IndexSymbol.h" @@ -57,7 +56,6 @@ return AbsolutePath.str(); } -// Split a qualified symbol name into scope and unqualified name, e.g. given // "a::b::c", return {"a::b", "c"}. Scope is empty if it doesn't exist. std::pair splitQualifiedName(llvm::StringRef QName) { @@ -70,6 +68,10 @@ } // namespace +SymbolCollector::SymbolCollector() + : CompletionAllocator(std::make_shared()), + CompletionTUInfo(CompletionAllocator) {} + // Always return true to continue indexing. bool SymbolCollector::handleDeclOccurence( const Decl *D, index::SymbolRoleSet Roles, @@ -98,10 +100,18 @@ SymbolLocation Location = { makeAbsolutePath(SM, SM.getFilename(D->getLocation())), SM.getFileOffset(D->getLocStart()), SM.getFileOffset(D->getLocEnd())}; + Symbol Sym; + Sym.ID = std::move(ID); + std::string QName = ND->getQualifiedNameAsString(); auto ScopeAndName = splitQualifiedName(QName); - Symbols.insert({std::move(ID), ScopeAndName.second, ScopeAndName.first, - index::getSymbolInfo(D), std::move(Location)}); + Sym.Name = ScopeAndName.second; + Sym.Scope = ScopeAndName.first; + + Sym.SymInfo = index::getSymbolInfo(D); + Sym.CanonicalDeclaration = std::move(Location); + addSymbolCompletionInfo(ND, &Sym); + Symbols.insert(std::move(Sym)); } return true; @@ -109,5 +119,29 @@ void SymbolCollector::finish() { Symbols.freeze(); } +void SymbolCollector::addSymbolCompletionInfo(const NamedDecl *ND, + Symbol *Sym) { + assert(ASTCtx && PP.get() && "ASTContext and Preprocessor must be set."); + CodeCompletionResult SymbolCompletion(ND, 0); + const auto *CCS = SymbolCompletion.CreateCodeCompletionString( + *ASTCtx, *PP, CodeCompletionContext::CCC_Name, *CompletionAllocator, + CompletionTUInfo, + /*IncludeBriefComments*/ true); + Sym->CompletionFilterText = getFilterText(*CCS); + + + getLabelAndInsertText(*CCS, &Sym->CompletionLabel, + &Sym->CompletionSnippetInsertText, + /*EnableSnippets=*/true); + std::string IgnoredLabel; + getLabelAndInsertText(*CCS, &IgnoredLabel, &Sym->CompletionPlainInsertText, + /*EnableSnippets=*/false); + if (Sym->CompletionPlainInsertText == Sym->CompletionSnippetInsertText) + Sym->CompletionSnippetInsertText.clear(); + Sym->Detail.emplace(); + Sym->Detail->Documentation = getDocumentation(*CCS); + Sym->Detail->CompletionDetail = getDetail(*CCS); +} + } // namespace clangd } // namespace clang Index: clangd/index/SymbolYAML.cpp =================================================================== --- clangd/index/SymbolYAML.cpp +++ clangd/index/SymbolYAML.cpp @@ -59,6 +59,13 @@ } }; +template <> struct MappingTraits { + static void mapping(IO &io, Symbol::Details &Detail) { + io.mapRequired("Documentation", Detail.Documentation); + io.mapRequired("CompletionDetail", Detail.CompletionDetail); + } +}; + template<> struct MappingTraits { static void mapping(IO &IO, Symbol &Sym) { MappingNormalization NSymbolID( @@ -68,6 +75,12 @@ IO.mapRequired("Scope", Sym.Scope); IO.mapRequired("SymInfo", Sym.SymInfo); IO.mapRequired("CanonicalDeclaration", Sym.CanonicalDeclaration); + IO.mapRequired("CompletionLabel", Sym.CompletionLabel); + IO.mapRequired("CompletionFilterText", Sym.CompletionFilterText); + IO.mapRequired("CompletionPlainInsertText", Sym.CompletionPlainInsertText); + IO.mapRequired("CompletionSnippetInsertText", + Sym.CompletionSnippetInsertText); + IO.mapOptional("Detail", Sym.Detail); } }; Index: unittests/clangd/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -67,6 +67,8 @@ MATCHER_P(Labeled, Label, "") { return arg.label == Label; } MATCHER_P(Kind, K, "") { return arg.kind == K; } MATCHER_P(Filter, F, "") { return arg.filterText == F; } +MATCHER_P(Doc, D, "") { return arg.documentation == D; } +MATCHER_P(Detail, D, "") { return arg.detail == D; } MATCHER_P(PlainText, Text, "") { return arg.insertTextFormat == clangd::InsertTextFormat::PlainText && arg.insertText == Text; @@ -461,6 +463,7 @@ Sym.Name = QName.substr(Pos + 2); Sym.Scope = QName.substr(0, Pos); } + Sym.CompletionPlainInsertText = Sym.Name; Sym.SymInfo.Kind = Pair.second; Snap->Slab.insert(std::move(Sym)); } @@ -511,7 +514,7 @@ void f() { ns::x^ } )cpp", Opts); - EXPECT_THAT(Results.items, Contains(AllOf(Named("XYZ"), Filter("x")))); + EXPECT_THAT(Results.items, Contains(Named("XYZ"))); EXPECT_THAT(Results.items, Not(Has("foo"))); } @@ -549,13 +552,17 @@ Server .addDocument(Context::empty(), getVirtualTestFilePath("foo.cpp"), R"cpp( - namespace ns { class XYZ {}; void foo() {} } + namespace ns { class XYZ {}; void foo(int x) {} } )cpp") .wait(); auto File = getVirtualTestFilePath("bar.cpp"); Annotations Test(R"cpp( - namespace ns { class XXX {}; void fooooo() {} } + namespace ns { + class XXX {}; + /// Doooc + void fooooo() {} + } void f() { ns::^ } )cpp"); Server.addDocument(Context::empty(), File, Test.code()).wait(); @@ -568,7 +575,9 @@ EXPECT_THAT(Results.items, Has("XYZ", CompletionItemKind::Class)); EXPECT_THAT(Results.items, Has("foo", CompletionItemKind::Function)); EXPECT_THAT(Results.items, Has("XXX", CompletionItemKind::Class)); - EXPECT_THAT(Results.items, Has("fooooo", CompletionItemKind::Function)); + EXPECT_THAT(Results.items, Contains(AllOf(Named("fooooo"), Filter("fooooo"), + Kind(CompletionItemKind::Function), + Doc("Doooc"), Detail("void")))); } } // namespace 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,11 +25,25 @@ #include #include +using testing::AllOf; using testing::Eq; using testing::Field; using testing::UnorderedElementsAre; // GMock helpers for matching Symbol. +MATCHER_P(Labeled, Label, "") { return arg.second.CompletionLabel == Label; } +MATCHER_P(Detail, D, "") { + return arg.second.Detail && arg.second.Detail->CompletionDetail == D; +} +MATCHER_P(Doc, D, "") { + return arg.second.Detail && arg.second.Detail->Documentation == D; +} +MATCHER_P(Plain, Text, "") { + return arg.second.CompletionPlainInsertText == Text; +} +MATCHER_P(Snippet, S, "") { + return arg.second.CompletionSnippetInsertText == S; +} MATCHER_P(QName, Name, "") { return (arg.second.Scope + (arg.second.Scope.empty() ? "" : "::") + arg.second.Name) == Name; @@ -110,6 +123,38 @@ 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.")))); +} + +TEST_F(SymbolCollectorTest, PlainAndSnippet) { + const std::string Main = R"( + namespace nx { + void f() {} + int ff(int x, double y) { return 0; } + } + )"; + runSymbolCollector(/*Header=*/"", Main); + EXPECT_THAT( + Symbols, + UnorderedElementsAre( + QName("nx"), + AllOf(QName("nx::f"), Labeled("f()"), Plain("f"), Snippet("f()")), + AllOf(QName("nx::ff"), Labeled("ff(int x, double y)"), Plain("ff"), + Snippet("ff(${1:int x}, ${2:double y})")))); +} + TEST_F(SymbolCollectorTest, YAMLConversions) { const std::string YAML1 = R"( --- @@ -123,6 +168,13 @@ StartOffset: 0 EndOffset: 1 FilePath: /path/foo.h +CompletionLabel: 'Foo1-label' +CompletionFilterText: 'filter' +CompletionPlainInsertText: 'plain' +CompletionSnippetInsertText: 'snippet' +Detail: + Documentation: 'Foo doc' + CompletionDetail: 'int' ... )"; const std::string YAML2 = R"( @@ -137,15 +189,22 @@ StartOffset: 10 EndOffset: 12 FilePath: /path/foo.h +CompletionLabel: 'Foo2-label' +CompletionFilterText: 'filter' +CompletionPlainInsertText: 'plain' +CompletionSnippetInsertText: 'snippet' ... )"; auto Symbols1 = SymbolFromYAML(YAML1); - EXPECT_THAT(Symbols1, - UnorderedElementsAre(QName("clang::Foo1"))); + EXPECT_THAT(Symbols1, UnorderedElementsAre( + AllOf(QName("clang::Foo1"), Labeled("Foo1-label"), + Doc("Foo doc"), Detail("int")))); + EXPECT_TRUE(Symbols1.begin()->second.Detail); auto Symbols2 = SymbolFromYAML(YAML2); - EXPECT_THAT(Symbols2, - UnorderedElementsAre(QName("clang::Foo2"))); + EXPECT_THAT(Symbols2, UnorderedElementsAre(AllOf(QName("clang::Foo2"), + Labeled("Foo2-label")))); + EXPECT_FALSE(Symbols2.begin()->second.Detail); std::string ConcatenatedYAML = SymbolToYAML(Symbols1) + SymbolToYAML(Symbols2);