From 6b54e09c96ff3c6ec2cd49ea2c4936cf0469c211 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Fri, 14 Nov 2025 15:48:21 +0100 Subject: [PATCH 1/2] Implement fold range for #pragma region --- .../clangd/SemanticSelection.cpp | 78 ++++++++++++++++++- .../unittests/SemanticSelectionTests.cpp | 15 ++++ 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp index 3353121a01825..a6a385cd0571d 100644 --- a/clang-tools-extra/clangd/SemanticSelection.cpp +++ b/clang-tools-extra/clangd/SemanticSelection.cpp @@ -11,9 +11,13 @@ #include "Protocol.h" #include "Selection.h" #include "SourceCode.h" +#include "support/Bracket.h" +#include "support/DirectiveTree.h" +#include "support/Token.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/TokenBufferTokenManager.h" @@ -22,9 +26,6 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Error.h" -#include "support/Bracket.h" -#include "support/DirectiveTree.h" -#include "support/Token.h" #include #include #include @@ -163,6 +164,66 @@ llvm::Expected getSemanticRanges(ParsedAST &AST, Position Pos) { return std::move(Head); } +class PragmaRegionFinder { + // Record the token range of a region: + // + // #pragma region [[name + // ... + // ]]#pragma region + std::vector &Ranges; + const TokenStream &Code; + // Stack of starting token (the name of the region) indices for nested #pragma + // region. + std::vector Stack; + +public: + PragmaRegionFinder(std::vector &Ranges, const TokenStream &Code) + : Ranges(Ranges), Code(Code) {} + + void walk(const DirectiveTree &T) { + for (const auto &C : T.Chunks) + std::visit(*this, C); + } + + void operator()(const DirectiveTree::Code &C) {} + + void operator()(const DirectiveTree::Directive &D) { + // Get the tokens that make up this directive. + auto Tokens = Code.tokens(D.Tokens); + if (Tokens.empty()) + return; + const Token &HashToken = Tokens.front(); + assert(HashToken.Kind == tok::hash); + const Token &Pragma = HashToken.nextNC(); + if (Pragma.text() != "pragma") + return; + const Token &Value = Pragma.nextNC(); + + // Handle "#pragma region name" + if (Value.text() == "region") { + // Record the name token. + if (&Value < Tokens.end()) + Stack.push_back((&Value + 1)->OriginalIndex); + return; + } + + // Handle "#pragma endregion" + if (Value.text() == "endregion") { + if (Stack.empty()) + return; // unmatched end region; ignore. + + unsigned StartIdx = Stack.back(); + Stack.pop_back(); + Ranges.push_back(Token::Range{StartIdx, HashToken.OriginalIndex}); + } + } + + void operator()(const DirectiveTree::Conditional &C) { + for (const auto &[_, SubTree] : C.Branches) + walk(SubTree); + } +}; + // FIXME(kirillbobyrev): Collect comments, PP conditional regions, includes and // other code regions (e.g. public/private/protected sections of classes, // control flow statement bodies). @@ -286,6 +347,17 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) { } AddFoldingRange(Start, End, FoldingRange::COMMENT_KIND); } + + // #pragma region + std::vector Ranges; + PragmaRegionFinder(Ranges, OrigStream).walk(DirectiveStructure); + auto Ts = OrigStream.tokens(); + for (const auto &R : Ranges) { + auto End = StartPosition(Ts[R.End]); + if (LineFoldingOnly) + End.line--; + AddFoldingRange(EndPosition(Ts[R.Begin]), End, FoldingRange::REGION_KIND); + } return Result; } diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp index 4efae25dcd077..08cc8a651adc8 100644 --- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp @@ -410,6 +410,15 @@ TEST(FoldingRanges, PseudoParserWithoutLineFoldings) { Variable = 3; # )cpp", + R"cpp( + #pragma region R1[[ + + #pragma region R2[[ + constexpr int a = 2; + ]]#pragma endregion + + ]]#pragma endregion + )cpp", }; for (const char *Test : Tests) { auto T = Annotations(Test); @@ -470,6 +479,12 @@ TEST(FoldingRanges, PseudoParserLineFoldingsOnly) { //[[ foo /* bar */]] )cpp", + R"cpp( + #pragma region abc[[ + constexpr int a = 2; + ]] + #pragma endregion + )cpp", // FIXME: Support folding template arguments. // R"cpp( // template <[[typename foo, class bar]]> struct baz {}; From ece8c07c52a6e740b794dad69356bd88ce2c1695 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Sun, 23 Nov 2025 21:26:30 +0100 Subject: [PATCH 2/2] Address comments, and improve the `#pragma region` handling. --- clang-tools-extra/clangd/SemanticSelection.cpp | 13 ++++++++----- .../clangd/unittests/SemanticSelectionTests.cpp | 7 +++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp index a6a385cd0571d..c2dad53bcec6b 100644 --- a/clang-tools-extra/clangd/SemanticSelection.cpp +++ b/clang-tools-extra/clangd/SemanticSelection.cpp @@ -167,9 +167,9 @@ llvm::Expected getSemanticRanges(ParsedAST &AST, Position Pos) { class PragmaRegionFinder { // Record the token range of a region: // - // #pragma region [[name + // #pragma region name[[ // ... - // ]]#pragma region + // ]]#pragma endregion std::vector &Ranges; const TokenStream &Code; // Stack of starting token (the name of the region) indices for nested #pragma @@ -201,9 +201,12 @@ class PragmaRegionFinder { // Handle "#pragma region name" if (Value.text() == "region") { - // Record the name token. - if (&Value < Tokens.end()) - Stack.push_back((&Value + 1)->OriginalIndex); + // Find the last token at the same line. + const Token *T = &Value.next(); + while (T < Tokens.end() && T->Line == Pragma.Line) + T = &T->next(); + --T; + Stack.push_back(T->OriginalIndex); return; } diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp index 08cc8a651adc8..2a381d1c8add5 100644 --- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp @@ -419,6 +419,13 @@ TEST(FoldingRanges, PseudoParserWithoutLineFoldings) { ]]#pragma endregion )cpp", + R"cpp( + #pragma region[[ + ]]#pragma endregion + + #pragma /*comment1*/ region /*comment2*/name[[ + ]]#pragma endregion + )cpp", }; for (const char *Test : Tests) { auto T = Annotations(Test);