Skip to content

[clangd] Add support for textDocument/rangesFormatting #141208

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
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions clang-tools-extra/clangd/ClangdLSPServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,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"},
Expand Down Expand Up @@ -952,9 +955,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)
Expand All @@ -970,7 +981,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)
Expand Down Expand Up @@ -1666,6 +1677,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);
Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clangd/ClangdLSPServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,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
Expand Down
29 changes: 16 additions & 13 deletions clang-tools-extra/clangd/ClangdServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -521,29 +521,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);
Expand Down
4 changes: 2 additions & 2 deletions clang-tools-extra/clangd/ClangdServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,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
Expand Down
9 changes: 9 additions & 0 deletions clang-tools-extra/clangd/Protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,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,
Expand Down
10 changes: 10 additions & 0 deletions clang-tools-extra/clangd/Protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,16 @@ struct DocumentRangeFormattingParams {
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;
Expand Down
59 changes: 57 additions & 2 deletions clang-tools-extra/clangd/test/formatting.test
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
4 changes: 3 additions & 1 deletion clang-tools-extra/clangd/test/initialize-params.test
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/unittests/ClangdTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,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));
Expand Down
5 changes: 3 additions & 2 deletions clang-tools-extra/clangd/unittests/SyncAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/unittests/SyncAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading