Index: clangd/ClangdLSPServer.h =================================================================== --- clangd/ClangdLSPServer.h +++ clangd/ClangdLSPServer.h @@ -62,6 +62,7 @@ void onDocumentRangeFormatting(DocumentRangeFormattingParams &Params) override; void onDocumentFormatting(DocumentFormattingParams &Params) override; + void onDocumentSymbol(DocumentSymbolParams &Params) override; void onCodeAction(CodeActionParams &Params) override; void onCompletion(TextDocumentPositionParams &Params) override; void onSignatureHelp(TextDocumentPositionParams &Params) override; Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -112,6 +112,7 @@ {"documentHighlightProvider", true}, {"hoverProvider", true}, {"renameProvider", true}, + {"documentSymbolProvider", true}, {"workspaceSymbolProvider", true}, {"executeCommandProvider", json::obj{ @@ -294,6 +295,19 @@ llvm::toString(ReplacementsOrError.takeError())); } +void ClangdLSPServer::onDocumentSymbol(DocumentSymbolParams &Params) { + Server.documentSymbols( + Params.textDocument.uri.file(), + [this](llvm::Expected> Items) { + if (!Items) + return replyError(ErrorCode::InvalidParams, + llvm::toString(Items.takeError())); + for (auto &Sym : *Items) + Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds); + reply(json::ary(*Items)); + }); +} + void ClangdLSPServer::onCodeAction(CodeActionParams &Params) { // We provide a code action for each diagnostic at the requested location // which has FixIts available. Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -167,6 +167,10 @@ void workspaceSymbols(StringRef Query, int Limit, Callback> CB); + /// Retrieve the symbols within the specified file. + void documentSymbols(StringRef File, + Callback> CB); + /// Run formatting for \p Rng inside \p File with content \p Code. llvm::Expected formatRange(StringRef Code, PathRef File, Range Rng); Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -456,6 +456,17 @@ RootPath ? *RootPath : "")); } +void ClangdServer::documentSymbols( + StringRef File, Callback> CB) { + auto Action = [](Callback> CB, + llvm::Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + CB(clangd::getDocumentSymbols(InpAST->AST)); + }; + WorkScheduler.runWithAST("Hover", File, Bind(Action, std::move(CB))); +} + std::vector> ClangdServer::getUsedBytesPerFile() const { return WorkScheduler.getUsedBytesPerFile(); Index: clangd/FindSymbols.h =================================================================== --- clangd/FindSymbols.h +++ clangd/FindSymbols.h @@ -17,6 +17,7 @@ #include "llvm/ADT/StringRef.h" namespace clang { +class ParsedAST; namespace clangd { class SymbolIndex; @@ -33,6 +34,11 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit, const SymbolIndex *const Index, llvm::StringRef HintPath); +/// Retrieves the symbols contained in the "main file" section of an AST in the +/// same order that they appear. +llvm::Expected> +getDocumentSymbols(ParsedAST &AST); + } // namespace clangd } // namespace clang Index: clangd/FindSymbols.cpp =================================================================== --- clangd/FindSymbols.cpp +++ clangd/FindSymbols.cpp @@ -8,12 +8,16 @@ //===----------------------------------------------------------------------===// #include "FindSymbols.h" -#include "Logger.h" +#include "AST.h" +#include "ClangdUnit.h" #include "FuzzyMatch.h" -#include "SourceCode.h" +#include "Logger.h" #include "Quality.h" +#include "SourceCode.h" #include "index/Index.h" +#include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" +#include "clang/Index/IndexingAction.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" @@ -172,5 +176,105 @@ return Result; } +namespace { +/// Finds document symbols in the main file of the AST. +class DocumentSymbolsConsumer : public index::IndexDataConsumer { + ASTContext &AST; + std::vector Symbols; + // We are always list document for the same file, so cache the value. + llvm::Optional MainFilePath; + +public: + DocumentSymbolsConsumer(raw_ostream &OS, ASTContext &AST) : AST(AST) {} + std::vector takeSymbols() { return std::move(Symbols); } + + void initialize(ASTContext &Ctx) override { + // Compute the absolute path of the main file which we will use for all + // results. + const SourceManager &SM = AST.getSourceManager(); + const FileEntry *F = SM.getFileEntryForID(SM.getMainFileID()); + if (!F) + return; + auto FilePath = getAbsoluteFilePath(F, SM); + if (FilePath) + MainFilePath = FilePath; + } + + bool shouldFilterDecl(const NamedDecl *ND) { + if (!ND || ND->isImplicit()) + return true; + // Skip anonymous declarations, e.g (anonymous enum/class/struct). + if (ND->getDeclName().isEmpty()) + return true; + return false; + } + + bool + handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, + SourceLocation Loc, + index::IndexDataConsumer::ASTNodeInfo ASTNode) override { + // No point in continuing the index consumer if we could not get the + // absolute path of the main file. + if (!MainFilePath) + return false; + // We only want declarations and definitions, i.e. no references. + if (!(Roles & static_cast(index::SymbolRole::Declaration) || + Roles & static_cast(index::SymbolRole::Definition))) + return true; + SourceLocation NameLoc = findNameLoc(D); + const SourceManager &SourceMgr = AST.getSourceManager(); + // We should be only be looking at "local" decls in the main file. + assert(SourceMgr.getMainFileID() == SourceMgr.getFileID(NameLoc)); + const NamedDecl *ND = llvm::dyn_cast(D); + if (shouldFilterDecl(ND)) + return true; + + SourceLocation EndLoc = + Lexer::getLocForEndOfToken(NameLoc, 0, SourceMgr, AST.getLangOpts()); + Position Begin = sourceLocToPosition(SourceMgr, NameLoc); + Position End = sourceLocToPosition(SourceMgr, EndLoc); + Range R = {Begin, End}; + Location L; + L.uri = URIForFile(*MainFilePath); + L.range = R; + + std::string QName; + llvm::raw_string_ostream OS(QName); + PrintingPolicy Policy(AST.getLangOpts()); + Policy.SuppressUnwrittenScope = true; + ND->printQualifiedName(OS, Policy); + OS.flush(); + assert(!StringRef(QName).startswith("::")); + StringRef Scope, Name; + std::tie(Scope, Name) = splitQualifiedName(QName); + Scope.consume_back("::"); + + index::SymbolInfo SymInfo = index::getSymbolInfo(ND); + SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); + + Symbols.push_back({Name, SK, L, Scope}); + return true; + } +}; +} // namespace + +llvm::Expected> +getDocumentSymbols(ParsedAST &AST) { + DocumentSymbolsConsumer DocumentSymbolsCons(llvm::errs(), + AST.getASTContext()); + + index::IndexingOptions IndexOpts; + // We don't need references, only declarations so that is makes a nice + // outline of the document. + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly; + IndexOpts.IndexFunctionLocals = false; + indexTopLevelDecls(AST.getASTContext(), AST.getLocalTopLevelDecls(), + DocumentSymbolsCons, IndexOpts); + + return DocumentSymbolsCons.takeSymbols(); +} + } // namespace clangd } // namespace clang Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -479,6 +479,12 @@ }; bool fromJSON(const json::Expr &, DocumentFormattingParams &); +struct DocumentSymbolParams { + // The text document to find symbols in. + TextDocumentIdentifier textDocument; +}; +bool fromJSON(const json::Expr &, DocumentSymbolParams &); + struct Diagnostic { /// The range at which the message applies. Range range; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -342,6 +342,11 @@ O.map("options", R.options); } +bool fromJSON(const json::Expr &Params, DocumentSymbolParams &R) { + json::ObjectMapper O(Params); + return O && O.map("textDocument", R.textDocument); +} + bool fromJSON(const json::Expr &Params, Diagnostic &R) { json::ObjectMapper O(Params); if (!O || !O.map("range", R.range) || !O.map("message", R.message)) Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -38,6 +38,7 @@ virtual void onDocumentDidChange(DidChangeTextDocumentParams &Params) = 0; virtual void onDocumentDidClose(DidCloseTextDocumentParams &Params) = 0; virtual void onDocumentFormatting(DocumentFormattingParams &Params) = 0; + virtual void onDocumentSymbol(DocumentSymbolParams &Params) = 0; virtual void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams &Params) = 0; virtual void Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -66,6 +66,7 @@ &ProtocolCallbacks::onSwitchSourceHeader); Register("textDocument/rename", &ProtocolCallbacks::onRename); Register("textDocument/hover", &ProtocolCallbacks::onHover); + Register("textDocument/documentSymbol", &ProtocolCallbacks::onDocumentSymbol); Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent); Register("workspace/executeCommand", &ProtocolCallbacks::onCommand); Register("textDocument/documentHighlight", Index: clangd/SourceCode.h =================================================================== --- clangd/SourceCode.h +++ clangd/SourceCode.h @@ -61,6 +61,9 @@ std::vector replacementsToEdits(StringRef Code, const tooling::Replacements &Repls); +/// Get the absolute file path of a given file entry. +llvm::Optional getAbsoluteFilePath(const FileEntry *F, + const SourceManager &SourceMgr); } // namespace clangd } // namespace clang #endif Index: clangd/SourceCode.cpp =================================================================== --- clangd/SourceCode.cpp +++ clangd/SourceCode.cpp @@ -8,9 +8,13 @@ //===----------------------------------------------------------------------===// #include "SourceCode.h" +#include "Logger.h" +#include "clang/AST/ASTContext.h" #include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" +#include "llvm/Support/Path.h" namespace clang { namespace clangd { @@ -181,5 +185,19 @@ return Edits; } +llvm::Optional +getAbsoluteFilePath(const FileEntry *F, const SourceManager &SourceMgr) { + SmallString<64> FilePath = F->tryGetRealPathName(); + if (FilePath.empty()) + FilePath = F->getName(); + if (!llvm::sys::path::is_absolute(FilePath)) { + if (!SourceMgr.getFileManager().makeAbsolutePath(FilePath)) { + log("Could not turn relative path to absolute: " + FilePath); + return llvm::None; + } + } + return FilePath.str().str(); +} + } // namespace clangd } // namespace clang Index: clangd/XRefs.cpp =================================================================== --- clangd/XRefs.cpp +++ clangd/XRefs.cpp @@ -174,20 +174,6 @@ return {DeclMacrosFinder.takeDecls(), DeclMacrosFinder.takeMacroInfos()}; } -llvm::Optional -getAbsoluteFilePath(const FileEntry *F, const SourceManager &SourceMgr) { - SmallString<64> FilePath = F->tryGetRealPathName(); - if (FilePath.empty()) - FilePath = F->getName(); - if (!llvm::sys::path::is_absolute(FilePath)) { - if (!SourceMgr.getFileManager().makeAbsolutePath(FilePath)) { - log("Could not turn relative path to absolute: " + FilePath); - return llvm::None; - } - } - return FilePath.str().str(); -} - llvm::Optional makeLocation(ParsedAST &AST, const SourceRange &ValSourceRange) { const SourceManager &SourceMgr = AST.getASTContext().getSourceManager(); Index: test/clangd/initialize-params-invalid.test =================================================================== --- test/clangd/initialize-params-invalid.test +++ test/clangd/initialize-params-invalid.test @@ -22,6 +22,7 @@ # CHECK-NEXT: "moreTriggerCharacter": [] # CHECK-NEXT: }, # CHECK-NEXT: "documentRangeFormattingProvider": true, +# CHECK-NEXT: "documentSymbolProvider": true, # CHECK-NEXT: "executeCommandProvider": { # CHECK-NEXT: "commands": [ # CHECK-NEXT: "clangd.applyFix" Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -22,6 +22,7 @@ # CHECK-NEXT: "moreTriggerCharacter": [] # CHECK-NEXT: }, # CHECK-NEXT: "documentRangeFormattingProvider": true, +# CHECK-NEXT: "documentSymbolProvider": true, # CHECK-NEXT: "executeCommandProvider": { # CHECK-NEXT: "commands": [ # CHECK-NEXT: "clangd.applyFix" Index: test/clangd/symbols.test =================================================================== --- test/clangd/symbols.test +++ test/clangd/symbols.test @@ -28,6 +28,57 @@ # CHECK-NEXT: ] # CHECK-NEXT:} --- +{"jsonrpc":"2.0","id":2,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"test:///main.cpp"}}} +# CHECK: "id": 2, +# CHECK-NEXT: "jsonrpc": "2.0", +# CHECK-NEXT: "result": [ +# CHECK-NEXT: { +# CHECK-NEXT: "containerName": "", +# CHECK-NEXT: "kind": 12, +# CHECK-NEXT: "location": { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": {{.*}}, +# CHECK-NEXT: "line": {{.*}} +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": {{.*}}, +# CHECK-NEXT: "line": {{.*}} +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file://{{.*}}/main.cpp" +# CHECK-NEXT: }, +# CHECK-NEXT: "name": "foo" +# CHECK-NEXT: } +# CHECK-NEXT: { +# CHECK-NEXT: "containerName": "", +# CHECK-NEXT: "kind": 12, +# CHECK-NEXT: "location": { +# CHECK-NEXT: "range": { +# CHECK-NEXT: "end": { +# CHECK-NEXT: "character": {{.*}}, +# CHECK-NEXT: "line": {{.*}} +# CHECK-NEXT: }, +# CHECK-NEXT: "start": { +# CHECK-NEXT: "character": {{.*}}, +# CHECK-NEXT: "line": {{.*}} +# CHECK-NEXT: } +# CHECK-NEXT: }, +# CHECK-NEXT: "uri": "file://{{.*}}/main.cpp" +# CHECK-NEXT: }, +# CHECK-NEXT: "name": "main" +# CHECK-NEXT: } +# CHECK-NEXT: ] +# CHECK-NEXT:} +--- +{"jsonrpc":"2.0","id":3,"method":"textDocument/documentSymbol","params":{"textDocument":{"uri":"test:///foo.cpp"}}} +# CHECK: "error": { +# CHECK-NEXT: "code": -32602, +# CHECK-NEXT: "message": "trying to get AST for non-added document" +# CHECK-NEXT: }, +# CHECK-NEXT: "id": 3, +# CHECK-NEXT: "jsonrpc": "2.0" +--- {"jsonrpc":"2.0","id":3,"method":"shutdown"} --- {"jsonrpc":"2.0","method":"exit"} Index: unittests/clangd/FindSymbolsTests.cpp =================================================================== --- unittests/clangd/FindSymbolsTests.cpp +++ unittests/clangd/FindSymbolsTests.cpp @@ -22,6 +22,7 @@ using ::testing::AllOf; using ::testing::AnyOf; using ::testing::ElementsAre; +using ::testing::ElementsAreArray; using ::testing::IsEmpty; using ::testing::UnorderedElementsAre; @@ -37,6 +38,7 @@ return (arg.containerName + "::" + arg.name) == Name; } MATCHER_P(WithKind, Kind, "") { return arg.kind == Kind; } +MATCHER_P(SymRange, Range, "") { return arg.location.range == Range; } ClangdServer::Options optsForTests() { auto ServerOpts = ClangdServer::optsForTest(); @@ -287,5 +289,242 @@ EXPECT_THAT(getSymbols("foo"), ElementsAre(QName("foo"))); } +namespace { +class DocumentSymbolsTest : public ::testing::Test { +public: + DocumentSymbolsTest() + : Server(CDB, FSProvider, DiagConsumer, optsForTests()) {} + +protected: + MockFSProvider FSProvider; + MockCompilationDatabase CDB; + IgnoreDiagnostics DiagConsumer; + ClangdServer Server; + + std::vector getSymbols(PathRef File) { + EXPECT_TRUE(Server.blockUntilIdleForTest()) << "Waiting for preamble"; + auto SymbolInfos = runDocumentSymbols(Server, File); + EXPECT_TRUE(bool(SymbolInfos)) << "documentSymbols returned an error"; + return *SymbolInfos; + } + + void addFile(StringRef FilePath, StringRef Contents) { + FSProvider.Files[FilePath] = Contents; + Server.addDocument(FilePath, Contents); + } +}; +} // namespace + +TEST_F(DocumentSymbolsTest, BasicSymbols) { + std::string FilePath = testPath("foo.cpp"); + addFile(FilePath, R"( + class Foo; + class Foo { + Foo() {} + Foo(int a) {} + void f(); + friend void f1(); + friend class Friend; + Foo& operator=(const Foo&); + ~Foo(); + class Nested { + void f(); + }; + }; + class Friend { + }; + + void f1(); + inline void f2() {} + static const int KInt = 2; + const char* kStr = "123"; + + void f1() {} + + namespace foo { + // Type alias + typedef int int32; + using int32_t = int32; + + // Variable + int v1; + + // Namespace + namespace bar { + int v2; + } + // Namespace alias + namespace baz = bar; + + // FIXME: using declaration is not supported as the IndexAction will ignore + // implicit declarations (the implicit using shadow declaration) by default, + // and there is no way to customize this behavior at the moment. + using bar::v2; + } // namespace foo + )"); + EXPECT_THAT(getSymbols(FilePath), + ElementsAreArray( + {AllOf(QName("Foo"), WithKind(SymbolKind::Class)), + AllOf(QName("Foo"), WithKind(SymbolKind::Class)), + AllOf(QName("Foo::Foo"), WithKind(SymbolKind::Method)), + AllOf(QName("Foo::Foo"), WithKind(SymbolKind::Method)), + AllOf(QName("Foo::f"), WithKind(SymbolKind::Method)), + AllOf(QName("f1"), WithKind(SymbolKind::Function)), + AllOf(QName("Foo::operator="), WithKind(SymbolKind::Method)), + AllOf(QName("Foo::~Foo"), WithKind(SymbolKind::Method)), + AllOf(QName("Foo::Nested"), WithKind(SymbolKind::Class)), + AllOf(QName("Foo::Nested::f"), WithKind(SymbolKind::Method)), + + AllOf(QName("Friend"), WithKind(SymbolKind::Class)), + AllOf(QName("f1"), WithKind(SymbolKind::Function)), + AllOf(QName("f2"), WithKind(SymbolKind::Function)), + AllOf(QName("KInt"), WithKind(SymbolKind::Variable)), + AllOf(QName("kStr"), WithKind(SymbolKind::Variable)), + AllOf(QName("f1"), WithKind(SymbolKind::Function)), + AllOf(QName("foo"), WithKind(SymbolKind::Namespace)), + AllOf(QName("foo::int32"), WithKind(SymbolKind::Class)), + AllOf(QName("foo::int32_t"), WithKind(SymbolKind::Class)), + AllOf(QName("foo::v1"), WithKind(SymbolKind::Variable)), + AllOf(QName("foo::bar"), WithKind(SymbolKind::Namespace)), + AllOf(QName("foo::bar::v2"), WithKind(SymbolKind::Variable)), + AllOf(QName("foo::baz"), WithKind(SymbolKind::Namespace))})); +} + +TEST_F(DocumentSymbolsTest, NoLocals) { + std::string FilePath = testPath("foo.cpp"); + addFile(FilePath, + R"cpp( + void test(int FirstParam, int SecondParam) { + struct LocalClass {}; + int local_var; + })cpp"); + EXPECT_THAT(getSymbols(FilePath), ElementsAre(QName("test"))); +} + +TEST_F(DocumentSymbolsTest, Unnamed) { + std::string FilePath = testPath("foo.h"); + addFile(FilePath, + R"cpp( + struct { + int InUnnamed; + } UnnamedStruct; + )cpp"); + EXPECT_THAT( + getSymbols(FilePath), + ElementsAre(AllOf(QName("UnnamedStruct"), WithKind(SymbolKind::Variable)), + AllOf(QName("(anonymous struct)::InUnnamed"), + WithKind(SymbolKind::Field)))); +} + +TEST_F(DocumentSymbolsTest, InHeaderFile) { + addFile("bar.h", R"cpp( + int foo() { + } + )cpp"); + std::string FilePath = testPath("foo.h"); + addFile(FilePath, R"cpp( + #include "bar.h" + int test() { + } + )cpp"); + addFile("foo.cpp", R"cpp( + #include "foo.h" + )cpp"); + EXPECT_THAT(getSymbols(FilePath), ElementsAre(QName("test"))); +} + +TEST_F(DocumentSymbolsTest, Template) { + std::string FilePath = testPath("foo.cpp"); + addFile(FilePath, R"( + // Primary templates and specializations are included but instantiations + // are not. + template struct Tmpl {T x = 0;}; + template <> struct Tmpl {}; + extern template struct Tmpl; + template struct Tmpl; + )"); + EXPECT_THAT(getSymbols(FilePath), + ElementsAre(AllOf(QName("Tmpl"), WithKind(SymbolKind::Struct)), + AllOf(QName("Tmpl::x"), WithKind(SymbolKind::Field)), + AllOf(QName("Tmpl"), WithKind(SymbolKind::Struct)))); +} + +TEST_F(DocumentSymbolsTest, Namespaces) { + std::string FilePath = testPath("foo.cpp"); + addFile(FilePath, R"cpp( + namespace ans1 { + int ai1; + namespace ans2 { + int ai2; + } + } + namespace { + void test() {} + } + + namespace na { + inline namespace nb { + class Foo {}; + } + } + namespace na { + // This is still inlined. + namespace nb { + class Bar {}; + } + } + )cpp"); + EXPECT_THAT( + getSymbols(FilePath), + ElementsAreArray({QName("ans1"), QName("ans1::ai1"), QName("ans1::ans2"), + QName("ans1::ans2::ai2"), QName("test"), QName("na"), + QName("na::nb"), QName("na::Foo"), QName("na"), + QName("na::nb"), QName("na::Bar")})); +} + +TEST_F(DocumentSymbolsTest, Enums) { + std::string FilePath = testPath("foo.cpp"); + addFile(FilePath, R"( + enum { + Red + }; + enum Color { + Green + }; + enum class Color2 { + Yellow + }; + namespace ns { + enum { + Black + }; + } + )"); + EXPECT_THAT(getSymbols(FilePath), + ElementsAre(QName("Red"), QName("Color"), QName("Green"), + QName("Color2"), QName("Color2::Yellow"), QName("ns"), + QName("ns::Black"))); +} + +TEST_F(DocumentSymbolsTest, FromMacro) { + std::string FilePath = testPath("foo.cpp"); + Annotations Main(R"( + #define FF(name) \ + class name##_Test {}; + + $expansion[[FF]](abc); + + #define FF2() \ + class $spelling[[Test]] {}; + + FF2(); + )"); + addFile(FilePath, Main.code()); + EXPECT_THAT( + getSymbols(FilePath), + ElementsAre(AllOf(QName("abc_Test"), SymRange(Main.range("expansion"))), + AllOf(QName("Test"), SymRange(Main.range("spelling"))))); +} + } // namespace clangd } // namespace clang Index: unittests/clangd/SyncAPI.h =================================================================== --- unittests/clangd/SyncAPI.h +++ unittests/clangd/SyncAPI.h @@ -43,6 +43,9 @@ llvm::Expected> runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit); +llvm::Expected> +runDocumentSymbols(ClangdServer &Server, PathRef File); + } // namespace clangd } // namespace clang Index: unittests/clangd/SyncAPI.cpp =================================================================== --- unittests/clangd/SyncAPI.cpp +++ unittests/clangd/SyncAPI.cpp @@ -117,5 +117,12 @@ return std::move(*Result); } +llvm::Expected> +runDocumentSymbols(ClangdServer &Server, PathRef File) { + llvm::Optional>> Result; + Server.documentSymbols(File, capture(Result)); + return std::move(*Result); +} + } // namespace clangd } // namespace clang