https://github.com/Febbe updated 
https://github.com/llvm/llvm-project/pull/156090

From 3d41a8cce0d25173c7ce9eb64d278c59b36e8615 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]>
Date: Wed, 2 Oct 2024 16:33:22 +0200
Subject: [PATCH 1/5] Add AST Search Capabilities to clangd  - utilizes
 clang_query to find nodes in the AST  - searches and returns the bound node
 names  - added astSearchProvider to capabilities  - added tests

---
 clang-tools-extra/clangd/CMakeLists.txt       |  2 +
 clang-tools-extra/clangd/ClangdLSPServer.cpp  | 15 +++++
 clang-tools-extra/clangd/ClangdLSPServer.h    |  1 +
 clang-tools-extra/clangd/ClangdServer.cpp     | 36 +++++++++++
 clang-tools-extra/clangd/ClangdServer.h       |  2 +
 clang-tools-extra/clangd/Protocol.cpp         | 13 ++++
 clang-tools-extra/clangd/Protocol.h           | 14 +++++
 clang-tools-extra/clangd/XRefs.cpp            | 59 +++++++++++++++++++
 clang-tools-extra/clangd/XRefs.h              | 10 ++++
 .../clangd/test/find-in-ast.test              | 37 ++++++++++++
 .../clangd/test/initialize-params.test        |  4 ++
 llvm/include/llvm/Support/JSON.h              |  4 ++
 12 files changed, 197 insertions(+)
 create mode 100644 clang-tools-extra/clangd/test/find-in-ast.test

