Skip to content

Commit dd0566b

Browse files
committed
Adds a json::Expr type to represent intermediate JSON expressions.
Summary: This form can be created with a nice clang-format-friendly literal syntax, and gets escaping right. It knows how to call unparse() on our Protocol types. All the places where we pass around JSON internally now use this type. Object properties are sorted (stored as std::map) and so serialization is canonicalized, with optional prettyprinting (triggered by a -pretty flag). This makes the lit tests much nicer to read and somewhat nicer to debug. (Unfortunately the completion tests use CHECK-DAG, which only has line-granularity, so pretty-printing is disabled there. In future we could make completion ordering deterministic, or switch to unittests). Compared to the current approach, it has some efficiencies like avoiding copies of string literals used as object keys, but is probably slower overall. I think the code/test quality benefits are worth it. This patch doesn't attempt to do anything about JSON *parsing*. It takes direction from the proposal in this doc[1], but is limited in scope and visibility, for now. I am of half a mind just to use Expr as the target of a parser, and maybe do a little string deduplication, but not bother with clever memory allocation. That would be simple, and fast enough for clangd... [1] https://docs.google.com/document/d/1OEF9IauWwNuSigZzvvbjc1cVS1uGHRyGTXaoy3DjqM4/edit +cc d0k so he can tell me not to use std::map. Reviewers: ioeric, malaperle Subscribers: bkramer, ilya-biryukov, mgorny, klimek Differential Revision: https://reviews.llvm.org/D39435 llvm-svn: 317486
1 parent ad9b972 commit dd0566b

31 files changed

+1816
-501
lines changed

