[clang-tools-extra] [clangd] Add support for textDocument/rangesFormatting (LSP 3.18) (PR #80180)

Tom Praschan via cfe-commits cfe-commits at lists.llvm.org
Sun Apr 7 04:53:52 PDT 2024


https://github.com/tom-anders updated https://github.com/llvm/llvm-project/pull/80180

>From 19ae86fb821c908fbd29515f36282c3aa0745048 Mon Sep 17 00:00:00 2001
From: tom-anders <13141438+tom-anders at users.noreply.github.com>
Date: Thu, 18 May 2023 12:05:01 +0200
Subject: [PATCH] [clangd] Add support for textDocument/rangesFormatting

As part of the upcoming 3.18 spec:

https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#documentRangesFormattingParams

Issue: https://github.com/clangd/clangd/issues/1635

Differential Revision: https://reviews.llvm.org/D150852
---
 clang-tools-extra/clangd/ClangdLSPServer.cpp  | 18 +++++-
 clang-tools-extra/clangd/ClangdLSPServer.h    |  2 +
 clang-tools-extra/clangd/ClangdServer.cpp     | 29 +++++----
 clang-tools-extra/clangd/ClangdServer.h       |  4 +-
 clang-tools-extra/clangd/Protocol.cpp         |  9 +++
 clang-tools-extra/clangd/Protocol.h           | 13 ++++
 clang-tools-extra/clangd/test/formatting.test | 59 ++++++++++++++++++-
 .../clangd/test/initialize-params.test        |  4 +-
 .../clangd/unittests/ClangdTests.cpp          |  2 +-
 .../clangd/unittests/SyncAPI.cpp              |  5 +-
 clang-tools-extra/clangd/unittests/SyncAPI.h  |  2 +-
 11 files changed, 122 insertions(+), 25 deletions(-)

diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp
index 7fd599d4e1a0b0..9c328ec8b9867f 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.cpp
+++ b/clang-tools-extra/clangd/ClangdLSPServer.cpp
@@ -583,7 +583,10 @@ void ClangdLSPServer::onInitialize(const InitializeParams &Params,
            {"save", true},
        }},
       {"documentFormattingProvider", true},
