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 @@ -559,16 +559,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,37 @@ // (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). + 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 @@ -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 @@ -8,12 +8,14 @@ //===----------------------------------------------------------------------===// #include "SymbolCollector.h" +#include "../CompletionString.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #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" @@ -57,7 +59,30 @@ return AbsolutePath.str(); } -// Split a qualified symbol name into scope and unqualified name, e.g. given +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->CompletionFilterText = getFilterText(*CCS); + bool IsSnippet = false; + auto LabelAndSnippetText = getLabelAndInsertText(*CCS, &IsSnippet); + Sym->CompletionLabel = LabelAndSnippetText.first; + if (IsSnippet) { + Sym->CompletionSnippetInsertText = LabelAndSnippetText.second; + Sym->CompletionPlainInsertText = getLabelAndInsertText(*CCS).second; + } else { + Sym->CompletionPlainInsertText = LabelAndSnippetText.second; + } + + Sym->Detail.emplace(); + Sym->Detail->Documentation = getDocumentation(*CCS); + Sym->Detail->CompletionDetail = getDetail(*CCS); +} + // "a::b::c", return {"a::b", "c"}. Scope is empty if it doesn't exist. std::pair splitQualifiedName(llvm::StringRef QName) { @@ -98,10 +123,19 @@ 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); + 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/CodeCompleteTests.cpp =================================================================== --- unittests/clangd/CodeCompleteTests.cpp +++ unittests/clangd/CodeCompleteTests.cpp @@ -86,6 +86,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; @@ -480,6 +482,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)); } @@ -530,7 +533,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"))); } @@ -568,13 +571,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"); auto Test = parseTextMarker(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.Text).wait(); @@ -587,7 +594,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("")), + 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"( ---