Skip to content

[clangd] Implement simple folding of preprocessor branches (rebased) #121449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

sr-tream
Copy link
Contributor

@sr-tream sr-tream commented Jan 2, 2025

It's a rebased PR #80592 without pseudo-parser usage.

Extract directive branches information from DirectiveTree, fold branches that don't end with eof.

Fixes clangd/clangd#1661

@llvmbot
Copy link
Member

llvmbot commented Jan 2, 2025

@llvm/pr-subscribers-clang-tools-extra

Author: SR_team (sr-tream)

Changes

It's a rebased PR #80592 without pseudo-parser usage.

Extract directive branches information from DirectiveTree, fold branches that don't end with eof.

Fixes clangd/clangd#1661


Full diff: https://github.com/llvm/llvm-project/pull/121449.diff

3 Files Affected:

  • (modified) clang-tools-extra/clangd/SemanticSelection.cpp (+18)
  • (modified) clang-tools-extra/clangd/support/DirectiveTree.cpp (+54)
  • (modified) clang-tools-extra/clangd/support/DirectiveTree.h (+3)
diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp
index dd7116e619e6d0..57e7ff8b97c658 100644
--- a/clang-tools-extra/clangd/SemanticSelection.cpp
+++ b/clang-tools-extra/clangd/SemanticSelection.cpp
@@ -220,6 +220,24 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
   auto EndPosition = [&](const Token &T) {
     return offsetToPosition(Code, EndOffset(T));
   };
+
+  // Preprocessor directives
+  auto PPRanges = pairDirectiveRanges(DirectiveStructure, OrigStream);
+  for (const auto &R : PPRanges) {
+    auto BTok = OrigStream.tokens()[R.Begin];
+    auto ETok = OrigStream.tokens()[R.End];
+    if (ETok.Kind == tok::eof)
+      continue;
+    if (BTok.Line >= ETok.Line)
+      continue;
+
+    Position Start = EndPosition(BTok);
+    Position End = StartPosition(ETok);
+    if (LineFoldingOnly)
+      End.line--;
+    AddFoldingRange(Start, End, FoldingRange::REGION_KIND);
+  }
+
   auto Tokens = ParseableStream.tokens();
   // Brackets.
   for (const auto &Tok : Tokens) {
diff --git a/clang-tools-extra/clangd/support/DirectiveTree.cpp b/clang-tools-extra/clangd/support/DirectiveTree.cpp
index 7ea08add7a107e..2a4448902df539 100644
--- a/clang-tools-extra/clangd/support/DirectiveTree.cpp
+++ b/clang-tools-extra/clangd/support/DirectiveTree.cpp
@@ -356,5 +356,59 @@ TokenStream DirectiveTree::stripDirectives(const TokenStream &In) const {
   return Out;
 }
 
+namespace {
+class RangePairer {
+  std::vector<Token::Range> &Ranges;
+
+public:
+  RangePairer(std::vector<Token::Range> &Ranges) : Ranges(Ranges) {}
+
+  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 &) {}
+
+  void operator()(const DirectiveTree::Conditional &C) {
+    Token::Range Range;
+    Token::Index Last;
+    auto First = true;
+    for (const auto &B : C.Branches) {
+      if (First) {
+        First = false;
+      } else {
+        Range = {Last, B.first.Tokens.Begin};
+        Ranges.push_back(Range);
+      }
+      Last = B.first.Tokens.Begin;
+    }
+    Range = {Last, C.End.Tokens.Begin};
+    Ranges.push_back(Range);
+
+    for (const auto &B : C.Branches)
+      walk(B.second);
+  }
+};
+} // namespace
+
+std::vector<Token::Range> pairDirectiveRanges(const DirectiveTree &Tree,
+                                              const TokenStream &Code) {
+  std::vector<Token::Range> Ranges;
+  RangePairer(Ranges).walk(Tree);
+
+  // Transform paired ranges to start with last token in its logical line
+  for (auto &R : Ranges) {
+    const Token *Tok = &Code.tokens()[R.Begin + 1];
+    while (Tok->Kind != tok::eof && !Tok->flag(LexFlags::StartsPPLine))
+      ++Tok;
+    Tok = Tok - 1;
+    R.Begin = Tok->OriginalIndex;
+  }
+  return std::move(Ranges);
+}
+
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/support/DirectiveTree.h b/clang-tools-extra/clangd/support/DirectiveTree.h
index 34f5a888863f26..1a377f5e0640ca 100644
--- a/clang-tools-extra/clangd/support/DirectiveTree.h
+++ b/clang-tools-extra/clangd/support/DirectiveTree.h
@@ -124,6 +124,9 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &,
 /// The choices are stored in Conditional::Taken nodes.
 void chooseConditionalBranches(DirectiveTree &, const TokenStream &Code);
 
+std::vector<Token::Range> pairDirectiveRanges(const DirectiveTree &Tree,
+                                              const TokenStream &Code);
+
 } // namespace clangd
 } // namespace clang
 

@llvmbot
Copy link
Member

llvmbot commented Jan 2, 2025

@llvm/pr-subscribers-clangd

Author: SR_team (sr-tream)

Changes

It's a rebased PR #80592 without pseudo-parser usage.

Extract directive branches information from DirectiveTree, fold branches that don't end with eof.

Fixes clangd/clangd#1661


Full diff: https://github.com/llvm/llvm-project/pull/121449.diff

