[clangd] Start using SyntaxTrees for folding ranges feature

This is an initial attempt to start using Syntax Trees in clangd while improving state of folding ranges feature and experimenting with Syntax Tree capabilities.

Reviewed By: sammccall

Differential Revision: https://reviews.llvm.org/D88553
This commit is contained in:
Kirill Bobyrev 2020-10-27 16:44:52 +01:00
parent 5984097823
commit 5ad6bbacf0
No known key found for this signature in database
GPG Key ID: 2307C055C8384FA0
4 changed files with 141 additions and 53 deletions

View File

@ -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 <queue>
#include <vector>
namespace clang {
namespace clangd {
@ -28,17 +37,65 @@ void addIfDistinct(const Range &R, std::vector<Range> &Result) {
}
}
// Recursively collects FoldingRange from a symbol and its children.
void collectFoldingRanges(DocumentSymbol Symbol,
std::vector<FoldingRange> &Result) {
llvm::Optional<FoldingRange> toFoldingRange(SourceRange SR,
const SourceManager &SM) {
const auto Begin = SM.getDecomposedLoc(SR.getBegin()),
End = SM.getDecomposedLoc(SR.getEnd());
// Do not produce folding ranges if either range ends is not within the main
// file. Macros have their own FileID so this also checks if locations are not
// within the macros.
if ((Begin.first != SM.getMainFileID()) || (End.first != SM.getMainFileID()))
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.getColumnNumber(Begin.first, Begin.second) - 1;
Range.startLine = SM.getLineNumber(Begin.first, Begin.second) - 1;
Range.endCharacter = SM.getColumnNumber(End.first, End.second) - 1;
Range.endLine = SM.getLineNumber(End.first, End.second) - 1;
return Range;
}
llvm::Optional<FoldingRange> extractFoldingRange(const syntax::Node *Node,
const SourceManager &SM) {
if (const auto *Stmt = dyn_cast<syntax::CompoundStatement>(Node)) {
const auto *LBrace = cast_or_null<syntax::Leaf>(
Stmt->findChild(syntax::NodeRole::OpenParen));
// FIXME(kirillbobyrev): This should find the last child. Compound
// statements have only one pair of braces so this is valid but for other
// node kinds it might not be correct.
const auto *RBrace = cast_or_null<syntax::Leaf>(
Stmt->findChild(syntax::NodeRole::CloseParen));
if (!LBrace || !RBrace)
return llvm::None;
// Fold the entire range within braces, including whitespace.
const SourceLocation LBraceLocInfo = LBrace->getToken()->endLocation(),
RBraceLocInfo = RBrace->getToken()->location();
auto Range = toFoldingRange(SourceRange(LBraceLocInfo, RBraceLocInfo), 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<FoldingRange> collectFoldingRanges(const syntax::Node *Root,
const SourceManager &SM) {
std::queue<const syntax::Node *> Nodes;
Nodes.push(Root);
std::vector<FoldingRange> 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<syntax::Tree>(Node))
for (const auto *NextNode = T->getFirstChild(); NextNode;
NextNode = NextNode->getNextSibling())
Nodes.push(NextNode);
}
return Result;
}
} // namespace
@ -100,20 +157,12 @@ llvm::Expected<SelectionRange> getSemanticRanges(ParsedAST &AST, Position Pos) {
// 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<std::vector<FoldingRange>> 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<FoldingRange> 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

View File

@ -203,26 +203,61 @@ TEST(SemanticSelection, RunViaClangdServer) {
TEST(FoldingRanges, All) {
const char *Tests[] = {
R"cpp(
[[int global_variable]];
#define FOO int foo() {\
int Variable = 42; \
}
[[void func() {
int v = 100;
}]]
// 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 {
void func() {[[
int Variable = 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 {
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) {

View File

@ -168,20 +168,24 @@ public:
Node *getFirstChild() { return FirstChild; }
const Node *getFirstChild() const { return FirstChild; }
Leaf *findFirstLeaf();
const Leaf *findFirstLeaf() const {
return const_cast<Tree *>(this)->findFirstLeaf();
const Leaf *findFirstLeaf() const;
Leaf *findFirstLeaf() {
return const_cast<Leaf *>(const_cast<const Tree *>(this)->findFirstLeaf());
}
Leaf *findLastLeaf();
const Leaf *findLastLeaf() const {
return const_cast<Tree *>(this)->findLastLeaf();
const Leaf *findLastLeaf() const;
Leaf *findLastLeaf() {
return const_cast<Leaf *>(const_cast<const Tree *>(this)->findLastLeaf());
}
/// Find the first node with a corresponding role.
const Node *findChild(NodeRole R) const;
Node *findChild(NodeRole R) {
return const_cast<Node *>(const_cast<const Tree *>(this)->findChild(R));
}
protected:
using Node::Node;
/// Find the first node with a corresponding role.
Node *findChild(NodeRole R);
private:
/// Prepend \p Child to the list of children and and sets the parent pointer.

View File

@ -271,29 +271,29 @@ void syntax::Node::assertInvariantsRecursive() const {
#endif
}
syntax::Leaf *syntax::Tree::findFirstLeaf() {
for (auto *C = getFirstChild(); C; C = C->getNextSibling()) {
if (auto *L = dyn_cast<syntax::Leaf>(C))
const syntax::Leaf *syntax::Tree::findFirstLeaf() const {
for (const auto *C = getFirstChild(); C; C = C->getNextSibling()) {
if (const auto *L = dyn_cast<syntax::Leaf>(C))
return L;
if (auto *L = cast<syntax::Tree>(C)->findFirstLeaf())
if (const auto *L = cast<syntax::Tree>(C)->findFirstLeaf())
return L;
}
return nullptr;
}
syntax::Leaf *syntax::Tree::findLastLeaf() {
syntax::Leaf *Last = nullptr;
for (auto *C = getFirstChild(); C; C = C->getNextSibling()) {
if (auto *L = dyn_cast<syntax::Leaf>(C))
const syntax::Leaf *syntax::Tree::findLastLeaf() const {
const syntax::Leaf *Last = nullptr;
for (const auto *C = getFirstChild(); C; C = C->getNextSibling()) {
if (const auto *L = dyn_cast<syntax::Leaf>(C))
Last = L;
else if (auto *L = cast<syntax::Tree>(C)->findLastLeaf())
else if (const auto *L = cast<syntax::Tree>(C)->findLastLeaf())
Last = L;
}
return Last;
}
syntax::Node *syntax::Tree::findChild(NodeRole R) {
for (auto *C = FirstChild; C; C = C->getNextSibling()) {
const syntax::Node *syntax::Tree::findChild(NodeRole R) const {
for (const auto *C = FirstChild; C; C = C->getNextSibling()) {
if (C->getRole() == R)
return C;
}