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 @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// + #include "SemanticSelection.h" #include "FindSymbols.h" #include "ParsedAST.h" @@ -13,8 +14,16 @@ #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/Nodes.h" +#include "clang/Tooling/Syntax/Tree.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" +#include +#include namespace clang { namespace clangd { @@ -28,17 +37,54 @@ } } -// Recursively collects FoldingRange from a symbol and its children. -void collectFoldingRanges(DocumentSymbol Symbol, - std::vector &Result) { +llvm::Optional toFoldingRange(SourceRange SR, + const SourceManager &SM) { + // Don't produce folding ranges when SourceRange either of bounds is coming + // from macros. + if (SR.getBegin().isMacroID() || SR.getEnd().isMacroID()) + return llvm::None; 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; +} + +llvm::Optional extractFoldingRange(const syntax::Node *Node, + const SourceManager &SM) { + if (const auto *Stmt = dyn_cast(Node)) { + const syntax::Token *LBrace = Stmt->getLbrace()->getToken(), + *RBrace = Stmt->getRbrace()->getToken(); + // Fold the entire range within braces, including whitespace. + const SourceRange SR(LBrace->endLocation(), RBrace->location()); + auto Range = toFoldingRange(SR, SM); + // Do not generate folding range for compound statements without any + // nodes and newlines. + if (Range && Range->startLine != Range->endLine) + return Range; + } + return llvm::None; +} + +// Traverse the tree and collect folding ranges along the way. +std::vector collectFoldingRanges(const syntax::Node *Root, + const SourceManager &SM) { + std::queue Nodes; + Nodes.push(Root); + std::vector Result; + while (!Nodes.empty()) { + const syntax::Node *Node = Nodes.front(); + Nodes.pop(); + const auto Range = extractFoldingRange(Node, SM); + if (Range) + Result.push_back(*Range); + if (const auto *T = dyn_cast(Node)) + for (const auto *NextNode = T->getFirstChild(); NextNode; + NextNode = NextNode->getNextSibling()) + Nodes.push(NextNode); + } + return Result; } } // namespace @@ -100,20 +146,12 @@ // 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(); - std::vector Result; - for (const auto &Symbol : *DocumentSymbols) - collectFoldingRanges(Symbol, Result); - return Result; + syntax::Arena A(AST.getSourceManager(), AST.getLangOpts(), AST.getTokens()); + const auto *SyntaxTree = + syntax::buildSyntaxTree(A, *AST.getASTContext().getTranslationUnitDecl()); + return collectFoldingRanges(SyntaxTree, AST.getSourceManager()); } } // namespace clangd 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,59 @@ TEST(FoldingRanges, All) { const char *Tests[] = { R"cpp( - [[int global_variable]]; + void func() {[[ + int Variable = 100; - [[void func() { - int v = 100; - }]] + if (Variable > 5) {[[ + Variable += 42; + ]]} else if (Variable++) + ++Variable; + else {[[ + Variable--; + ]]} + + // Do not generate FoldingRange for empty CompoundStmts. + for (;;) {} + + // If there are newlines between {}, we should generate one. + for (;;) {[[ + + ]]} + ]]} )cpp", R"cpp( - [[class Foo { + #define FOO int foo() {} + + // Do not generate folding range for braces within macro expansion. + FOO + + // Do not generate folding range within macro arguments. + #define FUNCTOR(functor) functor + void func() {[[ + FUNCTOR([](){}); + ]]} + + // Do not generate folding range with a brace coming from macro. + #define LBRACE { + void bar() LBRACE + int X = 42; + } + )cpp", + R"cpp( + class Foo { public: - [[Foo() { + Foo() {[[ int X = 1; - }]] + ]]} private: - [[int getBar() { + int getBar() {[[ return 42; - }]] + ]]} - [[void getFooBar() { }]] - }]]; + // Braces are located at the same line: no folding range here. + void getFooBar() { } + }; )cpp", }; for (const char *Test : Tests) { diff --git a/clang/include/clang/Tooling/Syntax/Nodes.h b/clang/include/clang/Tooling/Syntax/Nodes.h --- a/clang/include/clang/Tooling/Syntax/Nodes.h +++ b/clang/include/clang/Tooling/Syntax/Nodes.h @@ -768,10 +768,10 @@ static bool classof(const Node *N) { return N->getKind() == NodeKind::CompoundStatement; } - Leaf *getLbrace(); + Leaf *getLbrace() const; /// FIXME: use custom iterator instead of 'vector'. std::vector getStatements(); - Leaf *getRbrace(); + Leaf *getRbrace() const; }; /// A declaration that can appear at the top-level. Note that this does *not* diff --git a/clang/include/clang/Tooling/Syntax/Tree.h b/clang/include/clang/Tooling/Syntax/Tree.h --- a/clang/include/clang/Tooling/Syntax/Tree.h +++ b/clang/include/clang/Tooling/Syntax/Tree.h @@ -171,7 +171,7 @@ protected: /// Find the first node with a corresponding role. - Node *findChild(NodeRole R); + Node *findChild(NodeRole R) const; private: /// Prepend \p Child to the list of children and and sets the parent pointer. diff --git a/clang/lib/Tooling/Syntax/Nodes.cpp b/clang/lib/Tooling/Syntax/Nodes.cpp --- a/clang/lib/Tooling/Syntax/Nodes.cpp +++ b/clang/lib/Tooling/Syntax/Nodes.cpp @@ -522,7 +522,7 @@ findChild(syntax::NodeRole::Expression)); } -syntax::Leaf *syntax::CompoundStatement::getLbrace() { +syntax::Leaf *syntax::CompoundStatement::getLbrace() const { return cast_or_null(findChild(syntax::NodeRole::OpenParen)); } @@ -535,7 +535,7 @@ return Children; } -syntax::Leaf *syntax::CompoundStatement::getRbrace() { +syntax::Leaf *syntax::CompoundStatement::getRbrace() const { return cast_or_null(findChild(syntax::NodeRole::CloseParen)); } diff --git a/clang/lib/Tooling/Syntax/Tree.cpp b/clang/lib/Tooling/Syntax/Tree.cpp --- a/clang/lib/Tooling/Syntax/Tree.cpp +++ b/clang/lib/Tooling/Syntax/Tree.cpp @@ -292,7 +292,7 @@ return Last; } -syntax::Node *syntax::Tree::findChild(NodeRole R) { +syntax::Node *syntax::Tree::findChild(NodeRole R) const { for (auto *C = FirstChild; C; C = C->getNextSibling()) { if (C->getRole() == R) return C;