dgoldman updated this revision to Diff 364263.
dgoldman added a comment.

the third time is the charm


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D105904/new/

https://reviews.llvm.org/D105904

Files:
  clang-tools-extra/clangd/CollectMacros.cpp
  clang-tools-extra/clangd/CollectMacros.h
  clang-tools-extra/clangd/FindSymbols.cpp
  clang-tools-extra/clangd/FindTarget.cpp
  clang-tools-extra/clangd/ParsedAST.cpp
  clang-tools-extra/clangd/ParsedAST.h
  clang-tools-extra/clangd/Preamble.cpp
  clang-tools-extra/clangd/Preamble.h
  clang-tools-extra/clangd/SourceCode.cpp
  clang-tools-extra/clangd/SourceCode.h
  clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
  clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
  clang/include/clang/Lex/PPCallbacks.h

Index: clang/include/clang/Lex/PPCallbacks.h
===================================================================
--- clang/include/clang/Lex/PPCallbacks.h
+++ clang/include/clang/Lex/PPCallbacks.h
@@ -492,6 +492,11 @@
     Second->PragmaComment(Loc, Kind, Str);
   }
 
+  void PragmaMark(SourceLocation Loc, StringRef Trivia) override {
+    First->PragmaMark(Loc, Trivia);
+    Second->PragmaMark(Loc, Trivia);
+  }
+
   void PragmaDetectMismatch(SourceLocation Loc, StringRef Name,
                             StringRef Value) override {
     First->PragmaDetectMismatch(Loc, Name, Value);
Index: clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
+++ clang-tools-extra/clangd/unittests/ParsedASTTests.cpp
@@ -99,6 +99,8 @@
   return arg.beginOffset() == R.Begin && arg.endOffset() == R.End;
 }
 
+MATCHER_P(PragmaTrivia, P, "") { return arg.Trivia == P; }
+
 MATCHER(EqInc, "") {
   Inclusion Actual = testing::get<0>(arg);
   Inclusion Expected = testing::get<1>(arg);
@@ -886,6 +888,27 @@
   EXPECT_FALSE(mainIsGuarded(AST));
 }
 
+TEST(ParsedASTTest, DiscoversPragmaMarks) {
+  TestTU TU;
+  TU.AdditionalFiles["Header.h"] = R"(
+    #pragma mark - Something API
+    int something();
+    #pragma mark Something else
+  )";
+  TU.Code = R"cpp(
+    #include "Header.h"
+    #pragma mark In Preamble
+    #pragma mark - Something Impl
+    int something() { return 1; }
+    #pragma mark End
+  )cpp";
+  auto AST = TU.build();
+
+  EXPECT_THAT(AST.getMarks(), ElementsAre(PragmaTrivia(" In Preamble"),
+                                          PragmaTrivia(" - Something Impl"),
+                                          PragmaTrivia(" End")));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
+++ clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp
@@ -1027,6 +1027,75 @@
                     AllOf(WithName("-pur"), WithKind(SymbolKind::Method))))));
 }
 
