blob: fe330bf5f9b353476c8ec7540907dde2e78dee41 [file] [log] [blame]
Ilya Biryukov38d79772017-05-16 09:38:59 +00001//===--- ClangdLSPServer.cpp - LSP server ------------------------*- C++-*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===---------------------------------------------------------------------===//
9
10#include "ClangdLSPServer.h"
11#include "JSONRPCDispatcher.h"
Sam McCallb536a2a2017-12-19 12:23:48 +000012#include "SourceCode.h"
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000013#include "llvm/Support/FormatVariadic.h"
14
Ilya Biryukov38d79772017-05-16 09:38:59 +000015using namespace clang::clangd;
16using namespace clang;
17
Ilya Biryukovafb55542017-05-16 14:40:30 +000018namespace {
19
Raoul Wols212bcf82017-12-12 20:25:06 +000020TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R) {
21 Range ReplacementRange = {
22 offsetToPosition(Code, R.getOffset()),
23 offsetToPosition(Code, R.getOffset() + R.getLength())};
24 return {ReplacementRange, R.getReplacementText()};
25}
26
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000027std::vector<TextEdit>
Ilya Biryukovafb55542017-05-16 14:40:30 +000028replacementsToEdits(StringRef Code,
29 const std::vector<tooling::Replacement> &Replacements) {
30 // Turn the replacements into the format specified by the Language Server
Sam McCalldd0566b2017-11-06 15:40:30 +000031 // Protocol. Fuse them into one big JSON array.
32 std::vector<TextEdit> Edits;
Raoul Wols212bcf82017-12-12 20:25:06 +000033 for (const auto &R : Replacements)
34 Edits.push_back(replacementToEdit(Code, R));
35 return Edits;
36}
37
38std::vector<TextEdit> replacementsToEdits(StringRef Code,
39 const tooling::Replacements &Repls) {
40 std::vector<TextEdit> Edits;
41 for (const auto &R : Repls)
42 Edits.push_back(replacementToEdit(Code, R));
Ilya Biryukovafb55542017-05-16 14:40:30 +000043 return Edits;
44}
45
46} // namespace
47
Sam McCall8a5dded2017-10-12 13:29:58 +000048void ClangdLSPServer::onInitialize(Ctx C, InitializeParams &Params) {
Ilya Biryukov940901e2017-12-13 12:51:22 +000049 reply(C, json::obj{
Sam McCall0930ab02017-11-07 15:49:35 +000050 {{"capabilities",
51 json::obj{
52 {"textDocumentSync", 1},
53 {"documentFormattingProvider", true},
54 {"documentRangeFormattingProvider", true},
55 {"documentOnTypeFormattingProvider",
56 json::obj{
57 {"firstTriggerCharacter", "}"},
58 {"moreTriggerCharacter", {}},
59 }},
60 {"codeActionProvider", true},
61 {"completionProvider",
62 json::obj{
63 {"resolveProvider", false},
64 {"triggerCharacters", {".", ">", ":"}},
65 }},
66 {"signatureHelpProvider",
67 json::obj{
68 {"triggerCharacters", {"(", ","}},
69 }},
70 {"definitionProvider", true},
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +000071 {"documentHighlightProvider", true},
Haojian Wu345099c2017-11-09 11:30:04 +000072 {"renameProvider", true},
Sam McCall0930ab02017-11-07 15:49:35 +000073 {"executeCommandProvider",
74 json::obj{
75 {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
76 }},
77 }}}});
Sam McCall8a5dded2017-10-12 13:29:58 +000078 if (Params.rootUri && !Params.rootUri->file.empty())
79 Server.setRootPath(Params.rootUri->file);
80 else if (Params.rootPath && !Params.rootPath->empty())
81 Server.setRootPath(*Params.rootPath);
Ilya Biryukovafb55542017-05-16 14:40:30 +000082}
83
Sam McCall8a5dded2017-10-12 13:29:58 +000084void ClangdLSPServer::onShutdown(Ctx C, ShutdownParams &Params) {
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +000085 // Do essentially nothing, just say we're ready to exit.
86 ShutdownRequestReceived = true;
Ilya Biryukov940901e2017-12-13 12:51:22 +000087 reply(C, nullptr);
Sam McCall8a5dded2017-10-12 13:29:58 +000088}
Ilya Biryukovafb55542017-05-16 14:40:30 +000089
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +000090void ClangdLSPServer::onExit(Ctx C, ExitParams &Params) { IsDone = true; }
91
Sam McCall8a5dded2017-10-12 13:29:58 +000092void ClangdLSPServer::onDocumentDidOpen(Ctx C,
93 DidOpenTextDocumentParams &Params) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000094 if (Params.metadata && !Params.metadata->extraFlags.empty())
Sam McCall4db732a2017-09-30 10:08:52 +000095 CDB.setExtraFlagsForFile(Params.textDocument.uri.file,
96 std::move(Params.metadata->extraFlags));
Ilya Biryukov940901e2017-12-13 12:51:22 +000097 Server.addDocument(std::move(C), Params.textDocument.uri.file,
98 Params.textDocument.text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000099}
100
Sam McCall8a5dded2017-10-12 13:29:58 +0000101void ClangdLSPServer::onDocumentDidChange(Ctx C,
102 DidChangeTextDocumentParams &Params) {
Benjamin Kramerb560a9a2017-10-26 10:36:20 +0000103 if (Params.contentChanges.size() != 1)
Ilya Biryukov940901e2017-12-13 12:51:22 +0000104 return replyError(C, ErrorCode::InvalidParams,
105 "can only apply one change at a time");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000106 // We only support full syncing right now.
Ilya Biryukov940901e2017-12-13 12:51:22 +0000107 Server.addDocument(std::move(C), Params.textDocument.uri.file,
Sam McCall4db732a2017-09-30 10:08:52 +0000108 Params.contentChanges[0].text);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000109}
110
Sam McCall8a5dded2017-10-12 13:29:58 +0000111void ClangdLSPServer::onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) {
Marc-Andre Laperlebf114242017-10-02 18:00:37 +0000112 Server.onFileEvent(Params);
113}
114
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000115void ClangdLSPServer::onCommand(Ctx C, ExecuteCommandParams &Params) {
116 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
117 Params.workspaceEdit) {
118 // The flow for "apply-fix" :
119 // 1. We publish a diagnostic, including fixits
120 // 2. The user clicks on the diagnostic, the editor asks us for code actions
121 // 3. We send code actions, with the fixit embedded as context
122 // 4. The user selects the fixit, the editor asks us to apply it
123 // 5. We unwrap the changes and send them back to the editor
124 // 6. The editor applies the changes (applyEdit), and sends us a reply (but
125 // we ignore it)
126
127 ApplyWorkspaceEditParams ApplyEdit;
128 ApplyEdit.edit = *Params.workspaceEdit;
Ilya Biryukov940901e2017-12-13 12:51:22 +0000129 reply(C, "Fix applied.");
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000130 // We don't need the response so id == 1 is OK.
131 // Ideally, we would wait for the response and if there is no error, we
132 // would reply success/failure to the original RPC.
Ilya Biryukov940901e2017-12-13 12:51:22 +0000133 call(C, "workspace/applyEdit", ApplyEdit);
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000134 } else {
135 // We should not get here because ExecuteCommandParams would not have
136 // parsed in the first place and this handler should not be called. But if
137 // more commands are added, this will be here has a safe guard.
Ilya Biryukov940901e2017-12-13 12:51:22 +0000138 replyError(
139 C, ErrorCode::InvalidParams,
Haojian Wu2375c922017-11-07 10:21:02 +0000140 llvm::formatv("Unsupported command \"{0}\".", Params.command).str());
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000141 }
142}
143
Haojian Wu345099c2017-11-09 11:30:04 +0000144void ClangdLSPServer::onRename(Ctx C, RenameParams &Params) {
145 auto File = Params.textDocument.uri.file;
Ilya Biryukov940901e2017-12-13 12:51:22 +0000146 auto Replacements = Server.rename(C, File, Params.position, Params.newName);
Haojian Wu345099c2017-11-09 11:30:04 +0000147 if (!Replacements) {
Ilya Biryukov940901e2017-12-13 12:51:22 +0000148 replyError(C, ErrorCode::InternalError,
149 llvm::toString(Replacements.takeError()));
Haojian Wu345099c2017-11-09 11:30:04 +0000150 return;
151 }
152 std::string Code = Server.getDocument(File);
153 std::vector<TextEdit> Edits = replacementsToEdits(Code, *Replacements);
154 WorkspaceEdit WE;
Sam McCallec109022017-11-28 09:37:43 +0000155 WE.changes = {{Params.textDocument.uri.uri, Edits}};
Ilya Biryukov940901e2017-12-13 12:51:22 +0000156 reply(C, WE);
Haojian Wu345099c2017-11-09 11:30:04 +0000157}
158
Sam McCall8a5dded2017-10-12 13:29:58 +0000159void ClangdLSPServer::onDocumentDidClose(Ctx C,
160 DidCloseTextDocumentParams &Params) {
Ilya Biryukov940901e2017-12-13 12:51:22 +0000161 Server.removeDocument(std::move(C), Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000162}
163
Sam McCall4db732a2017-09-30 10:08:52 +0000164void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCall8a5dded2017-10-12 13:29:58 +0000165 Ctx C, DocumentOnTypeFormattingParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000166 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +0000167 std::string Code = Server.getDocument(File);
Raoul Wols212bcf82017-12-12 20:25:06 +0000168 auto ReplacementsOrError = Server.formatOnType(Code, File, Params.position);
169 if (ReplacementsOrError)
Ilya Biryukov940901e2017-12-13 12:51:22 +0000170 reply(C, json::ary(replacementsToEdits(Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000171 else
Ilya Biryukov940901e2017-12-13 12:51:22 +0000172 replyError(C, ErrorCode::UnknownErrorCode,
173 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000174}
175
Sam McCall4db732a2017-09-30 10:08:52 +0000176void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCall8a5dded2017-10-12 13:29:58 +0000177 Ctx C, DocumentRangeFormattingParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000178 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +0000179 std::string Code = Server.getDocument(File);
Raoul Wols212bcf82017-12-12 20:25:06 +0000180 auto ReplacementsOrError = Server.formatRange(Code, File, Params.range);
181 if (ReplacementsOrError)
Ilya Biryukov940901e2017-12-13 12:51:22 +0000182 reply(C, json::ary(replacementsToEdits(Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000183 else
Ilya Biryukov940901e2017-12-13 12:51:22 +0000184 replyError(C, ErrorCode::UnknownErrorCode,
185 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000186}
187
Sam McCall8a5dded2017-10-12 13:29:58 +0000188void ClangdLSPServer::onDocumentFormatting(Ctx C,
189 DocumentFormattingParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000190 auto File = Params.textDocument.uri.file;
191 std::string Code = Server.getDocument(File);
Raoul Wols212bcf82017-12-12 20:25:06 +0000192 auto ReplacementsOrError = Server.formatFile(Code, File);
193 if (ReplacementsOrError)
Ilya Biryukov940901e2017-12-13 12:51:22 +0000194 reply(C, json::ary(replacementsToEdits(Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000195 else
Ilya Biryukov940901e2017-12-13 12:51:22 +0000196 replyError(C, ErrorCode::UnknownErrorCode,
197 llvm::toString(ReplacementsOrError.takeError()));
Sam McCall4db732a2017-09-30 10:08:52 +0000198}
199
Sam McCall8a5dded2017-10-12 13:29:58 +0000200void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000201 // We provide a code action for each diagnostic at the requested location
202 // which has FixIts available.
Sam McCall4db732a2017-09-30 10:08:52 +0000203 std::string Code = Server.getDocument(Params.textDocument.uri.file);
Sam McCalldd0566b2017-11-06 15:40:30 +0000204 json::ary Commands;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000205 for (Diagnostic &D : Params.context.diagnostics) {
Sam McCall8111d3b2017-12-13 08:48:42 +0000206 auto Edits = getFixIts(Params.textDocument.uri.file, D);
Sam McCalldd0566b2017-11-06 15:40:30 +0000207 if (!Edits.empty()) {
208 WorkspaceEdit WE;
209 WE.changes = {{Params.textDocument.uri.uri, std::move(Edits)}};
210 Commands.push_back(json::obj{
211 {"title", llvm::formatv("Apply FixIt {0}", D.message)},
212 {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND},
213 {"arguments", {WE}},
214 });
215 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000216 }
Ilya Biryukov940901e2017-12-13 12:51:22 +0000217 reply(C, std::move(Commands));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000218}
219
Sam McCall8a5dded2017-10-12 13:29:58 +0000220void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
Ilya Biryukov940901e2017-12-13 12:51:22 +0000221 auto Reply = Server
222 .codeComplete(std::move(C), Params.textDocument.uri.file,
223 Position{Params.position.line,
224 Params.position.character},
225 CCOpts)
226 .get(); // FIXME(ibiryukov): This could be made async if we
227 // had an API that would allow to attach callbacks to
228 // futures returned by ClangdServer.
229
230 // We have std::move'd from C, now restore it from response of codeComplete.
231 C = std::move(Reply.first);
232 auto List = std::move(Reply.second.Value);
233 reply(C, List);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000234}
235
Sam McCall8a5dded2017-10-12 13:29:58 +0000236void ClangdLSPServer::onSignatureHelp(Ctx C,
237 TextDocumentPositionParams &Params) {
Benjamin Krameree19f162017-10-26 12:28:13 +0000238 auto SignatureHelp = Server.signatureHelp(
Ilya Biryukov940901e2017-12-13 12:51:22 +0000239 C, Params.textDocument.uri.file,
Benjamin Krameree19f162017-10-26 12:28:13 +0000240 Position{Params.position.line, Params.position.character});
241 if (!SignatureHelp)
Ilya Biryukov940901e2017-12-13 12:51:22 +0000242 return replyError(C, ErrorCode::InvalidParams,
243 llvm::toString(SignatureHelp.takeError()));
244 reply(C, SignatureHelp->Value);
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000245}
246
Sam McCall8a5dded2017-10-12 13:29:58 +0000247void ClangdLSPServer::onGoToDefinition(Ctx C,
248 TextDocumentPositionParams &Params) {
Benjamin Krameree19f162017-10-26 12:28:13 +0000249 auto Items = Server.findDefinitions(
Ilya Biryukov940901e2017-12-13 12:51:22 +0000250 C, Params.textDocument.uri.file,
Benjamin Krameree19f162017-10-26 12:28:13 +0000251 Position{Params.position.line, Params.position.character});
252 if (!Items)
Ilya Biryukov940901e2017-12-13 12:51:22 +0000253 return replyError(C, ErrorCode::InvalidParams,
254 llvm::toString(Items.takeError()));
255 reply(C, json::ary(Items->Value));
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000256}
257
Sam McCall8a5dded2017-10-12 13:29:58 +0000258void ClangdLSPServer::onSwitchSourceHeader(Ctx C,
259 TextDocumentIdentifier &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000260 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000261 std::string ResultUri;
Ilya Biryukov940901e2017-12-13 12:51:22 +0000262 reply(C, Result ? URI::fromFile(*Result).uri : "");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000263}
264
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000265void ClangdLSPServer::onDocumentHighlight(Ctx C,
266 TextDocumentPositionParams &Params) {
267
268 auto Highlights = Server.findDocumentHighlights(
Ilya Biryukov940901e2017-12-13 12:51:22 +0000269 C, Params.textDocument.uri.file,
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000270 Position{Params.position.line, Params.position.character});
271
272 if (!Highlights) {
Ilya Biryukov940901e2017-12-13 12:51:22 +0000273 replyError(C, ErrorCode::InternalError,
274 llvm::toString(Highlights.takeError()));
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000275 return;
276 }
277
Ilya Biryukov940901e2017-12-13 12:51:22 +0000278 reply(C, json::ary(Highlights->Value));
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000279}
280
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000281ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Ilya Biryukove9eb7f02017-11-16 16:25:18 +0000282 bool StorePreamblesInMemory,
Sam McCalladccab62017-11-23 16:58:22 +0000283 const clangd::CodeCompleteOptions &CCOpts,
Ilya Biryukov0c1ca6b2017-10-02 15:13:20 +0000284 llvm::Optional<StringRef> ResourceDir,
285 llvm::Optional<Path> CompileCommandsDir)
Ilya Biryukov940901e2017-12-13 12:51:22 +0000286 : Out(Out), CDB(std::move(CompileCommandsDir)), CCOpts(CCOpts),
287 Server(CDB, /*DiagConsumer=*/*this, FSProvider, AsyncThreadsCount,
288 StorePreamblesInMemory, ResourceDir) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000289
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000290bool ClangdLSPServer::run(std::istream &In) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000291 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000292
Ilya Biryukovafb55542017-05-16 14:40:30 +0000293 // Set up JSONRPCDispatcher.
Ilya Biryukov940901e2017-12-13 12:51:22 +0000294 JSONRPCDispatcher Dispatcher([](Context Ctx, const json::Expr &Params) {
295 replyError(Ctx, ErrorCode::MethodNotFound, "method not found");
296 });
Sam McCall4db732a2017-09-30 10:08:52 +0000297 registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000298
Ilya Biryukovafb55542017-05-16 14:40:30 +0000299 // Run the Language Server loop.
300 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
301
302 // Make sure IsDone is set to true after this method exits to ensure assertion
303 // at the start of the method fires if it's ever executed again.
304 IsDone = true;
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000305
306 return ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000307}
308
Sam McCall8111d3b2017-12-13 08:48:42 +0000309std::vector<TextEdit> ClangdLSPServer::getFixIts(StringRef File,
310 const clangd::Diagnostic &D) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000311 std::lock_guard<std::mutex> Lock(FixItsMutex);
312 auto DiagToFixItsIter = FixItsMap.find(File);
313 if (DiagToFixItsIter == FixItsMap.end())
314 return {};
315
316 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
317 auto FixItsIter = DiagToFixItsMap.find(D);
318 if (FixItsIter == DiagToFixItsMap.end())
319 return {};
320
321 return FixItsIter->second;
322}
323
Sam McCall4db732a2017-09-30 10:08:52 +0000324void ClangdLSPServer::onDiagnosticsReady(
325 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
Sam McCalldd0566b2017-11-06 15:40:30 +0000326 json::ary DiagnosticsJSON;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000327
328 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCall4db732a2017-09-30 10:08:52 +0000329 for (auto &DiagWithFixes : Diagnostics.Value) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000330 auto Diag = DiagWithFixes.Diag;
Sam McCalldd0566b2017-11-06 15:40:30 +0000331 DiagnosticsJSON.push_back(json::obj{
332 {"range", Diag.range},
333 {"severity", Diag.severity},
334 {"message", Diag.message},
335 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000336 // We convert to Replacements to become independent of the SourceManager.
337 auto &FixItsForDiagnostic = LocalFixIts[Diag];
338 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
339 std::back_inserter(FixItsForDiagnostic));
340 }
341
342 // Cache FixIts
343 {
344 // FIXME(ibiryukov): should be deleted when documents are removed
345 std::lock_guard<std::mutex> Lock(FixItsMutex);
346 FixItsMap[File] = LocalFixIts;
347 }
348
349 // Publish diagnostics.
Sam McCalldd0566b2017-11-06 15:40:30 +0000350 Out.writeMessage(json::obj{
351 {"jsonrpc", "2.0"},
352 {"method", "textDocument/publishDiagnostics"},
353 {"params",
354 json::obj{
355 {"uri", URI::fromFile(File)},
356 {"diagnostics", std::move(DiagnosticsJSON)},
357 }},
358 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000359}