blob: 53dc8aced08deaef093eda7f684fd7efb5ec30db [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"
Kadir Cetinkaya689bf932018-08-24 13:09:41 +000011#include "Cancellation.h"
Ilya Biryukov71028b82018-03-12 15:28:22 +000012#include "Diagnostics.h"
Ilya Biryukov38d79772017-05-16 09:38:59 +000013#include "JSONRPCDispatcher.h"
Sam McCallb536a2a2017-12-19 12:23:48 +000014#include "SourceCode.h"
Eric Liu78ed91a72018-01-29 15:37:46 +000015#include "URI.h"
Kadir Cetinkaya689bf932018-08-24 13:09:41 +000016#include "llvm/ADT/ScopeExit.h"
Simon Marchi9569fd52018-03-16 14:30:42 +000017#include "llvm/Support/Errc.h"
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000018#include "llvm/Support/FormatVariadic.h"
Eric Liu5740ff52018-01-31 16:26:27 +000019#include "llvm/Support/Path.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 Cetinkaya689bf932018-08-24 13:09:41 +000074std::string NormalizeRequestID(const json::Value &ID) {
75 auto NormalizedID = parseNumberOrString(&ID);
76 assert(NormalizedID && "Was not able to parse request id.");
77 return std::move(*NormalizedID);
78}
Ilya Biryukovafb55542017-05-16 14:40:30 +000079} // namespace
80
Sam McCalld1a7a372018-01-31 13:40:48 +000081void ClangdLSPServer::onInitialize(InitializeParams &Params) {
Simon Marchi88016782018-08-01 11:28:49 +000082 if (Params.initializationOptions)
83 applyConfiguration(*Params.initializationOptions);
84
Ilya Biryukov7d60d202018-02-16 12:20:47 +000085 if (Params.rootUri && *Params.rootUri)
86 Server.setRootPath(Params.rootUri->file());
Ilya Biryukov23bc73b2018-02-15 14:32:57 +000087 else if (Params.rootPath && !Params.rootPath->empty())
88 Server.setRootPath(*Params.rootPath);
89
90 CCOpts.EnableSnippets =
91 Params.capabilities.textDocument.completion.completionItem.snippetSupport;
Alex Lorenz8626d362018-08-10 17:25:07 +000092 DiagOpts.EmbedFixesInDiagnostics =
93 Params.capabilities.textDocument.publishDiagnostics.clangdFixSupport;
Alex Lorenz0ce8a7a2018-08-22 20:30:06 +000094 DiagOpts.SendDiagnosticCategory =
95 Params.capabilities.textDocument.publishDiagnostics.categorySupport;
Ilya Biryukov23bc73b2018-02-15 14:32:57 +000096
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000097 if (Params.capabilities.workspace && Params.capabilities.workspace->symbol &&
98 Params.capabilities.workspace->symbol->symbolKind) {
99 for (SymbolKind Kind :
100 *Params.capabilities.workspace->symbol->symbolKind->valueSet) {
101 SupportedSymbolKinds.set(static_cast<size_t>(Kind));
102 }
103 }
104
Sam McCalld20d7982018-07-09 14:25:59 +0000105 reply(json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000106 {{"capabilities",
Sam McCalld20d7982018-07-09 14:25:59 +0000107 json::Object{
Simon Marchi98082622018-03-26 14:41:40 +0000108 {"textDocumentSync", (int)TextDocumentSyncKind::Incremental},
Sam McCall0930ab02017-11-07 15:49:35 +0000109 {"documentFormattingProvider", true},
110 {"documentRangeFormattingProvider", true},
111 {"documentOnTypeFormattingProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000112 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000113 {"firstTriggerCharacter", "}"},
114 {"moreTriggerCharacter", {}},
115 }},
116 {"codeActionProvider", true},
117 {"completionProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000118 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000119 {"resolveProvider", false},
120 {"triggerCharacters", {".", ">", ":"}},
121 }},
122 {"signatureHelpProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000123 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000124 {"triggerCharacters", {"(", ","}},
125 }},
126 {"definitionProvider", true},
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000127 {"documentHighlightProvider", true},
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000128 {"hoverProvider", true},
Haojian Wu345099c2017-11-09 11:30:04 +0000129 {"renameProvider", true},
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000130 {"documentSymbolProvider", true},
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000131 {"workspaceSymbolProvider", true},
Sam McCall1ad142f2018-09-05 11:53:07 +0000132 {"referencesProvider", true},
Sam McCall0930ab02017-11-07 15:49:35 +0000133 {"executeCommandProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000134 json::Object{
Eric Liu2c190532018-05-15 15:23:53 +0000135 {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
Sam McCall0930ab02017-11-07 15:49:35 +0000136 }},
137 }}}});
Ilya Biryukovafb55542017-05-16 14:40:30 +0000138}
139
Sam McCalld1a7a372018-01-31 13:40:48 +0000140void ClangdLSPServer::onShutdown(ShutdownParams &Params) {
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000141 // Do essentially nothing, just say we're ready to exit.
142 ShutdownRequestReceived = true;
Sam McCalld1a7a372018-01-31 13:40:48 +0000143 reply(nullptr);
Sam McCall8a5dded2017-10-12 13:29:58 +0000144}
Ilya Biryukovafb55542017-05-16 14:40:30 +0000145
Sam McCalld1a7a372018-01-31 13:40:48 +0000146void ClangdLSPServer::onExit(ExitParams &Params) { IsDone = true; }
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000147
Sam McCalld1a7a372018-01-31 13:40:48 +0000148void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000149 PathRef File = Params.textDocument.uri.file();
Alex Lorenzf8087862018-08-01 17:39:29 +0000150 if (Params.metadata && !Params.metadata->extraFlags.empty())
151 CDB.setExtraFlagsForFile(File, std::move(Params.metadata->extraFlags));
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000152
Simon Marchi9569fd52018-03-16 14:30:42 +0000153 std::string &Contents = Params.textDocument.text;
154
Simon Marchi98082622018-03-26 14:41:40 +0000155 DraftMgr.addDraft(File, Contents);
Simon Marchi9569fd52018-03-16 14:30:42 +0000156 Server.addDocument(File, Contents, WantDiagnostics::Yes);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000157}
158
Sam McCalld1a7a372018-01-31 13:40:48 +0000159void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) {
Eric Liu51fed182018-02-22 18:40:39 +0000160 auto WantDiags = WantDiagnostics::Auto;
161 if (Params.wantDiagnostics.hasValue())
162 WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes
163 : WantDiagnostics::No;
Simon Marchi9569fd52018-03-16 14:30:42 +0000164
165 PathRef File = Params.textDocument.uri.file();
Simon Marchi98082622018-03-26 14:41:40 +0000166 llvm::Expected<std::string> Contents =
167 DraftMgr.updateDraft(File, Params.contentChanges);
168 if (!Contents) {
169 // If this fails, we are most likely going to be not in sync anymore with
170 // the client. It is better to remove the draft and let further operations
171 // fail rather than giving wrong results.
172 DraftMgr.removeDraft(File);
173 Server.removeDocument(File);
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000174 CDB.invalidate(File);
Sam McCallbed58852018-07-11 10:35:11 +0000175 elog("Failed to update {0}: {1}", File, Contents.takeError());
Simon Marchi98082622018-03-26 14:41:40 +0000176 return;
177 }
Simon Marchi9569fd52018-03-16 14:30:42 +0000178
Simon Marchi98082622018-03-26 14:41:40 +0000179 Server.addDocument(File, *Contents, WantDiags);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000180}
181
Sam McCalld1a7a372018-01-31 13:40:48 +0000182void ClangdLSPServer::onFileEvent(DidChangeWatchedFilesParams &Params) {
Marc-Andre Laperlebf114242017-10-02 18:00:37 +0000183 Server.onFileEvent(Params);
184}
185
Sam McCalld1a7a372018-01-31 13:40:48 +0000186void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) {
Eric Liuc5105f92018-02-16 14:15:55 +0000187 auto ApplyEdit = [](WorkspaceEdit WE) {
188 ApplyWorkspaceEditParams Edit;
189 Edit.edit = std::move(WE);
190 // We don't need the response so id == 1 is OK.
191 // Ideally, we would wait for the response and if there is no error, we
192 // would reply success/failure to the original RPC.
193 call("workspace/applyEdit", Edit);
194 };
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000195 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
196 Params.workspaceEdit) {
197 // The flow for "apply-fix" :
198 // 1. We publish a diagnostic, including fixits
199 // 2. The user clicks on the diagnostic, the editor asks us for code actions
200 // 3. We send code actions, with the fixit embedded as context
201 // 4. The user selects the fixit, the editor asks us to apply it
202 // 5. We unwrap the changes and send them back to the editor
203 // 6. The editor applies the changes (applyEdit), and sends us a reply (but
204 // we ignore it)
205
Sam McCalld1a7a372018-01-31 13:40:48 +0000206 reply("Fix applied.");
Eric Liuc5105f92018-02-16 14:15:55 +0000207 ApplyEdit(*Params.workspaceEdit);
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000208 } else {
209 // We should not get here because ExecuteCommandParams would not have
210 // parsed in the first place and this handler should not be called. But if
211 // more commands are added, this will be here has a safe guard.
Ilya Biryukov940901e2017-12-13 12:51:22 +0000212 replyError(
Sam McCalld1a7a372018-01-31 13:40:48 +0000213 ErrorCode::InvalidParams,
Haojian Wu2375c922017-11-07 10:21:02 +0000214 llvm::formatv("Unsupported command \"{0}\".", Params.command).str());
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000215 }
216}
217
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000218void ClangdLSPServer::onWorkspaceSymbol(WorkspaceSymbolParams &Params) {
219 Server.workspaceSymbols(
220 Params.query, CCOpts.Limit,
221 [this](llvm::Expected<std::vector<SymbolInformation>> Items) {
222 if (!Items)
223 return replyError(ErrorCode::InternalError,
224 llvm::toString(Items.takeError()));
225 for (auto &Sym : *Items)
226 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
227
Sam McCalld20d7982018-07-09 14:25:59 +0000228 reply(json::Array(*Items));
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000229 });
230}
231
Sam McCalld1a7a372018-01-31 13:40:48 +0000232void ClangdLSPServer::onRename(RenameParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000233 Path File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000234 llvm::Optional<std::string> Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000235 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000236 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000237 "onRename called for non-added file");
238
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000239 Server.rename(
240 File, Params.position, Params.newName,
241 [File, Code,
242 Params](llvm::Expected<std::vector<tooling::Replacement>> Replacements) {
243 if (!Replacements)
244 return replyError(ErrorCode::InternalError,
245 llvm::toString(Replacements.takeError()));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000246
Eric Liu9133ecd2018-05-11 12:12:08 +0000247 // Turn the replacements into the format specified by the Language
248 // Server Protocol. Fuse them into one big JSON array.
249 std::vector<TextEdit> Edits;
250 for (const auto &R : *Replacements)
251 Edits.push_back(replacementToEdit(*Code, R));
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000252 WorkspaceEdit WE;
253 WE.changes = {{Params.textDocument.uri.uri(), Edits}};
254 reply(WE);
255 });
Haojian Wu345099c2017-11-09 11:30:04 +0000256}
257
Sam McCalld1a7a372018-01-31 13:40:48 +0000258void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000259 PathRef File = Params.textDocument.uri.file();
260 DraftMgr.removeDraft(File);
261 Server.removeDocument(File);
Alex Lorenzf8087862018-08-01 17:39:29 +0000262 CDB.invalidate(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000263}
264
Sam McCall4db732a2017-09-30 10:08:52 +0000265void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCalld1a7a372018-01-31 13:40:48 +0000266 DocumentOnTypeFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000267 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000268 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000269 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000270 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000271 "onDocumentOnTypeFormatting called for non-added file");
272
273 auto ReplacementsOrError = Server.formatOnType(*Code, File, Params.position);
Raoul Wols212bcf82017-12-12 20:25:06 +0000274 if (ReplacementsOrError)
Sam McCalld20d7982018-07-09 14:25:59 +0000275 reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000276 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000277 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000278 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000279}
280
Sam McCall4db732a2017-09-30 10:08:52 +0000281void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCalld1a7a372018-01-31 13:40:48 +0000282 DocumentRangeFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000283 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000284 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000285 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000286 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000287 "onDocumentRangeFormatting called for non-added file");
288
289 auto ReplacementsOrError = Server.formatRange(*Code, File, Params.range);
Raoul Wols212bcf82017-12-12 20:25:06 +0000290 if (ReplacementsOrError)
Sam McCalld20d7982018-07-09 14:25:59 +0000291 reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000292 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000293 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000294 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000295}
296
Sam McCalld1a7a372018-01-31 13:40:48 +0000297void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000298 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000299 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000300 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000301 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000302 "onDocumentFormatting called for non-added file");
303
304 auto ReplacementsOrError = Server.formatFile(*Code, File);
Raoul Wols212bcf82017-12-12 20:25:06 +0000305 if (ReplacementsOrError)
Sam McCalld20d7982018-07-09 14:25:59 +0000306 reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000307 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000308 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000309 llvm::toString(ReplacementsOrError.takeError()));
Sam McCall4db732a2017-09-30 10:08:52 +0000310}
311
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000312void ClangdLSPServer::onDocumentSymbol(DocumentSymbolParams &Params) {
313 Server.documentSymbols(
314 Params.textDocument.uri.file(),
315 [this](llvm::Expected<std::vector<SymbolInformation>> Items) {
316 if (!Items)
317 return replyError(ErrorCode::InvalidParams,
318 llvm::toString(Items.takeError()));
319 for (auto &Sym : *Items)
320 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
Sam McCalld20d7982018-07-09 14:25:59 +0000321 reply(json::Array(*Items));
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000322 });
323}
324
Sam McCalld1a7a372018-01-31 13:40:48 +0000325void ClangdLSPServer::onCodeAction(CodeActionParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000326 // We provide a code action for each diagnostic at the requested location
327 // which has FixIts available.
Simon Marchi9569fd52018-03-16 14:30:42 +0000328 auto Code = DraftMgr.getDraft(Params.textDocument.uri.file());
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000329 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000330 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000331 "onCodeAction called for non-added file");
332
Sam McCalld20d7982018-07-09 14:25:59 +0000333 json::Array Commands;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000334 for (Diagnostic &D : Params.context.diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000335 for (auto &F : getFixes(Params.textDocument.uri.file(), D)) {
Sam McCalldd0566b2017-11-06 15:40:30 +0000336 WorkspaceEdit WE;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000337 std::vector<TextEdit> Edits(F.Edits.begin(), F.Edits.end());
Eric Liu78ed91a72018-01-29 15:37:46 +0000338 WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}};
Sam McCalld20d7982018-07-09 14:25:59 +0000339 Commands.push_back(json::Object{
Ilya Biryukov71028b82018-03-12 15:28:22 +0000340 {"title", llvm::formatv("Apply fix: {0}", F.Message)},
Sam McCalldd0566b2017-11-06 15:40:30 +0000341 {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND},
342 {"arguments", {WE}},
343 });
344 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000345 }
Sam McCalld1a7a372018-01-31 13:40:48 +0000346 reply(std::move(Commands));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000347}
348
Sam McCalld1a7a372018-01-31 13:40:48 +0000349void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) {
Kadir Cetinkaya689bf932018-08-24 13:09:41 +0000350 CreateSpaceForTaskHandle();
351 TaskHandle TH = Server.codeComplete(
352 Params.textDocument.uri.file(), Params.position, CCOpts,
353 [this](llvm::Expected<CodeCompleteResult> List) {
354 auto _ = llvm::make_scope_exit([this]() { CleanupTaskHandle(); });
355
356 if (!List)
357 return replyError(List.takeError());
358 CompletionList LSPList;
359 LSPList.isIncomplete = List->HasMore;
360 for (const auto &R : List->Completions)
361 LSPList.items.push_back(R.render(CCOpts));
362 return reply(std::move(LSPList));
363 });
364 StoreTaskHandle(std::move(TH));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000365}
366
Sam McCalld1a7a372018-01-31 13:40:48 +0000367void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000368 Server.signatureHelp(Params.textDocument.uri.file(), Params.position,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000369 [](llvm::Expected<SignatureHelp> SignatureHelp) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000370 if (!SignatureHelp)
371 return replyError(
372 ErrorCode::InvalidParams,
373 llvm::toString(SignatureHelp.takeError()));
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000374 reply(*SignatureHelp);
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000375 });
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000376}
377
Sam McCalld1a7a372018-01-31 13:40:48 +0000378void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) {
Kadir Cetinkaya689bf932018-08-24 13:09:41 +0000379 Server.findDefinitions(Params.textDocument.uri.file(), Params.position,
380 [](llvm::Expected<std::vector<Location>> Items) {
381 if (!Items)
382 return replyError(
383 ErrorCode::InvalidParams,
384 llvm::toString(Items.takeError()));
385 reply(json::Array(*Items));
386 });
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000387}
388
Sam McCalld1a7a372018-01-31 13:40:48 +0000389void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000390 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file());
Sam McCalld1a7a372018-01-31 13:40:48 +0000391 reply(Result ? URI::createFile(*Result).toString() : "");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000392}
393
Sam McCalld1a7a372018-01-31 13:40:48 +0000394void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000395 Server.findDocumentHighlights(
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000396 Params.textDocument.uri.file(), Params.position,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000397 [](llvm::Expected<std::vector<DocumentHighlight>> Highlights) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000398 if (!Highlights)
399 return replyError(ErrorCode::InternalError,
400 llvm::toString(Highlights.takeError()));
Sam McCalld20d7982018-07-09 14:25:59 +0000401 reply(json::Array(*Highlights));
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000402 });
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000403}
404
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000405void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) {
406 Server.findHover(Params.textDocument.uri.file(), Params.position,
Sam McCall682cfe72018-06-04 10:37:16 +0000407 [](llvm::Expected<llvm::Optional<Hover>> H) {
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000408 if (!H) {
409 replyError(ErrorCode::InternalError,
410 llvm::toString(H.takeError()));
411 return;
412 }
413
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000414 reply(*H);
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000415 });
416}
417
Simon Marchi88016782018-08-01 11:28:49 +0000418void ClangdLSPServer::applyConfiguration(
419 const ClangdConfigurationParamsChange &Settings) {
Simon Marchi5178f922018-02-22 14:00:39 +0000420 // Compilation database change.
421 if (Settings.compilationDatabasePath.hasValue()) {
Alex Lorenzf8087862018-08-01 17:39:29 +0000422 CDB.setCompileCommandsDir(Settings.compilationDatabasePath.getValue());
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000423
Simon Marchi9569fd52018-03-16 14:30:42 +0000424 reparseOpenedFiles();
Simon Marchi5178f922018-02-22 14:00:39 +0000425 }
Alex Lorenzf8087862018-08-01 17:39:29 +0000426
427 // Update to the compilation database.
428 if (Settings.compilationDatabaseChanges) {
429 const auto &CompileCommandUpdates = *Settings.compilationDatabaseChanges;
430 bool ShouldReparseOpenFiles = false;
431 for (auto &Entry : CompileCommandUpdates) {
432 /// The opened files need to be reparsed only when some existing
433 /// entries are changed.
434 PathRef File = Entry.first;
435 if (!CDB.setCompilationCommandForFile(
436 File, tooling::CompileCommand(
437 std::move(Entry.second.workingDirectory), File,
438 std::move(Entry.second.compilationCommand),
439 /*Output=*/"")))
440 ShouldReparseOpenFiles = true;
441 }
442 if (ShouldReparseOpenFiles)
443 reparseOpenedFiles();
444 }
Simon Marchi5178f922018-02-22 14:00:39 +0000445}
446
Simon Marchi88016782018-08-01 11:28:49 +0000447// FIXME: This function needs to be properly tested.
448void ClangdLSPServer::onChangeConfiguration(
449 DidChangeConfigurationParams &Params) {
450 applyConfiguration(Params.settings);
451}
452
Sam McCall1ad142f2018-09-05 11:53:07 +0000453void ClangdLSPServer::onReference(ReferenceParams &Params) {
454 Server.findReferences(Params.textDocument.uri.file(), Params.position,
455 [](llvm::Expected<std::vector<Location>> Locations) {
456 if (!Locations)
457 return replyError(
458 ErrorCode::InternalError,
459 llvm::toString(Locations.takeError()));
460 reply(llvm::json::Array(*Locations));
461 });
462}
463
Sam McCall7363a2f2018-03-05 17:28:54 +0000464ClangdLSPServer::ClangdLSPServer(JSONOutput &Out,
Sam McCalladccab62017-11-23 16:58:22 +0000465 const clangd::CodeCompleteOptions &CCOpts,
Eric Liubfac8f72017-12-19 18:00:37 +0000466 llvm::Optional<Path> CompileCommandsDir,
Alex Lorenzf8087862018-08-01 17:39:29 +0000467 bool ShouldUseInMemoryCDB,
Sam McCall7363a2f2018-03-05 17:28:54 +0000468 const ClangdServer::Options &Opts)
Alex Lorenzf8087862018-08-01 17:39:29 +0000469 : Out(Out), CDB(ShouldUseInMemoryCDB ? CompilationDB::makeInMemory()
470 : CompilationDB::makeDirectoryBased(
471 std::move(CompileCommandsDir))),
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000472 CCOpts(CCOpts), SupportedSymbolKinds(defaultSymbolKinds()),
Alex Lorenzf8087862018-08-01 17:39:29 +0000473 Server(CDB.getCDB(), FSProvider, /*DiagConsumer=*/*this, Opts) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000474
Sam McCall27a07cf2018-06-05 09:34:46 +0000475bool ClangdLSPServer::run(std::FILE *In, JSONStreamStyle InputStyle) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000476 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000477
Ilya Biryukovafb55542017-05-16 14:40:30 +0000478 // Set up JSONRPCDispatcher.
Sam McCalld20d7982018-07-09 14:25:59 +0000479 JSONRPCDispatcher Dispatcher([](const json::Value &Params) {
Sam McCalld1a7a372018-01-31 13:40:48 +0000480 replyError(ErrorCode::MethodNotFound, "method not found");
Ilya Biryukov940901e2017-12-13 12:51:22 +0000481 });
Simon Marchi6e8eb9d2018-03-07 21:47:25 +0000482 registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000483
Ilya Biryukovafb55542017-05-16 14:40:30 +0000484 // Run the Language Server loop.
Sam McCall5ed599e2018-02-06 10:47:30 +0000485 runLanguageServerLoop(In, Out, InputStyle, Dispatcher, IsDone);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000486
487 // Make sure IsDone is set to true after this method exits to ensure assertion
488 // at the start of the method fires if it's ever executed again.
489 IsDone = true;
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000490
491 return ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000492}
493
Ilya Biryukov71028b82018-03-12 15:28:22 +0000494std::vector<Fix> ClangdLSPServer::getFixes(StringRef File,
495 const clangd::Diagnostic &D) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000496 std::lock_guard<std::mutex> Lock(FixItsMutex);
497 auto DiagToFixItsIter = FixItsMap.find(File);
498 if (DiagToFixItsIter == FixItsMap.end())
499 return {};
500
501 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
502 auto FixItsIter = DiagToFixItsMap.find(D);
503 if (FixItsIter == DiagToFixItsMap.end())
504 return {};
505
506 return FixItsIter->second;
507}
508
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000509void ClangdLSPServer::onDiagnosticsReady(PathRef File,
510 std::vector<Diag> Diagnostics) {
Sam McCalld20d7982018-07-09 14:25:59 +0000511 json::Array DiagnosticsJSON;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000512
513 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000514 for (auto &Diag : Diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000515 toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) {
Alex Lorenz8626d362018-08-10 17:25:07 +0000516 json::Object LSPDiag({
Ilya Biryukov71028b82018-03-12 15:28:22 +0000517 {"range", Diag.range},
518 {"severity", Diag.severity},
519 {"message", Diag.message},
520 });
Alex Lorenz8626d362018-08-10 17:25:07 +0000521 // LSP extension: embed the fixes in the diagnostic.
522 if (DiagOpts.EmbedFixesInDiagnostics && !Fixes.empty()) {
523 json::Array ClangdFixes;
524 for (const auto &Fix : Fixes) {
525 WorkspaceEdit WE;
526 URIForFile URI{File};
527 WE.changes = {{URI.uri(), std::vector<TextEdit>(Fix.Edits.begin(),
528 Fix.Edits.end())}};
529 ClangdFixes.push_back(
530 json::Object{{"edit", toJSON(WE)}, {"title", Fix.Message}});
531 }
532 LSPDiag["clangd_fixes"] = std::move(ClangdFixes);
533 }
Alex Lorenz0ce8a7a2018-08-22 20:30:06 +0000534 if (DiagOpts.SendDiagnosticCategory && !Diag.category.empty())
Alex Lorenz37146432018-08-14 22:21:40 +0000535 LSPDiag["category"] = Diag.category;
Alex Lorenz8626d362018-08-10 17:25:07 +0000536 DiagnosticsJSON.push_back(std::move(LSPDiag));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000537
538 auto &FixItsForDiagnostic = LocalFixIts[Diag];
539 std::copy(Fixes.begin(), Fixes.end(),
540 std::back_inserter(FixItsForDiagnostic));
Sam McCalldd0566b2017-11-06 15:40:30 +0000541 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000542 }
543
544 // Cache FixIts
545 {
546 // FIXME(ibiryukov): should be deleted when documents are removed
547 std::lock_guard<std::mutex> Lock(FixItsMutex);
548 FixItsMap[File] = LocalFixIts;
549 }
550
551 // Publish diagnostics.
Sam McCalld20d7982018-07-09 14:25:59 +0000552 Out.writeMessage(json::Object{
Sam McCalldd0566b2017-11-06 15:40:30 +0000553 {"jsonrpc", "2.0"},
554 {"method", "textDocument/publishDiagnostics"},
555 {"params",
Sam McCalld20d7982018-07-09 14:25:59 +0000556 json::Object{
Eric Liu78ed91a72018-01-29 15:37:46 +0000557 {"uri", URIForFile{File}},
Sam McCalldd0566b2017-11-06 15:40:30 +0000558 {"diagnostics", std::move(DiagnosticsJSON)},
559 }},
560 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000561}
Simon Marchi9569fd52018-03-16 14:30:42 +0000562
563void ClangdLSPServer::reparseOpenedFiles() {
564 for (const Path &FilePath : DraftMgr.getActiveFiles())
565 Server.addDocument(FilePath, *DraftMgr.getDraft(FilePath),
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000566 WantDiagnostics::Auto);
Simon Marchi9569fd52018-03-16 14:30:42 +0000567}
Alex Lorenzf8087862018-08-01 17:39:29 +0000568
569ClangdLSPServer::CompilationDB ClangdLSPServer::CompilationDB::makeInMemory() {
570 return CompilationDB(llvm::make_unique<InMemoryCompilationDb>(), nullptr,
571 /*IsDirectoryBased=*/false);
572}
573
574ClangdLSPServer::CompilationDB
575ClangdLSPServer::CompilationDB::makeDirectoryBased(
576 llvm::Optional<Path> CompileCommandsDir) {
577 auto CDB = llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(
578 std::move(CompileCommandsDir));
579 auto CachingCDB = llvm::make_unique<CachingCompilationDb>(*CDB);
580 return CompilationDB(std::move(CDB), std::move(CachingCDB),
581 /*IsDirectoryBased=*/true);
582}
583
584void ClangdLSPServer::CompilationDB::invalidate(PathRef File) {
585 if (!IsDirectoryBased)
586 static_cast<InMemoryCompilationDb *>(CDB.get())->invalidate(File);
587 else
588 CachingCDB->invalidate(File);
589}
590
591bool ClangdLSPServer::CompilationDB::setCompilationCommandForFile(
592 PathRef File, tooling::CompileCommand CompilationCommand) {
593 if (IsDirectoryBased) {
594 elog("Trying to set compile command for {0} while using directory-based "
595 "compilation database",
596 File);
597 return false;
598 }
599 return static_cast<InMemoryCompilationDb *>(CDB.get())
600 ->setCompilationCommandForFile(File, std::move(CompilationCommand));
601}
602
603void ClangdLSPServer::CompilationDB::setExtraFlagsForFile(
604 PathRef File, std::vector<std::string> ExtraFlags) {
605 if (!IsDirectoryBased) {
606 elog("Trying to set extra flags for {0} while using in-memory compilation "
607 "database",
608 File);
609 return;
610 }
611 static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
612 ->setExtraFlagsForFile(File, std::move(ExtraFlags));
613 CachingCDB->invalidate(File);
614}
615
616void ClangdLSPServer::CompilationDB::setCompileCommandsDir(Path P) {
617 if (!IsDirectoryBased) {
618 elog("Trying to set compile commands dir while using in-memory compilation "
619 "database");
620 return;
621 }
622 static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
623 ->setCompileCommandsDir(P);
624 CachingCDB->clear();
625}
626
627GlobalCompilationDatabase &ClangdLSPServer::CompilationDB::getCDB() {
628 if (CachingCDB)
629 return *CachingCDB;
630 return *CDB;
631}
Kadir Cetinkaya689bf932018-08-24 13:09:41 +0000632
633void ClangdLSPServer::onCancelRequest(CancelParams &Params) {
634 std::lock_guard<std::mutex> Lock(TaskHandlesMutex);
635 const auto &It = TaskHandles.find(Params.ID);
636 if (It == TaskHandles.end())
637 return;
638 if (It->second)
639 It->second->cancel();
640 TaskHandles.erase(It);
641}
642
643void ClangdLSPServer::CleanupTaskHandle() {
644 const json::Value *ID = getRequestId();
645 if (!ID)
646 return;
647 std::string NormalizedID = NormalizeRequestID(*ID);
648 std::lock_guard<std::mutex> Lock(TaskHandlesMutex);
649 TaskHandles.erase(NormalizedID);
650}
651
652void ClangdLSPServer::CreateSpaceForTaskHandle() {
653 const json::Value *ID = getRequestId();
654 if (!ID)
655 return;
656 std::string NormalizedID = NormalizeRequestID(*ID);
657 std::lock_guard<std::mutex> Lock(TaskHandlesMutex);
658 if (!TaskHandles.insert({NormalizedID, nullptr}).second)
659 elog("Creation of space for task handle: {0} failed.", NormalizedID);
660}
661
662void ClangdLSPServer::StoreTaskHandle(TaskHandle TH) {
663 const json::Value *ID = getRequestId();
664 if (!ID)
665 return;
666 std::string NormalizedID = NormalizeRequestID(*ID);
667 std::lock_guard<std::mutex> Lock(TaskHandlesMutex);
668 auto It = TaskHandles.find(NormalizedID);
669 if (It == TaskHandles.end()) {
670 elog("CleanupTaskHandle called before store can happen for request:{0}.",
671 NormalizedID);
672 return;
673 }
674 if (It->second != nullptr)
675 elog("TaskHandle didn't get cleared for: {0}.", NormalizedID);
676 It->second = std::move(TH);
677}