+TEST(DocumentSymbolsTest, PragmaMarkGroups) {
+  TestTU TU;
+  TU.ExtraArgs = {"-xobjective-c++", "-Wno-objc-root-class"};
+  Annotations Main(R"cpp(
+      $DogDef[[@interface Dog
+      @end]]
+
+      $DogImpl[[@implementation Dog
+
+      + (id)sharedDoggo { return 0; }
+
+      #pragma $Overrides[[mark - Overrides
+
+      - (id)init {
+        return self;
+      }
+      - (void)bark {}]]
+
+      #pragma $Specifics[[mark - Dog Specifics
+
+      - (int)isAGoodBoy {
+        return 1;
+      }]]
+      @]]end  // FIXME: Why doesn't this include the 'end'?
+
+      #pragma $End[[mark - End
+]]
+    )cpp");
+  TU.Code = Main.code().str();
+  EXPECT_THAT(
+      getSymbols(TU.build()),
+      UnorderedElementsAre(
+          AllOf(WithName("Dog"), SymRange(Main.range("DogDef"))),
+          AllOf(WithName("Dog"), SymRange(Main.range("DogImpl")),
+                Children(AllOf(WithName("+sharedDoggo"),
+                               WithKind(SymbolKind::Method)),
+                         AllOf(WithName("Overrides"),
+                               SymRange(Main.range("Overrides")),
+                               Children(AllOf(WithName("-init"),
+                                              WithKind(SymbolKind::Method)),
+                                        AllOf(WithName("-bark"),
+                                              WithKind(SymbolKind::Method)))),
+                         AllOf(WithName("Dog Specifics"),
+                               SymRange(Main.range("Specifics")),
+                               Children(AllOf(WithName("-isAGoodBoy"),
+                                              WithKind(SymbolKind::Method)))))),
+          AllOf(WithName("End"), SymRange(Main.range("End")))));
+}
+
+TEST(DocumentSymbolsTest, PragmaMarkGroupsNoNesting) {
+  TestTU TU;
+  TU.ExtraArgs = {"-xobjective-c++", "-Wno-objc-root-class"};
+  Annotations Main(R"cpp(
+      #pragma mark Helpers
+      void helpA(id obj) {}
+
+      #pragma mark -
+      #pragma mark Core
+
+      void coreMethod() {}
+    )cpp");
+  TU.Code = Main.code().str();
+  EXPECT_THAT(getSymbols(TU.build()),
+              UnorderedElementsAre(
+                  AllOf(WithName("Helpers")), AllOf(WithName("helpA")),
+                  AllOf(WithName("(unnamed group)")), AllOf(WithName("Core")),
+                  AllOf(WithName("coreMethod"))));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
Index: clang-tools-extra/clangd/SourceCode.h
===================================================================
--- clang-tools-extra/clangd/SourceCode.h
+++ clang-tools-extra/clangd/SourceCode.h
@@ -129,6 +129,9 @@
 // Note that clang also uses closed source ranges, which this can't handle!
 Range halfOpenToRange(const SourceManager &SM, CharSourceRange R);
 
+// Expand range `A` to also contain `B`.
+void unionRanges(Range &A, Range B);
+
 // Converts an offset to a clang line/column (1-based, columns are bytes).
 // The offset must be in range [0, Code.size()].
 // Prefer to use SourceManager if one is available.
Index: clang-tools-extra/clangd/SourceCode.cpp
===================================================================
--- clang-tools-extra/clangd/SourceCode.cpp
+++ clang-tools-extra/clangd/SourceCode.cpp
@@ -471,6 +471,13 @@
   return {Begin, End};
 }
 