3 Files Affected:

  • (modified) clang-tools-extra/clangd/SemanticSelection.cpp (+18)
  • (modified) clang-tools-extra/clangd/support/DirectiveTree.cpp (+54)
  • (modified) clang-tools-extra/clangd/support/DirectiveTree.h (+3)
diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp
index dd7116e619e6d0..57e7ff8b97c658 100644
--- a/clang-tools-extra/clangd/SemanticSelection.cpp
+++ b/clang-tools-extra/clangd/SemanticSelection.cpp
@@ -220,6 +220,24 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
   auto EndPosition = [&](const Token &T) {
     return offsetToPosition(Code, EndOffset(T));
   };
+
+  // Preprocessor directives
+  auto PPRanges = pairDirectiveRanges(DirectiveStructure, OrigStream);
+  for (const auto &R : PPRanges) {
+    auto BTok = OrigStream.tokens()[R.Begin];
+    auto ETok = OrigStream.tokens()[R.End];
+    if (ETok.Kind == tok::eof)
+      continue;
+    if (BTok.Line >= ETok.Line)
+      continue;
+
+    Position Start = EndPosition(BTok);
+    Position End = StartPosition(ETok);
+    if (LineFoldingOnly)
+      End.line--;
+    AddFoldingRange(Start, End, FoldingRange::REGION_KIND);
+  }
+
   auto Tokens = ParseableStream.tokens();
   // Brackets.
   for (const auto &Tok : Tokens) {
diff --git a/clang-tools-extra/clangd/support/DirectiveTree.cpp b/clang-tools-extra/clangd/support/DirectiveTree.cpp
index 7ea08add7a107e..2a4448902df539 100644
--- a/clang-tools-extra/clangd/support/DirectiveTree.cpp
+++ b/clang-tools-extra/clangd/support/DirectiveTree.cpp
@@ -356,5 +356,59 @@ TokenStream DirectiveTree::stripDirectives(const TokenStream &In) const {
   return Out;
 }
 
+namespace {
+class RangePairer {
+  std::vector<Token::Range> &Ranges;
+
+public:
+  RangePairer(std::vector<Token::Range> &Ranges) : Ranges(Ranges) {}
+
+  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 &) {}
+
+  void operator()(const DirectiveTree::Conditional &C) {
+    Token::Range Range;
+    Token::Index Last;
+    auto First = true;
+    for (const auto &B : C.Branches) {
+      if (First) {
+        First = false;
+      } else {
+        Range = {Last, B.first.Tokens.Begin};
+        Ranges.push_back(Range);
+      }
+      Last = B.first.Tokens.Begin;
+    }
+    Range = {Last, C.End.Tokens.Begin};
+    Ranges.push_back(Range);
+
+    for (const auto &B : C.Branches)
+      walk(B.second);
+  }
+};
+} // namespace
+
+std::vector<Token::Range> pairDirectiveRanges(const DirectiveTree &Tree,
+                                              const TokenStream &Code) {
+  std::vector<Token::Range> Ranges;
+  RangePairer(Ranges).walk(Tree);
+
+  // Transform paired ranges to start with last token in its logical line
+  for (auto &R : Ranges) {
+    const Token *Tok = &Code.tokens()[R.Begin + 1];
+    while (Tok->Kind != tok::eof && !Tok->flag(LexFlags::StartsPPLine))
+      ++Tok;
+    Tok = Tok - 1;
+    R.Begin = Tok->OriginalIndex;
+  }
+  return std::move(Ranges);
+}
+
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/support/DirectiveTree.h b/clang-tools-extra/clangd/support/DirectiveTree.h
index 34f5a888863f26..1a377f5e0640ca 100644
--- a/clang-tools-extra/clangd/support/DirectiveTree.h
+++ b/clang-tools-extra/clangd/support/DirectiveTree.h
@@ -124,6 +124,9 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &,
 /// The choices are stored in Conditional::Taken nodes.
 void chooseConditionalBranches(DirectiveTree &, const TokenStream &Code);
 
+std::vector<Token::Range> pairDirectiveRanges(const DirectiveTree &Tree,
+                                              const TokenStream &Code);
+
 } // namespace clangd
 } // namespace clang
 

Copy link
Collaborator

@HighCommander4 HighCommander4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is my first time looking at this code (SemanticSelection.cpp / DirectiveTree.cpp) so I'm not sure if I'm the best reviewer, but on a high level what this patch is doing seems reasonable to me.

Some unit tests for the change would be great. We have some existing unit tests for folding ranges in SemanticSelectionTests.cpp which you can extend.

Extract directive branches information from DirectiveTree, fold branches
that don't end with eof.

Fixes clangd/clangd#1661
@bricle
Copy link

bricle commented Apr 6, 2025

hope you guys are doing well, really need this feature, thank you for your contribution!

luckzylp added a commit to luckzylp/llvm-project that referenced this pull request May 30, 2025
Merge the PR: llvm#121449 by sr-tream and PR: llvm#140959 by aketchum15.
luckzylp added a commit to luckzylp/llvm-project that referenced this pull request May 30, 2025
1. Merge the PR: #llvm#121449 by sr-tream and PR: #llvm#140959 by aketchum15.
2. Fix the issue of `ItaniumMangle.cpp` on the linux platform under clang-9
```c
TypeNameOS << Operand.getKind();
` ` `
The problem of compilation failure caused by the inability to find appropriate operator overloading operations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Precompiled macro #ifndef/#if/#elif/#else/#endif areas do not code fold
5 participants