Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -306,7 +306,7 @@ void ClangdLSPServer::onDocumentSymbol(DocumentSymbolParams &Params) { Server.documentSymbols( Params.textDocument.uri.file(), - [this](llvm::Expected> Items) { + [this](llvm::Expected> Items) { if (!Items) return replyError(ErrorCode::InvalidParams, llvm::toString(Items.takeError())); Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -165,7 +165,7 @@ /// Retrieve the symbols within the specified file. void documentSymbols(StringRef File, - Callback> CB); + Callback> CB); /// Retrieve locations for symbol references. void findReferences(PathRef File, Position Pos, Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -544,9 +544,9 @@ RootPath ? *RootPath : "")); } -void ClangdServer::documentSymbols( - StringRef File, Callback> CB) { - auto Action = [](Callback> CB, +void ClangdServer::documentSymbols(StringRef File, + Callback> CB) { + auto Action = [](Callback> CB, llvm::Expected InpAST) { if (!InpAST) return CB(InpAST.takeError()); Index: clangd/FindSymbols.h =================================================================== --- clangd/FindSymbols.h +++ clangd/FindSymbols.h @@ -36,8 +36,7 @@ /// Retrieves the symbols contained in the "main file" section of an AST in the /// same order that they appear. -llvm::Expected> -getDocumentSymbols(ParsedAST &AST); +llvm::Expected> getDocumentSymbols(ParsedAST &AST); } // namespace clangd } // namespace clang Index: clangd/FindSymbols.cpp =================================================================== --- clangd/FindSymbols.cpp +++ clangd/FindSymbols.cpp @@ -12,12 +12,20 @@ #include "ClangdUnit.h" #include "FuzzyMatch.h" #include "Logger.h" +#include "Protocol.h" #include "Quality.h" #include "SourceCode.h" #include "index/Index.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Index/IndexDataConsumer.h" #include "clang/Index/IndexSymbol.h" #include "clang/Index/IndexingAction.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" @@ -176,104 +184,91 @@ } namespace { -/// Finds document symbols in the main file of the AST. -class DocumentSymbolsConsumer : public index::IndexDataConsumer { - ASTContext * - std::vector Symbols; - // We are always list document for the same file, so cache the value. - llvm::Optional MainFileUri; +DocumentSymbol declToSym(ASTContext &Ctx, const NamedDecl &ND, + SourceLocation NameLoc, + const URIForFile &MainFileUri) { + auto &SM = Ctx.getSourceManager(); + SourceLocation EndLoc = + Lexer::getLocForEndOfToken(NameLoc, 0, SM, Ctx.getLangOpts()); + Position Begin = sourceLocToPosition(SM, NameLoc); + Position End = sourceLocToPosition(SM, EndLoc); + Range R = {Begin, End}; -public: - DocumentSymbolsConsumer(ASTContext &AST) : AST(AST) {} - std::vector takeSymbols() { return std::move(Symbols); } + std::string QName = printQualifiedName(ND); + StringRef Scope, Name; + std::tie(Scope, Name) = splitQualifiedName(QName); + Scope.consume_back("::"); - 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 = getRealPath(F, SM); - if (FilePath) - MainFileUri = URIForFile(*FilePath); - } + index::SymbolInfo SymInfo = index::getSymbolInfo(&ND); + SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); - bool shouldIncludeSymbol(const NamedDecl *ND) { - if (!ND || ND->isImplicit()) - return false; - // Skip anonymous declarations, e.g (anonymous enum/class/struct). - if (ND->getDeclName().isEmpty()) - return false; - return true; - } + DocumentSymbol SI; + SI.name = Name; + SI.kind = SK; + SI.deprecated = ND.isDeprecated(); + // FIXME(ibiryukov): set the range properly. + SI.range = R; + SI.selectionRange = R; + return SI; +} - bool - handleDeclOccurence(const Decl *, index::SymbolRoleSet Roles, - ArrayRef Relations, - SourceLocation Loc, - index::IndexDataConsumer::ASTNodeInfo ASTNode) override { - assert(ASTNode.OrigD); - // No point in continuing the index consumer if we could not get the - // absolute path of the main file. - if (!MainFileUri) - 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(ASTNode.OrigD); - const SourceManager &SourceMgr = AST.getSourceManager(); - // We should be only be looking at "local" decls in the main file. - if (!SourceMgr.isWrittenInMainFile(NameLoc)) { - // Even thought we are visiting only local (non-preamble) decls, - // we can get here when in the presence of "extern" decls. - return true; +std::vector collectDocSymbols(ASTContext &Ctx, + const DeclContext &Scope, + const URIForFile &MainFileUri) { + std::vector Symbols; + auto &SM = Ctx.getSourceManager(); + for (const Decl *D : Scope.decls()) { + if (D->isImplicit()) + continue; + if (!SM.isWrittenInMainFile(D->getLocation())) + continue; + // Add symbol to a list. + DocumentSymbol *S = nullptr; + if (const NamedDecl *ND = llvm::dyn_cast(D)) { + Symbols.push_back(declToSym(Ctx, *ND, findNameLoc(ND), MainFileUri)); + S = &Symbols.back(); + } + // Add childrens if decl is also a DeclContext. + // FIXME(ibiryukov): filtering only functions is probably not enough. + if (llvm::isa(D)) + continue; // don't add function locals. + // FIXME(ibiryukov): do we even need to handle "DeclContext, but not + // NamedDecl" case? Can this happen? + if (const DeclContext *ChildScope = llvm::dyn_cast(D)) { + auto ChildSymbols = collectDocSymbols(Ctx, *ChildScope, MainFileUri); + if (S) + S->children = std::move(ChildSymbols); + else + Symbols.insert(Symbols.end(), ChildSymbols.begin(), ChildSymbols.end()); } - const NamedDecl *ND = llvm::dyn_cast(ASTNode.OrigD); - if (!shouldIncludeSymbol(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 = *MainFileUri; - L.range = R; - - std::string QName = printQualifiedName(*ND); - StringRef Scope, Name; - std::tie(Scope, Name) = splitQualifiedName(QName); - Scope.consume_back("::"); - - index::SymbolInfo SymInfo = index::getSymbolInfo(ND); - SymbolKind SK = indexSymbolKindToSymbolKind(SymInfo.Kind); - - SymbolInformation SI; - SI.name = Name; - SI.kind = SK; - SI.location = L; - SI.containerName = Scope; - Symbols.push_back(std::move(SI)); - return true; } -}; + return Symbols; +} } // namespace -llvm::Expected> -getDocumentSymbols(ParsedAST &AST) { - DocumentSymbolsConsumer DocumentSymbolsCons(AST.getASTContext()); - - index::IndexingOptions IndexOpts; - IndexOpts.SystemSymbolFilter = - index::IndexingOptions::SystemSymbolFilterKind::DeclarationsOnly; - IndexOpts.IndexFunctionLocals = false; - indexTopLevelDecls(AST.getASTContext(), AST.getLocalTopLevelDecls(), - DocumentSymbolsCons, IndexOpts); +llvm::Expected> getDocumentSymbols(ParsedAST &AST) { + // Compute the absolute path of the main file which we will use for all + // results. + const SourceManager &SM = AST.getASTContext().getSourceManager(); + const FileEntry *F = SM.getFileEntryForID(SM.getMainFileID()); + if (!F) + return llvm::createStringError( + llvm::errc::invalid_argument, + "Could not retrieve FileEntry for main file."); + auto FilePath = getRealPath(F, SM); + // No point in continuing the index consumer if we could not get the + // absolute path of the main file. + if (!FilePath) + return llvm::createStringError( + llvm::errc::invalid_argument, + "Could not retrieve absolute path of the main file."); + URIForFile MainFileUri = URIForFile(*FilePath); - return DocumentSymbolsCons.takeSymbols(); + // Collect all local symbols in the AST, along with start locations of their + // names. + ASTContext &Ctx = AST.getASTContext(); + // Convert into the hierarchical tree of symbols. + return collectDocSymbols(Ctx, *Ctx.getTranslationUnitDecl(), MainFileUri); } } // namespace clangd Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -40,6 +40,7 @@ InvalidRequest = -32600, MethodNotFound = -32601, InvalidParams = -32602, + InternalError = -32603, ServerNotInitialized = -32002, @@ -623,6 +624,38 @@ llvm::json::Value toJSON(const Command &C); +/// Represents programming constructs like variables, classes, interfaces etc. +/// that appear in a document. Document symbols can be hierarchical and they +/// have two ranges: one that encloses its definition and one that points to its +/// most interesting range, e.g. the range of an identifier. +struct DocumentSymbol { + /// The name of this symbol. + std::string name; + + /// More detail for this symbol, e.g the signature of a function. + std::string detail; + + /// The kind of this symbol. + SymbolKind kind; + + /// Indicates if this symbol is deprecated. + bool deprecated; + + /// The range enclosing this symbol not including leading/trailing whitespace + /// but everything else like comments. This information is typically used to + /// determine if the clients cursor is inside the symbol to reveal in the + /// symbol in the UI. + Range range; + + /// The range that should be selected and revealed when this symbol is being + /// picked, e.g the name of a function. Must be contained by the `range`. + Range selectionRange; + + /// Children of this symbol, e.g. properties of a class. + std::vector children; +}; +llvm::json::Value toJSON(const DocumentSymbol &S); + /// Represents information about programming constructs like variables, classes, /// interfaces etc. struct SymbolInformation { Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -18,6 +18,7 @@ #include "llvm/ADT/SmallString.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" @@ -448,6 +449,16 @@ return std::move(Cmd); } +llvm::json::Value toJSON(const DocumentSymbol &S) { + return json::Object{{"name", S.name}, + {"detail", S.detail}, + {"kind", static_cast(S.kind)}, + {"deprecated", S.deprecated}, + {"children", json::Array{S.children}}, + {"range", S.range}, + {"selectionRange", S.selectionRange}}; +} + json::Value toJSON(const WorkspaceEdit &WE) { if (!WE.changes) return json::Object{}; Index: clangd/clients/clangd-vscode/package.json =================================================================== --- clangd/clients/clangd-vscode/package.json +++ clangd/clients/clangd-vscode/package.json @@ -6,7 +6,7 @@ "publisher": "llvm-vs-code-extensions", "homepage": "https://clang.llvm.org/extra/clangd.html", "engines": { - "vscode": "^1.18.0" + "vscode": "^1.27.0" }, "categories": [ "Programming Languages", @@ -32,8 +32,8 @@ "test": "node ./node_modules/vscode/bin/test" }, "dependencies": { - "vscode-languageclient": "^4.0.0", - "vscode-languageserver": "^4.0.0" + "vscode-languageclient": "^5.1.0", + "vscode-languageserver": "^5.1.0" }, "devDependencies": { "typescript": "^2.0.3", Index: unittests/clangd/SyncAPI.h =================================================================== --- unittests/clangd/SyncAPI.h +++ unittests/clangd/SyncAPI.h @@ -47,7 +47,7 @@ llvm::Expected> runWorkspaceSymbols(ClangdServer &Server, StringRef Query, int Limit); -llvm::Expected> +llvm::Expected> runDocumentSymbols(ClangdServer &Server, PathRef File); } // namespace clangd Index: unittests/clangd/SyncAPI.cpp =================================================================== --- unittests/clangd/SyncAPI.cpp +++ unittests/clangd/SyncAPI.cpp @@ -118,7 +118,7 @@ return std::move(*Result); } -llvm::Expected> +llvm::Expected> runDocumentSymbols(ClangdServer &Server, PathRef File) { llvm::Optional>> Result; Server.documentSymbols(File, capture(Result));