blob: cf964c14e30693d7ce2c91e2aa184185f83faf29 [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
Ilya Biryukov38d79772017-05-16 09:38:59 +000021using namespace clang::clangd;
22using namespace clang;
Sam McCalld20d7982018-07-09 14:25:59 +000023using namespace llvm;
Ilya Biryukov38d79772017-05-16 09:38:59 +000024
Ilya Biryukovafb55542017-05-16 14:40:30 +000025namespace {
26
Eric Liu5740ff52018-01-31 16:26:27 +000027/// \brief Supports a test URI scheme with relaxed constraints for lit tests.
28/// The path in a test URI will be combined with a platform-specific fake
29/// directory to form an absolute path. For example, test:///a.cpp is resolved
30/// C:\clangd-test\a.cpp on Windows and /clangd-test/a.cpp on Unix.
31class TestScheme : public URIScheme {
32public:
33 llvm::Expected<std::string>
34 getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
35 llvm::StringRef /*HintPath*/) const override {
36 using namespace llvm::sys;
37 // Still require "/" in body to mimic file scheme, as we want lengths of an
38 // equivalent URI in both schemes to be the same.
39 if (!Body.startswith("/"))
40 return llvm::make_error<llvm::StringError>(
41 "Expect URI body to be an absolute path starting with '/': " + Body,
42 llvm::inconvertibleErrorCode());
43 Body = Body.ltrim('/');
Nico Weber0da22902018-04-10 13:14:03 +000044#ifdef _WIN32
Eric Liu5740ff52018-01-31 16:26:27 +000045 constexpr char TestDir[] = "C:\\clangd-test";
46#else
47 constexpr char TestDir[] = "/clangd-test";
48#endif
49 llvm::SmallVector<char, 16> Path(Body.begin(), Body.end());
50 path::native(Path);
51 auto Err = fs::make_absolute(TestDir, Path);
Eric Liucda25262018-02-01 12:44:52 +000052 if (Err)
53 llvm_unreachable("Failed to make absolute path in test scheme.");
Eric Liu5740ff52018-01-31 16:26:27 +000054 return std::string(Path.begin(), Path.end());
55 }
56
57 llvm::Expected<URI>
58 uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
59 llvm_unreachable("Clangd must never create a test URI.");
60 }
61};
62
63static URISchemeRegistry::Add<TestScheme>
64 X("test", "Test scheme for clangd lit tests.");
65
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000066SymbolKindBitset defaultSymbolKinds() {
67 SymbolKindBitset Defaults;
68 for (size_t I = SymbolKindMin; I <= static_cast<size_t>(SymbolKind::Array);
69 ++I)
70 Defaults.set(I);
71 return Defaults;
72}
73
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +000074CompletionItemKindBitset defaultCompletionItemKinds() {
75 CompletionItemKindBitset Defaults;
76 for (size_t I = CompletionItemKindMin;
77 I <= static_cast<size_t>(CompletionItemKind::Reference); ++I)
78 Defaults.set(I);
79 return Defaults;
80}
81
Ilya Biryukovafb55542017-05-16 14:40:30 +000082} // namespace
83
Sam McCall2c30fbc2018-10-18 12:32:04 +000084// MessageHandler dispatches incoming LSP messages.
85// It handles cross-cutting concerns:
86// - serializes/deserializes protocol objects to JSON
87// - logging of inbound messages
88// - cancellation handling
89// - basic call tracing
Sam McCall3d0adbe2018-10-18 14:41:50 +000090// MessageHandler ensures that initialize() is called before any other handler.
Sam McCall2c30fbc2018-10-18 12:32:04 +000091class ClangdLSPServer::MessageHandler : public Transport::MessageHandler {
92public:
93 MessageHandler(ClangdLSPServer &Server) : Server(Server) {}
94
95 bool onNotify(StringRef Method, json::Value Params) override {
96 log("<-- {0}", Method);
97 if (Method == "exit")
98 return false;
Sam McCall3d0adbe2018-10-18 14:41:50 +000099 if (!Server.Server)
100 elog("Notification {0} before initialization", Method);
101 else if (Method == "$/cancelRequest")
Sam McCall2c30fbc2018-10-18 12:32:04 +0000102 onCancel(std::move(Params));
103 else if (auto Handler = Notifications.lookup(Method))
104 Handler(std::move(Params));
105 else
106 log("unhandled notification {0}", Method);
107 return true;
108 }
109
110 bool onCall(StringRef Method, json::Value Params, json::Value ID) override {
111 log("<-- {0}({1})", Method, ID);
Sam McCall3d0adbe2018-10-18 14:41:50 +0000112 if (!Server.Server && Method != "initialize") {
113 elog("Call {0} before initialization.", Method);
114 Server.reply(ID, make_error<LSPError>("server not initialized",
115 ErrorCode::ServerNotInitialized));
116 } else if (auto Handler = Calls.lookup(Method))
Sam McCall2c30fbc2018-10-18 12:32:04 +0000117 Handler(std::move(Params), std::move(ID));
118 else
119 Server.reply(ID, llvm::make_error<LSPError>("method not found",
120 ErrorCode::MethodNotFound));
121 return true;
122 }
123
124 bool onReply(json::Value ID, Expected<json::Value> Result) override {
125 // We ignore replies, just log them.
126 if (Result)
127 log("<-- reply({0})", ID);
128 else
129 log("<-- reply({0}) error: {1}", ID, llvm::toString(Result.takeError()));
130 return true;
131 }
132
133 // Bind an LSP method name to a call.
134 template <typename Param, typename Reply>
135 void bind(const char *Method,
136 void (ClangdLSPServer::*Handler)(const Param &, Callback<Reply>)) {
137 Calls[Method] = [Method, Handler, this](json::Value RawParams,
138 json::Value ID) {
139 Param P;
140 if (!fromJSON(RawParams, P)) {
141 elog("Failed to decode {0} request.", Method);
142 Server.reply(ID, make_error<LSPError>("failed to decode request",
143 ErrorCode::InvalidRequest));
144 return;
145 }
146 trace::Span Tracer(Method);
147 SPAN_ATTACH(Tracer, "Params", RawParams);
148 auto *Trace = Tracer.Args; // We attach reply from another thread.
149 // Calls can be canceled by the client. Add cancellation context.
150 WithContext WithCancel(cancelableRequestContext(ID));
151 // FIXME: this function should assert it's called exactly once.
152 (Server.*Handler)(P, [this, ID, Trace](llvm::Expected<Reply> Result) {
153 if (Result) {
154 if (Trace)
155 (*Trace)["Reply"] = *Result;
156 Server.reply(ID, json::Value(std::move(*Result)));
157 } else {
158 auto Err = Result.takeError();
159 if (Trace)
160 (*Trace)["Error"] = llvm::to_string(Err);
161 Server.reply(ID, std::move(Err));
162 }
163 });
164 };
165 }
166
167 // Bind an LSP method name to a notification.
168 template <typename Param>
169 void bind(const char *Method,
170 void (ClangdLSPServer::*Handler)(const Param &)) {
171 Notifications[Method] = [Method, Handler, this](json::Value RawParams) {
172 Param P;
173 if (!fromJSON(RawParams, P)) {
174 elog("Failed to decode {0} request.", Method);
175 return;
176 }
177 trace::Span Tracer(Method);
178 SPAN_ATTACH(Tracer, "Params", RawParams);
179 (Server.*Handler)(P);
180 };
181 }
182
183private:
184 llvm::StringMap<std::function<void(json::Value)>> Notifications;
185 llvm::StringMap<std::function<void(json::Value, json::Value)>> Calls;
186
187 // Method calls may be cancelled by ID, so keep track of their state.
188 // This needs a mutex: handlers may finish on a different thread, and that's
189 // when we clean up entries in the map.
190 mutable std::mutex RequestCancelersMutex;
191 llvm::StringMap<std::pair<Canceler, /*Cookie*/ unsigned>> RequestCancelers;
192 unsigned NextRequestCookie = 0; // To disambiguate reused IDs, see below.
193 void onCancel(const llvm::json::Value &Params) {
194 const json::Value *ID = nullptr;
195 if (auto *O = Params.getAsObject())
196 ID = O->get("id");
197 if (!ID) {
198 elog("Bad cancellation request: {0}", Params);
199 return;
200 }
201 auto StrID = llvm::to_string(*ID);
202 std::lock_guard<std::mutex> Lock(RequestCancelersMutex);
203 auto It = RequestCancelers.find(StrID);
204 if (It != RequestCancelers.end())
205 It->second.first(); // Invoke the canceler.
206 }
207 // We run cancelable requests in a context that does two things:
208 // - allows cancellation using RequestCancelers[ID]
209 // - cleans up the entry in RequestCancelers when it's no longer needed
210 // If a client reuses an ID, the last wins and the first cannot be canceled.
211 Context cancelableRequestContext(const json::Value &ID) {
212 auto Task = cancelableTask();
213 auto StrID = llvm::to_string(ID); // JSON-serialize ID for map key.
214 auto Cookie = NextRequestCookie++; // No lock, only called on main thread.
215 {
216 std::lock_guard<std::mutex> Lock(RequestCancelersMutex);
217 RequestCancelers[StrID] = {std::move(Task.second), Cookie};
218 }
219 // When the request ends, we can clean up the entry we just added.
220 // The cookie lets us check that it hasn't been overwritten due to ID
221 // reuse.
222 return Task.first.derive(make_scope_exit([this, StrID, Cookie] {
223 std::lock_guard<std::mutex> Lock(RequestCancelersMutex);
224 auto It = RequestCancelers.find(StrID);
225 if (It != RequestCancelers.end() && It->second.second == Cookie)
226 RequestCancelers.erase(It);
227 }));
228 }
229
230 ClangdLSPServer &Server;
231};
232
233// call(), notify(), and reply() wrap the Transport, adding logging and locking.
234void ClangdLSPServer::call(StringRef Method, json::Value Params) {
235 auto ID = NextCallID++;
236 log("--> {0}({1})", Method, ID);
237 // We currently don't handle responses, so no need to store ID anywhere.
238 std::lock_guard<std::mutex> Lock(TranspWriter);
239 Transp.call(Method, std::move(Params), ID);
240}
241
242void ClangdLSPServer::notify(StringRef Method, json::Value Params) {
243 log("--> {0}", Method);
244 std::lock_guard<std::mutex> Lock(TranspWriter);
245 Transp.notify(Method, std::move(Params));
246}
247
248void ClangdLSPServer::reply(llvm::json::Value ID,
249 llvm::Expected<llvm::json::Value> Result) {
250 if (Result) {
251 log("--> reply({0})", ID);
252 std::lock_guard<std::mutex> Lock(TranspWriter);
253 Transp.reply(std::move(ID), std::move(Result));
254 } else {
255 Error Err = Result.takeError();
256 log("--> reply({0}) error: {1}", ID, Err);
257 std::lock_guard<std::mutex> Lock(TranspWriter);
258 Transp.reply(std::move(ID), std::move(Err));
259 }
260}
261
262void ClangdLSPServer::onInitialize(const InitializeParams &Params,
263 Callback<json::Value> Reply) {
Sam McCall0d9b40f2018-10-19 15:42:23 +0000264 if (Params.rootUri && *Params.rootUri)
265 ClangdServerOpts.WorkspaceRoot = Params.rootUri->file();
266 else if (Params.rootPath && !Params.rootPath->empty())
267 ClangdServerOpts.WorkspaceRoot = *Params.rootPath;
Sam McCall3d0adbe2018-10-18 14:41:50 +0000268 if (Server)
269 return Reply(make_error<LSPError>("server already initialized",
270 ErrorCode::InvalidRequest));
271 Server.emplace(CDB.getCDB(), FSProvider,
272 static_cast<DiagnosticsConsumer &>(*this), ClangdServerOpts);
Simon Marchiabeed662018-10-16 15:55:03 +0000273 if (Params.initializationOptions) {
274 const ClangdInitializationOptions &Opts = *Params.initializationOptions;
275
276 // Explicit compilation database path.
277 if (Opts.compilationDatabasePath.hasValue()) {
278 CDB.setCompileCommandsDir(Opts.compilationDatabasePath.getValue());
279 }
280
281 applyConfiguration(Opts.ParamsChange);
282 }
Simon Marchi88016782018-08-01 11:28:49 +0000283
Sam McCallbf6a2fc2018-10-17 07:33:42 +0000284 CCOpts.EnableSnippets = Params.capabilities.CompletionSnippets;
285 DiagOpts.EmbedFixesInDiagnostics = Params.capabilities.DiagnosticFixes;
286 DiagOpts.SendDiagnosticCategory = Params.capabilities.DiagnosticCategory;
287 if (Params.capabilities.WorkspaceSymbolKinds)
288 SupportedSymbolKinds |= *Params.capabilities.WorkspaceSymbolKinds;
289 if (Params.capabilities.CompletionItemKinds)
290 SupportedCompletionItemKinds |= *Params.capabilities.CompletionItemKinds;
291 SupportsCodeAction = Params.capabilities.CodeActionStructure;
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +0000292
Sam McCall2c30fbc2018-10-18 12:32:04 +0000293 Reply(json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000294 {{"capabilities",
Sam McCalld20d7982018-07-09 14:25:59 +0000295 json::Object{
Simon Marchi98082622018-03-26 14:41:40 +0000296 {"textDocumentSync", (int)TextDocumentSyncKind::Incremental},
Sam McCall0930ab02017-11-07 15:49:35 +0000297 {"documentFormattingProvider", true},
298 {"documentRangeFormattingProvider", true},
299 {"documentOnTypeFormattingProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000300 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000301 {"firstTriggerCharacter", "}"},
302 {"moreTriggerCharacter", {}},
303 }},
304 {"codeActionProvider", true},
305 {"completionProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000306 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000307 {"resolveProvider", false},
308 {"triggerCharacters", {".", ">", ":"}},
309 }},
310 {"signatureHelpProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000311 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000312 {"triggerCharacters", {"(", ","}},
313 }},
314 {"definitionProvider", true},
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000315 {"documentHighlightProvider", true},
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000316 {"hoverProvider", true},
Haojian Wu345099c2017-11-09 11:30:04 +0000317 {"renameProvider", true},
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000318 {"documentSymbolProvider", true},
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000319 {"workspaceSymbolProvider", true},
Sam McCall1ad142f2018-09-05 11:53:07 +0000320 {"referencesProvider", true},
Sam McCall0930ab02017-11-07 15:49:35 +0000321 {"executeCommandProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000322 json::Object{
Eric Liu2c190532018-05-15 15:23:53 +0000323 {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
Sam McCall0930ab02017-11-07 15:49:35 +0000324 }},
325 }}}});
Ilya Biryukovafb55542017-05-16 14:40:30 +0000326}
327
Sam McCall2c30fbc2018-10-18 12:32:04 +0000328void ClangdLSPServer::onShutdown(const ShutdownParams &Params,
329 Callback<std::nullptr_t> Reply) {
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000330 // Do essentially nothing, just say we're ready to exit.
331 ShutdownRequestReceived = true;
Sam McCall2c30fbc2018-10-18 12:32:04 +0000332 Reply(nullptr);
Sam McCall8a5dded2017-10-12 13:29:58 +0000333}
Ilya Biryukovafb55542017-05-16 14:40:30 +0000334
Sam McCall2c30fbc2018-10-18 12:32:04 +0000335void ClangdLSPServer::onDocumentDidOpen(
336 const DidOpenTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000337 PathRef File = Params.textDocument.uri.file();
Alex Lorenzf8087862018-08-01 17:39:29 +0000338 if (Params.metadata && !Params.metadata->extraFlags.empty())
339 CDB.setExtraFlagsForFile(File, std::move(Params.metadata->extraFlags));
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000340
Sam McCall2c30fbc2018-10-18 12:32:04 +0000341 const std::string &Contents = Params.textDocument.text;
Simon Marchi9569fd52018-03-16 14:30:42 +0000342
Simon Marchi98082622018-03-26 14:41:40 +0000343 DraftMgr.addDraft(File, Contents);
Ilya Biryukov652364b2018-09-26 05:48:29 +0000344 Server->addDocument(File, Contents, WantDiagnostics::Yes);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000345}
346
Sam McCall2c30fbc2018-10-18 12:32:04 +0000347void ClangdLSPServer::onDocumentDidChange(
348 const DidChangeTextDocumentParams &Params) {
Eric Liu51fed182018-02-22 18:40:39 +0000349 auto WantDiags = WantDiagnostics::Auto;
350 if (Params.wantDiagnostics.hasValue())
351 WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes
352 : WantDiagnostics::No;
Simon Marchi9569fd52018-03-16 14:30:42 +0000353
354 PathRef File = Params.textDocument.uri.file();
Simon Marchi98082622018-03-26 14:41:40 +0000355 llvm::Expected<std::string> Contents =
356 DraftMgr.updateDraft(File, Params.contentChanges);
357 if (!Contents) {
358 // If this fails, we are most likely going to be not in sync anymore with
359 // the client. It is better to remove the draft and let further operations
360 // fail rather than giving wrong results.
361 DraftMgr.removeDraft(File);
Ilya Biryukov652364b2018-09-26 05:48:29 +0000362 Server->removeDocument(File);
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000363 CDB.invalidate(File);
Sam McCallbed58852018-07-11 10:35:11 +0000364 elog("Failed to update {0}: {1}", File, Contents.takeError());
Simon Marchi98082622018-03-26 14:41:40 +0000365 return;
366 }
Simon Marchi9569fd52018-03-16 14:30:42 +0000367
Ilya Biryukov652364b2018-09-26 05:48:29 +0000368 Server->addDocument(File, *Contents, WantDiags);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000369}
370
Sam McCall2c30fbc2018-10-18 12:32:04 +0000371void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000372 Server->onFileEvent(Params);
Marc-Andre Laperlebf114242017-10-02 18:00:37 +0000373}
374
Sam McCall2c30fbc2018-10-18 12:32:04 +0000375void ClangdLSPServer::onCommand(const ExecuteCommandParams &Params,
376 Callback<json::Value> Reply) {
377 auto ApplyEdit = [&](WorkspaceEdit WE) {
Eric Liuc5105f92018-02-16 14:15:55 +0000378 ApplyWorkspaceEditParams Edit;
379 Edit.edit = std::move(WE);
Eric Liuc5105f92018-02-16 14:15:55 +0000380 // Ideally, we would wait for the response and if there is no error, we
381 // would reply success/failure to the original RPC.
382 call("workspace/applyEdit", Edit);
383 };
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000384 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
385 Params.workspaceEdit) {
386 // The flow for "apply-fix" :
387 // 1. We publish a diagnostic, including fixits
388 // 2. The user clicks on the diagnostic, the editor asks us for code actions
389 // 3. We send code actions, with the fixit embedded as context
390 // 4. The user selects the fixit, the editor asks us to apply it
391 // 5. We unwrap the changes and send them back to the editor
392 // 6. The editor applies the changes (applyEdit), and sends us a reply (but
393 // we ignore it)
394
Sam McCall2c30fbc2018-10-18 12:32:04 +0000395 Reply("Fix applied.");
Eric Liuc5105f92018-02-16 14:15:55 +0000396 ApplyEdit(*Params.workspaceEdit);
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000397 } else {
398 // We should not get here because ExecuteCommandParams would not have
399 // parsed in the first place and this handler should not be called. But if
400 // more commands are added, this will be here has a safe guard.
Sam McCall2c30fbc2018-10-18 12:32:04 +0000401 Reply(make_error<LSPError>(
402 llvm::formatv("Unsupported command \"{0}\".", Params.command).str(),
403 ErrorCode::InvalidParams));
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000404 }
405}
406
Sam McCall2c30fbc2018-10-18 12:32:04 +0000407void ClangdLSPServer::onWorkspaceSymbol(
408 const WorkspaceSymbolParams &Params,
409 Callback<std::vector<SymbolInformation>> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000410 Server->workspaceSymbols(
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000411 Params.query, CCOpts.Limit,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000412 Bind(
413 [this](decltype(Reply) Reply,
414 llvm::Expected<std::vector<SymbolInformation>> Items) {
415 if (!Items)
416 return Reply(Items.takeError());
417 for (auto &Sym : *Items)
418 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000419
Sam McCall2c30fbc2018-10-18 12:32:04 +0000420 Reply(std::move(*Items));
421 },
422 std::move(Reply)));
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000423}
424
Sam McCall2c30fbc2018-10-18 12:32:04 +0000425void ClangdLSPServer::onRename(const RenameParams &Params,
426 Callback<WorkspaceEdit> Reply) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000427 Path File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000428 llvm::Optional<std::string> Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000429 if (!Code)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000430 return Reply(make_error<LSPError>("onRename called for non-added file",
431 ErrorCode::InvalidParams));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000432
Ilya Biryukov652364b2018-09-26 05:48:29 +0000433 Server->rename(
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000434 File, Params.position, Params.newName,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000435 Bind(
436 [File, Code, Params](
437 decltype(Reply) Reply,
438 llvm::Expected<std::vector<tooling::Replacement>> Replacements) {
439 if (!Replacements)
440 return Reply(Replacements.takeError());
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000441
Sam McCall2c30fbc2018-10-18 12:32:04 +0000442 // Turn the replacements into the format specified by the Language
443 // Server Protocol. Fuse them into one big JSON array.
444 std::vector<TextEdit> Edits;
445 for (const auto &R : *Replacements)
446 Edits.push_back(replacementToEdit(*Code, R));
447 WorkspaceEdit WE;
448 WE.changes = {{Params.textDocument.uri.uri(), Edits}};
449 Reply(WE);
450 },
451 std::move(Reply)));
Haojian Wu345099c2017-11-09 11:30:04 +0000452}
453
Sam McCall2c30fbc2018-10-18 12:32:04 +0000454void ClangdLSPServer::onDocumentDidClose(
455 const DidCloseTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000456 PathRef File = Params.textDocument.uri.file();
457 DraftMgr.removeDraft(File);
Ilya Biryukov652364b2018-09-26 05:48:29 +0000458 Server->removeDocument(File);
Alex Lorenzf8087862018-08-01 17:39:29 +0000459 CDB.invalidate(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000460}
461
Sam McCall4db732a2017-09-30 10:08:52 +0000462void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCall2c30fbc2018-10-18 12:32:04 +0000463 const DocumentOnTypeFormattingParams &Params,
464 Callback<std::vector<TextEdit>> Reply) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000465 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000466 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000467 if (!Code)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000468 return Reply(make_error<LSPError>(
469 "onDocumentOnTypeFormatting called for non-added file",
470 ErrorCode::InvalidParams));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000471
Ilya Biryukov652364b2018-09-26 05:48:29 +0000472 auto ReplacementsOrError = Server->formatOnType(*Code, File, Params.position);
Raoul Wols212bcf82017-12-12 20:25:06 +0000473 if (ReplacementsOrError)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000474 Reply(replacementsToEdits(*Code, ReplacementsOrError.get()));
Raoul Wols212bcf82017-12-12 20:25:06 +0000475 else
Sam McCall2c30fbc2018-10-18 12:32:04 +0000476 Reply(ReplacementsOrError.takeError());
Ilya Biryukovafb55542017-05-16 14:40:30 +0000477}
478
Sam McCall4db732a2017-09-30 10:08:52 +0000479void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCall2c30fbc2018-10-18 12:32:04 +0000480 const DocumentRangeFormattingParams &Params,
481 Callback<std::vector<TextEdit>> Reply) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000482 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000483 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000484 if (!Code)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000485 return Reply(make_error<LSPError>(
486 "onDocumentRangeFormatting called for non-added file",
487 ErrorCode::InvalidParams));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000488
Ilya Biryukov652364b2018-09-26 05:48:29 +0000489 auto ReplacementsOrError = Server->formatRange(*Code, File, Params.range);
Raoul Wols212bcf82017-12-12 20:25:06 +0000490 if (ReplacementsOrError)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000491 Reply(replacementsToEdits(*Code, ReplacementsOrError.get()));
Raoul Wols212bcf82017-12-12 20:25:06 +0000492 else
Sam McCall2c30fbc2018-10-18 12:32:04 +0000493 Reply(ReplacementsOrError.takeError());
Ilya Biryukovafb55542017-05-16 14:40:30 +0000494}
495
Sam McCall2c30fbc2018-10-18 12:32:04 +0000496void ClangdLSPServer::onDocumentFormatting(
497 const DocumentFormattingParams &Params,
498 Callback<std::vector<TextEdit>> Reply) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000499 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000500 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000501 if (!Code)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000502 return Reply(
503 make_error<LSPError>("onDocumentFormatting called for non-added file",
504 ErrorCode::InvalidParams));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000505
Ilya Biryukov652364b2018-09-26 05:48:29 +0000506 auto ReplacementsOrError = Server->formatFile(*Code, File);
Raoul Wols212bcf82017-12-12 20:25:06 +0000507 if (ReplacementsOrError)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000508 Reply(replacementsToEdits(*Code, ReplacementsOrError.get()));
Raoul Wols212bcf82017-12-12 20:25:06 +0000509 else
Sam McCall2c30fbc2018-10-18 12:32:04 +0000510 Reply(ReplacementsOrError.takeError());
Sam McCall4db732a2017-09-30 10:08:52 +0000511}
512
Sam McCall2c30fbc2018-10-18 12:32:04 +0000513void ClangdLSPServer::onDocumentSymbol(
514 const DocumentSymbolParams &Params,
515 Callback<std::vector<SymbolInformation>> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000516 Server->documentSymbols(
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000517 Params.textDocument.uri.file(),
Sam McCall2c30fbc2018-10-18 12:32:04 +0000518 Bind(
519 [this](decltype(Reply) Reply,
520 llvm::Expected<std::vector<SymbolInformation>> Items) {
521 if (!Items)
522 return Reply(Items.takeError());
523 for (auto &Sym : *Items)
524 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
525 Reply(std::move(*Items));
526 },
527 std::move(Reply)));
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000528}
529
Sam McCall20841d42018-10-16 16:29:41 +0000530static Optional<Command> asCommand(const CodeAction &Action) {
531 Command Cmd;
532 if (Action.command && Action.edit)
533 return llvm::None; // Not representable. (We never emit these anyway).
534 if (Action.command) {
535 Cmd = *Action.command;
536 } else if (Action.edit) {
537 Cmd.command = Command::CLANGD_APPLY_FIX_COMMAND;
538 Cmd.workspaceEdit = *Action.edit;
539 } else {
540 return llvm::None;
541 }
542 Cmd.title = Action.title;
543 if (Action.kind && *Action.kind == CodeAction::QUICKFIX_KIND)
544 Cmd.title = "Apply fix: " + Cmd.title;
545 return Cmd;
546}
547
Sam McCall2c30fbc2018-10-18 12:32:04 +0000548void ClangdLSPServer::onCodeAction(const CodeActionParams &Params,
549 Callback<json::Value> Reply) {
550 auto Code = DraftMgr.getDraft(Params.textDocument.uri.file());
551 if (!Code)
552 return Reply(make_error<LSPError>("onCodeAction called for non-added file",
553 ErrorCode::InvalidParams));
Sam McCall20841d42018-10-16 16:29:41 +0000554 // We provide a code action for Fixes on the specified diagnostics.
Sam McCall20841d42018-10-16 16:29:41 +0000555 std::vector<CodeAction> Actions;
Sam McCall2c30fbc2018-10-18 12:32:04 +0000556 for (const Diagnostic &D : Params.context.diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000557 for (auto &F : getFixes(Params.textDocument.uri.file(), D)) {
Sam McCall20841d42018-10-16 16:29:41 +0000558 Actions.emplace_back();
559 Actions.back().title = F.Message;
560 Actions.back().kind = CodeAction::QUICKFIX_KIND;
561 Actions.back().diagnostics = {D};
562 Actions.back().edit.emplace();
563 Actions.back().edit->changes.emplace();
564 (*Actions.back().edit->changes)[Params.textDocument.uri.uri()] = {
565 F.Edits.begin(), F.Edits.end()};
Sam McCalldd0566b2017-11-06 15:40:30 +0000566 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000567 }
Sam McCall20841d42018-10-16 16:29:41 +0000568
569 if (SupportsCodeAction)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000570 Reply(json::Array(Actions));
Sam McCall20841d42018-10-16 16:29:41 +0000571 else {
572 std::vector<Command> Commands;
573 for (const auto &Action : Actions)
574 if (auto Command = asCommand(Action))
575 Commands.push_back(std::move(*Command));
Sam McCall2c30fbc2018-10-18 12:32:04 +0000576 Reply(json::Array(Commands));
Sam McCall20841d42018-10-16 16:29:41 +0000577 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000578}
579
Sam McCall2c30fbc2018-10-18 12:32:04 +0000580void ClangdLSPServer::onCompletion(const TextDocumentPositionParams &Params,
581 Callback<CompletionList> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000582 Server->codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000583 Bind(
584 [this](decltype(Reply) Reply,
585 llvm::Expected<CodeCompleteResult> List) {
586 if (!List)
587 return Reply(List.takeError());
588 CompletionList LSPList;
589 LSPList.isIncomplete = List->HasMore;
590 for (const auto &R : List->Completions) {
591 CompletionItem C = R.render(CCOpts);
592 C.kind = adjustKindToCapability(
593 C.kind, SupportedCompletionItemKinds);
594 LSPList.items.push_back(std::move(C));
595 }
596 return Reply(std::move(LSPList));
597 },
598 std::move(Reply)));
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000599}
600
Sam McCall2c30fbc2018-10-18 12:32:04 +0000601void ClangdLSPServer::onSignatureHelp(const TextDocumentPositionParams &Params,
602 Callback<SignatureHelp> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000603 Server->signatureHelp(Params.textDocument.uri.file(), Params.position,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000604 std::move(Reply));
Ilya Biryukov652364b2018-09-26 05:48:29 +0000605}
606
Sam McCall2c30fbc2018-10-18 12:32:04 +0000607void ClangdLSPServer::onGoToDefinition(const TextDocumentPositionParams &Params,
608 Callback<std::vector<Location>> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000609 Server->findDefinitions(Params.textDocument.uri.file(), Params.position,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000610 std::move(Reply));
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000611}
612
Sam McCall2c30fbc2018-10-18 12:32:04 +0000613void ClangdLSPServer::onSwitchSourceHeader(const TextDocumentIdentifier &Params,
614 Callback<std::string> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000615 llvm::Optional<Path> Result = Server->switchSourceHeader(Params.uri.file());
Sam McCall2c30fbc2018-10-18 12:32:04 +0000616 Reply(Result ? URI::createFile(*Result).toString() : "");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000617}
618
Sam McCall2c30fbc2018-10-18 12:32:04 +0000619void ClangdLSPServer::onDocumentHighlight(
620 const TextDocumentPositionParams &Params,
621 Callback<std::vector<DocumentHighlight>> Reply) {
622 Server->findDocumentHighlights(Params.textDocument.uri.file(),
623 Params.position, std::move(Reply));
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000624}
625
Sam McCall2c30fbc2018-10-18 12:32:04 +0000626void ClangdLSPServer::onHover(const TextDocumentPositionParams &Params,
627 Callback<llvm::Optional<Hover>> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000628 Server->findHover(Params.textDocument.uri.file(), Params.position,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000629 std::move(Reply));
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000630}
631
Simon Marchi88016782018-08-01 11:28:49 +0000632void ClangdLSPServer::applyConfiguration(
Simon Marchiabeed662018-10-16 15:55:03 +0000633 const ClangdConfigurationParamsChange &Params) {
634 // Per-file update to the compilation database.
635 if (Params.compilationDatabaseChanges) {
636 const auto &CompileCommandUpdates = *Params.compilationDatabaseChanges;
Alex Lorenzf8087862018-08-01 17:39:29 +0000637 bool ShouldReparseOpenFiles = false;
638 for (auto &Entry : CompileCommandUpdates) {
639 /// The opened files need to be reparsed only when some existing
640 /// entries are changed.
641 PathRef File = Entry.first;
642 if (!CDB.setCompilationCommandForFile(
643 File, tooling::CompileCommand(
644 std::move(Entry.second.workingDirectory), File,
645 std::move(Entry.second.compilationCommand),
646 /*Output=*/"")))
647 ShouldReparseOpenFiles = true;
648 }
649 if (ShouldReparseOpenFiles)
650 reparseOpenedFiles();
651 }
Simon Marchi5178f922018-02-22 14:00:39 +0000652}
653
Simon Marchi88016782018-08-01 11:28:49 +0000654// FIXME: This function needs to be properly tested.
655void ClangdLSPServer::onChangeConfiguration(
Sam McCall2c30fbc2018-10-18 12:32:04 +0000656 const DidChangeConfigurationParams &Params) {
Simon Marchi88016782018-08-01 11:28:49 +0000657 applyConfiguration(Params.settings);
658}
659
Sam McCall2c30fbc2018-10-18 12:32:04 +0000660void ClangdLSPServer::onReference(const ReferenceParams &Params,
661 Callback<std::vector<Location>> Reply) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000662 Server->findReferences(Params.textDocument.uri.file(), Params.position,
Sam McCall2c30fbc2018-10-18 12:32:04 +0000663 std::move(Reply));
Sam McCall1ad142f2018-09-05 11:53:07 +0000664}
665
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000666ClangdLSPServer::ClangdLSPServer(class Transport &Transp,
Sam McCalladccab62017-11-23 16:58:22 +0000667 const clangd::CodeCompleteOptions &CCOpts,
Eric Liubfac8f72017-12-19 18:00:37 +0000668 llvm::Optional<Path> CompileCommandsDir,
Alex Lorenzf8087862018-08-01 17:39:29 +0000669 bool ShouldUseInMemoryCDB,
Sam McCall7363a2f2018-03-05 17:28:54 +0000670 const ClangdServer::Options &Opts)
Sam McCall2c30fbc2018-10-18 12:32:04 +0000671 : Transp(Transp), MsgHandler(new MessageHandler(*this)),
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000672 CDB(ShouldUseInMemoryCDB ? CompilationDB::makeInMemory()
673 : CompilationDB::makeDirectoryBased(
674 std::move(CompileCommandsDir))),
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000675 CCOpts(CCOpts), SupportedSymbolKinds(defaultSymbolKinds()),
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +0000676 SupportedCompletionItemKinds(defaultCompletionItemKinds()),
Sam McCall3d0adbe2018-10-18 14:41:50 +0000677 ClangdServerOpts(Opts) {
Sam McCall2c30fbc2018-10-18 12:32:04 +0000678 // clang-format off
679 MsgHandler->bind("initialize", &ClangdLSPServer::onInitialize);
680 MsgHandler->bind("shutdown", &ClangdLSPServer::onShutdown);
681 MsgHandler->bind("textDocument/rangeFormatting", &ClangdLSPServer::onDocumentRangeFormatting);
682 MsgHandler->bind("textDocument/onTypeFormatting", &ClangdLSPServer::onDocumentOnTypeFormatting);
683 MsgHandler->bind("textDocument/formatting", &ClangdLSPServer::onDocumentFormatting);
684 MsgHandler->bind("textDocument/codeAction", &ClangdLSPServer::onCodeAction);
685 MsgHandler->bind("textDocument/completion", &ClangdLSPServer::onCompletion);
686 MsgHandler->bind("textDocument/signatureHelp", &ClangdLSPServer::onSignatureHelp);
687 MsgHandler->bind("textDocument/definition", &ClangdLSPServer::onGoToDefinition);
688 MsgHandler->bind("textDocument/references", &ClangdLSPServer::onReference);
689 MsgHandler->bind("textDocument/switchSourceHeader", &ClangdLSPServer::onSwitchSourceHeader);
690 MsgHandler->bind("textDocument/rename", &ClangdLSPServer::onRename);
691 MsgHandler->bind("textDocument/hover", &ClangdLSPServer::onHover);
692 MsgHandler->bind("textDocument/documentSymbol", &ClangdLSPServer::onDocumentSymbol);
693 MsgHandler->bind("workspace/executeCommand", &ClangdLSPServer::onCommand);
694 MsgHandler->bind("textDocument/documentHighlight", &ClangdLSPServer::onDocumentHighlight);
695 MsgHandler->bind("workspace/symbol", &ClangdLSPServer::onWorkspaceSymbol);
696 MsgHandler->bind("textDocument/didOpen", &ClangdLSPServer::onDocumentDidOpen);
697 MsgHandler->bind("textDocument/didClose", &ClangdLSPServer::onDocumentDidClose);
698 MsgHandler->bind("textDocument/didChange", &ClangdLSPServer::onDocumentDidChange);
699 MsgHandler->bind("workspace/didChangeWatchedFiles", &ClangdLSPServer::onFileEvent);
700 MsgHandler->bind("workspace/didChangeConfiguration", &ClangdLSPServer::onChangeConfiguration);
701 // clang-format on
702}
703
704ClangdLSPServer::~ClangdLSPServer() = default;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000705
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000706bool ClangdLSPServer::run() {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000707 // Run the Language Server loop.
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000708 bool CleanExit = true;
Sam McCall2c30fbc2018-10-18 12:32:04 +0000709 if (auto Err = Transp.loop(*MsgHandler)) {
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000710 elog("Transport error: {0}", std::move(Err));
711 CleanExit = false;
712 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000713
Ilya Biryukov652364b2018-09-26 05:48:29 +0000714 // Destroy ClangdServer to ensure all worker threads finish.
715 Server.reset();
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000716 return CleanExit && ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000717}
718
Ilya Biryukov71028b82018-03-12 15:28:22 +0000719std::vector<Fix> ClangdLSPServer::getFixes(StringRef File,
720 const clangd::Diagnostic &D) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000721 std::lock_guard<std::mutex> Lock(FixItsMutex);
722 auto DiagToFixItsIter = FixItsMap.find(File);
723 if (DiagToFixItsIter == FixItsMap.end())
724 return {};
725
726 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
727 auto FixItsIter = DiagToFixItsMap.find(D);
728 if (FixItsIter == DiagToFixItsMap.end())
729 return {};
730
731 return FixItsIter->second;
732}
733
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000734void ClangdLSPServer::onDiagnosticsReady(PathRef File,
735 std::vector<Diag> Diagnostics) {
Sam McCalld20d7982018-07-09 14:25:59 +0000736 json::Array DiagnosticsJSON;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000737
738 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000739 for (auto &Diag : Diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000740 toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) {
Alex Lorenz8626d362018-08-10 17:25:07 +0000741 json::Object LSPDiag({
Ilya Biryukov71028b82018-03-12 15:28:22 +0000742 {"range", Diag.range},
743 {"severity", Diag.severity},
744 {"message", Diag.message},
745 });
Alex Lorenz8626d362018-08-10 17:25:07 +0000746 // LSP extension: embed the fixes in the diagnostic.
747 if (DiagOpts.EmbedFixesInDiagnostics && !Fixes.empty()) {
748 json::Array ClangdFixes;
749 for (const auto &Fix : Fixes) {
750 WorkspaceEdit WE;
751 URIForFile URI{File};
752 WE.changes = {{URI.uri(), std::vector<TextEdit>(Fix.Edits.begin(),
753 Fix.Edits.end())}};
754 ClangdFixes.push_back(
755 json::Object{{"edit", toJSON(WE)}, {"title", Fix.Message}});
756 }
757 LSPDiag["clangd_fixes"] = std::move(ClangdFixes);
758 }
Alex Lorenz0ce8a7a2018-08-22 20:30:06 +0000759 if (DiagOpts.SendDiagnosticCategory && !Diag.category.empty())
Alex Lorenz37146432018-08-14 22:21:40 +0000760 LSPDiag["category"] = Diag.category;
Alex Lorenz8626d362018-08-10 17:25:07 +0000761 DiagnosticsJSON.push_back(std::move(LSPDiag));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000762
763 auto &FixItsForDiagnostic = LocalFixIts[Diag];
Kirill Bobyrev4a5ff882018-10-07 14:49:41 +0000764 llvm::copy(Fixes, std::back_inserter(FixItsForDiagnostic));
Sam McCalldd0566b2017-11-06 15:40:30 +0000765 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000766 }
767
768 // Cache FixIts
769 {
770 // FIXME(ibiryukov): should be deleted when documents are removed
771 std::lock_guard<std::mutex> Lock(FixItsMutex);
772 FixItsMap[File] = LocalFixIts;
773 }
774
775 // Publish diagnostics.
Sam McCall2c30fbc2018-10-18 12:32:04 +0000776 notify("textDocument/publishDiagnostics",
777 json::Object{
778 {"uri", URIForFile{File}},
779 {"diagnostics", std::move(DiagnosticsJSON)},
780 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000781}
Simon Marchi9569fd52018-03-16 14:30:42 +0000782
783void ClangdLSPServer::reparseOpenedFiles() {
784 for (const Path &FilePath : DraftMgr.getActiveFiles())
Ilya Biryukov652364b2018-09-26 05:48:29 +0000785 Server->addDocument(FilePath, *DraftMgr.getDraft(FilePath),
786 WantDiagnostics::Auto);
Simon Marchi9569fd52018-03-16 14:30:42 +0000787}
Alex Lorenzf8087862018-08-01 17:39:29 +0000788
789ClangdLSPServer::CompilationDB ClangdLSPServer::CompilationDB::makeInMemory() {
790 return CompilationDB(llvm::make_unique<InMemoryCompilationDb>(), nullptr,
791 /*IsDirectoryBased=*/false);
792}
793
794ClangdLSPServer::CompilationDB
795ClangdLSPServer::CompilationDB::makeDirectoryBased(
796 llvm::Optional<Path> CompileCommandsDir) {
797 auto CDB = llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(
798 std::move(CompileCommandsDir));
799 auto CachingCDB = llvm::make_unique<CachingCompilationDb>(*CDB);
800 return CompilationDB(std::move(CDB), std::move(CachingCDB),
801 /*IsDirectoryBased=*/true);
802}
803
804void ClangdLSPServer::CompilationDB::invalidate(PathRef File) {
805 if (!IsDirectoryBased)
806 static_cast<InMemoryCompilationDb *>(CDB.get())->invalidate(File);
807 else
808 CachingCDB->invalidate(File);
809}
810
811bool ClangdLSPServer::CompilationDB::setCompilationCommandForFile(
812 PathRef File, tooling::CompileCommand CompilationCommand) {
813 if (IsDirectoryBased) {
814 elog("Trying to set compile command for {0} while using directory-based "
815 "compilation database",
816 File);
817 return false;
818 }
819 return static_cast<InMemoryCompilationDb *>(CDB.get())
820 ->setCompilationCommandForFile(File, std::move(CompilationCommand));
821}
822
823void ClangdLSPServer::CompilationDB::setExtraFlagsForFile(
824 PathRef File, std::vector<std::string> ExtraFlags) {
825 if (!IsDirectoryBased) {
826 elog("Trying to set extra flags for {0} while using in-memory compilation "
827 "database",
828 File);
829 return;
830 }
831 static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
832 ->setExtraFlagsForFile(File, std::move(ExtraFlags));
833 CachingCDB->invalidate(File);
834}
835
836void ClangdLSPServer::CompilationDB::setCompileCommandsDir(Path P) {
837 if (!IsDirectoryBased) {
838 elog("Trying to set compile commands dir while using in-memory compilation "
839 "database");
840 return;
841 }
842 static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
843 ->setCompileCommandsDir(P);
844 CachingCDB->clear();
845}
846
847GlobalCompilationDatabase &ClangdLSPServer::CompilationDB::getCDB() {
848 if (CachingCDB)
849 return *CachingCDB;
850 return *CDB;
851}