[clangd] Add support for textDocument/rangesFormatting (#141208)
Uses the protocol changes proposed in https://github.com/microsoft/language-server-protocol/pull/1556 and https://github.com/microsoft/vscode/pull/163190 Related issue: https://github.com/clangd/clangd/issues/1635 Old Phabricator review: https://reviews.llvm.org/D150852 Relevant LSP 3.18 spec: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#documentRangesFormattingParams Old PR: https://github.com/llvm/llvm-project/pull/80180, https://github.com/llvm/llvm-project/pull/141052
This commit is contained in:
parent
3f29acb517
commit
926c201323
@ -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"},
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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);
|
||||
|
@ -110,6 +110,8 @@ private:
|
||||
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
|
||||
|
@ -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);
|
||||
|
@ -329,8 +329,8 @@ public:
|
||||
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
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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: {
|
||||
|
@ -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": [
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user