Skip to content

Commit 6413e5a

Browse files
authored
[clangd] Implement fold range for #pragma region (#168177)
The implementation is based on the directive tree. Fixes clangd/clangd#1623
1 parent c745a51 commit 6413e5a

File tree

2 files changed

+100
-3
lines changed

2 files changed

+100
-3
lines changed

clang-tools-extra/clangd/SemanticSelection.cpp

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@
1111
#include "Protocol.h"
1212
#include "Selection.h"
1313
#include "SourceCode.h"
14+
#include "support/Bracket.h"
15+
#include "support/DirectiveTree.h"
16+
#include "support/Token.h"
1417
#include "clang/AST/DeclBase.h"
1518
#include "clang/Basic/SourceLocation.h"
1619
#include "clang/Basic/SourceManager.h"
20+
#include "clang/Basic/TokenKinds.h"
1721
#include "clang/Tooling/Syntax/BuildTree.h"
1822
#include "clang/Tooling/Syntax/Nodes.h"
1923
#include "clang/Tooling/Syntax/TokenBufferTokenManager.h"
@@ -22,9 +26,6 @@
2226
#include "llvm/ADT/StringRef.h"
2327
#include "llvm/Support/Casting.h"
2428
#include "llvm/Support/Error.h"
25-
#include "support/Bracket.h"
26-
#include "support/DirectiveTree.h"
27-
#include "support/Token.h"
2829
#include <optional>
2930
#include <queue>
3031
#include <vector>
@@ -163,6 +164,69 @@ llvm::Expected<SelectionRange> getSemanticRanges(ParsedAST &AST, Position Pos) {
163164
return std::move(Head);
164165
}
165166

167+
class PragmaRegionFinder {
168+
// Record the token range of a region:
169+
//
170+
// #pragma region name[[
171+
// ...
172+
// ]]#pragma endregion
173+
std::vector<Token::Range> &Ranges;
174+
const TokenStream &Code;
175+
// Stack of starting token (the name of the region) indices for nested #pragma
176+
// region.
177+
std::vector<unsigned> Stack;
178+
179+
public:
180+
PragmaRegionFinder(std::vector<Token::Range> &Ranges, const TokenStream &Code)
181+
: Ranges(Ranges), Code(Code) {}
182+
183+
void walk(const DirectiveTree &T) {
184+
for (const auto &C : T.Chunks)
185+
std::visit(*this, C);
186+
}
187+
188+
void operator()(const DirectiveTree::Code &C) {}
189+
190+
void operator()(const DirectiveTree::Directive &D) {
191+
// Get the tokens that make up this directive.
192+
auto Tokens = Code.tokens(D.Tokens);
193+
if (Tokens.empty())
194+
return;
195+
const Token &HashToken = Tokens.front();
196+
assert(HashToken.Kind == tok::hash);
197+
const Token &Pragma = HashToken.nextNC();
198+
if (Pragma.text() != "pragma")
199+
return;
200+
const Token &Value = Pragma.nextNC();
201+
202+
// Handle "#pragma region name"
203+
if (Value.text() == "region") {
204+
// Find the last token at the same line.
205+
const Token *T = &Value.next();
206+
while (T < Tokens.end() && T->Line == Pragma.Line)
207+
T = &T->next();
208+
--T;
209+
Stack.push_back(T->OriginalIndex);
210+
return;
211+
}
212+
213+
// Handle "#pragma endregion"
214+
if (Value.text() == "endregion") {
215+
if (Stack.empty())
216+
return; // unmatched end region; ignore.
217+
218+
unsigned StartIdx = Stack.back();
219+
Stack.pop_back();
220+
Ranges.push_back(Token::Range{StartIdx, HashToken.OriginalIndex});
221+
}
222+
}
223+
224+
void operator()(const DirectiveTree::Conditional &C) {
225+
for (const auto &[_, SubTree] : C.Branches)
226+
walk(SubTree);
227+
}
228+
};
229+
166230
// FIXME(kirillbobyrev): Collect comments, PP conditional regions, includes and
167231
// other code regions (e.g. public/private/protected sections of classes,
168232
// control flow statement bodies).
@@ -286,6 +350,17 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
286350
}
287351
AddFoldingRange(Start, End, FoldingRange::COMMENT_KIND);
288352
}
353+
354+
// #pragma region
355+
std::vector<Token::Range> Ranges;
356+
PragmaRegionFinder(Ranges, OrigStream).walk(DirectiveStructure);
357+
auto Ts = OrigStream.tokens();
358+
for (const auto &R : Ranges) {
359+
auto End = StartPosition(Ts[R.End]);
360+
if (LineFoldingOnly)
361+
End.line--;
362+
AddFoldingRange(EndPosition(Ts[R.Begin]), End, FoldingRange::REGION_KIND);
363+
}
289364
return Result;
290365
}
291366

clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,22 @@ TEST(FoldingRanges, PseudoParserWithoutLineFoldings) {
410410
Variable = 3;
411411
#
412412
)cpp",
413+
R"cpp(
414+
#pragma region R1[[
415+
416+
#pragma region R2[[
417+
constexpr int a = 2;
418+
]]#pragma endregion
419+
420+
]]#pragma endregion
421+
)cpp",
422+
R"cpp(
423+
#pragma region[[
424+
]]#pragma endregion
425+
426+
#pragma /*comment1*/ region /*comment2*/name[[
427+
]]#pragma endregion
428+
)cpp",
413429
};
414430
for (const char *Test : Tests) {
415431
auto T = Annotations(Test);
@@ -470,6 +486,12 @@ TEST(FoldingRanges, PseudoParserLineFoldingsOnly) {
470486
//[[ foo
471487
/* bar */]]
472488
)cpp",
489+
R"cpp(
490+
#pragma region abc[[
491+
constexpr int a = 2;
492+
]]
493+
#pragma endregion
494+
)cpp",
473495
// FIXME: Support folding template arguments.
474496
// R"cpp(
475497
// template <[[typename foo, class bar]]> struct baz {};

0 commit comments

Comments
 (0)