diff --git a/clang-tools-extra/clangd/ClangdLSPServer.cpp b/clang-tools-extra/clangd/ClangdLSPServer.cpp index 7fd599d4e1a0b..9c328ec8b9867 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> Reply) { + onDocumentRangesFormatting( + DocumentRangesFormattingParams{Params.textDocument, {Params.range}}, + std::move(Reply)); +} + +void ClangdLSPServer::onDocumentRangesFormatting( + const DocumentRangesFormattingParams &Params, + Callback> 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 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 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 8bcb29522509b..ab83606f130f3 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>); void onDocumentRangeFormatting(const DocumentRangeFormattingParams &, Callback>); + void onDocumentRangesFormatting(const DocumentRangesFormattingParams &, + Callback>); void onDocumentFormatting(const DocumentFormattingParams &, Callback>); // The results are serialized 'vector' if diff --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-tools-extra/clangd/ClangdServer.cpp index 5790273d625ef..52b55237802aa 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 Rng, +void ClangdServer::formatFile(PathRef File, const std::vector &Rngs, Callback CB) { auto Code = getDraft(File); if (!Code) return CB(llvm::make_error("trying to format non-added document", ErrorCode::InvalidParams)); - tooling::Range RequestedRange; - if (Rng) { - llvm::Expected Begin = positionToOffset(*Code, Rng->start); - if (!Begin) - return CB(Begin.takeError()); - llvm::Expected End = positionToOffset(*Code, Rng->end); - if (!End) - return CB(End.takeError()); - RequestedRange = tooling::Range(*Begin, *End - *Begin); + std::vector RequestedRanges; + if (!Rngs.empty()) { + RequestedRanges.reserve(Rngs.size()); + for (const auto &Rng : Rngs) { + llvm::Expected Begin = positionToOffset(*Code, Rng.start); + if (!Begin) + return CB(Begin.takeError()); + llvm::Expected 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{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 1661028be88b4..71a04d70cd606 100644 --- a/clang-tools-extra/clangd/ClangdServer.h +++ b/clang-tools-extra/clangd/ClangdServer.h @@ -316,8 +316,8 @@ class ClangdServer { bool AddContainer, Callback 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 Rng, + /// If \p Rng is non-empty, formats only those regions. + void formatFile(PathRef File, const std::vector &Rngs, Callback 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 c08f80442eaa0..10d718a226c16 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 a0f8b04bc4ffd..6e7b54ab5095e 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> 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 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 aab1c8170f1be..860d58a669369 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 7c96eb9835b71..d976b7d19fd0e 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 864337b98f446..4c4b34e742eb8 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(Replaces)); auto Changed = tooling::applyAllReplacements(Code, *Replaces); EXPECT_TRUE(static_cast(Changed)); diff --git a/clang-tools-extra/clangd/unittests/SyncAPI.cpp b/clang-tools-extra/clangd/unittests/SyncAPI.cpp index d48622eba5378..7e8c8e22acf95 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 -runFormatFile(ClangdServer &Server, PathRef File, std::optional Rng) { +runFormatFile(ClangdServer &Server, PathRef File, + const std::vector &Rngs) { std::optional> 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 cf3de4f742e84..35ebd2574dda3 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 -runFormatFile(ClangdServer &Server, PathRef File, std::optional); +runFormatFile(ClangdServer &Server, PathRef File, const std::vector &); SymbolSlab runFuzzyFind(const SymbolIndex &Index, StringRef Query); SymbolSlab runFuzzyFind(const SymbolIndex &Index, const FuzzyFindRequest &Req);