+void unionRanges(Range &A, Range B) {
+  if (B.start < A.start)
+    A.start = B.start;
+  if (A.end < B.end)
+    A.end = B.end;
+}
+
 std::pair<size_t, size_t> offsetToClangLineColumn(llvm::StringRef Code,
                                                   size_t Offset) {
   Offset = std::min(Code.size(), Offset);
Index: clang-tools-extra/clangd/Preamble.h
===================================================================
--- clang-tools-extra/clangd/Preamble.h
+++ clang-tools-extra/clangd/Preamble.h
@@ -61,6 +61,8 @@
   // Users care about headers vs main-file, not preamble vs non-preamble.
   // These should be treated as main-file entities e.g. for code completion.
   MainFileMacros Macros;
+  // Pragma marks defined in the preamble section of the main file.
+  std::vector<PragmaMark> Marks;
   // Cache of FS operations performed when building the preamble.
   // When reusing a preamble, this cache can be consumed to save IO.
   std::unique_ptr<PreambleFileStatusCache> StatCache;
Index: clang-tools-extra/clangd/Preamble.cpp
===================================================================
--- clang-tools-extra/clangd/Preamble.cpp
+++ clang-tools-extra/clangd/Preamble.cpp
@@ -73,6 +73,8 @@
 
   MainFileMacros takeMacros() { return std::move(Macros); }
 
+  std::vector<PragmaMark> takeMarks() { return std::move(Marks); }
+
   CanonicalIncludes takeCanonicalIncludes() { return std::move(CanonIncludes); }
 
   bool isMainFileIncludeGuarded() const { return IsMainFileIncludeGuarded; }
@@ -103,7 +105,9 @@
 
     return std::make_unique<PPChainedCallbacks>(
         collectIncludeStructureCallback(*SourceMgr, &Includes),
-        std::make_unique<CollectMainFileMacros>(*SourceMgr, Macros));
+        std::make_unique<PPChainedCallbacks>(
+            std::make_unique<CollectMainFileMacros>(*SourceMgr, Macros),
+            collectPragmaMarksCallback(*SourceMgr, Marks)));
   }
 
   CommentHandler *getCommentHandler() override {
@@ -130,6 +134,7 @@
   IncludeStructure Includes;
   CanonicalIncludes CanonIncludes;
   MainFileMacros Macros;
+  std::vector<PragmaMark> Marks;
   bool IsMainFileIncludeGuarded = false;
   std::unique_ptr<CommentHandler> IWYUHandler = nullptr;
   const clang::LangOptions *LangOpts = nullptr;
@@ -387,6 +392,7 @@
     Result->Diags = std::move(Diags);
     Result->Includes = CapturedInfo.takeIncludes();
     Result->Macros = CapturedInfo.takeMacros();
+    Result->Marks = CapturedInfo.takeMarks();
     Result->CanonIncludes = CapturedInfo.takeCanonicalIncludes();
     Result->StatCache = std::move(StatCache);
     Result->MainIsIncludeGuarded = CapturedInfo.isMainFileIncludeGuarded();
Index: clang-tools-extra/clangd/ParsedAST.h
===================================================================
--- clang-tools-extra/clangd/ParsedAST.h
+++ clang-tools-extra/clangd/ParsedAST.h
@@ -101,6 +101,8 @@
   /// Gets all macro references (definition, expansions) present in the main
   /// file, including those in the preamble region.
   const MainFileMacros &getMacros() const;
+  /// Gets all pragma marks in the main file.
+  const std::vector<PragmaMark> &getMarks() const;
   /// Tokens recorded while parsing the main file.
   /// (!) does not have tokens from the preamble.
   const syntax::TokenBuffer &getTokens() const { return Tokens; }
@@ -121,7 +123,8 @@
             std::shared_ptr<const PreambleData> Preamble,
             std::unique_ptr<CompilerInstance> Clang,
             std::unique_ptr<FrontendAction> Action, syntax::TokenBuffer Tokens,
-            MainFileMacros Macros, std::vector<Decl *> LocalTopLevelDecls,
+            MainFileMacros Macros, std::vector<PragmaMark> Marks,
+            std::vector<Decl *> LocalTopLevelDecls,
             llvm::Optional<std::vector<Diag>> Diags, IncludeStructure Includes,
             CanonicalIncludes CanonIncludes);
 
@@ -144,6 +147,8 @@
 
   /// All macro definitions and expansions in the main file.
   MainFileMacros Macros;
+  // Pragma marks in the main file.
+  std::vector<PragmaMark> Marks;
   // Data, stored after parsing. None if AST was built with a stale preamble.
   llvm::Optional<std::vector<Diag>> Diags;
   // Top-level decls inside the current file. Not that this does not include
Index: clang-tools-extra/clangd/ParsedAST.cpp
===================================================================
--- clang-tools-extra/clangd/ParsedAST.cpp
+++ clang-tools-extra/clangd/ParsedAST.cpp
@@ -436,6 +436,12 @@
       std::make_unique<CollectMainFileMacros>(Clang->getSourceManager(),
                                               Macros));
 
+  std::vector<PragmaMark> Marks;
+  if (Preamble)
+    Marks = Preamble->Marks;
+  Clang->getPreprocessor().addPPCallbacks(
+      collectPragmaMarksCallback(Clang->getSourceManager(), Marks));
+
   // Copy over the includes from the preamble, then combine with the
   // non-preamble includes below.
   CanonicalIncludes CanonIncludes;
@@ -497,7 +503,7 @@
   }
   return ParsedAST(Inputs.Version, std::move(Preamble), std::move(Clang),
                    std::move(Action), std::move(Tokens), std::move(Macros),
-                   std::move(ParsedDecls), std::move(Diags),
+                   std::move(Marks), std::move(ParsedDecls), std::move(Diags),
                    std::move(Includes), std::move(CanonIncludes));
 }
 