clang-tools-extra/clangd/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_clang_library(clangDaemon
1010
DraftStore.cpp
1111
GlobalCompilationDatabase.cpp
1212
JSONRPCDispatcher.cpp
13+
JSONExpr.cpp
1314
Logger.cpp
1415
Protocol.cpp
1516
ProtocolHandlers.cpp

clang-tools-extra/clangd/ClangdLSPServer.cpp

Lines changed: 64 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,46 @@ namespace {
2020
std::vector<TextEdit>
2121
replacementsToEdits(StringRef Code,
2222
const std::vector<tooling::Replacement> &Replacements) {
23-
std::vector<TextEdit> Edits;
2423
// Turn the replacements into the format specified by the Language Server
25-
// Protocol.
24+
// Protocol. Fuse them into one big JSON array.
25+
std::vector<TextEdit> Edits;
2626
for (auto &R : Replacements) {
2727
Range ReplacementRange = {
2828
offsetToPosition(Code, R.getOffset()),
2929
offsetToPosition(Code, R.getOffset() + R.getLength())};
3030
Edits.push_back({ReplacementRange, R.getReplacementText()});
3131
}
32-
3332
return Edits;
3433
}
3534

3635
} // namespace
3736

3837
void ClangdLSPServer::onInitialize(Ctx C, InitializeParams &Params) {
39-
C.reply(
40-
R"({"capabilities":{
41-
"textDocumentSync": 1,
42-
"documentFormattingProvider": true,
43-
"documentRangeFormattingProvider": true,
44-
"documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
45-
"codeActionProvider": true,
46-
"completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
47-
"signatureHelpProvider": {"triggerCharacters": ["(",","]},
48-
"definitionProvider": true,
49-
"executeCommandProvider": {"commands": [")" +
50-
ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND + R"("]}
51-
}})");
38+
C.reply(json::obj{
39+
{"textDocumentSync", 1},
40+
{"documentFormattingProvider", true},
41+
{"documentRangeFormattingProvider", true},
42+
{"documentOnTypeFormattingProvider",
43+
json::obj{
44+
{"firstTriggerCharacter", "}"},
45+
{"moreTriggerCharacter", {}},
46+
}},
47+
{"codeActionProvider", true},
48+
{"completionProvider",
49+
json::obj{
50+
{"resolveProvider", false},
51+
{"triggerCharacters", {".", ">", ":"}},
52+
}},
53+
{"signatureHelpProvider",
54+
json::obj{
55+
{"triggerCharacters", {"(", ","}},
56+
}},
57+
{"definitionProvider", true},
58+
{"executeCommandProvider",
59+
json::obj{
60+
{"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
61+
}},
62+
});
5263
if (Params.rootUri && !Params.rootUri->file.empty())
5364
Server.setRootPath(Params.rootUri->file);
5465
else if (Params.rootPath && !Params.rootPath->empty())
@@ -58,7 +69,7 @@ void ClangdLSPServer::onInitialize(Ctx C, InitializeParams &Params) {
5869
void ClangdLSPServer::onShutdown(Ctx C, ShutdownParams &Params) {
5970
// Do essentially nothing, just say we're ready to exit.
6071
ShutdownRequestReceived = true;
61-
C.reply("null");
72+
C.reply(nullptr);
6273
}
6374

6475
void ClangdLSPServer::onExit(Ctx C, ExitParams &Params) { IsDone = true; }
@@ -98,7 +109,7 @@ void ClangdLSPServer::onCommand(Ctx C, ExecuteCommandParams &Params) {
98109

99110
ApplyWorkspaceEditParams ApplyEdit;
100111
ApplyEdit.edit = *Params.workspaceEdit;
101-
C.reply("\"Fix applied.\"");
112+
C.reply("Fix applied.");
102113
// We don't need the response so id == 1 is OK.
103114
// Ideally, we would wait for the response and if there is no error, we
104115
// would reply success/failure to the original RPC.
@@ -121,51 +132,45 @@ void ClangdLSPServer::onDocumentOnTypeFormatting(
121132
Ctx C, DocumentOnTypeFormattingParams &Params) {
122133
auto File = Params.textDocument.uri.file;
123134
std::string Code = Server.getDocument(File);
124-
std::string Edits = TextEdit::unparse(
125-
replacementsToEdits(Code, Server.formatOnType(File, Params.position)));
126-
C.reply(Edits);
135+
C.reply(json::ary(
136+
replacementsToEdits(Code, Server.formatOnType(File, Params.position))));
127137
}
128138

129139
void ClangdLSPServer::onDocumentRangeFormatting(
130140
Ctx C, DocumentRangeFormattingParams &Params) {
131141
auto File = Params.textDocument.uri.file;
132142
std::string Code = Server.getDocument(File);
133-
std::string Edits = TextEdit::unparse(
134-
replacementsToEdits(Code, Server.formatRange(File, Params.range)));
135-
C.reply(Edits);
143+
C.reply(json::ary(
144+
replacementsToEdits(Code, Server.formatRange(File, Params.range))));
136145
}
137146

138147
void ClangdLSPServer::onDocumentFormatting(Ctx C,
139148
DocumentFormattingParams &Params) {
140149
auto File = Params.textDocument.uri.file;
141150
std::string Code = Server.getDocument(File);
142-
std::string Edits =
143-
TextEdit::unparse(replacementsToEdits(Code, Server.formatFile(File)));
144-
C.reply(Edits);
151+
C.reply(json::ary(replacementsToEdits(Code, Server.formatFile(File))));
145152
}
146153

147154
void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
148155
// We provide a code action for each diagnostic at the requested location
149156
// which has FixIts available.
150157
std::string Code = Server.getDocument(Params.textDocument.uri.file);
151-
std::string Commands;
158+
json::ary Commands;
152159
for (Diagnostic &D : Params.context.diagnostics) {
153160
std::vector<clang::tooling::Replacement> Fixes =
154161
getFixIts(Params.textDocument.uri.file, D);
155162
auto Edits = replacementsToEdits(Code, Fixes);
156-
WorkspaceEdit WE;
157-
WE.changes = {{llvm::yaml::escape(Params.textDocument.uri.uri), Edits}};
158-
159-
if (!Edits.empty())
160-
Commands +=
161-
R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
162-
R"('", "command": ")" +
163-
ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND +
164-
R"(", "arguments": [)" + WorkspaceEdit::unparse(WE) + R"(]},)";
163+
if (!Edits.empty()) {
164+
WorkspaceEdit WE;
165+
WE.changes = {{Params.textDocument.uri.uri, std::move(Edits)}};
166+
Commands.push_back(json::obj{
167+
{"title", llvm::formatv("Apply FixIt {0}", D.message)},
168+
{"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND},
169+
{"arguments", {WE}},
170+
});
171+
}
165172
}
166-
if (!Commands.empty())
167-
Commands.pop_back();
168-
C.reply("[" + Commands + "]");
173+
C.reply(std::move(Commands));
169174
}
170175

171176
void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
@@ -177,15 +182,7 @@ void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
177182
// had an API that would allow to attach callbacks to
178183
// futures returned by ClangdServer.
179184
.Value;
180-
181-
std::string Completions;
182-
for (const auto &Item : Items) {
183-
Completions += CompletionItem::unparse(Item);
184-
Completions += ",";
185-
}
186-
if (!Completions.empty())
187-
Completions.pop_back();
188-
C.reply("[" + Completions + "]");
185+
C.reply(json::ary(Items));
189186
}
190187

