blob: 019b7ebc3daf29f8037160ef46d6991f9dc13156 [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//
Kirill Bobyrev8e35f1e2018-08-14 16:03:32 +00008//===----------------------------------------------------------------------===//
Ilya Biryukov38d79772017-05-16 09:38:59 +00009
10#include "ClangdLSPServer.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000011#include "Diagnostics.h"
Sam McCallb536a2a2017-12-19 12:23:48 +000012#include "SourceCode.h"
Sam McCall2c30fbc2018-10-18 12:32:04 +000013#include "Trace.h"
Eric Liu78ed91a72018-01-29 15:37:46 +000014#include "URI.h"
Kadir Cetinkaya689bf932018-08-24 13:09:41 +000015#include "llvm/ADT/ScopeExit.h"
Simon Marchi9569fd52018-03-16 14:30:42 +000016#include "llvm/Support/Errc.h"
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000017#include "llvm/Support/FormatVariadic.h"
Eric Liu5740ff52018-01-31 16:26:27 +000018#include "llvm/Support/Path.h"
Sam McCall2c30fbc2018-10-18 12:32:04 +000019#include "llvm/Support/ScopedPrinter.h"
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000020
Sam McCalld20d7982018-07-09 14:25:59 +000021using namespace llvm;
Sam McCallc008af62018-10-20 15:30:37 +000022namespace clang {
23namespace clangd {
Ilya Biryukovafb55542017-05-16 14:40:30 +000024namespace {
25
Eric Liu5740ff52018-01-31 16:26:27 +000026/// \brief Supports a test URI scheme with relaxed constraints for lit tests.
27/// The path in a test URI will be combined with a platform-specific fake
28/// directory to form an absolute path. For example, test:///a.cpp is resolved
29/// C:\clangd-test\a.cpp on Windows and /clangd-test/a.cpp on Unix.
30class TestScheme : public URIScheme {
31public:
Sam McCallc008af62018-10-20 15:30:37 +000032 Expected<std::string> getAbsolutePath(StringRef /*Authority*/, StringRef Body,
33 StringRef /*HintPath*/) const override {
Eric Liu5740ff52018-01-31 16:26:27 +000034 using namespace llvm::sys;
35 // Still require "/" in body to mimic file scheme, as we want lengths of an
36 // equivalent URI in both schemes to be the same.
37 if (!Body.startswith("/"))
Sam McCallc008af62018-10-20 15:30:37 +000038 return make_error<StringError>(
Eric Liu5740ff52018-01-31 16:26:27 +000039 "Expect URI body to be an absolute path starting with '/': " + Body,
Sam McCallc008af62018-10-20 15:30:37 +000040 inconvertibleErrorCode());
Eric Liu5740ff52018-01-31 16:26:27 +000041 Body = Body.ltrim('/');
Nico Weber0da22902018-04-10 13:14:03 +000042#ifdef _WIN32
Eric Liu5740ff52018-01-31 16:26:27 +000043 constexpr char TestDir[] = "C:\\clangd-test";
44#else
45 constexpr char TestDir[] = "/clangd-test";
46#endif
Sam McCallc008af62018-10-20 15:30:37 +000047 SmallVector<char, 16> Path(Body.begin(), Body.end());
Eric Liu5740ff52018-01-31 16:26:27 +000048 path::native(Path);
49 auto Err = fs::make_absolute(TestDir, Path);
Eric Liucda25262018-02-01 12:44:52 +000050 if (Err)
51 llvm_unreachable("Failed to make absolute path in test scheme.");
Eric Liu5740ff52018-01-31 16:26:27 +000052 return std::string(Path.begin(), Path.end());
53 }
54
Sam McCallc008af62018-10-20 15:30:37 +000055 Expected<URI> uriFromAbsolutePath(StringRef AbsolutePath) const override {
Eric Liu5740ff52018-01-31 16:26:27 +000056 llvm_unreachable("Clangd must never create a test URI.");
57 }
58};
59
60static URISchemeRegistry::Add<TestScheme>
61 X("test", "Test scheme for clangd lit tests.");
62
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000063SymbolKindBitset defaultSymbolKinds() {
64 SymbolKindBitset Defaults;
65 for (size_t I = SymbolKindMin; I <= static_cast<size_t>(SymbolKind::Array);
66 ++I)
67 Defaults.set(I);
68 return Defaults;
69}
70
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +000071CompletionItemKindBitset defaultCompletionItemKinds() {
72 CompletionItemKindBitset Defaults;
73 for (size_t I = CompletionItemKindMin;
74 I <= static_cast<size_t>(CompletionItemKind::Reference); ++I)
75 Defaults.set(I);
76 return Defaults;
77}
78
Ilya Biryukovafb55542017-05-16 14:40:30 +000079} // namespace
80
Sam McCall2c30fbc2018-10-18 12:32:04 +000081// MessageHandler dispatches incoming LSP messages.
82// It handles cross-cutting concerns:
83// - serializes/deserializes protocol objects to JSON
84// - logging of inbound messages
85// - cancellation handling
86// - basic call tracing
Sam McCall3d0adbe2018-10-18 14:41:50 +000087// MessageHandler ensures that initialize() is called before any other handler.
Sam McCall2c30fbc2018-10-18 12:32:04 +000088class ClangdLSPServer::MessageHandler : public Transport::MessageHandler {
89public:
90 MessageHandler(ClangdLSPServer &Server) : Server(Server) {}
91
92 bool onNotify(StringRef Method, json::Value Params) override {
93 log("<-- {0}", Method);
94 if (Method == "exit")
95 return false;
Sam McCall3d0adbe2018-10-18 14:41:50 +000096 if (!Server.Server)
97 elog("Notification {0} before initialization", Method);
98 else if (Method == "$/cancelRequest")
Sam McCall2c30fbc2018-10-18 12:32:04 +000099 onCancel(std::move(Params));
100 else if (auto Handler = Notifications.lookup(Method))
101 Handler(std::move(Params));
102 else
103 log("unhandled notification {0}", Method);
104 return true;
105 }
106
107 bool onCall(StringRef Method, json::Value Params, json::Value ID) override {
108 log("<-- {0}({1})", Method, ID);
Sam McCall3d0adbe2018-10-18 14:41:50 +0000109 if (!Server.Server && Method != "initialize") {
110 elog("Call {0} before initialization.", Method);
111 Server.reply(ID, make_error<LSPError>("server not initialized",
112 ErrorCode::ServerNotInitialized));
113 } else if (auto Handler = Calls.lookup(Method))
Sam McCall2c30fbc2018-10-18 12:32:04 +0000114 Handler(std::move(Params), std::move(ID));
115 else
Sam McCallc008af62018-10-20 15:30:37 +0000116 Server.reply(ID, make_error<LSPError>("method not found",
117 ErrorCode::MethodNotFound));
Sam McCall2c30fbc2018-10-18 12:32:04 +0000118 return true;
119 }
120
121 bool onReply(json::Value ID, Expected<json::Value> Result) override {
122 // We ignore replies, just log them.
123 if (Result)
124 log("<-- reply({0})", ID);
125 else
Sam McCallc008af62018-10-20 15:30:37 +0000126 log("<-- reply({0}) error: {1}", ID, toString(Result.takeError()));
Sam McCall2c30fbc2018-10-18 12:32:04 +0000127 return true;
128 }
129
130 // Bind an LSP method name to a call.
131 template <typename Param, typename Reply>
132 void bind(const char *Method,
133 void (ClangdLSPServer::*Handler)(const Param &, Callback<Reply>)) {
134 Calls[Method] = [Method, Handler, this](json::Value RawParams,
135 json::Value ID) {
136 Param P;
137 if (!fromJSON(RawParams, P)) {
138 elog("Failed to decode {0} request.", Method);
139 Server.reply(ID, make_error<LSPError>("failed to decode request",
140 ErrorCode::InvalidRequest));
141 return;
142 }
143 trace::Span Tracer(Method);
144 SPAN_ATTACH(Tracer, "Params", RawParams);
145 auto *Trace = Tracer.Args; // We attach reply from another thread.
146 // Calls can be canceled by the client. Add cancellation context.
147 WithContext WithCancel(cancelableRequestContext(ID));
148 // FIXME: this function should assert it's called exactly once.
Sam McCallc008af62018-10-20 15:30:37 +0000149 (Server.*Handler)(P, [this, ID, Trace](Expected<Reply> Result) {
Sam McCall2c30fbc2018-10-18 12:32:04 +0000150 if (Result) {
151 if (Trace)
152 (*Trace)["Reply"] = *Result;
153 Server.reply(ID, json::Value(std::move(*Result)));
154 } else {
155 auto Err = Result.takeError();
156 if (Trace)
Sam McCallc008af62018-10-20 15:30:37 +0000157 (*Trace)["Error"] = to_string(Err);
Sam McCall2c30fbc2018-10-18 12:32:04 +0000158 Server.reply(ID, std::move(Err));
159 }
160 });
161 };
162 }
163
164 // Bind an LSP method name to a notification.
165 template <typename Param>
166 void bind(const char *Method,
167 void (ClangdLSPServer::*Handler)(const Param &)) {
168 Notifications[Method] = [Method, Handler, this](json::Value RawParams) {
169 Param P;
170 if (!fromJSON(RawParams, P)) {
171 elog("Failed to decode {0} request.", Method);
172 return;
173 }
174 trace::Span Tracer(Method);
175 SPAN_ATTACH(Tracer, "Params", RawParams);
176 (Server.*Handler)(P);
177 };
178 }
179
180private:
Sam McCallc008af62018-10-20 15:30:37 +0000181 StringMap<std::function<void(json::Value)>> Notifications;
182 StringMap<std::function<void(json::Value, json::Value)>> Calls;
Sam McCall2c30fbc2018-10-18 12:32:04 +0000183
184 // Method calls may be cancelled by ID, so keep track of their state.
185 // This needs a mutex: handlers may finish on a different thread, and that's
186 // when we clean up entries in the map.
187 mutable std::mutex RequestCancelersMutex;
Sam McCallc008af62018-10-20 15:30:37 +0000188 StringMap<std::pair<Canceler, /*Cookie*/ unsigned>> RequestCancelers;
Sam McCall2c30fbc2018-10-18 12:32:04 +0000189 unsigned NextRequestCookie = 0; // To disambiguate reused IDs, see below.
Sam McCallc008af62018-10-20 15:30:37 +0000190 void onCancel(const json::Value &Params) {
Sam McCall2c30fbc2018-10-18 12:32:04 +0000191 const json::Value *ID = nullptr;
192 if (auto *O = Params.getAsObject())
193 ID = O->get("id");
194 if (!ID) {
195 elog("Bad cancellation request: {0}", Params);
196 return;
197 }
Sam McCallc008af62018-10-20 15:30:37 +0000198 auto StrID = to_string(*ID);
Sam McCall2c30fbc2018-10-18 12:32:04 +0000199 std::lock_guard<std::mutex> Lock(RequestCancelersMutex);
200 auto It = RequestCancelers.find(StrID);
201 if (It != RequestCancelers.end())
202 It->second.first(); // Invoke the canceler.
203 }
204 // We run cancelable requests in a context that does two things:
205 // - allows cancellation using RequestCancelers[ID]
206 // - cleans up the entry in RequestCancelers when it's no longer needed
207 // If a client reuses an ID, the last wins and the first cannot be canceled.
208 Context cancelableRequestContext(const json::Value &ID) {
209 auto Task = cancelableTask();
Sam McCallc008af62018-10-20 15:30:37 +0000210 auto StrID = to_string(ID); // JSON-serialize ID for map key.
Sam McCall2c30fbc2018-10-18 12:32:04 +0000211 auto Cookie = NextRequestCookie++; // No lock, only called on main thread.
212 {
213 std::lock_guard<std::mutex> Lock(RequestCancelersMutex);
214 RequestCancelers[StrID] = {std::move(Task.second), Cookie};
215 }
216 // When the request ends, we can clean up the entry we just added.
217 // The cookie lets us check that it hasn't been overwritten due to ID
218 // reuse.
219 return Task.first.derive(make_scope_exit([this, StrID, Cookie] {
220 std::lock_guard<std::mutex> Lock(RequestCancelersMutex);
221 auto It = RequestCancelers.find(StrID);
222 if (It != RequestCancelers.end() && It->second.second == Cookie)
223 RequestCancelers.erase(It);
224 }));
225 }
226
227 ClangdLSPServer &Server;
228};
229
230// call(), notify(), and reply() wrap the Transport, adding logging and locking.
231void ClangdLSPServer::call(StringRef Method, json::Value Params) {
232 auto ID = NextCallID++;
233 log("--> {0}({1})", Method, ID);
234 // We currently don't handle responses, so no need to store ID anywhere.
235 std::lock_guard<std::mutex> Lock(TranspWriter);
236 Transp.call(Method, std::move(Params), ID);
237}
238
239void ClangdLSPServer::notify(StringRef Method, json::Value Params) {
240 log("--> {0}", Method);
241 std::lock_guard<std::mutex> Lock(TranspWriter);
242 Transp.notify(Method, std::move(Params));
243}
244
Sam McCallc008af62018-10-20 15:30:37 +0000245void ClangdLSPServer::reply(json::Value ID, Expected<json::Value> Result) {
Sam McCall2c30fbc2018-10-18 12:32:04 +0000246 if (Result) {
247 log("--> reply({0})", ID);
248 std::lock_guard<std::mutex> Lock(TranspWriter);
249 Transp.reply(std::move(ID), std::move(Result));
250 } else {
251 Error Err = Result.takeError();
252 log("--> reply({0}) error: {1}", ID, Err);
253 std::lock_guard<std::mutex> Lock(TranspWriter);
254 Transp.reply(std::move(ID), std::move(Err));
255 }
256}
257
258void ClangdLSPServer::onInitialize(const InitializeParams &Params,
259 Callback<json::Value> Reply) {
Sam McCall0d9b40f2018-10-19 15:42:23 +0000260 if (Params.rootUri && *Params.rootUri)
261 ClangdServerOpts.WorkspaceRoot = Params.rootUri->file();
262 else if (Params.rootPath && !Params.rootPath->empty())
263 ClangdServerOpts.WorkspaceRoot = *Params.rootPath;
Sam McCall3d0adbe2018-10-18 14:41:50 +0000264 if (Server)
265 return Reply(make_error<LSPError>("server already initialized",
266 ErrorCode::InvalidRequest));
Sam McCalld1c9d112018-10-23 14:19:54 +0000267 Optional<Path> CompileCommandsDir;
268 if (Params.initializationOptions)
269 CompileCommandsDir = Params.initializationOptions->compilationDatabasePath;
270 CDB.emplace(UseInMemoryCDB
271 ? CompilationDB::makeInMemory()
272 : CompilationDB::makeDirectoryBased(CompileCommandsDir));
273 Server.emplace(CDB->getCDB(), FSProvider,
Sam McCall3d0adbe2018-10-18 14:41:50 +0000274 static_cast<DiagnosticsConsumer &>(*this), ClangdServerOpts);
Sam McCalld1c9d112018-10-23 14:19:54 +0000275 if (Params.initializationOptions)
276 applyConfiguration(Params.initializationOptions->ParamsChange);
Simon Marchi88016782018-08-01 11:28:49 +0000277
Sam McCallbf6a2fc2018-10-17 07:33:42 +0000278 CCOpts.EnableSnippets = Params.capabilities.CompletionSnippets;
279 DiagOpts.EmbedFixesInDiagnostics = Params.capabilities.DiagnosticFixes;
280 DiagOpts.SendDiagnosticCategory = Params.capabilities.DiagnosticCategory;
281 if (Params.capabilities.WorkspaceSymbolKinds)
282 SupportedSymbolKinds |= *Params.capabilities.WorkspaceSymbolKinds;
283 if (Params.capabilities.CompletionItemKinds)
284 SupportedCompletionItemKinds |= *Params.capabilities.CompletionItemKinds;
285 SupportsCodeAction = Params.capabilities.CodeActionStructure;
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +0000286
Sam McCall2c30fbc2018-10-18 12:32:04 +0000287 Reply(json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000288 {{"capabilities",
Sam McCalld20d7982018-07-09 14:25:59 +0000289 json::Object{
Simon Marchi98082622018-03-26 14:41:40 +0000290 {"textDocumentSync", (int)TextDocumentSyncKind::Incremental},
Sam McCall0930ab02017-11-07 15:49:35 +0000291 {"documentFormattingProvider", true},
292 {"documentRangeFormattingProvider", true},
293 {"documentOnTypeFormattingProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000294 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000295 {"firstTriggerCharacter", "}"},
296 {"moreTriggerCharacter", {}},
297 }},
298 {"codeActionProvider", true},
299 {"completionProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000300 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000301 {"resolveProvider", false},
302 {"triggerCharacters", {".", ">", ":"}},
303 }},
304 {"signatureHelpProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000305 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000306 {"triggerCharacters", {"(", ","}},
307 }},
308 {"definitionProvider", true},
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000309 {"documentHighlightProvider", true},
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000310 {"hoverProvider", true},
Haojian Wu345099c2017-11-09 11:30:04 +0000311 {"renameProvider", true},
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000312 {"documentSymbolProvider", true},
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000313 {"workspaceSymbolProvider", true},
Sam McCall1ad142f2018-09-05 11:53:07 +0000314 {"referencesProvider", true},
Sam McCall0930ab02017-11-07 15:49:35 +0000315 {"executeCommandProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000316 json::Object{
Eric Liu2c190532018-05-15 15:23:53 +0000317 {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
Sam McCall0930ab02017-11-07 15:49:35 +0000318 }},
319 }}}});
Ilya Biryukovafb55542017-05-16 14:40:30 +0000320}
321
Sam McCall2c30fbc2018-10-18 12:32:04 +0000322void ClangdLSPServer::onShutdown(const ShutdownParams &Params,
323 Callback<std::nullptr_t> Reply) {
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000324 // Do essentially nothing, just say we're ready to exit.
325 ShutdownRequestReceived = true;
Sam McCall2c30fbc2018-10-18 12:32:04 +0000326 Reply(nullptr);
Sam McCall8a5dded2017-10-12 13:29:58 +0000327}
Ilya Biryukovafb55542017-05-16 14:40:30 +0000328
Sam McCall2c30fbc2018-10-18 12:32:04 +0000329void ClangdLSPServer::onDocumentDidOpen(
330 const DidOpenTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000331 PathRef File = Params.textDocument.uri.file();
Alex Lorenzf8087862018-08-01 17:39:29 +0000332 if (Params.metadata && !Params.metadata->extraFlags.empty())
Sam McCalld1c9d112018-10-23 14:19:54 +0000333 CDB->setExtraFlagsForFile(File, std::move(Params.metadata->extraFlags));
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000334
Sam McCall2c30fbc2018-10-18 12:32:04 +0000335 const std::string &Contents = Params.textDocument.text;
Simon Marchi9569fd52018-03-16 14:30:42 +0000336
Simon Marchi98082622018-03-26 14:41:40 +0000337 DraftMgr.addDraft(File, Contents);
Ilya Biryukov652364b2018-09-26 05:48:29 +0000338 Server->addDocument(File, Contents, WantDiagnostics::Yes);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000339}
340
Sam McCall2c30fbc2018-10-18 12:32:04 +0000341void ClangdLSPServer::onDocumentDidChange(
342 const DidChangeTextDocumentParams &Params) {
Eric Liu51fed182018-02-22 18:40:39 +0000343 auto WantDiags = WantDiagnostics::Auto;
344 if (Params.wantDiagnostics.hasValue())
345 WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes
346 : WantDiagnostics::No;
Simon Marchi9569fd52018-03-16 14:30:42 +0000347
348 PathRef File = Params.textDocument.uri.file();
Sam McCallc008af62018-10-20 15:30:37 +0000349 Expected<std::string> Contents =
Simon Marchi98082622018-03-26 14:41:40 +0000350 DraftMgr.updateDraft(File, Params.contentChanges);
351 if (!Contents) {
352 // If this fails, we are most likely going to be not in sync anymore with
353 // the client. It is better to remove the draft and let further operations
354 // fail rather than giving wrong results.
355 DraftMgr.removeDraft(File);
Ilya Biryukov652364b2018-09-26 05:48:29 +0000356 Server->removeDocument(File);
Sam McCalld1c9d112018-10-23 14:19:54 +0000357 CDB->invalidate(File);
Sam McCallbed58852018-07-11 10:35:11 +0000358 elog("Failed to update {0}: {1}", File, Contents.takeError());
Simon Marchi98082622018-03-26 14:41:40 +0000359 return;
360 }
Simon Marchi9569fd52018-03-16 14:30:42 +0000361
Ilya Biryukov652364b2018-09-26 05:48:29 +0000362 Server->addDocument(File, *Contents, WantDiags);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000363}
364
Sam McCall2c30fbc2018-10-18 12:32:04 +0000365void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000366 Server->onFileEvent(Params);
Marc-Andre Laperlebf114242017-10-02 18:00:37 +0000367}
368
Sam McCall2c30fbc2018-10-18 12:32:04 +0000369void ClangdLSPServer::onCommand(const ExecuteCommandParams &Params,
370 Callback<json::Value> Reply) {
371 auto ApplyEdit = [&](WorkspaceEdit WE) {
Eric Liuc5105f92018-02-16 14:15:55 +0000372 ApplyWorkspaceEditParams Edit;
373 Edit.edit = std::move(WE);
Eric Liuc5105f92018-02-16 14:15:55 +0000374 // Ideally, we would wait for the response and if there is no error, we
375 // would reply success/failure to the original RPC.
376 call("workspace/applyEdit", Edit);
377 };
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000378 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
379 Params.workspaceEdit) {
380 // The flow for "apply-fix" :
381 // 1. We publish a diagnostic, including fixits
382 // 2. The user clicks on the diagnostic, the editor asks us for code actions
383 // 3. We send code actions, with the fixit embedded as context
384 // 4. The user selects the fixit, the editor asks us to apply it
385 // 5. We unwrap the changes and send them back to the editor
386 // 6. The editor applies the changes (applyEdit), and sends us a reply (but
387 // we ignore it)
388
Sam McCall2c30fbc2018-10-18 12:32:04 +0000389 Reply("Fix applied.");
Eric Liuc5105f92018-02-16 14:15:55 +0000390 ApplyEdit(*Params.workspaceEdit);
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000391 } else {
392 // We should not get here because ExecuteCommandParams would not have
393 // parsed in the first place and this handler should not be called. But if
394 // more commands are added, this will be here has a safe guard.
Sam McCall2c30fbc2018-10-18 12:32:04 +0000395 Reply(make_error<LSPError>(
Sam McCallc008af62018-10-20 15:30:37 +0000396 formatv("Unsupported command \"{0}\".", Params.command).str(),
Sam McCall2c30fbc2018-10-18 12:32:04 +0000397 ErrorCode::InvalidParams));
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000398 }
399}
400
Sam McCall2c30fbc2018-10-18 12:32:04 +0000401void ClangdLSPServer::onWorkspaceSymbol(
402 const WorkspaceSymbolParams &Params,
403 Callback<std::vector<SymbolInformation>> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000404 Server->workspaceSymbols(
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000405 Params.query, CCOpts.Limit,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000406 Bind(
407 [this](decltype(Reply) Reply,
Sam McCallc008af62018-10-20 15:30:37 +0000408 Expected<std::vector<SymbolInformation>> Items) {
Sam McCall2c30fbc2018-10-18 12:32:04 +0000409 if (!Items)
410 return Reply(Items.takeError());
411 for (auto &Sym : *Items)
412 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000413
Sam McCall2c30fbc2018-10-18 12:32:04 +0000414 Reply(std::move(*Items));
415 },
416 std::move(Reply)));
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000417}
418
Sam McCall2c30fbc2018-10-18 12:32:04 +0000419void ClangdLSPServer::onRename(const RenameParams &Params,
420 Callback<WorkspaceEdit> Reply) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000421 Path File = Params.textDocument.uri.file();
Sam McCallc008af62018-10-20 15:30:37 +0000422 Optional<std::string> Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000423 if (!Code)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000424 return Reply(make_error<LSPError>("onRename called for non-added file",
425 ErrorCode::InvalidParams));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000426
Ilya Biryukov652364b2018-09-26 05:48:29 +0000427 Server->rename(
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000428 File, Params.position, Params.newName,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000429 Bind(
Sam McCallc008af62018-10-20 15:30:37 +0000430 [File, Code,
431 Params](decltype(Reply) Reply,
432 Expected<std::vector<tooling::Replacement>> Replacements) {
Sam McCall2c30fbc2018-10-18 12:32:04 +0000433 if (!Replacements)
434 return Reply(Replacements.takeError());
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000435
Sam McCall2c30fbc2018-10-18 12:32:04 +0000436 // Turn the replacements into the format specified by the Language
437 // Server Protocol. Fuse them into one big JSON array.
438 std::vector<TextEdit> Edits;
439 for (const auto &R : *Replacements)
440 Edits.push_back(replacementToEdit(*Code, R));
441 WorkspaceEdit WE;
442 WE.changes = {{Params.textDocument.uri.uri(), Edits}};
443 Reply(WE);
444 },
445 std::move(Reply)));
Haojian Wu345099c2017-11-09 11:30:04 +0000446}
447
Sam McCall2c30fbc2018-10-18 12:32:04 +0000448void ClangdLSPServer::onDocumentDidClose(
449 const DidCloseTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000450 PathRef File = Params.textDocument.uri.file();
451 DraftMgr.removeDraft(File);
Ilya Biryukov652364b2018-09-26 05:48:29 +0000452 Server->removeDocument(File);
Sam McCalld1c9d112018-10-23 14:19:54 +0000453 CDB->invalidate(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000454}
455
Sam McCall4db732a2017-09-30 10:08:52 +0000456void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCall2c30fbc2018-10-18 12:32:04 +0000457 const DocumentOnTypeFormattingParams &Params,
458 Callback<std::vector<TextEdit>> Reply) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000459 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000460 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000461 if (!Code)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000462 return Reply(make_error<LSPError>(
463 "onDocumentOnTypeFormatting called for non-added file",
464 ErrorCode::InvalidParams));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000465
Ilya Biryukov652364b2018-09-26 05:48:29 +0000466 auto ReplacementsOrError = Server->formatOnType(*Code, File, Params.position);
Raoul Wols212bcf82017-12-12 20:25:06 +0000467 if (ReplacementsOrError)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000468 Reply(replacementsToEdits(*Code, ReplacementsOrError.get()));
Raoul Wols212bcf82017-12-12 20:25:06 +0000469 else
Sam McCall2c30fbc2018-10-18 12:32:04 +0000470 Reply(ReplacementsOrError.takeError());
Ilya Biryukovafb55542017-05-16 14:40:30 +0000471}
472
Sam McCall4db732a2017-09-30 10:08:52 +0000473void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCall2c30fbc2018-10-18 12:32:04 +0000474 const DocumentRangeFormattingParams &Params,
475 Callback<std::vector<TextEdit>> Reply) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000476 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000477 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000478 if (!Code)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000479 return Reply(make_error<LSPError>(
480 "onDocumentRangeFormatting called for non-added file",
481 ErrorCode::InvalidParams));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000482
Ilya Biryukov652364b2018-09-26 05:48:29 +0000483 auto ReplacementsOrError = Server->formatRange(*Code, File, Params.range);
Raoul Wols212bcf82017-12-12 20:25:06 +0000484 if (ReplacementsOrError)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000485 Reply(replacementsToEdits(*Code, ReplacementsOrError.get()));
Raoul Wols212bcf82017-12-12 20:25:06 +0000486 else
Sam McCall2c30fbc2018-10-18 12:32:04 +0000487 Reply(ReplacementsOrError.takeError());
Ilya Biryukovafb55542017-05-16 14:40:30 +0000488}
489
Sam McCall2c30fbc2018-10-18 12:32:04 +0000490void ClangdLSPServer::onDocumentFormatting(
491 const DocumentFormattingParams &Params,
492 Callback<std::vector<TextEdit>> Reply) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000493 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000494 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000495 if (!Code)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000496 return Reply(
497 make_error<LSPError>("onDocumentFormatting called for non-added file",
498 ErrorCode::InvalidParams));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000499
Ilya Biryukov652364b2018-09-26 05:48:29 +0000500 auto ReplacementsOrError = Server->formatFile(*Code, File);
Raoul Wols212bcf82017-12-12 20:25:06 +0000501 if (ReplacementsOrError)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000502 Reply(replacementsToEdits(*Code, ReplacementsOrError.get()));
Raoul Wols212bcf82017-12-12 20:25:06 +0000503 else
Sam McCall2c30fbc2018-10-18 12:32:04 +0000504 Reply(ReplacementsOrError.takeError());
Sam McCall4db732a2017-09-30 10:08:52 +0000505}
506
Sam McCall2c30fbc2018-10-18 12:32:04 +0000507void ClangdLSPServer::onDocumentSymbol(
508 const DocumentSymbolParams &Params,
509 Callback<std::vector<SymbolInformation>> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000510 Server->documentSymbols(
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000511 Params.textDocument.uri.file(),
Sam McCall2c30fbc2018-10-18 12:32:04 +0000512 Bind(
513 [this](decltype(Reply) Reply,
Sam McCallc008af62018-10-20 15:30:37 +0000514 Expected<std::vector<SymbolInformation>> Items) {
Sam McCall2c30fbc2018-10-18 12:32:04 +0000515 if (!Items)
516 return Reply(Items.takeError());
517 for (auto &Sym : *Items)
518 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
519 Reply(std::move(*Items));
520 },
521 std::move(Reply)));
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000522}
523
Sam McCall20841d42018-10-16 16:29:41 +0000524static Optional<Command> asCommand(const CodeAction &Action) {
525 Command Cmd;
526 if (Action.command && Action.edit)
Sam McCallc008af62018-10-20 15:30:37 +0000527 return None; // Not representable. (We never emit these anyway).
Sam McCall20841d42018-10-16 16:29:41 +0000528 if (Action.command) {
529 Cmd = *Action.command;
530 } else if (Action.edit) {
531 Cmd.command = Command::CLANGD_APPLY_FIX_COMMAND;
532 Cmd.workspaceEdit = *Action.edit;
533 } else {
Sam McCallc008af62018-10-20 15:30:37 +0000534 return None;
Sam McCall20841d42018-10-16 16:29:41 +0000535 }
536 Cmd.title = Action.title;
537 if (Action.kind && *Action.kind == CodeAction::QUICKFIX_KIND)
538 Cmd.title = "Apply fix: " + Cmd.title;
539 return Cmd;
540}
541
Sam McCall2c30fbc2018-10-18 12:32:04 +0000542void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
543 Callback<json::Value> Reply) {
544 auto Code = DraftMgr.getDraft(Params.textDocument.uri.file());
545 if (!Code)
546 return Reply(make_error<LSPError>("onCodeAction called for non-added file",
547 ErrorCode::InvalidParams));
Sam McCall20841d42018-10-16 16:29:41 +0000548 // We provide a code action for Fixes on the specified diagnostics.
Sam McCall20841d42018-10-16 16:29:41 +0000549 std::vector<CodeAction> Actions;
Sam McCall2c30fbc2018-10-18 12:32:04 +0000550 for (const Diagnostic &D : Params.context.diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000551 for (auto &F : getFixes(Params.textDocument.uri.file(), D)) {
Sam McCall20841d42018-10-16 16:29:41 +0000552 Actions.emplace_back();
553 Actions.back().title = F.Message;
554 Actions.back().kind = CodeAction::QUICKFIX_KIND;
555 Actions.back().diagnostics = {D};
556 Actions.back().edit.emplace();
557 Actions.back().edit->changes.emplace();
558 (*Actions.back().edit->changes)[Params.textDocument.uri.uri()] = {
559 F.Edits.begin(), F.Edits.end()};
Sam McCalldd0566b2017-11-06 15:40:30 +0000560 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000561 }
Sam McCall20841d42018-10-16 16:29:41 +0000562
563 if (SupportsCodeAction)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000564 Reply(json::Array(Actions));
Sam McCall20841d42018-10-16 16:29:41 +0000565 else {
566 std::vector<Command> Commands;
567 for (const auto &Action : Actions)
568 if (auto Command = asCommand(Action))
569 Commands.push_back(std::move(*Command));
Sam McCall2c30fbc2018-10-18 12:32:04 +0000570 Reply(json::Array(Commands));
Sam McCall20841d42018-10-16 16:29:41 +0000571 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000572}
573
Sam McCall2c30fbc2018-10-18 12:32:04 +0000574void ClangdLSPServer::onCompletion(const TextDocumentPositionParams &Params,
575 Callback<CompletionList> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000576 Server->codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000577 Bind(
578 [this](decltype(Reply) Reply,
Sam McCallc008af62018-10-20 15:30:37 +0000579 Expected<CodeCompleteResult> List) {
Sam McCall2c30fbc2018-10-18 12:32:04 +0000580 if (!List)
581 return Reply(List.takeError());
582 CompletionList LSPList;
583 LSPList.isIncomplete = List->HasMore;
584 for (const auto &R : List->Completions) {
585 CompletionItem C = R.render(CCOpts);
586 C.kind = adjustKindToCapability(
587 C.kind, SupportedCompletionItemKinds);
588 LSPList.items.push_back(std::move(C));
589 }
590 return Reply(std::move(LSPList));
591 },
592 std::move(Reply)));
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000593}
594
Sam McCall2c30fbc2018-10-18 12:32:04 +0000595void ClangdLSPServer::onSignatureHelp(const TextDocumentPositionParams &Params,
596 Callback<SignatureHelp> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000597 Server->signatureHelp(Params.textDocument.uri.file(), Params.position,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000598 std::move(Reply));
Ilya Biryukov652364b2018-09-26 05:48:29 +0000599}
600
Sam McCall2c30fbc2018-10-18 12:32:04 +0000601void ClangdLSPServer::onGoToDefinition(const TextDocumentPositionParams &Params,
602 Callback<std::vector<Location>> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000603 Server->findDefinitions(Params.textDocument.uri.file(), Params.position,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000604 std::move(Reply));
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000605}
606
Sam McCall2c30fbc2018-10-18 12:32:04 +0000607void ClangdLSPServer::onSwitchSourceHeader(const TextDocumentIdentifier &Params,
608 Callback<std::string> Reply) {
Sam McCallc008af62018-10-20 15:30:37 +0000609 Optional<Path> Result = Server->switchSourceHeader(Params.uri.file());
Sam McCall2c30fbc2018-10-18 12:32:04 +0000610 Reply(Result ? URI::createFile(*Result).toString() : "");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000611}
612
Sam McCall2c30fbc2018-10-18 12:32:04 +0000613void ClangdLSPServer::onDocumentHighlight(
614 const TextDocumentPositionParams &Params,
615 Callback<std::vector<DocumentHighlight>> Reply) {
616 Server->findDocumentHighlights(Params.textDocument.uri.file(),
617 Params.position, std::move(Reply));
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000618}
619
Sam McCall2c30fbc2018-10-18 12:32:04 +0000620void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
Sam McCallc008af62018-10-20 15:30:37 +0000621 Callback<Optional<Hover>> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000622 Server->findHover(Params.textDocument.uri.file(), Params.position,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000623 std::move(Reply));
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000624}
625
Simon Marchi88016782018-08-01 11:28:49 +0000626void ClangdLSPServer::applyConfiguration(
Simon Marchiabeed662018-10-16 15:55:03 +0000627 const ClangdConfigurationParamsChange &Params) {
628 // Per-file update to the compilation database.
629 if (Params.compilationDatabaseChanges) {
630 const auto &CompileCommandUpdates = *Params.compilationDatabaseChanges;
Alex Lorenzf8087862018-08-01 17:39:29 +0000631 bool ShouldReparseOpenFiles = false;
632 for (auto &Entry : CompileCommandUpdates) {
633 /// The opened files need to be reparsed only when some existing
634 /// entries are changed.
635 PathRef File = Entry.first;
Sam McCalld1c9d112018-10-23 14:19:54 +0000636 if (!CDB->setCompilationCommandForFile(
Alex Lorenzf8087862018-08-01 17:39:29 +0000637 File, tooling::CompileCommand(
638 std::move(Entry.second.workingDirectory), File,
639 std::move(Entry.second.compilationCommand),
640 /*Output=*/"")))
641 ShouldReparseOpenFiles = true;
642 }
643 if (ShouldReparseOpenFiles)
644 reparseOpenedFiles();
645 }
Simon Marchi5178f922018-02-22 14:00:39 +0000646}
647
Simon Marchi88016782018-08-01 11:28:49 +0000648// FIXME: This function needs to be properly tested.
649void ClangdLSPServer::onChangeConfiguration(
Sam McCall2c30fbc2018-10-18 12:32:04 +0000650 const DidChangeConfigurationParams &Params) {
Simon Marchi88016782018-08-01 11:28:49 +0000651 applyConfiguration(Params.settings);
652}
653
Sam McCall2c30fbc2018-10-18 12:32:04 +0000654void ClangdLSPServer::onReference(const ReferenceParams &Params,
655 Callback<std::vector<Location>> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000656 Server->findReferences(Params.textDocument.uri.file(), Params.position,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000657 std::move(Reply));
Sam McCall1ad142f2018-09-05 11:53:07 +0000658}
659
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000660ClangdLSPServer::ClangdLSPServer(class Transport &Transp,
Sam McCalladccab62017-11-23 16:58:22 +0000661 const clangd::CodeCompleteOptions &CCOpts,
Sam McCallc008af62018-10-20 15:30:37 +0000662 Optional<Path> CompileCommandsDir,
Alex Lorenzf8087862018-08-01 17:39:29 +0000663 bool ShouldUseInMemoryCDB,
Sam McCall7363a2f2018-03-05 17:28:54 +0000664 const ClangdServer::Options &Opts)
Sam McCalld1c9d112018-10-23 14:19:54 +0000665 : Transp(Transp), MsgHandler(new MessageHandler(*this)), CCOpts(CCOpts),
666 SupportedSymbolKinds(defaultSymbolKinds()),
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +0000667 SupportedCompletionItemKinds(defaultCompletionItemKinds()),
Sam McCalld1c9d112018-10-23 14:19:54 +0000668 UseInMemoryCDB(ShouldUseInMemoryCDB), ClangdServerOpts(Opts) {
Sam McCall2c30fbc2018-10-18 12:32:04 +0000669 // clang-format off
670 MsgHandler->bind("initialize", &ClangdLSPServer::onInitialize);
671 MsgHandler->bind("shutdown", &ClangdLSPServer::onShutdown);
672 MsgHandler->bind("textDocument/rangeFormatting", &ClangdLSPServer::onDocumentRangeFormatting);
673 MsgHandler->bind("textDocument/onTypeFormatting", &ClangdLSPServer::onDocumentOnTypeFormatting);
674 MsgHandler->bind("textDocument/formatting", &ClangdLSPServer::onDocumentFormatting);
675 MsgHandler->bind("textDocument/codeAction", &ClangdLSPServer::onCodeAction);
676 MsgHandler->bind("textDocument/completion", &ClangdLSPServer::onCompletion);
677 MsgHandler->bind("textDocument/signatureHelp", &ClangdLSPServer::onSignatureHelp);
678 MsgHandler->bind("textDocument/definition", &ClangdLSPServer::onGoToDefinition);
679 MsgHandler->bind("textDocument/references", &ClangdLSPServer::onReference);
680 MsgHandler->bind("textDocument/switchSourceHeader", &ClangdLSPServer::onSwitchSourceHeader);
681 MsgHandler->bind("textDocument/rename", &ClangdLSPServer::onRename);
682 MsgHandler->bind("textDocument/hover", &ClangdLSPServer::onHover);
683 MsgHandler->bind("textDocument/documentSymbol", &ClangdLSPServer::onDocumentSymbol);
684 MsgHandler->bind("workspace/executeCommand", &ClangdLSPServer::onCommand);
685 MsgHandler->bind("textDocument/documentHighlight", &ClangdLSPServer::onDocumentHighlight);
686 MsgHandler->bind("workspace/symbol", &ClangdLSPServer::onWorkspaceSymbol);
687 MsgHandler->bind("textDocument/didOpen", &ClangdLSPServer::onDocumentDidOpen);
688 MsgHandler->bind("textDocument/didClose", &ClangdLSPServer::onDocumentDidClose);
689 MsgHandler->bind("textDocument/didChange", &ClangdLSPServer::onDocumentDidChange);
690 MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent);
691 MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration);
692 // clang-format on
693}
694
695ClangdLSPServer::~ClangdLSPServer() = default;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000696
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000697bool ClangdLSPServer::run() {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000698 // Run the Language Server loop.
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000699 bool CleanExit = true;
Sam McCall2c30fbc2018-10-18 12:32:04 +0000700 if (auto Err = Transp.loop(*MsgHandler)) {
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000701 elog("Transport error: {0}", std::move(Err));
702 CleanExit = false;
703 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000704
Ilya Biryukov652364b2018-09-26 05:48:29 +0000705 // Destroy ClangdServer to ensure all worker threads finish.
706 Server.reset();
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000707 return CleanExit && ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000708}
709
Ilya Biryukov71028b82018-03-12 15:28:22 +0000710std::vector<Fix> ClangdLSPServer::getFixes(StringRef File,
711 const clangd::Diagnostic &D) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000712 std::lock_guard<std::mutex> Lock(FixItsMutex);
713 auto DiagToFixItsIter = FixItsMap.find(File);
714 if (DiagToFixItsIter == FixItsMap.end())
715 return {};
716
717 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
718 auto FixItsIter = DiagToFixItsMap.find(D);
719 if (FixItsIter == DiagToFixItsMap.end())
720 return {};
721
722 return FixItsIter->second;
723}
724
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000725void ClangdLSPServer::onDiagnosticsReady(PathRef File,
726 std::vector<Diag> Diagnostics) {
Sam McCalld20d7982018-07-09 14:25:59 +0000727 json::Array DiagnosticsJSON;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000728
729 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000730 for (auto &Diag : Diagnostics) {
Sam McCallc008af62018-10-20 15:30:37 +0000731 toLSPDiags(Diag, [&](clangd::Diagnostic Diag, ArrayRef<Fix> Fixes) {
Alex Lorenz8626d362018-08-10 17:25:07 +0000732 json::Object LSPDiag({
Ilya Biryukov71028b82018-03-12 15:28:22 +0000733 {"range", Diag.range},
734 {"severity", Diag.severity},
735 {"message", Diag.message},
736 });
Alex Lorenz8626d362018-08-10 17:25:07 +0000737 // LSP extension: embed the fixes in the diagnostic.
738 if (DiagOpts.EmbedFixesInDiagnostics && !Fixes.empty()) {
739 json::Array ClangdFixes;
740 for (const auto &Fix : Fixes) {
741 WorkspaceEdit WE;
742 URIForFile URI{File};
743 WE.changes = {{URI.uri(), std::vector<TextEdit>(Fix.Edits.begin(),
744 Fix.Edits.end())}};
745 ClangdFixes.push_back(
746 json::Object{{"edit", toJSON(WE)}, {"title", Fix.Message}});
747 }
748 LSPDiag["clangd_fixes"] = std::move(ClangdFixes);
749 }
Alex Lorenz0ce8a7a2018-08-22 20:30:06 +0000750 if (DiagOpts.SendDiagnosticCategory && !Diag.category.empty())
Alex Lorenz37146432018-08-14 22:21:40 +0000751 LSPDiag["category"] = Diag.category;
Alex Lorenz8626d362018-08-10 17:25:07 +0000752 DiagnosticsJSON.push_back(std::move(LSPDiag));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000753
754 auto &FixItsForDiagnostic = LocalFixIts[Diag];
Kirill Bobyrev4a5ff882018-10-07 14:49:41 +0000755 llvm::copy(Fixes, std::back_inserter(FixItsForDiagnostic));
Sam McCalldd0566b2017-11-06 15:40:30 +0000756 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000757 }
758
759 // Cache FixIts
760 {
761 // FIXME(ibiryukov): should be deleted when documents are removed
762 std::lock_guard<std::mutex> Lock(FixItsMutex);
763 FixItsMap[File] = LocalFixIts;
764 }
765
766 // Publish diagnostics.
Sam McCall2c30fbc2018-10-18 12:32:04 +0000767 notify("textDocument/publishDiagnostics",
768 json::Object{
769 {"uri", URIForFile{File}},
770 {"diagnostics", std::move(DiagnosticsJSON)},
771 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000772}
Simon Marchi9569fd52018-03-16 14:30:42 +0000773
774void ClangdLSPServer::reparseOpenedFiles() {
775 for (const Path &FilePath : DraftMgr.getActiveFiles())
Ilya Biryukov652364b2018-09-26 05:48:29 +0000776 Server->addDocument(FilePath, *DraftMgr.getDraft(FilePath),
777 WantDiagnostics::Auto);
Simon Marchi9569fd52018-03-16 14:30:42 +0000778}
Alex Lorenzf8087862018-08-01 17:39:29 +0000779
780ClangdLSPServer::CompilationDB ClangdLSPServer::CompilationDB::makeInMemory() {
Sam McCall2172ee92018-10-23 13:14:02 +0000781 return CompilationDB(llvm::make_unique<InMemoryCompilationDb>(),
Alex Lorenzf8087862018-08-01 17:39:29 +0000782 /*IsDirectoryBased=*/false);
783}
784
785ClangdLSPServer::CompilationDB
786ClangdLSPServer::CompilationDB::makeDirectoryBased(
Sam McCallc008af62018-10-20 15:30:37 +0000787 Optional<Path> CompileCommandsDir) {
Alex Lorenzf8087862018-08-01 17:39:29 +0000788 auto CDB = llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(
789 std::move(CompileCommandsDir));
Sam McCall2172ee92018-10-23 13:14:02 +0000790 return CompilationDB(std::move(CDB),
Alex Lorenzf8087862018-08-01 17:39:29 +0000791 /*IsDirectoryBased=*/true);
792}
793
794void ClangdLSPServer::CompilationDB::invalidate(PathRef File) {
795 if (!IsDirectoryBased)
796 static_cast<InMemoryCompilationDb *>(CDB.get())->invalidate(File);
Alex Lorenzf8087862018-08-01 17:39:29 +0000797}
798
799bool ClangdLSPServer::CompilationDB::setCompilationCommandForFile(
800 PathRef File, tooling::CompileCommand CompilationCommand) {
801 if (IsDirectoryBased) {
802 elog("Trying to set compile command for {0} while using directory-based "
803 "compilation database",
804 File);
805 return false;
806 }
807 return static_cast<InMemoryCompilationDb *>(CDB.get())
808 ->setCompilationCommandForFile(File, std::move(CompilationCommand));
809}
810
811void ClangdLSPServer::CompilationDB::setExtraFlagsForFile(
812 PathRef File, std::vector<std::string> ExtraFlags) {
813 if (!IsDirectoryBased) {
814 elog("Trying to set extra flags for {0} while using in-memory compilation "
815 "database",
816 File);
817 return;
818 }
819 static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
820 ->setExtraFlagsForFile(File, std::move(ExtraFlags));
Alex Lorenzf8087862018-08-01 17:39:29 +0000821}
822
Sam McCallc008af62018-10-20 15:30:37 +0000823} // namespace clangd
824} // namespace clang