blob: 4f70acb869250af36b88189b256a014349e622a3 [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"
12
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
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000020std::vector<TextEdit>
Ilya Biryukovafb55542017-05-16 14:40:30 +000021replacementsToEdits(StringRef Code,
22 const std::vector<tooling::Replacement> &Replacements) {
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000023 std::vector<TextEdit> Edits;
Ilya Biryukovafb55542017-05-16 14:40:30 +000024 // Turn the replacements into the format specified by the Language Server
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000025 // Protocol.
Ilya Biryukovafb55542017-05-16 14:40:30 +000026 for (auto &R : Replacements) {
27 Range ReplacementRange = {
28 offsetToPosition(Code, R.getOffset()),
29 offsetToPosition(Code, R.getOffset() + R.getLength())};
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000030 Edits.push_back({ReplacementRange, R.getReplacementText()});
Ilya Biryukovafb55542017-05-16 14:40:30 +000031 }
Ilya Biryukovafb55542017-05-16 14:40:30 +000032
33 return Edits;
34}
35
36} // namespace
37
Sam McCall8a5dded2017-10-12 13:29:58 +000038void ClangdLSPServer::onInitialize(Ctx C, InitializeParams &Params) {
39 C.reply(
40 R"({"capabilities":{
Ilya Biryukovafb55542017-05-16 14:40:30 +000041 "textDocumentSync": 1,
42 "documentFormattingProvider": true,
43 "documentRangeFormattingProvider": true,
44 "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
45 "codeActionProvider": true,
Ilya Biryukova2e7ca92017-07-31 09:27:52 +000046 "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +000047 "signatureHelpProvider": {"triggerCharacters": ["(",","]},
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000048 "definitionProvider": true,
49 "executeCommandProvider": {"commands": [")" +
50 ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND + R"("]}
Sam McCall8a5dded2017-10-12 13:29:58 +000051 }})");
52 if (Params.rootUri && !Params.rootUri->file.empty())
53 Server.setRootPath(Params.rootUri->file);
54 else if (Params.rootPath && !Params.rootPath->empty())
55 Server.setRootPath(*Params.rootPath);
Ilya Biryukovafb55542017-05-16 14:40:30 +000056}
57
Sam McCall8a5dded2017-10-12 13:29:58 +000058void ClangdLSPServer::onShutdown(Ctx C, ShutdownParams &Params) {
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +000059 // Do essentially nothing, just say we're ready to exit.
60 ShutdownRequestReceived = true;
61 C.reply("null");
Sam McCall8a5dded2017-10-12 13:29:58 +000062}
Ilya Biryukovafb55542017-05-16 14:40:30 +000063
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +000064void ClangdLSPServer::onExit(Ctx C, ExitParams &Params) { IsDone = true; }
65
Sam McCall8a5dded2017-10-12 13:29:58 +000066void ClangdLSPServer::onDocumentDidOpen(Ctx C,
67 DidOpenTextDocumentParams &Params) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000068 if (Params.metadata && !Params.metadata->extraFlags.empty())
Sam McCall4db732a2017-09-30 10:08:52 +000069 CDB.setExtraFlagsForFile(Params.textDocument.uri.file,
70 std::move(Params.metadata->extraFlags));
71 Server.addDocument(Params.textDocument.uri.file, Params.textDocument.text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000072}
73
Sam McCall8a5dded2017-10-12 13:29:58 +000074void ClangdLSPServer::onDocumentDidChange(Ctx C,
75 DidChangeTextDocumentParams &Params) {
Benjamin Kramerb560a9a2017-10-26 10:36:20 +000076 if (Params.contentChanges.size() != 1)
77 return C.replyError(-32602, "can only apply one change at a time");
Ilya Biryukovafb55542017-05-16 14:40:30 +000078 // We only support full syncing right now.
Sam McCall4db732a2017-09-30 10:08:52 +000079 Server.addDocument(Params.textDocument.uri.file,
80 Params.contentChanges[0].text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000081}
82
Sam McCall8a5dded2017-10-12 13:29:58 +000083void ClangdLSPServer::onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) {
Marc-Andre Laperlebf114242017-10-02 18:00:37 +000084 Server.onFileEvent(Params);
85}
86
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000087void ClangdLSPServer::onCommand(Ctx C, ExecuteCommandParams &Params) {
88 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
89 Params.workspaceEdit) {
90 // The flow for "apply-fix" :
91 // 1. We publish a diagnostic, including fixits
92 // 2. The user clicks on the diagnostic, the editor asks us for code actions
93 // 3. We send code actions, with the fixit embedded as context
94 // 4. The user selects the fixit, the editor asks us to apply it
95 // 5. We unwrap the changes and send them back to the editor
96 // 6. The editor applies the changes (applyEdit), and sends us a reply (but
97 // we ignore it)
98
99 ApplyWorkspaceEditParams ApplyEdit;
100 ApplyEdit.edit = *Params.workspaceEdit;
101 C.reply("\"Fix applied.\"");
102 // We don't need the response so id == 1 is OK.
103 // Ideally, we would wait for the response and if there is no error, we
104 // would reply success/failure to the original RPC.
105 C.call("workspace/applyEdit", ApplyWorkspaceEditParams::unparse(ApplyEdit));
106 } else {
107 // We should not get here because ExecuteCommandParams would not have
108 // parsed in the first place and this handler should not be called. But if
109 // more commands are added, this will be here has a safe guard.
110 C.replyError(
111 1, llvm::formatv("Unsupported command \"{0}\".", Params.command).str());
112 }
113}
114
Sam McCall8a5dded2017-10-12 13:29:58 +0000115void ClangdLSPServer::onDocumentDidClose(Ctx C,
116 DidCloseTextDocumentParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000117 Server.removeDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000118}
119
Sam McCall4db732a2017-09-30 10:08:52 +0000120void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCall8a5dded2017-10-12 13:29:58 +0000121 Ctx C, DocumentOnTypeFormattingParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000122 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +0000123 std::string Code = Server.getDocument(File);
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000124 std::string Edits = TextEdit::unparse(
125 replacementsToEdits(Code, Server.formatOnType(File, Params.position)));
126 C.reply(Edits);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000127}
128
Sam McCall4db732a2017-09-30 10:08:52 +0000129void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCall8a5dded2017-10-12 13:29:58 +0000130 Ctx C, DocumentRangeFormattingParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000131 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +0000132 std::string Code = Server.getDocument(File);
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000133 std::string Edits = TextEdit::unparse(
134 replacementsToEdits(Code, Server.formatRange(File, Params.range)));
135 C.reply(Edits);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000136}
137
Sam McCall8a5dded2017-10-12 13:29:58 +0000138void ClangdLSPServer::onDocumentFormatting(Ctx C,
139 DocumentFormattingParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000140 auto File = Params.textDocument.uri.file;
141 std::string Code = Server.getDocument(File);
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000142 std::string Edits =
143 TextEdit::unparse(replacementsToEdits(Code, Server.formatFile(File)));
144 C.reply(Edits);
Sam McCall4db732a2017-09-30 10:08:52 +0000145}
146
Sam McCall8a5dded2017-10-12 13:29:58 +0000147void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000148 // We provide a code action for each diagnostic at the requested location
149 // which has FixIts available.
Sam McCall4db732a2017-09-30 10:08:52 +0000150 std::string Code = Server.getDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000151 std::string Commands;
152 for (Diagnostic &D : Params.context.diagnostics) {
153 std::vector<clang::tooling::Replacement> Fixes =
Sam McCall4db732a2017-09-30 10:08:52 +0000154 getFixIts(Params.textDocument.uri.file, D);
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000155 auto Edits = replacementsToEdits(Code, Fixes);
156 WorkspaceEdit WE;
157 WE.changes = {{llvm::yaml::escape(Params.textDocument.uri.uri), Edits}};
Ilya Biryukovafb55542017-05-16 14:40:30 +0000158
159 if (!Edits.empty())
160 Commands +=
161 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000162 R"('", "command": ")" +
163 ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND +
164 R"(", "arguments": [)" + WorkspaceEdit::unparse(WE) + R"(]},)";
Ilya Biryukovafb55542017-05-16 14:40:30 +0000165 }
166 if (!Commands.empty())
167 Commands.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000168 C.reply("[" + Commands + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000169}
170
Sam McCall8a5dded2017-10-12 13:29:58 +0000171void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000172 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000173 .codeComplete(Params.textDocument.uri.file,
174 Position{Params.position.line,
175 Params.position.character})
Ilya Biryukovdcd21692017-10-05 17:04:13 +0000176 .get() // FIXME(ibiryukov): This could be made async if we
177 // had an API that would allow to attach callbacks to
178 // futures returned by ClangdServer.
Ilya Biryukov574b7532017-08-02 09:08:39 +0000179 .Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000180
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();
Sam McCall8a5dded2017-10-12 13:29:58 +0000188 C.reply("[" + Completions + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000189}
190
Sam McCall8a5dded2017-10-12 13:29:58 +0000191void ClangdLSPServer::onSignatureHelp(Ctx C,
192 TextDocumentPositionParams &Params) {
Benjamin Krameree19f162017-10-26 12:28:13 +0000193 auto SignatureHelp = Server.signatureHelp(
194 Params.textDocument.uri.file,
195 Position{Params.position.line, Params.position.character});
196 if (!SignatureHelp)
197 return C.replyError(-32602, llvm::toString(SignatureHelp.takeError()));
198 C.reply(SignatureHelp::unparse(SignatureHelp->Value));
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000199}
200
Sam McCall8a5dded2017-10-12 13:29:58 +0000201void ClangdLSPServer::onGoToDefinition(Ctx C,
202 TextDocumentPositionParams &Params) {
Benjamin Krameree19f162017-10-26 12:28:13 +0000203 auto Items = Server.findDefinitions(
204 Params.textDocument.uri.file,
205 Position{Params.position.line, Params.position.character});
206 if (!Items)
207 return C.replyError(-32602, llvm::toString(Items.takeError()));
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000208
209 std::string Locations;
Benjamin Krameree19f162017-10-26 12:28:13 +0000210 for (const auto &Item : Items->Value) {
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000211 Locations += Location::unparse(Item);
212 Locations += ",";
213 }
214 if (!Locations.empty())
215 Locations.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000216 C.reply("[" + Locations + "]");
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000217}
218
Sam McCall8a5dded2017-10-12 13:29:58 +0000219void ClangdLSPServer::onSwitchSourceHeader(Ctx C,
220 TextDocumentIdentifier &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000221 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000222 std::string ResultUri;
Sam McCall8a5dded2017-10-12 13:29:58 +0000223 C.reply(Result ? URI::unparse(URI::fromFile(*Result)) : R"("")");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000224}
225
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000226ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000227 bool SnippetCompletions,
Ilya Biryukov0c1ca6b2017-10-02 15:13:20 +0000228 llvm::Optional<StringRef> ResourceDir,
229 llvm::Optional<Path> CompileCommandsDir)
230 : Out(Out), CDB(/*Logger=*/Out, std::move(CompileCommandsDir)),
Sam McCall4db732a2017-09-30 10:08:52 +0000231 Server(CDB, /*DiagConsumer=*/*this, FSProvider, AsyncThreadsCount,
Ilya Biryukovb080cb12017-10-23 14:46:48 +0000232 clangd::CodeCompleteOptions(
233 /*EnableSnippetsAndCodePatterns=*/SnippetCompletions),
234 /*Logger=*/Out, ResourceDir) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000235
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000236bool ClangdLSPServer::run(std::istream &In) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000237 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000238
Ilya Biryukovafb55542017-05-16 14:40:30 +0000239 // Set up JSONRPCDispatcher.
Sam McCall8a5dded2017-10-12 13:29:58 +0000240 JSONRPCDispatcher Dispatcher(
241 [](RequestContext Ctx, llvm::yaml::MappingNode *Params) {
242 Ctx.replyError(-32601, "method not found");
243 });
Sam McCall4db732a2017-09-30 10:08:52 +0000244 registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000245
Ilya Biryukovafb55542017-05-16 14:40:30 +0000246 // Run the Language Server loop.
247 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
248
249 // Make sure IsDone is set to true after this method exits to ensure assertion
250 // at the start of the method fires if it's ever executed again.
251 IsDone = true;
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000252
253 return ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000254}
255
256std::vector<clang::tooling::Replacement>
257ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
258 std::lock_guard<std::mutex> Lock(FixItsMutex);
259 auto DiagToFixItsIter = FixItsMap.find(File);
260 if (DiagToFixItsIter == FixItsMap.end())
261 return {};
262
263 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
264 auto FixItsIter = DiagToFixItsMap.find(D);
265 if (FixItsIter == DiagToFixItsMap.end())
266 return {};
267
268 return FixItsIter->second;
269}
270
Sam McCall4db732a2017-09-30 10:08:52 +0000271void ClangdLSPServer::onDiagnosticsReady(
272 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000273 std::string DiagnosticsJSON;
274
275 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCall4db732a2017-09-30 10:08:52 +0000276 for (auto &DiagWithFixes : Diagnostics.Value) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000277 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
284 // We convert to Replacements to become independent of the SourceManager.
285 auto &FixItsForDiagnostic = LocalFixIts[Diag];
286 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
287 std::back_inserter(FixItsForDiagnostic));
288 }
289
290 // Cache FixIts
291 {
292 // FIXME(ibiryukov): should be deleted when documents are removed
293 std::lock_guard<std::mutex> Lock(FixItsMutex);
294 FixItsMap[File] = LocalFixIts;
295 }
296
297 // 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"(]}})");
304}