191188
void ClangdLSPServer::onSignatureHelp(Ctx C,
@@ -195,7 +192,7 @@ void ClangdLSPServer::onSignatureHelp(Ctx C,
195192
Position{Params.position.line, Params.position.character});
196193
if (!SignatureHelp)
197194
return C.replyError(-32602, llvm::toString(SignatureHelp.takeError()));
198-
C.reply(SignatureHelp::unparse(SignatureHelp->Value));
195+
C.reply(SignatureHelp->Value);
199196
}
200197

201198
void ClangdLSPServer::onGoToDefinition(Ctx C,
@@ -205,22 +202,14 @@ void ClangdLSPServer::onGoToDefinition(Ctx C,
205202
Position{Params.position.line, Params.position.character});
206203
if (!Items)
207204
return C.replyError(-32602, llvm::toString(Items.takeError()));
208-
209-
std::string Locations;
210-
for (const auto &Item : Items->Value) {
211-
Locations += Location::unparse(Item);
212-
Locations += ",";
213-
}
214-
if (!Locations.empty())
215-
Locations.pop_back();
216-
C.reply("[" + Locations + "]");
205+
C.reply(json::ary(Items->Value));
217206
}
218207

219208
void ClangdLSPServer::onSwitchSourceHeader(Ctx C,
220209
TextDocumentIdentifier &Params) {
221210
llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
222211
std::string ResultUri;
223-
C.reply(Result ? URI::unparse(URI::fromFile(*Result)) : R"("")");
212+
C.reply(Result ? URI::fromFile(*Result).uri : "");
224213
}
225214

226215
ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
@@ -270,17 +259,16 @@ ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
270259

271260
void ClangdLSPServer::onDiagnosticsReady(
272261
PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
273-
std::string DiagnosticsJSON;
262+
json::ary DiagnosticsJSON;
274263

275264
DiagnosticToReplacementMap LocalFixIts; // Temporary storage
276265
for (auto &DiagWithFixes : Diagnostics.Value) {
277266
auto Diag = DiagWithFixes.Diag;
278-
DiagnosticsJSON +=
279-
R"({"range":)" + Range::unparse(Diag.range) +
280-
R"(,"severity":)" + std::to_string(Diag.severity) +
281-
R"(,"message":")" + llvm::yaml::escape(Diag.message) +
282-
R"("},)";
283-
267+
DiagnosticsJSON.push_back(json::obj{
268+
{"range", Diag.range},
269+
{"severity", Diag.severity},
270+
{"message", Diag.message},
271+
});
284272
// We convert to Replacements to become independent of the SourceManager.
285273
auto &FixItsForDiagnostic = LocalFixIts[Diag];
286274
std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
@@ -295,10 +283,13 @@ void ClangdLSPServer::onDiagnosticsReady(
295283
}
296284

297285
// Publish diagnostics.
298-
if (!DiagnosticsJSON.empty())
299-
DiagnosticsJSON.pop_back(); // Drop trailing comma.
300-
Out.writeMessage(
301-
R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
302-
URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
303-
R"(]}})");
286+
Out.writeMessage(json::obj{
287+
{"jsonrpc", "2.0"},
288+
{"method", "textDocument/publishDiagnostics"},
289+
{"params",
290+
json::obj{
291+
{"uri", URI::fromFile(File)},
292+
{"diagnostics", std::move(DiagnosticsJSON)},
293+
}},
294+
});
304295
}

0 commit comments

Comments
 (0)