@@ -537,6 +543,7 @@
 }
 
 const MainFileMacros &ParsedAST::getMacros() const { return Macros; }
+const std::vector<PragmaMark> &ParsedAST::getMarks() const { return Marks; }
 
 std::size_t ParsedAST::getUsedBytes() const {
   auto &AST = getASTContext();
@@ -583,12 +590,14 @@
                      std::unique_ptr<CompilerInstance> Clang,
                      std::unique_ptr<FrontendAction> Action,
                      syntax::TokenBuffer Tokens, MainFileMacros Macros,
+                     std::vector<PragmaMark> Marks,
                      std::vector<Decl *> LocalTopLevelDecls,
                      llvm::Optional<std::vector<Diag>> Diags,
                      IncludeStructure Includes, CanonicalIncludes CanonIncludes)
     : Version(Version), Preamble(std::move(Preamble)), Clang(std::move(Clang)),
       Action(std::move(Action)), Tokens(std::move(Tokens)),
-      Macros(std::move(Macros)), Diags(std::move(Diags)),
+      Macros(std::move(Macros)), Marks(std::move(Marks)),
+      Diags(std::move(Diags)),
       LocalTopLevelDecls(std::move(LocalTopLevelDecls)),
       Includes(std::move(Includes)), CanonIncludes(std::move(CanonIncludes)) {
   Resolver = std::make_unique<HeuristicResolver>(getASTContext());
Index: clang-tools-extra/clangd/FindTarget.cpp
===================================================================
--- clang-tools-extra/clangd/FindTarget.cpp
+++ clang-tools-extra/clangd/FindTarget.cpp
@@ -652,7 +652,7 @@
     void visitProtocolList(
         llvm::iterator_range<ObjCProtocolList::iterator> Protocols,
         llvm::iterator_range<const SourceLocation *> Locations) {
-      for (const auto &P : llvm::zip(Protocols, Locations)) {
+      for (auto P : llvm::zip(Protocols, Locations)) {
         Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
                                     std::get<1>(P),
                                     /*IsDecl=*/false,
Index: clang-tools-extra/clangd/FindSymbols.cpp
===================================================================
--- clang-tools-extra/clangd/FindSymbols.cpp
+++ clang-tools-extra/clangd/FindSymbols.cpp
@@ -523,9 +523,286 @@
   ParsedAST &AST;
 };
 
+struct PragmaMarkSymbol {
+  DocumentSymbol DocSym;
+  bool IsGroup;
+};
+
+/// Merge in `PragmaMarkSymbols` into the given `DocumentSymbols`. All symbols
+/// should be sorted in ascending order by range.
+std::vector<DocumentSymbol> mergePragmas(std::vector<DocumentSymbol> &Syms,
+                                         ArrayRef<PragmaMarkSymbol> &Pragmas,
+                                         Range ParentRange) {
+  std::vector<DocumentSymbol> Res;
+  size_t MarkIndex = 0;
+  bool HaveOpenMarkGroup = false;
+  auto It = Syms.begin();
+
+  while (It != Syms.end() && !Pragmas.empty()) {
+    auto &Sym = *It;
+    const auto &Pragma = Pragmas.front();
+
+    // Pragma mark is contained in the current symbol, we need to recurse to
+    // nest it inside the symbol.
+    if (Sym.range.contains(Pragma.DocSym.range)) {
+      Sym.children = mergePragmas(Sym.children, Pragmas, Sym.range);
+      continue;
+    }
+    // Pragma mark is after the current symbol.
+    if (Sym.range < Pragma.DocSym.range) {
+      if (HaveOpenMarkGroup) { // Sym belongs to the previous TextMark group.
+        unionRanges(Res[MarkIndex].range, Sym.range);
+        Res[MarkIndex].children.push_back(std::move(Sym));
+      } else { // Sym doesn't belong to a group.
+        Res.push_back(std::move(Sym));
+      }
+      It++;
+      continue;
+    }
+    // Pragma mark is before the current symbol, add in a node for the mark.
+    assert(Pragma.DocSym.range < Sym.range && "Mark should be before Sym");
+    if (Pragma.IsGroup) {
+      MarkIndex = Res.size();
+      HaveOpenMarkGroup = true;
+    } else { // New mark ends the previous group if one existed.
+      HaveOpenMarkGroup = false;
+    }
+    Res.push_back(std::move(Pragma.DocSym));
+    Pragmas = Pragmas.drop_front();
+  }
+
+  // If we ran out of Symbols, we add any leftover marks as long as they are
+  // contained in our parent. Otherwise, we can't add them, since they belong
+  // to a different ancestor besides our parent.
+  while (!Pragmas.empty()) {
+    const auto &Pragma = Pragmas.back();
+    if (!ParentRange.contains(Pragma.DocSym.range))
+      break;
+    Res.push_back(std::move(Pragma.DocSym));
+    Pragmas = Pragmas.drop_front();
+  }
+
+  // If we ran out of marks, but still have symbols left, add them. If we have
+  // a group open, leftover symbols belong to that group.
+  for (; It != Syms.end(); It++) {
+    auto &Sym = *It;
+    if (HaveOpenMarkGroup) {
+      unionRanges(Res[MarkIndex].range, Sym.range);
+      Res[MarkIndex].children.push_back(std::move(Sym));
+    } else {
+      Res.push_back(std::move(Sym));
+    }
+  }
+  return Res;
+}
+
+/// Merge in `PragmaMarkSymbols` into the given `DocumentSymbols`. All symbols
+/// should be sorted in ascending order by range.
+std::vector<DocumentSymbol> mergePragmas2(std::vector<DocumentSymbol> &Syms,
+                                          ArrayRef<PragmaMarkSymbol> &Pragmas,
+                                          Range ParentRange) {
+  std::vector<DocumentSymbol> Res;
+  auto It = Syms.begin();
+
+  while (!Pragmas.empty()) {
+    const auto &Pragma = Pragmas.front();
+    // If the Pragma isn't contained in our parent, we're done, it must belong
+    // to a different ancestor.
+    if (!ParentRange.contains(Pragma.DocSym.range))
+      break;
+
+    // No more symbols left, just add the pragma.
+    if (It == Syms.end()) {
+      Res.push_back(std::move(Pragma.DocSym));
+      Pragmas = Pragmas.drop_front();
+      continue;
+    }
+    auto &Sym = *It;
+
+    // Pragma mark is contained in the current symbol, we need to recurse to
+    // nest it inside the symbol.
+    if (Sym.range.contains(Pragma.DocSym.range)) {
+      Sym.children = mergePragmas2(Sym.children, Pragmas, Sym.range);
+      continue;
+    }
+    // Pragma mark is after the child node, so we can process the child, it
+    // doesn't belong to a group.
+    if (Sym.range < Pragma.DocSym.range) {
+      Res.push_back(std::move(Sym));
+      It++;
+      continue;
+    }
+    // Pragma mark is before the current symbol, add in a node for the mark, and
+    // any Syms who should belong to the group, if it is one.
+    assert(Pragma.DocSym.range < Sym.range && "Mark should be before Sym");
+    if (!Pragma.IsGroup) {
+      Res.push_back(std::move(Pragma.DocSym));
+      Pragmas = Pragmas.drop_front();
+      continue;
+    }
+    DocumentSymbol PragmaSym = Pragma.DocSym;
+    Pragmas = Pragmas.drop_front();
+    // We've got a pragma which is a group. We add all Syms we encounter to our
+    // group until it ends; our group will end if there's a pragma after us
+    // which implicitly ends our group.
+    while (It != Syms.end() && PragmaSym.range < It->range) {
+      auto &Sym = *It;
+      if (Pragmas.empty() ||
+          It->range < Pragmas.front().DocSym.range) { // Sym is ours.
+        unionRanges(PragmaSym.range, Sym.range);
+        PragmaSym.children.push_back(std::move(Sym));
+        ++It;
+        continue;
+      }
+      const auto &NextPragma = Pragmas.front();
+      // Next pragma is contained in the Sym, it belongs there and doesn't
+      // affect us at all.
+      if (Sym.range.contains(NextPragma.DocSym.range)) {
+        Sym.children = mergePragmas2(Sym.children, Pragmas, Sym.range);
+        continue;
+      }
+      // NextPragma is after us but before the next symbol. Our reign is over.
+      break;
+    }
+    Res.push_back(PragmaSym);
+  }
+  // If we ran out of marks, but still have symbols left, add them.
+  for (; It != Syms.end(); It++) {
+    Res.push_back(std::move(*It));
+  }
+  return Res;
+}
+
+/// Merge in `PragmaMarkSymbols` into the given `DocumentSymbols`.
+void mergePragmas3(DocumentSymbol &Root, ArrayRef<PragmaMarkSymbol> Pragmas) {
+  while (!Pragmas.empty()) {
+    // We'll figure out where the Pragmas.front() should go.
+    PragmaMarkSymbol P = Pragmas.front();
+    Pragmas = Pragmas.drop_front();
+    DocumentSymbol *Cur = &Root;
+    while (Cur->range.contains(P.DocSym.range)) {
+      bool Swapped = false;
+      for (auto &C : Cur->children) {
+        // We assume at most 1 child can contain the pragma (as pragmas are on
+        // a single line, and children have disjoint ranges).
+        if (C.range.contains(P.DocSym.range)) {
+          Cur = &C;
+          Swapped = true;
+          break;
+        }
+      }
+      // Cur is the parent of P since none of the children contain P.
+      if (!Swapped)
+        break;
+    }
+    // Pragma isn't a group so we can just insert it and we are done.
+    if (!P.IsGroup) {
+      Cur->children.emplace_back(std::move(P.DocSym));
+      continue;
+    }
+    // Pragma is a group, so we need to figure out where it terminates:
+    // - If the next Pragma is not contained in Cur, P owns all of its
+    //   parent's children which occur after P.
+    // - If the next pragma is contained in Cur but actually belongs to one
+    //   of the parent's children, we temporarily skip over it and look at
+    //   the next pragma to decide where we end.
+    // - Otherwise nest all of its parent's children which occur after P but
+    //   before the next pragma.
+    ArrayRef<PragmaMarkSymbol> NextPragmas = Pragmas;
+    while (true) {
+      // We own all children that occur after us.
+      if (NextPragmas.empty() ||
+          !Cur->range.contains(NextPragmas.front().DocSym.range)) {
+        auto It = std::remove_if(Cur->children.begin(), Cur->children.end(),
+                                 [&P](const DocumentSymbol S) -> bool {
+                                   if (P.DocSym.range < S.range) {
+                                     P.DocSym.children.push_back(S);
+                                     return true;
+                                   }
+                                   return false;
+                                 });
+        Cur->children.erase(It, Cur->children.end());
+        break;
+      }
+      const auto &N = NextPragmas.front();
+      // If the next pragma is contained in one of the children, we need
+      // to skip over it since it doesn't affect us.
+      bool SkipPragma = false;
+      for (auto &C : Cur->children) {
+        if (C.range.contains(N.DocSym.range)) {
+          SkipPragma = true;
+          break;
+        }
+      }
+      if (SkipPragma) {
+        NextPragmas = NextPragmas.drop_front();
+        continue;
+      }
+      // We own all children that occur after us but before N.
+      auto It = std::remove_if(Cur->children.begin(), Cur->children.end(),
+                               [&P, &N](const DocumentSymbol S) -> bool {
+                                 if (P.DocSym.range < S.range &&
+                                     S.range < N.DocSym.range) {
+                                   P.DocSym.children.push_back(S);
+                                   return true;
+                                 }
+                                 return false;
+                               });
+      Cur->children.erase(It, Cur->children.end());
+      break;
+    }
+    for (DocumentSymbol &Sym : P.DocSym.children)
+      unionRanges(P.DocSym.range, Sym.range);
+    Cur->children.emplace_back(std::move(P.DocSym));
+  }
+}
+
+PragmaMarkSymbol markToSymbol(const PragmaMark &P) {
+  StringRef Name = StringRef(P.Trivia).trim();
+  bool IsGroup = false;
+  // "-\s+<group name>" or "<name>" after an initial trim. The former is
+  // considered a group, the latter just a mark. Like Xcode, we don't consider
+  // `-Foo` to be a group (space(s) after the `-` is required).
+  //
+  // We need to include a name here, otherwise editors won't properly render the
+  // symbol.
+  StringRef MaybeGroupName = Name;
+  if (MaybeGroupName.consume_front("-") &&
+      (MaybeGroupName.ltrim() != MaybeGroupName || MaybeGroupName.empty())) {
+    Name = MaybeGroupName.empty() ? "(unnamed group)" : MaybeGroupName.ltrim();
+    IsGroup = true;
+  } else if (Name.empty()) {
+    Name = "(unnamed mark)";
+  }
+  DocumentSymbol Sym;
+  Sym.name = Name.str();
+  Sym.kind = SymbolKind::File;
+  Sym.range = P.Rng;
+  Sym.selectionRange = P.Rng;
+  return {Sym, IsGroup};
+}
+
 std::vector<DocumentSymbol> collectDocSymbols(ParsedAST &AST) {
-  return DocumentOutline(AST).build();
+  std::vector<DocumentSymbol> Syms = DocumentOutline(AST).build();
+
+  const auto &PragmaMarks = AST.getMarks();
+  if (PragmaMarks.empty())
+    return Syms;
+
+  std::vector<PragmaMarkSymbol> Pragmas;
+  for (const auto &P : PragmaMarks)
+    Pragmas.push_back(markToSymbol(P));
+  Range EntireFile = {
+      {-1, -1},
+      {std::numeric_limits<int>::max(), std::numeric_limits<int>::max()}};
+  ArrayRef<PragmaMarkSymbol> PragmasRef(Pragmas);
+  DocumentSymbol Root;
+  Root.children = std::move(Syms);
+  Root.range = EntireFile;
+  mergePragmas3(Root, PragmasRef);
+  return Root.children;
 }
+
 } // namespace
 
 llvm::Expected<std::vector<DocumentSymbol>> getDocumentSymbols(ParsedAST &AST) {
Index: clang-tools-extra/clangd/CollectMacros.h
===================================================================
--- clang-tools-extra/clangd/CollectMacros.h
+++ clang-tools-extra/clangd/CollectMacros.h
@@ -99,6 +99,18 @@
   MainFileMacros &Out;
 };
 
+/// Represents a `#pragma mark` in the main file.
+///
+/// There can be at most one pragma mark per line.
+struct PragmaMark {
+  Range Rng;
+  std::string Trivia;
+};
+
+/// Collect all pragma marks from the main file.
+std::unique_ptr<PPCallbacks>
+collectPragmaMarksCallback(const SourceManager &, std::vector<PragmaMark> &Out);
+
 } // namespace clangd
 } // namespace clang
 
