diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp --- a/clang-tools-extra/clangd/SemanticSelection.cpp +++ b/clang-tools-extra/clangd/SemanticSelection.cpp @@ -13,8 +13,14 @@ #include "SourceCode.h" #include "clang/AST/DeclBase.h" #include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TokenKinds.h" +#include "clang/Tooling/Syntax/BuildTree.h" +#include "clang/Tooling/Syntax/Tree.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" +#include namespace clang { namespace clangd { @@ -28,17 +34,39 @@ } } -// Recursively collects FoldingRange from a symbol and its children. -void collectFoldingRanges(DocumentSymbol Symbol, - std::vector &Result) { +FoldingRange constructFoldingRange(SourceRange SR, const SourceManager &SM) { FoldingRange Range; - Range.startLine = Symbol.range.start.line; - Range.startCharacter = Symbol.range.start.character; - Range.endLine = Symbol.range.end.line; - Range.endCharacter = Symbol.range.end.character; - Result.push_back(Range); - for (const auto &Child : Symbol.children) - collectFoldingRanges(Child, Result); + Range.startCharacter = SM.getSpellingColumnNumber(SR.getBegin()) - 1; + Range.startLine = SM.getSpellingLineNumber(SR.getBegin()) - 1; + Range.endCharacter = SM.getSpellingColumnNumber(SR.getEnd()) - 1; + Range.endLine = SM.getSpellingLineNumber(SR.getEnd()) - 1; + return Range; +} + +// Traverse the tree and collect folding ranges along the way. +void collectRanges(const syntax::Node *Node, const SourceManager &SM, + std::vector &Ranges) { + if (Node->getKind() == syntax::NodeKind::CompoundStatement) { + const auto *Tree = dyn_cast(Node); + assert(Tree); + const syntax::Token *FirstToken = Tree->findFirstLeaf()->getToken(), + *LastToken = Tree->findLastLeaf()->getToken(); + assert(FirstToken->kind() == tok::TokenKind::l_brace); + assert(LastToken->kind() == tok::TokenKind::r_brace); + const SourceRange SR(FirstToken->endLocation(), LastToken->location()); + FoldingRange Range = constructFoldingRange(SR, SM); + // Do not generate folding range for compound statements without any + // nodes and newlines. + if (Tree->findFirstLeaf()->getNextSibling() != Tree->findLastLeaf() || + Range.startLine != Range.endLine) + Ranges.push_back(Range); + } + if (auto *T = dyn_cast(Node)) { + for (const auto *NextNode = T->getFirstChild(); NextNode; + NextNode = NextNode->getNextSibling()) { + collectRanges(NextNode, SM, Ranges); + } + } } } // namespace @@ -100,19 +128,13 @@ // FIXME(kirillbobyrev): Collect comments, PP conditional regions, includes and // other code regions (e.g. public/private/protected sections of classes, // control flow statement bodies). -// Related issue: -// https://github.com/clangd/clangd/issues/310 +// Related issue: https://github.com/clangd/clangd/issues/310 llvm::Expected> getFoldingRanges(ParsedAST &AST) { - // FIXME(kirillbobyrev): getDocumentSymbols() is conveniently available but - // limited (e.g. doesn't yield blocks inside functions and provides ranges for - // nodes themselves instead of their contents which is less useful). Replace - // this with a more general RecursiveASTVisitor implementation instead. - auto DocumentSymbols = getDocumentSymbols(AST); - if (!DocumentSymbols) - return DocumentSymbols.takeError(); + syntax::Arena A(AST.getSourceManager(), AST.getLangOpts(), AST.getTokens()); + const auto *SyntaxTree = + syntax::buildSyntaxTree(A, *AST.getASTContext().getTranslationUnitDecl()); std::vector Result; - for (const auto &Symbol : *DocumentSymbols) - collectFoldingRanges(Symbol, Result); + collectRanges(SyntaxTree, AST.getSourceManager(), Result); return Result; } diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp --- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp @@ -203,26 +203,91 @@ TEST(FoldingRanges, All) { const char *Tests[] = { R"cpp( - [[int global_variable]]; + void func() {[[ + int Variable = 100; - [[void func() { - int v = 100; - }]] + if (Variable > 42) {[[ + Variable = 10; + ++Variable; + ]]} + + + if (Variable >= 9000) {[[ + Variable -= 500; + ]]} else {[[ + Variable = 42; + ]]} + + if (42 > 5) {[[ + Variable += 42; + ]]} else if (Variable++) {[[ + ++Variable; + ]]} else {[[ + Variable--; + ]]} + + bool OK = true; + + if (OK) {[[ + Variable++; + ]]} else + --Variable; + + {[[ + bool CompoundStmt = true; + ]]} + ]]} + )cpp", + R"cpp( + void foo() {[[ + // Do not generate FoldingRange for empty CompoundStmts. + for (;;) {} + + // However, if there are newlines between {}, we will still generate + // one. + for (;;) {[[ + + ]]} + + unsigned Variable = 42; + + for (int i = 0; i < 42; ++i) {[[ + Variable += 10; + ]]} + + while (Variable) {[[ + Variable--; + ]]} + + do {[[ + + ]]} while (Variable); + + switch (Variable) {[[ + case 1: + ++Variable; + break; + case 2: + break; + default: + break; + ]]} + ]]} )cpp", R"cpp( - [[class Foo { + class Foo { public: - [[Foo() { + Foo() {[[ int X = 1; - }]] + ]]} private: - [[int getBar() { + int getBar() {[[ return 42; - }]] + ]]} - [[void getFooBar() { }]] - }]]; + void getFooBar() { } + }; )cpp", }; for (const char *Test : Tests) {