-      {"documentRangeFormattingProvider", true},
+      {"documentRangeFormattingProvider",
+       llvm::json::Object{
+           {"rangesSupport", true},
+       }},
       {"documentOnTypeFormattingProvider",
        llvm::json::Object{
            {"firstTriggerCharacter", "\n"},
@@ -944,9 +947,17 @@ void ClangdLSPServer::onDocumentOnTypeFormatting(
 void ClangdLSPServer::onDocumentRangeFormatting(
     const DocumentRangeFormattingParams &Params,
     Callback<std::vector<TextEdit>> Reply) {
+  onDocumentRangesFormatting(
+      DocumentRangesFormattingParams{Params.textDocument, {Params.range}},
+      std::move(Reply));
+}
+
+void ClangdLSPServer::onDocumentRangesFormatting(
+    const DocumentRangesFormattingParams &Params,
+    Callback<std::vector<TextEdit>> Reply) {
   auto File = Params.textDocument.uri.file();
   auto Code = Server->getDraft(File);
-  Server->formatFile(File, Params.range,
+  Server->formatFile(File, Params.ranges,
                      [Code = std::move(Code), Reply = std::move(Reply)](
                          llvm::Expected<tooling::Replacements> Result) mutable {
                        if (Result)
@@ -962,7 +973,7 @@ void ClangdLSPServer::onDocumentFormatting(
   auto File = Params.textDocument.uri.file();
   auto Code = Server->getDraft(File);
   Server->formatFile(File,
-                     /*Rng=*/std::nullopt,
+                     /*Rngs=*/{},
                      [Code = std::move(Code), Reply = std::move(Reply)](
                          llvm::Expected<tooling::Replacements> Result) mutable {
                        if (Result)
@@ -1655,6 +1666,7 @@ void ClangdLSPServer::bindMethods(LSPBinder &Bind,
   Bind.method("shutdown", this, &ClangdLSPServer::onShutdown);
   Bind.method("sync", this, &ClangdLSPServer::onSync);
   Bind.method("textDocument/rangeFormatting", this, &ClangdLSPServer::onDocumentRangeFormatting);
+  Bind.method("textDocument/rangesFormatting", this, &ClangdLSPServer::onDocumentRangesFormatting);
   Bind.method("textDocument/onTypeFormatting", this, &ClangdLSPServer::onDocumentOnTypeFormatting);
   Bind.method("textDocument/formatting", this, &ClangdLSPServer::onDocumentFormatting);
   Bind.method("textDocument/codeAction", this, &ClangdLSPServer::onCodeAction);
diff --git a/clang-tools-extra/clangd/ClangdLSPServer.h b/clang-tools-extra/clangd/ClangdLSPServer.h
index 8bcb29522509b7..ab83606f130f38 100644
--- a/clang-tools-extra/clangd/ClangdLSPServer.h
+++ b/clang-tools-extra/clangd/ClangdLSPServer.h
@@ -104,6 +104,8 @@ class ClangdLSPServer : private ClangdServer::Callbacks,
                                   Callback<std::vector<TextEdit>>);
   void onDocumentRangeFormatting(const DocumentRangeFormattingParams &,
                                  Callback<std::vector<TextEdit>>);
+  void onDocumentRangesFormatting(const DocumentRangesFormattingParams &,
+                                  Callback<std::vector<TextEdit>>);
   void onDocumentFormatting(const DocumentFormattingParams &,
                             Callback<std::vector<TextEdit>>);
   // The results are serialized 'vector<DocumentSymbol>' if
diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp
index 5790273d625ef1..52b55237802aa0 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -500,29 +500,32 @@ void ClangdServer::signatureHelp(PathRef File, Position Pos,
                                  std::move(Action));
 }
 
-void ClangdServer::formatFile(PathRef File, std::optional<Range> Rng,
+void ClangdServer::formatFile(PathRef File, const std::vector<Range> &Rngs,
                               Callback<tooling::Replacements> CB) {
   auto Code = getDraft(File);
   if (!Code)
     return CB(llvm::make_error<LSPError>("trying to format non-added document",
                                          ErrorCode::InvalidParams));
-  tooling::Range RequestedRange;
-  if (Rng) {
-    llvm::Expected<size_t> Begin = positionToOffset(*Code, Rng->start);
-    if (!Begin)
-      return CB(Begin.takeError());
-    llvm::Expected<size_t> End = positionToOffset(*Code, Rng->end);
-    if (!End)
-      return CB(End.takeError());
-    RequestedRange = tooling::Range(*Begin, *End - *Begin);
+  std::vector<tooling::Range> RequestedRanges;
+  if (!Rngs.empty()) {
+    RequestedRanges.reserve(Rngs.size());
+    for (const auto &Rng : Rngs) {
+      llvm::Expected<size_t> Begin = positionToOffset(*Code, Rng.start);
+      if (!Begin)
+        return CB(Begin.takeError());
+      llvm::Expected<size_t> End = positionToOffset(*Code, Rng.end);
+      if (!End)
+        return CB(End.takeError());
+      RequestedRanges.emplace_back(*Begin, *End - *Begin);
+    }
   } else {
-    RequestedRange = tooling::Range(0, Code->size());
+    RequestedRanges = {tooling::Range(0, Code->size())};
   }
 
   // Call clang-format.
   auto Action = [File = File.str(), Code = std::move(*Code),
-                 Ranges = std::vector<tooling::Range>{RequestedRange},
-                 CB = std::move(CB), this]() mutable {
+                 Ranges = std::move(RequestedRanges), CB = std::move(CB),
+                 this]() mutable {
     format::FormatStyle Style = getFormatStyleForFile(File, Code, TFS, true);
     tooling::Replacements IncludeReplaces =
         format::sortIncludes(Style, Code, Ranges, File);
diff --git a/clang-tools-extra/clangd/ClangdServer.h b/clang-tools-extra/clangd/ClangdServer.h
index 1661028be88b4e..71a04d70cd606b 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -316,8 +316,8 @@ class ClangdServer {
                       bool AddContainer, Callback<ReferencesResult> CB);
 
   /// Run formatting for the \p File with content \p Code.
-  /// If \p Rng is non-null, formats only that region.
-  void formatFile(PathRef File, std::optional<Range> Rng,
+  /// If \p Rng is non-empty, formats only those regions.
+  void formatFile(PathRef File, const std::vector<Range> &Rngs,
                   Callback<tooling::Replacements> CB);
 
   /// Run formatting after \p TriggerText was typed at \p Pos in \p File with
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index c08f80442eaa06..10d718a226c161 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -633,6 +633,15 @@ bool fromJSON(const llvm::json::Value &Params, DocumentRangeFormattingParams &R,
               llvm::json::Path P) {
   llvm::json::ObjectMapper O(Params, P);
   return O && O.map("textDocument", R.textDocument) && O.map("range", R.range);
+  ;
+}
+
+bool fromJSON(const llvm::json::Value &Params,
+              DocumentRangesFormattingParams &R, llvm::json::Path P) {
+  llvm::json::ObjectMapper O(Params, P);
+  return O && O.map("textDocument", R.textDocument) &&
+         O.map("ranges", R.ranges);
+  ;
 }
 
 bool fromJSON(const llvm::json::Value &Params,
diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h
index a0f8b04bc4ffdb..6e7b54ab5095e7 100644
--- a/clang-tools-extra/clangd/Protocol.h
+++ b/clang-tools-extra/clangd/Protocol.h
@@ -851,10 +851,23 @@ struct DocumentRangeFormattingParams {
 
   /// The range to format
   Range range;
+
+  /// The list of ranges to format
+  std::optional<std::vector<Range>> ranges;
 };
 bool fromJSON(const llvm::json::Value &, DocumentRangeFormattingParams &,
               llvm::json::Path);
 
+struct DocumentRangesFormattingParams {
+  /// The document to format.
+  TextDocumentIdentifier textDocument;
+
+  /// The list of ranges to format
+  std::vector<Range> ranges;
+};
+bool fromJSON(const llvm::json::Value &, DocumentRangesFormattingParams &,
+              llvm::json::Path);
+
 struct DocumentOnTypeFormattingParams {
   /// The document to format.
   TextDocumentIdentifier textDocument;
diff --git a/clang-tools-extra/clangd/test/formatting.test b/clang-tools-extra/clangd/test/formatting.test
index aab1c8170f1be4..860d58a669369f 100644
--- a/clang-tools-extra/clangd/test/formatting.test
+++ b/clang-tools-extra/clangd/test/formatting.test
@@ -135,10 +135,65 @@
 # CHECK-NEXT:  "jsonrpc": "2.0",
 # CHECK-NEXT:  "result": []
 ---
+{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c","version":9},"contentChanges":[{"text":"int foo(  int x){\n  x=x+1;\nreturn x;\n}"}]}}
+---
+{"jsonrpc":"2.0","id":5,"method":"textDocument/rangesFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"ranges":[{"start":{"line":0,"character":0},"end":{"line":0,"character":15}}, {"start":{"line":2,"character":0},"end":{"line":2,"character":5}}]}}
+---
+#      CHECK:   "id": 5,
+# CHECK-NEXT:  "jsonrpc": "2.0",
+# CHECK-NEXT:  "result": [
+# CHECK-NEXT:    {
+# CHECK-NEXT:      "newText": "",
+# CHECK-NEXT:      "range": {
+# CHECK-NEXT:        "end": {
+# CHECK-NEXT:          "character": 10,
+# CHECK-NEXT:          "line": 0
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "start": {
+# CHECK-NEXT:          "character": 8,
+# CHECK-NEXT:          "line": 0
+# CHECK-NEXT:        }
+# CHECK-NEXT:      }
+# CHECK-NEXT:    },
+# CHECK-NEXT:    {
+# CHECK-NEXT:      "newText": " ",
+# CHECK-NEXT:      "range": {
+# CHECK-NEXT:        "end": {
+# CHECK-NEXT:          "character": 16,
+# CHECK-NEXT:          "line": 0
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "start": {
+# CHECK-NEXT:          "character": 16,
+# CHECK-NEXT:          "line": 0
+# CHECK-NEXT:        }
+# CHECK-NEXT:      }
+# CHECK-NEXT:    },
+# CHECK-NEXT:    {
+# CHECK-NEXT:      "newText": "\n  ",
+# CHECK-NEXT:      "range": {
+# CHECK-NEXT:        "end": {
+# CHECK-NEXT:          "character": 0,
+# CHECK-NEXT:          "line": 2
+# CHECK-NEXT:        },
+# CHECK-NEXT:        "start": {
+# CHECK-NEXT:          "character": 8,
+# CHECK-NEXT:          "line": 1
+# CHECK-NEXT:        }
+# CHECK-NEXT:      }
+# CHECK-NEXT:    }
+# CHECK-NEXT:  ]
+---
+{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c","version":9},"contentChanges":[{"text":"int foo(int x) {\n  x=x+1;\n  return x;\n}"}]}}
+---
+{"jsonrpc":"2.0","id":6,"method":"textDocument/rangesFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"ranges":[{"start":{"line":0,"character":0},"end":{"line":0,"character":15}}, {"start":{"line":2,"character":0},"end":{"line":2,"character":5}}]}}
+#      CHECK:  "id": 6,
+# CHECK-NEXT:  "jsonrpc": "2.0",
+# CHECK-NEXT:  "result": []
+---
 {"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"test:///foo.c","version":5},"contentChanges":[{"text":"int x=\n"}]}}
 ---
-{"jsonrpc":"2.0","id":5,"method":"textDocument/onTypeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"position":{"line":1,"character":0},"ch":"\n"}}
-#      CHECK:  "id": 5,
+{"jsonrpc":"2.0","id":7,"method":"textDocument/onTypeFormatting","params":{"textDocument":{"uri":"test:///foo.c"},"position":{"line":1,"character":0},"ch":"\n"}}
+#      CHECK:  "id": 7,
 # CHECK-NEXT:  "jsonrpc": "2.0",
 # CHECK-NEXT:  "result": [
 # CHECK-NEXT:    {
diff --git a/clang-tools-extra/clangd/test/initialize-params.test b/clang-tools-extra/clangd/test/initialize-params.test
index 7c96eb9835b713..d976b7d19fd0eb 100644
--- a/clang-tools-extra/clangd/test/initialize-params.test
+++ b/clang-tools-extra/clangd/test/initialize-params.test
@@ -35,7 +35,9 @@
 # CHECK-NEXT:        "firstTriggerCharacter": "\n",
 # CHECK-NEXT:        "moreTriggerCharacter": []
 # CHECK-NEXT:      },
-# CHECK-NEXT:      "documentRangeFormattingProvider": true,
+# CHECK-NEXT:      "documentRangeFormattingProvider": {
+# CHECK-NEXT:        "rangesSupport": true
+# CHECK-NEXT:      },
 # CHECK-NEXT:      "documentSymbolProvider": true,
 # CHECK-NEXT:      "executeCommandProvider": {
 # CHECK-NEXT:        "commands": [
diff --git a/clang-tools-extra/clangd/unittests/ClangdTests.cpp b/clang-tools-extra/clangd/unittests/ClangdTests.cpp
index 864337b98f4463..4c4b34e742eb8e 100644
--- a/clang-tools-extra/clangd/unittests/ClangdTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ClangdTests.cpp
@@ -943,7 +943,7 @@ void f() {}
   FS.Files[Path] = Code;
   runAddDocument(Server, Path, Code);
 
-  auto Replaces = runFormatFile(Server, Path, /*Rng=*/std::nullopt);
+  auto Replaces = runFormatFile(Server, Path, /*Rngs=*/{});
   EXPECT_TRUE(static_cast<bool>(Replaces));
   auto Changed = tooling::applyAllReplacements(Code, *Replaces);
   EXPECT_TRUE(static_cast<bool>(Changed));
diff --git a/clang-tools-extra/clangd/unittests/SyncAPI.cpp b/clang-tools-extra/clangd/unittests/SyncAPI.cpp
index d48622eba53783..7e8c8e22acf956 100644
--- a/clang-tools-extra/clangd/unittests/SyncAPI.cpp
+++ b/clang-tools-extra/clangd/unittests/SyncAPI.cpp
@@ -116,9 +116,10 @@ runPrepareRename(ClangdServer &Server, PathRef File, Position Pos,
 }
 
 llvm::Expected<tooling::Replacements>
-runFormatFile(ClangdServer &Server, PathRef File, std::optional<Range> Rng) {
+runFormatFile(ClangdServer &Server, PathRef File,
+              const std::vector<Range> &Rngs) {
   std::optional<llvm::Expected<tooling::Replacements>> Result;
-  Server.formatFile(File, Rng, capture(Result));
+  Server.formatFile(File, Rngs, capture(Result));
   return std::move(*Result);
 }
 
diff --git a/clang-tools-extra/clangd/unittests/SyncAPI.h b/clang-tools-extra/clangd/unittests/SyncAPI.h
index cf3de4f742e84d..35ebd2574dda36 100644
--- a/clang-tools-extra/clangd/unittests/SyncAPI.h
+++ b/clang-tools-extra/clangd/unittests/SyncAPI.h
@@ -53,7 +53,7 @@ runPrepareRename(ClangdServer &Server, PathRef File, Position Pos,
                  const clangd::RenameOptions &RenameOpts);
 
 llvm::Expected<tooling::Replacements>
-runFormatFile(ClangdServer &Server, PathRef File, std::optional<Range>);
+runFormatFile(ClangdServer &Server, PathRef File, const std::vector<Range> &);
 
 SymbolSlab runFuzzyFind(const SymbolIndex &Index, StringRef Query);
 SymbolSlab runFuzzyFind(const SymbolIndex &Index, const FuzzyFindRequest &Req);



More information about the cfe-commits mailing list