diff --git a/clang-tools-extra/clangd/CMakeLists.txt 
b/clang-tools-extra/clangd/CMakeLists.txt
index 9fa7d92d8374c..cfdfd61a31329 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -60,6 +60,7 @@ endif()
 
 include_directories(BEFORE "${CMAKE_CURRENT_BINARY_DIR}/../clang-tidy")
 include_directories(BEFORE 
"${CMAKE_CURRENT_SOURCE_DIR}/../include-cleaner/include")
+include_directories(BEFORE "${CMAKE_CURRENT_SOURCE_DIR}/../clang-query")
 
 add_clang_library(clangDaemon STATIC
   AST.cpp
@@ -186,6 +187,7 @@ target_link_libraries(clangDaemon
   ${LLVM_PTHREAD_LIB}
 
   clangIncludeCleaner
+  clangQuery
   clangTidy
   clangTidyUtils
 
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp 
b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 43e8b35e45c89..5e0b474e47651 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -75,6 +75,7 @@ std::optional<int64_t> decodeVersion(llvm::StringRef Encoded) 
{
 const llvm::StringLiteral ApplyFixCommand = "clangd.applyFix";
 const llvm::StringLiteral ApplyTweakCommand = "clangd.applyTweak";
 const llvm::StringLiteral ApplyRenameCommand = "clangd.applyRename";
+constexpr llvm::StringLiteral SearchASTMethod = "textDocument/searchAST";
 
 CodeAction toCodeAction(const ClangdServer::CodeActionResult::Rename &R,
                         const URIForFile &File) {
@@ -637,6 +638,9 @@ void ClangdLSPServer::onInitialize(const InitializeParams 
&Params,
       {"workspaceSymbolProvider", true},
       {"referencesProvider", true},
       {"astProvider", true}, // clangd extension
+      {"astSearchProvider",
+       llvm::json::Object{{"search", true},
+                          {"replace", false}}}, // clangd extension
       {"typeHierarchyProvider", true},
       // Unfortunately our extension made use of the same capability name as 
the
       // standard. Advertise this capability to tell clients that implement our
@@ -851,6 +855,16 @@ void ClangdLSPServer::onCommandApplyRename(const 
RenameParams &R,
   });
 }
 
+void ClangdLSPServer::onMethodSearchAST(const SearchASTArgs &Args,
+                                        Callback<llvm::json::Value> Reply) {
+  Server->findAST(Args, [Reply = std::move(Reply)](
+                            llvm::Expected<BoundASTNodes> BoundNodes) mutable {
+    if (!BoundNodes)
+      return Reply(BoundNodes.takeError());
+    return Reply(*BoundNodes);
+  });
+}
+
 void ClangdLSPServer::applyEdit(WorkspaceEdit WE, llvm::json::Value Success,
                                 Callback<llvm::json::Value> Reply) {
   ApplyWorkspaceEditParams Edit;
@@ -1733,6 +1747,7 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
   Bind.command(ApplyFixCommand, this, &ClangdLSPServer::onCommandApplyEdit);
   Bind.command(ApplyTweakCommand, this, &ClangdLSPServer::onCommandApplyTweak);
   Bind.command(ApplyRenameCommand, this, 
&ClangdLSPServer::onCommandApplyRename);
+  Bind.method(SearchASTMethod, this, &ClangdLSPServer::onMethodSearchAST);
 
   ApplyWorkspaceEdit = Bind.outgoingMethod("workspace/applyEdit");
   PublishDiagnostics = 
Bind.outgoingNotification("textDocument/publishDiagnostics");
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h 
b/clang-tools-extra/clangd/ClangdLSPServer.h
index 6ada3fd9e6e47..8d7f4ccd67eea 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -186,6 +186,7 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
   void onCommandApplyEdit(const WorkspaceEdit &, Callback<llvm::json::Value>);
   void onCommandApplyTweak(const TweakArgs &, Callback<llvm::json::Value>);
   void onCommandApplyRename(const RenameParams &, Callback<llvm::json::Value>);
+  void onMethodSearchAST(const SearchASTArgs &, Callback<llvm::json::Value>);
 
   /// Outgoing LSP calls.
   LSPBinder::OutgoingMethod<ApplyWorkspaceEditParams,
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp 
b/clang-tools-extra/clangd/ClangdServer.cpp
index 37eb82116f3a9..ebb2297d2b7c8 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -815,6 +815,42 @@ void ClangdServer::locateSymbolAt(PathRef File, Position 
Pos,
   WorkScheduler->runWithAST("Definitions", File, std::move(Action));
 }
 
+void ClangdServer::findAST(SearchASTArgs const &Args,
+                           Callback<BoundASTNodes> CB) {
+  auto Action = [Args, CB = std::move(CB)](
+                    llvm::Expected<InputsAndAST> InpAST) mutable {
+    if (!InpAST)
+      return CB(InpAST.takeError());
+    auto BoundNodes = clangd::locateASTQuery(InpAST->AST, Args);
+    if (!BoundNodes)
+      return CB(BoundNodes.takeError());
+    if (BoundNodes->empty())
+      return CB(error("No matching AST nodes found"));
+
+    auto &&AST = InpAST->AST;
+    // Convert BoundNodes to a vector of vectors to ASTNode's.
+    BoundASTNodes Result;
+    Result.reserve(BoundNodes->size());
+    for (auto &&BN : *BoundNodes) {
+      auto &&Map = BN.getMap();
+      BoundASTNodes::value_type BAN;
+      for (const auto &[Key, Value] : Map) {
+        BAN.emplace(Key, dumpAST(Value, AST.getTokens(), AST.getASTContext()));
+      }
+      if (BAN.empty())
+        continue;
+      Result.push_back(std::move(BAN));
+    }
+    if (Result.empty()) {
+      return CB(error("No AST nodes found for the query"));
+    }
+    CB(std::move(Result));
+  };
+
+  WorkScheduler->runWithAST("Definitions", Args.textDocument.uri.file(),
+                            std::move(Action));
+}
+
 void ClangdServer::switchSourceHeader(
     PathRef Path, Callback<std::optional<clangd::Path>> CB) {
   // We want to return the result as fast as possible, strategy is:
diff --git a/clang-tools-extra/clangd/ClangdServer.h 
b/clang-tools-extra/clangd/ClangdServer.h
index 264ab7437c248..9a4ff39a2ad3e 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -268,6 +268,8 @@ class ClangdServer {
   void locateSymbolAt(PathRef File, Position Pos,
                       Callback<std::vector<LocatedSymbol>> CB);
 
+  void findAST(const SearchASTArgs &Args, Callback<BoundASTNodes> CB);
+
   /// Switch to a corresponding source file when given a header file, and vice
   /// versa.
   void switchSourceHeader(PathRef Path,
diff --git a/clang-tools-extra/clangd/Protocol.cpp 
b/clang-tools-extra/clangd/Protocol.cpp
index d5c50cc1cfa35..8b73529a94b88 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -13,6 +13,7 @@
 #include "Protocol.h"
 #include "URI.h"
 #include "support/Logger.h"
+#include "clang/AST/ASTTypeTraits.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Index/IndexSymbol.h"
 #include "llvm/ADT/StringExtras.h"
@@ -1674,6 +1675,18 @@ bool fromJSON(const llvm::json::Value &Params, 
SelectionRangeParams &S,
          O.map("positions", S.positions);
 }
 
+bool fromJSON(const llvm::json::Value &Params, SearchASTArgs &Args,
+              llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.map("query", Args.searchQuery) &&
+         O.map("textDocument", Args.textDocument)
+      // && O.map("bindRoot", Args.bindRoot); TODO: add bindRoot to extend this
+      // feature
+      // && O.map("traversalKind", Args.tk); TODO: add traversalKind to extend
+      // this feature
+      ;
+}
+
 llvm::json::Value toJSON(const SelectionRange &Out) {
   if (Out.parent) {
     return llvm::json::Object{{"range", Out.range},
diff --git a/clang-tools-extra/clangd/Protocol.h 
b/clang-tools-extra/clangd/Protocol.h
index b511ca495e47f..8cbab565828fc 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -26,6 +26,7 @@
 #include "URI.h"
 #include "index/SymbolID.h"
 #include "support/MemoryTree.h"
+#include "clang/AST/ASTTypeTraits.h"
 #include "clang/Index/IndexSymbol.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/JSON.h"
@@ -1507,6 +1508,19 @@ struct RenameParams {
 bool fromJSON(const llvm::json::Value &, RenameParams &, llvm::json::Path);
 llvm::json::Value toJSON(const RenameParams &);
 
+struct SearchASTArgs {
+  std::string searchQuery;
+  TextDocumentIdentifier textDocument;
+
+  // Todo (extend feature): make them members and modifiable:
+  /// wheter the whole query is shown
+  static auto constexpr BindRoot = true;
+  /// Simplify things for users; default for now.
+  static auto constexpr Tk = TraversalKind::TK_IgnoreUnlessSpelledInSource;
+};
+bool fromJSON(const llvm::json::Value &, SearchASTArgs &, llvm::json::Path);
+using BoundASTNodes = std::vector<std::map<std::string, struct ASTNode>>;
+
 struct PrepareRenameResult {
   /// Range of the string to rename.
   Range range;
diff --git a/clang-tools-extra/clangd/XRefs.cpp 
b/clang-tools-extra/clangd/XRefs.cpp
index 6886d235811d5..fd1b4af288979 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -14,6 +14,8 @@
 #include "ParsedAST.h"
 #include "Protocol.h"
 #include "Quality.h"
+#include "Query.h"
+#include "QuerySession.h"
 #include "Selection.h"
 #include "SourceCode.h"
 #include "clang-include-cleaner/Analysis.h"
@@ -41,6 +43,10 @@
 #include "clang/AST/StmtCXX.h"
 #include "clang/AST/StmtVisitor.h"
 #include "clang/AST/Type.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/Dynamic/Diagnostics.h"
+#include "clang/ASTMatchers/Dynamic/Parser.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
@@ -50,6 +56,7 @@
 #include "clang/Index/IndexingAction.h"
 #include "clang/Index/IndexingOptions.h"
 #include "clang/Lex/Lexer.h"
+#include "clang/Parse/Parser.h"
 #include "clang/Sema/HeuristicResolver.h"
 #include "clang/Tooling/Syntax/Tokens.h"
 #include "clang/UnifiedSymbolResolution/USRGeneration.h"
@@ -66,6 +73,7 @@
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
+#include <cmath>
 #include <optional>
 #include <string>
 #include <vector>
@@ -822,6 +830,57 @@ const syntax::Token *findNearbyIdentifier(const 
SpelledWord &Word,
   return BestTok;
 }
 
+auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &Query)
+    -> llvm::Expected<std::vector<ast_matchers::BoundNodes>> {
+  using namespace ast_matchers;
+  using namespace ast_matchers::dynamic;
+  using ast_matchers::dynamic::Parser;
+
+  Diagnostics Diag;
+  auto MatcherSource = llvm::StringRef(Query.searchQuery).ltrim();
+
+  std::optional<DynTypedMatcher> Matcher = Parser::parseMatcherExpression(
+      MatcherSource,
+      nullptr /* is this sema instance usefull, to reduce overhead?*/,
+      nullptr /*we currently don't support let*/, &Diag);
+  if (!Matcher) {
+    return error("Not a valid top-level matcher: {}.", Diag.toString());
+  }
+
+  struct CollectBoundNodes : MatchFinder::MatchCallback {
+    std::vector<BoundNodes> *Bindings;
+    CollectBoundNodes(std::vector<BoundNodes> &Bindings)
+        : Bindings(&Bindings) {}
+    void run(const MatchFinder::MatchResult &Result) override {
+      Bindings->push_back(Result.Nodes);
+    }
+  };
+
+  DynTypedMatcher MaybeBoundMatcher = *Matcher;
+  if (Query.BindRoot) {
+    std::optional<DynTypedMatcher> M = Matcher->tryBind("root");
+    if (M)
+      MaybeBoundMatcher = *M;
+  }
+  std::vector<BoundNodes> Matches;
+  CollectBoundNodes Collect(Matches);
+
+  MatchFinder::MatchFinderOptions Opt;
+  Opt.IgnoreSystemHeaders = true;
+  MatchFinder Finder{Opt};
+  if (!Finder.addDynamicMatcher(MaybeBoundMatcher, &Collect)) {
+    return error("Can't add matcher.");
+  }
+
+  ASTContext &Ctx = AST.getASTContext();
+
+  auto OldTK = Ctx.getParentMapContext().getTraversalKind();
+  Ctx.getParentMapContext().setTraversalKind(Query.Tk);
+  Finder.matchAST(Ctx);
+  Ctx.getParentMapContext().setTraversalKind(OldTK);
+  return Matches;
+}
+
 std::vector<LocatedSymbol> locateSymbolAt(ParsedAST &AST, Position Pos,
                                           const SymbolIndex *Index) {
   const auto &SM = AST.getSourceManager();
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index 247e52314c3f9..d6c0e3b99941b 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -19,6 +19,7 @@
 #include "index/SymbolID.h"
 #include "support/Path.h"
 #include "clang/AST/ASTTypeTraits.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/raw_ostream.h"
 #include <optional>
@@ -32,6 +33,15 @@ class TokenBuffer;
 namespace clangd {
 class ParsedAST;
 
+struct LocatedAST {
+  ast_matchers::BoundNodes &AST;
+};
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &, const LocatedAST &);
+
+auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &)
+    -> llvm::Expected<std::vector<ast_matchers::BoundNodes>>;
+
 // Describes where a symbol is declared and defined (as far as clangd knows).
 // There are three cases:
 //  - a declaration only, no definition is known (e.g. only header seen)
diff --git a/clang-tools-extra/clangd/test/find-in-ast.test 
b/clang-tools-extra/clangd/test/find-in-ast.test
new file mode 100644
index 0000000000000..6031ad10de1b3
--- /dev/null
+++ b/clang-tools-extra/clangd/test/find-in-ast.test
@@ -0,0 +1,37 @@
+# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace --dump-input 
always %s
+void bob();
+void f() {
+  bob();
+}
+---
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":
 {"foldingRange": {"lineFoldingOnly": true}}},"trace":"off"}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"cpp","text":"void
 bob();\nvoid f() {\n  bob();\n}\n","uri":"test:///foo.cpp","version":1}}}
+---
+{"id":1,"jsonrpc":"2.0","method":"textDocument/searchAST","params":{"textDocument":{"uri":"test:///foo.cpp"},"query":"declRefExpr(to(namedDecl(hasName(\"bob\"))))"}}
+#      CHECK:  "id": 1,
+# CHECK-NEXT:  "jsonrpc": "2.0",
+# CHECK-NEXT:  "result": [
+# CHECK-NEXT:    {
+# CHECK-NEXT:      "root": {
+# CHECK-NEXT:        "arcana": "DeclRefExpr {{.*}} 'void ()' lvalue Function 
{{.*}} 'bob' 'void ()'",
+# CHECK-NEXT:        "detail": "bob",
+# CHECK-NEXT:        "kind": "DeclRef",
+# CHECK-NEXT:        "range": {
+# CHECK-NEXT:          "end": {
+# CHECK-NEXT:            "character": 5,
+# CHECK-NEXT:            "line": 2
+# CHECK-NEXT:          },
+# CHECK-NEXT:          "start": {
+# CHECK-NEXT:            "character": 2,
+# CHECK-NEXT:            "line": 2
+# CHECK-NEXT:          }
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "role": "expression"
+# CHECK-NEXT:      }
+# CHECK-NEXT:    }
+# CHECK-NEXT:  ]
+---
+{"jsonrpc":"2.0","id":5,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}
diff --git a/clang-tools-extra/clangd/test/initialize-params.test 
b/clang-tools-extra/clangd/test/initialize-params.test
index d976b7d19fd0e..54d5b64ff2972 100644
--- a/clang-tools-extra/clangd/test/initialize-params.test
+++ b/clang-tools-extra/clangd/test/initialize-params.test
@@ -6,6 +6,10 @@
 # CHECK-NEXT:  "result": {
 # CHECK-NEXT:    "capabilities": {
 # CHECK-NEXT:      "astProvider": true,
+# CHECK-NEXT:      "astSearchProvider": {
+# CHECK-NEXT:        "replace": false,
+# CHECK-NEXT:        "search": true
+# CHECK-NEXT:      },
 # CHECK-NEXT:      "callHierarchyProvider": true,
 # CHECK-NEXT:      "clangdInlayHintsProvider": true,
 # CHECK-NEXT:      "codeActionProvider": true,
diff --git a/llvm/include/llvm/Support/JSON.h b/llvm/include/llvm/Support/JSON.h
index 3b145a69c74cb..74066e4efcd3c 100644
--- a/llvm/include/llvm/Support/JSON.h
+++ b/llvm/include/llvm/Support/JSON.h
@@ -111,6 +111,10 @@ class Object {
   // (using std::pair forces extra copies).
   struct KV;
   explicit Object(std::initializer_list<KV> Properties);
+  template <typename Collection> explicit Object(Collection &&C) {
+    for (auto &&P : C)
+      M.insert(P);
+  }
 
   iterator begin() { return M.begin(); }
   const_iterator begin() const { return M.begin(); }

From 7ab24e572d819fa5cd94cd43b7dde618eb4b9e87 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]>
Date: Sat, 15 Aug 2026 22:38:48 +0200
Subject: [PATCH 2/5] Add capabilities to complete a matcher expression

 - adds a clang/completeASTMatcher method to the
   clangd LSP server, which returns a list of all
   available AST matcher expressions and the text
   to complete the current expression.
 - adds a test for the new method, which checks that
   the returned completions return the completion.
 - uses already existing code from ast-matchers.
 - mirrors behaviour of clang-query
---
 clang-tools-extra/clangd/ClangdLSPServer.cpp  | 15 ++++++++++++
 clang-tools-extra/clangd/ClangdLSPServer.h    |  2 ++
 clang-tools-extra/clangd/ClangdServer.cpp     | 21 +++++++++++++++++
 clang-tools-extra/clangd/ClangdServer.h       |  3 +++
 clang-tools-extra/clangd/Protocol.cpp         | 19 +++++++++++++++
 clang-tools-extra/clangd/Protocol.h           | 19 +++++++++++++++
 .../clangd/test/completion-matcher.test       | 23 +++++++++++++++++++
 7 files changed, 102 insertions(+)
 create mode 100644 clang-tools-extra/clangd/test/completion-matcher.test

diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp 
b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 5e0b474e47651..ce9e264f9bd9b 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -76,6 +76,8 @@ const llvm::StringLiteral ApplyFixCommand = "clangd.applyFix";
 const llvm::StringLiteral ApplyTweakCommand = "clangd.applyTweak";
 const llvm::StringLiteral ApplyRenameCommand = "clangd.applyRename";
 constexpr llvm::StringLiteral SearchASTMethod = "textDocument/searchAST";
+constexpr llvm::StringLiteral CompleteASTMatcherMethod =
+    "clangd/completeASTMatcher";
 
 CodeAction toCodeAction(const ClangdServer::CodeActionResult::Rename &R,
                         const URIForFile &File) {
@@ -855,6 +857,18 @@ void ClangdLSPServer::onCommandApplyRename(const 
RenameParams &R,
   });
 }
 
+void ClangdLSPServer::onMethodCompleteASTMatcher(
+    const CompleteASTMatcherArgs &Args, Callback<llvm::json::Value> Reply) {
+  Server->completeASTMatcher(
+      Args,
+      [Reply = std::move(Reply)](
+          llvm::Expected<std::vector<ASTMatcherCompletion>> ToolTips) mutable {
+        if (!ToolTips)
+          return Reply(ToolTips.takeError());
+        return Reply(*ToolTips);
+      });
+}
+
 void ClangdLSPServer::onMethodSearchAST(const SearchASTArgs &Args,
                                         Callback<llvm::json::Value> Reply) {
   Server->findAST(Args, [Reply = std::move(Reply)](
@@ -1747,6 +1761,7 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
   Bind.command(ApplyFixCommand, this, &ClangdLSPServer::onCommandApplyEdit);
   Bind.command(ApplyTweakCommand, this, &ClangdLSPServer::onCommandApplyTweak);
   Bind.command(ApplyRenameCommand, this, 
&ClangdLSPServer::onCommandApplyRename);
+  Bind.method(CompleteASTMatcherMethod, this, 
&ClangdLSPServer::onMethodCompleteASTMatcher);
   Bind.method(SearchASTMethod, this, &ClangdLSPServer::onMethodSearchAST);
 
   ApplyWorkspaceEdit = Bind.outgoingMethod("workspace/applyEdit");
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h 
b/clang-tools-extra/clangd/ClangdLSPServer.h
index 8d7f4ccd67eea..07a4b16a71e07 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -186,6 +186,8 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
   void onCommandApplyEdit(const WorkspaceEdit &, Callback<llvm::json::Value>);
   void onCommandApplyTweak(const TweakArgs &, Callback<llvm::json::Value>);
   void onCommandApplyRename(const RenameParams &, Callback<llvm::json::Value>);
+  void onMethodCompleteASTMatcher(const CompleteASTMatcherArgs &,
+                                  Callback<llvm::json::Value>);
   void onMethodSearchAST(const SearchASTArgs &, Callback<llvm::json::Value>);
 
   /// Outgoing LSP calls.
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp 
b/clang-tools-extra/clangd/ClangdServer.cpp
index ebb2297d2b7c8..fb4592bd57db8 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -35,6 +35,8 @@
 #include "support/MemoryTree.h"
 #include "support/ThreadsafeFS.h"
 #include "support/Trace.h"
+#include "clang/ASTMatchers/Dynamic/Parser.h"
+#include "clang/ASTMatchers/Dynamic/Registry.h"
 #include "clang/Basic/Stack.h"
 #include "clang/Format/Format.h"
 #include "clang/Lex/Preprocessor.h"
@@ -815,6 +817,25 @@ void ClangdServer::locateSymbolAt(PathRef File, Position 
Pos,
   WorkScheduler->runWithAST("Definitions", File, std::move(Action));
 }
 
+void ClangdServer::completeASTMatcher(
+    const CompleteASTMatcherArgs &Args,
+    Callback<std::vector<ASTMatcherCompletion>> CB) {
+  auto Action = [Args, CB = std::move(CB)]() mutable -> void {
+    using ::clang::ast_matchers::dynamic::Parser;
+    using ::clang::ast_matchers::dynamic::MatcherCompletion;
+
+    std::vector<ASTMatcherCompletion> ToolTips;
+    auto QueryRef = StringRef(Args.searchQuery);
+    std::vector<MatcherCompletion> Comps =
+        Parser::completeExpression(QueryRef, Args.offset);
+    for (auto I = Comps.begin(), E = Comps.end(); I != E; ++I) {
+      ToolTips.push_back(ASTMatcherCompletion{I->TypedText, I->MatcherDecl});
+    }
+    return CB(std::move(ToolTips));
+  };
+  WorkScheduler->runQuick("ASTMatcherCompletion", "", std::move(Action));
+}
+
 void ClangdServer::findAST(SearchASTArgs const &Args,
                            Callback<BoundASTNodes> CB) {
   auto Action = [Args, CB = std::move(CB)](
diff --git a/clang-tools-extra/clangd/ClangdServer.h 
b/clang-tools-extra/clangd/ClangdServer.h
index 9a4ff39a2ad3e..73241b0c055cd 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -270,6 +270,9 @@ class ClangdServer {
 
   void findAST(const SearchASTArgs &Args, Callback<BoundASTNodes> CB);
 
+  void completeASTMatcher(const CompleteASTMatcherArgs &Args,
+                          Callback<std::vector<ASTMatcherCompletion>> CB);
+
   /// Switch to a corresponding source file when given a header file, and vice
   /// versa.
   void switchSourceHeader(PathRef Path,
diff --git a/clang-tools-extra/clangd/Protocol.cpp 
b/clang-tools-extra/clangd/Protocol.cpp
index 8b73529a94b88..f65807ca8f3bd 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -1675,6 +1675,13 @@ bool fromJSON(const llvm::json::Value &Params, 
SelectionRangeParams &S,
          O.map("positions", S.positions);
 }
 
+bool fromJSON(const llvm::json::Value &Params, CompleteASTMatcherArgs &Args,
+              llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.map("searchQuery", Args.searchQuery) &&
+         O.map("offset", Args.offset);
+}
+
 bool fromJSON(const llvm::json::Value &Params, SearchASTArgs &Args,
               llvm::json::Path P) {
   llvm::json::ObjectMapper O(Params, P);
@@ -1782,6 +1789,18 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, 
const ASTNode &Root) {
   return OS;
 }
 
+llvm::json::Value toJSON(const ASTMatcherCompletion &ASTTT) {
+  llvm::json::Object Result{{"typedText", ASTTT.typedText},
+                            {"matcherDecl", ASTTT.matcherDecl}};
+  return Result;
+}
+
+llvm::raw_ostream &operator<<(llvm::raw_ostream &OS,
+                              const ASTMatcherCompletion &ASTTT) {
+  OS << ASTTT.typedText << " - " << ASTTT.matcherDecl;
+  return OS;
+}
+
 bool fromJSON(const llvm::json::Value &E, SymbolID &S, llvm::json::Path P) {
   auto Str = E.getAsString();
   if (!Str) {
diff --git a/clang-tools-extra/clangd/Protocol.h 
b/clang-tools-extra/clangd/Protocol.h
index 8cbab565828fc..f710246e45a7f 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -1508,6 +1508,13 @@ struct RenameParams {
 bool fromJSON(const llvm::json::Value &, RenameParams &, llvm::json::Path);
 llvm::json::Value toJSON(const RenameParams &);
 
+struct CompleteASTMatcherArgs {
+  std::string searchQuery;
+  size_t offset;
+};
+bool fromJSON(const llvm::json::Value &, CompleteASTMatcherArgs &,
+              llvm::json::Path);
+
 struct SearchASTArgs {
   std::string searchQuery;
   TextDocumentIdentifier textDocument;
@@ -2115,6 +2122,18 @@ struct ASTNode {
 llvm::json::Value toJSON(const ASTNode &);
 llvm::raw_ostream &operator<<(llvm::raw_ostream &, const ASTNode &);
 
+/// The query completion tooltip.
+struct ASTMatcherCompletion {
+  /// Text to complete the matcher.
+  std::string typedText;
+  /// Full Matcher declaration including type and parameters
+  std::string matcherDecl;
+};
+
+llvm::json::Value toJSON(const ASTMatcherCompletion &);
+llvm::raw_ostream &operator<<(llvm::raw_ostream &,
+                              const ASTMatcherCompletion &);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/test/completion-matcher.test 
b/clang-tools-extra/clangd/test/completion-matcher.test
new file mode 100644
index 0000000000000..1ff751ef33a4b
--- /dev/null
+++ b/clang-tools-extra/clangd/test/completion-matcher.test
@@ -0,0 +1,23 @@
+# RUN: clangd -lit-test < %s | FileCheck -strict-whitespace --dump-input 
always %s
+void bob();
+void f() {
+  bob();
+}
+---
+{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{"textDocument":
 {"foldingRange": {"lineFoldingOnly": true}}},"trace":"off"}}
+---
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"cpp","text":"void
 bob();\nvoid f() {\n  bob();\n}\n","uri":"test:///foo.cpp","version":1}}}
+---
+{"id":1,"jsonrpc":"2.0","method":"clangd/completeASTMatcher","params":{"query":"declRefE",
 "offset": 8}}
+#      CHECK:  "id": 1,
+# CHECK-NEXT:  "jsonrpc": "2.0",
+# CHECK-NEXT:  "result": [
+# CHECK-NEXT:    {
+# CHECK-NEXT:      "matcherDecl": "Matcher<Stmt> 
declRefExpr(Matcher<DeclRefExpr>...)",
+# CHECK-NEXT:      "typedText": "xpr("
+# CHECK-NEXT:    }
+# CHECK-NEXT:  ]
+---
+{"jsonrpc":"2.0","id":5,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}

From cbc1e12b3c56ffab08ea0533a76c188c4d77515a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]>
Date: Sat, 15 Aug 2026 23:44:48 +0200
Subject: [PATCH 3/5] fixes for first commit

---
 clang-tools-extra/clangd/ClangdServer.cpp | 2 +-
 clang-tools-extra/clangd/XRefs.cpp        | 3 ---
 clang-tools-extra/clangd/XRefs.h          | 6 ------
 3 files changed, 1 insertion(+), 10 deletions(-)

diff --git a/clang-tools-extra/clangd/ClangdServer.cpp 
b/clang-tools-extra/clangd/ClangdServer.cpp
index fb4592bd57db8..4817e28d0f75c 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -868,7 +868,7 @@ void ClangdServer::findAST(SearchASTArgs const &Args,
     CB(std::move(Result));
   };
 
-  WorkScheduler->runWithAST("Definitions", Args.textDocument.uri.file(),
+  WorkScheduler->runWithAST("FindAST", Args.textDocument.uri.file(),
                             std::move(Action));
 }
 
diff --git a/clang-tools-extra/clangd/XRefs.cpp 
b/clang-tools-extra/clangd/XRefs.cpp
index fd1b4af288979..6c9fb53dd2722 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -14,8 +14,6 @@
 #include "ParsedAST.h"
 #include "Protocol.h"
 #include "Quality.h"
-#include "Query.h"
-#include "QuerySession.h"
 #include "Selection.h"
 #include "SourceCode.h"
 #include "clang-include-cleaner/Analysis.h"
@@ -73,7 +71,6 @@
 #include "llvm/Support/Path.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
-#include <cmath>
 #include <optional>
 #include <string>
 #include <vector>
diff --git a/clang-tools-extra/clangd/XRefs.h b/clang-tools-extra/clangd/XRefs.h
index d6c0e3b99941b..63e1a72093459 100644
--- a/clang-tools-extra/clangd/XRefs.h
+++ b/clang-tools-extra/clangd/XRefs.h
@@ -33,12 +33,6 @@ class TokenBuffer;
 namespace clangd {
 class ParsedAST;
 
-struct LocatedAST {
-  ast_matchers::BoundNodes &AST;
-};
-
-llvm::raw_ostream &operator<<(llvm::raw_ostream &, const LocatedAST &);
-
 auto locateASTQuery(ParsedAST &AST, SearchASTArgs const &)
     -> llvm::Expected<std::vector<ast_matchers::BoundNodes>>;
 

From dae08a52bddc901a5c0544fede790d4898698134 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]>
Date: Sat, 15 Aug 2026 23:44:59 +0200
Subject: [PATCH 4/5] fix for second

---
 clang-tools-extra/clangd/test/completion-matcher.test | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang-tools-extra/clangd/test/completion-matcher.test 
b/clang-tools-extra/clangd/test/completion-matcher.test
index 1ff751ef33a4b..009352538016b 100644
--- a/clang-tools-extra/clangd/test/completion-matcher.test
+++ b/clang-tools-extra/clangd/test/completion-matcher.test
@@ -8,7 +8,7 @@ void f() {
 ---
 
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"languageId":"cpp","text":"void
 bob();\nvoid f() {\n  bob();\n}\n","uri":"test:///foo.cpp","version":1}}}
 ---
-{"id":1,"jsonrpc":"2.0","method":"clangd/completeASTMatcher","params":{"query":"declRefE",
 "offset": 8}}
+{"id":1,"jsonrpc":"2.0","method":"clangd/completeASTMatcher","params":{"searchQuery":"declRefE",
 "offset": 8}}
 #      CHECK:  "id": 1,
 # CHECK-NEXT:  "jsonrpc": "2.0",
 # CHECK-NEXT:  "result": [

From 9ca70d99c7791496725a8fc10785db674480bee9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fabian=20Ke=C3=9Fler?= <[email protected]>
Date: Sat, 15 Aug 2026 23:59:52 +0200
Subject: [PATCH 5/5] missing but used map include

---
 clang-tools-extra/clangd/Protocol.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang-tools-extra/clangd/Protocol.h 
b/clang-tools-extra/clangd/Protocol.h
index f710246e45a7f..513b8c62d4087 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -32,6 +32,7 @@
 #include "llvm/Support/JSON.h"
 #include "llvm/Support/raw_ostream.h"
 #include <bitset>
+#include <map>
 #include <memory>
 #include <optional>
 #include <string>

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to