From 13119ff8356a01308d75b2254d945982d565acd1 Mon Sep 17 00:00:00 2001 From: Shiyu Date: Fri, 24 Jan 2025 14:36:04 +0800 Subject: [PATCH] Update `FoldingRange` to support index. --- include/Feature/DocumentSymbol.h | 48 +++++-- src/Feature/DocumentSymbol.cpp | 190 +++++++++++++++++++-------- unittests/Feature/DocumentSymbol.cpp | 24 ++-- 3 files changed, 185 insertions(+), 77 deletions(-) diff --git a/include/Feature/DocumentSymbol.h b/include/Feature/DocumentSymbol.h index bb4a4565..07b8cbd4 100644 --- a/include/Feature/DocumentSymbol.h +++ b/include/Feature/DocumentSymbol.h @@ -1,4 +1,6 @@ #include "Basic/Document.h" +#include "Basic/SourceCode.h" +#include "Index/Shared.h" #include "Support/JSON.h" namespace clice { @@ -75,9 +77,6 @@ struct DocumentSymbol { /// The kind of this symbol. SymbolKind kind; - /// Indicates if this symbol is deprecated. - bool deprecated = false; - /// Tags for this symbol. std::vector tags; @@ -101,13 +100,46 @@ using DocumentSymbolResult = std::vector; class ASTInfo; class SourceConverter; -namespace feature { +namespace feature::document_symbol { + +json::Value capability(json::Value clientCapabilities); + +struct DocumentSymbol { + /// The kind of this symbol. + proto::SymbolKind kind; + + /// The name of this symbol. + std::string name; + + /// More detail for this symbol, e.g the signature of a function. + std::string detail; + + /// Tags for this symbol. + std::vector tags; + + /// Children of this symbol, e.g. properties of a class. + std::vector children; + + /// The range enclosing the symbol not including leading/trailing whitespace but everything + /// else. + LocalSourceRange range; + + /// Must be contained by the `range`. + LocalSourceRange selectionRange; +}; + +using Result = std::vector; + +/// Get all document symbols in each file. +index::Shared documentSymbol(ASTInfo& info, const SourceConverter& SC); -json::Value documentSymbolCapability(json::Value clientCapabilities); +/// Get document symbols in the main file. +Result documentSymbolInMainFile(ASTInfo& info, const SourceConverter& SC); -/// Run document symbol in given file. -proto::DocumentSymbolResult documentSymbol(ASTInfo& info, const SourceConverter& converter); +/// Convert the result to LSP format. +proto::DocumentSymbolResult toLspResult(llvm::ArrayRef result, + llvm::StringRef content, const SourceConverter& SC); -} // namespace feature +} // namespace feature::document_symbol } // namespace clice diff --git a/src/Feature/DocumentSymbol.cpp b/src/Feature/DocumentSymbol.cpp index 507ddd49..05081627 100644 --- a/src/Feature/DocumentSymbol.cpp +++ b/src/Feature/DocumentSymbol.cpp @@ -6,6 +6,8 @@ namespace clice { namespace { +using feature::document_symbol::DocumentSymbol; + /// Clangd's DocumentSymbol Implementation: /// https://github.com/llvm/llvm-project/blob/main/clang-tools-extra/clangd/FindSymbols.cpp#L286 @@ -14,44 +16,53 @@ struct DocumentSymbolCollector : clang::RecursiveASTVisitor; + using Storage = index::Shared; + const clang::SourceManager& src; const SourceConverter& cvtr; /// DFS state stack. - std::vector stack; + std::vector> stack; /// Result of document symbols. - std::vector result; + Storage result; + + /// True if only collect symbols in the main file. + const bool onlyMain; + + /// Main file ID, available if `onlyMain` is true. + const clang::FileID mainID; /// Entry a new AST node which may has some children nodes. - void entry(proto::DocumentSymbol symbol) { - stack.push_back(std::move(symbol)); + void entry(DocumentSymbol symbol, clang::SourceLocation loc) { + stack.push_back({std::move(symbol), loc}); } /// Leave the current AST node. void leave() { - stack.back().children.shrink_to_fit(); + stack.back().first.children.shrink_to_fit(); auto last = std::move(stack.back()); stack.pop_back(); - collect(std::move(last)); + collect(std::move(last.first), last.second); } /// Collect a leaf node as the DocumentSymbol. - void collect(proto::DocumentSymbol symbol) { - auto& ref = stack.empty() ? result : stack.back().children; - ref.push_back(std::move(symbol)); + void collect(DocumentSymbol symbol, clang::SourceLocation loc) { + feature::document_symbol::Result* state; + + if(!stack.empty()) + state = &stack.back().first.children; + else + state = &result[onlyMain ? mainID : src.getFileID(loc)]; + + state->push_back(std::move(symbol)); } /// Mark the symbol as deprecated. - void markDeprecated(proto::DocumentSymbol& symbol) { + void markDeprecated(DocumentSymbol& symbol) { symbol.tags.push_back(proto::SymbolTag{proto::SymbolTag::Deprecated}); - symbol.deprecated = true; - } - - bool isInMainFile(clang::SourceLocation loc) { - return loc.isValid() && src.isInMainFile(loc); } /// For a given location, it could be one of SpellingLoc or ExpansionLoc (from macro expansion). @@ -70,10 +81,10 @@ struct DocumentSymbolCollector : clang::RecursiveASTVisitor(decl)) + if(!llvm::isa(decl) || decl->isImplicit()) return true; - if(!isInMainFile(decl->getLocation()) || decl->isImplicit()) + if(auto loc = decl->getLocation(); loc.isInvalid() || (onlyMain && !src.isInMainFile(loc))) return true; if(auto* ctsd = llvm::dyn_cast(decl)) { @@ -93,14 +104,16 @@ struct DocumentSymbolCollector : clang::RecursiveASTVisitorgetSourceRange()), src); - proto::DocumentSymbol symbol{ - .name = decl->isAnonymousNamespace() ? Default : decl->getNameAsString(), + DocumentSymbol symbol{ .kind = proto::SymbolKind::Namespace, - .range = range, - .selectionRange = range, + .name = decl->isAnonymousNamespace() ? Default : decl->getNameAsString(), + + /// FIXME: + // .range = range, + // .selectionRange = range, }; - entry(std::move(symbol)); + entry(std::move(symbol), decl->getBeginLoc()); bool res = Base::TraverseNamespaceDecl(decl); leave(); @@ -109,14 +122,16 @@ struct DocumentSymbolCollector : clang::RecursiveASTVisitorgetSourceRange()), src); - proto::DocumentSymbol symbol{ - .name = decl->getNameAsString(), + DocumentSymbol symbol{ .kind = proto::SymbolKind::Enum, - .range = range, - .selectionRange = range, + .name = decl->getNameAsString(), + + /// FIXME: + // .range = range, + // .selectionRange = range, }; - entry(std::move(symbol)); + entry(std::move(symbol), decl->getBeginLoc()); bool res = Base::TraverseEnumDecl(decl); leave(); @@ -126,11 +141,12 @@ struct DocumentSymbolCollector : clang::RecursiveASTVisitorenumerators()) { auto range = cvtr.toRange(toLiteralRange(etor->getSourceRange()), src); - proto::DocumentSymbol symbol{ - .name = etor->getNameAsString(), + DocumentSymbol symbol{ .kind = proto::SymbolKind::EnumMember, - .range = range, - .selectionRange = range, + .name = etor->getNameAsString(), + /// FIXME: + // .range = range, + // .selectionRange = range, }; // Show the initializer value as the detail. @@ -145,7 +161,7 @@ struct DocumentSymbolCollector : clang::RecursiveASTVisitorisDeprecated()) markDeprecated(symbol); - collect(std::move(symbol)); + collect(std::move(symbol), etor->getBeginLoc()); } return true; @@ -155,9 +171,10 @@ struct DocumentSymbolCollector : clang::RecursiveASTVisitorgetSourceRange()), src); - proto::DocumentSymbol symbol{ - .range = range, - .selectionRange = range, + DocumentSymbol symbol{ + /// FIXME: + // .range = range, + // .selectionRange = range, }; symbol.kind = decl->isAbstract() ? proto::SymbolKind::Interface : decl->isClass() ? proto::SymbolKind::Class @@ -168,26 +185,28 @@ struct DocumentSymbolCollector : clang::RecursiveASTVisitorgetBeginLoc()); bool res = Base::TraverseCXXRecordDecl(decl); leave(); return res; } bool VisitFieldDecl(const clang::FieldDecl* decl) { - auto range = cvtr.toRange(decl->getSourceRange(), src); - proto::DocumentSymbol symbol{ - .name = decl->getNameAsString(), + auto range = cvtr.toRange(toLiteralRange(decl->getSourceRange()), src); + DocumentSymbol symbol{ .kind = proto::SymbolKind::Field, - .range = range, - .selectionRange = range, + .name = decl->getNameAsString(), + + /// FIXME: + // .range = range, + // .selectionRange = range, }; symbol.detail = decl->getType().getAsString(); if(decl->isDeprecated()) markDeprecated(symbol); - collect(std::move(symbol)); + collect(std::move(symbol), decl->getBeginLoc()); return true; } @@ -220,18 +239,20 @@ struct DocumentSymbolCollector : clang::RecursiveASTVisitorgetSourceRange()), src); - proto::DocumentSymbol symbol{ - .name = decl->getNameAsString(), + DocumentSymbol symbol{ .kind = proto::SymbolKind::Function, - .range = range, - .selectionRange = range, + .name = decl->getNameAsString(), + + /// FIXME: + // .range = range, + // .selectionRange = range, }; symbol.detail = composeFuncSignature(decl); if(decl->isDeprecated()) markDeprecated(symbol); - collect(std::move(symbol)); + collect(std::move(symbol), decl->getBeginLoc()); return true; } @@ -247,26 +268,28 @@ struct DocumentSymbolCollector : clang::RecursiveASTVisitorgetSourceRange()), src); - proto::DocumentSymbol symbol{ + // auto range = cvtr.toRange(toLiteralRange(decl->getSourceRange()), src); + DocumentSymbol symbol{ + .kind = decl->isConstexpr() ? proto::SymbolKind::Constant : proto::SymbolKind::Variable, .name = decl->getNameAsString(), .detail = decl->getType().getAsString(), - .kind = decl->isConstexpr() ? proto::SymbolKind::Constant : proto::SymbolKind::Variable, - .range = range, - .selectionRange = range, + + /// FIXME: + // .range = range, + // .selectionRange = range, }; if(decl->isDeprecated()) markDeprecated(symbol); - collect(std::move(symbol)); + collect(std::move(symbol), decl->getBeginLoc()); return true; } }; } // namespace -namespace feature { +namespace feature::document_symbol { // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#serverCapabilities // ``` @@ -276,22 +299,73 @@ namespace feature { // documentSymbolProvider?: boolean | DocumentSymbolOptions; // ``` // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#documentSymbolOptions -json::Value documentSymbolCapability(json::Value clientCapabilities) { +json::Value capability(json::Value clientCapabilities) { return json::Object{ {"documentSymbolProvider", true}, }; } -proto::DocumentSymbolResult documentSymbol(ASTInfo& info, const SourceConverter& converter) { +/// Get all document symbols in each file. +index::Shared documentSymbol(ASTInfo& info, const SourceConverter& SC) { DocumentSymbolCollector collector{ .src = info.srcMgr(), - .cvtr = converter, + .cvtr = SC, + .result = DocumentSymbolCollector::Storage{}, + .onlyMain = false, }; collector.TraverseTranslationUnitDecl(info.tu()); return std::move(collector.result); } -} // namespace feature +Result documentSymbolInMainFile(ASTInfo& info, const SourceConverter& SC) { + DocumentSymbolCollector collector{ + .src = info.srcMgr(), + .cvtr = SC, + .result = DocumentSymbolCollector::Storage{}, + .onlyMain = true, + .mainID = info.srcMgr().getMainFileID(), + }; + + collector.TraverseTranslationUnitDecl(info.tu()); + return std::move(collector.result[collector.mainID]); +} + +void toLspType(proto::DocumentSymbol& lspRes, const DocumentSymbol& result, + const SourceConverter& SC) { + lspRes.name = result.name; + lspRes.detail = result.detail; + lspRes.kind = result.kind; + lspRes.tags = result.tags; + + /// FIXME: + /// lspResult.range = SC.toRange(result.range); + /// lspResult.selectionRange = SC.toRange(result.selectionRange); + + lspRes.children.reserve(result.children.size()); + for(auto& child: result.children) { + proto::DocumentSymbol lsp; + toLspType(lsp, child, SC); + lspRes.children.push_back(std::move(lsp)); + } + lspRes.children.shrink_to_fit(); +} + +proto::DocumentSymbolResult toLspResult(llvm::ArrayRef result, + llvm::StringRef content, const SourceConverter& SC) { + proto::DocumentSymbolResult lspRes; + lspRes.reserve(result.size()); + + for(auto& symbol: result) { + proto::DocumentSymbol lspSymbol; + toLspType(lspSymbol, symbol, SC); + lspRes.push_back(std::move(lspSymbol)); + } + + lspRes.shrink_to_fit(); + return lspRes; +} + +} // namespace feature::document_symbol } // namespace clice diff --git a/unittests/Feature/DocumentSymbol.cpp b/unittests/Feature/DocumentSymbol.cpp index 1a37e398..53cf736a 100644 --- a/unittests/Feature/DocumentSymbol.cpp +++ b/unittests/Feature/DocumentSymbol.cpp @@ -6,20 +6,22 @@ namespace clice::testing { namespace { -void total_size(const proto::DocumentSymbolResult& result, size_t& size) { +using namespace feature::document_symbol; + +void total_size(const Result& result, size_t& size) { for(auto& item: result) { ++size; total_size(item.children, size); } } -size_t total_size(const proto::DocumentSymbolResult& result) { +size_t total_size(const Result& result) { size_t size = 0; total_size(result, size); return size; } -const SourceConverter converter{proto::PositionEncodingKind::UTF8}; +const SourceConverter SC{proto::PositionEncodingKind::UTF8}; TEST(DocumentSymbol, Namespace) { const char* main = R"cpp( @@ -46,7 +48,7 @@ namespace _1::_2{ Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(*txs.info, converter); + auto res = documentSymbolInMainFile(*txs.info, SC); // dbg(res); ASSERT_EQ(total_size(res), 8); } @@ -75,7 +77,7 @@ int main(int argc, char* argv[]) { Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(*txs.info, converter); + auto res = documentSymbolInMainFile(*txs.info, SC); // dbg(res); ASSERT_EQ(total_size(res), 9); } @@ -100,7 +102,7 @@ struct x { Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(*txs.info, converter); + auto res = documentSymbolInMainFile(*txs.info, SC); // dbg(res); ASSERT_EQ(total_size(res), 7); } @@ -120,7 +122,7 @@ struct S { Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(*txs.info, converter); + auto res = documentSymbolInMainFile(*txs.info, SC); // dbg(res); ASSERT_EQ(total_size(res), 6); } @@ -142,7 +144,7 @@ struct _0 { Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(*txs.info, converter); + auto res = documentSymbolInMainFile(*txs.info, SC); // dbg(res); ASSERT_EQ(total_size(res), 7); } @@ -167,7 +169,7 @@ enum B { Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(*txs.info, converter); + auto res = documentSymbolInMainFile(*txs.info, SC); // dbg(res); ASSERT_EQ(total_size(res), 8); } @@ -181,7 +183,7 @@ int y = 2; Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(*txs.info, converter); + auto res = documentSymbolInMainFile(*txs.info, SC); // dbg(res); ASSERT_EQ(total_size(res), 2); } @@ -211,7 +213,7 @@ VAR(test) Tester txs("main.cpp", main); txs.run(); - auto res = feature::documentSymbol(*txs.info, converter); + auto res = documentSymbolInMainFile(*txs.info, SC); // dbg(res); // clang-format off