Index: clang-tools-extra/clangd/CollectMacros.cpp
===================================================================
--- clang-tools-extra/clangd/CollectMacros.cpp
+++ clang-tools-extra/clangd/CollectMacros.cpp
@@ -30,5 +30,31 @@
   else
     Out.UnknownMacros.push_back({Range, IsDefinition});
 }
+
+class CollectPragmaMarks : public PPCallbacks {
+public:
+  explicit CollectPragmaMarks(const SourceManager &SM,
+                              std::vector<clangd::PragmaMark> &Out)
+      : SM(SM), Out(Out) {}
+
+  void PragmaMark(SourceLocation Loc, StringRef Trivia) override {
+    if (isInsideMainFile(Loc, SM)) {
+      Position Start = sourceLocToPosition(SM, Loc);
+      Position End = {Start.line + 1, 0};
+      Out.emplace_back(clangd::PragmaMark{{Start, End}, Trivia.str()});
+    }
+  }
+
+private:
+  const SourceManager &SM;
+  std::vector<clangd::PragmaMark> &Out;
+};
+
+std::unique_ptr<PPCallbacks>
+collectPragmaMarksCallback(const SourceManager &SM,
+                           std::vector<PragmaMark> &Out) {
+  return std::make_unique<CollectPragmaMarks>(SM, Out);
+}
+
 } // namespace clangd
 } // namespace clang
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to