blob: 1585fbf61f7f02ac7b437a1777a683ed6b170c56 [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"
Ilya Biryukov38d79772017-05-16 09:38:59 +000012#include "JSONRPCDispatcher.h"
Sam McCallb536a2a2017-12-19 12:23:48 +000013#include "SourceCode.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"
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000019
Ilya Biryukov38d79772017-05-16 09:38:59 +000020using namespace clang::clangd;
21using namespace clang;
Sam McCalld20d7982018-07-09 14:25:59 +000022using namespace llvm;
Ilya Biryukov38d79772017-05-16 09:38:59 +000023
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:
32 llvm::Expected<std::string>
33 getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
34 llvm::StringRef /*HintPath*/) const override {
35 using namespace llvm::sys;
36 // Still require "/" in body to mimic file scheme, as we want lengths of an
37 // equivalent URI in both schemes to be the same.
38 if (!Body.startswith("/"))
39 return llvm::make_error<llvm::StringError>(
40 "Expect URI body to be an absolute path starting with '/': " + Body,
41 llvm::inconvertibleErrorCode());
42 Body = Body.ltrim('/');
Nico Weber0da22902018-04-10 13:14:03 +000043#ifdef _WIN32
Eric Liu5740ff52018-01-31 16:26:27 +000044 constexpr char TestDir[] = "C:\\clangd-test";
45#else
46 constexpr char TestDir[] = "/clangd-test";
47#endif
48 llvm::SmallVector<char, 16> Path(Body.begin(), Body.end());
49 path::native(Path);
50 auto Err = fs::make_absolute(TestDir, Path);
Eric Liucda25262018-02-01 12:44:52 +000051 if (Err)
52 llvm_unreachable("Failed to make absolute path in test scheme.");
Eric Liu5740ff52018-01-31 16:26:27 +000053 return std::string(Path.begin(), Path.end());
54 }
55
56 llvm::Expected<URI>
57 uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
58 llvm_unreachable("Clangd must never create a test URI.");
59 }
60};
61
62static URISchemeRegistry::Add<TestScheme>
63 X("test", "Test scheme for clangd lit tests.");
64
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000065SymbolKindBitset defaultSymbolKinds() {
66 SymbolKindBitset Defaults;
67 for (size_t I = SymbolKindMin; I <= static_cast<size_t>(SymbolKind::Array);
68 ++I)
69 Defaults.set(I);
70 return Defaults;
71}
72
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +000073CompletionItemKindBitset defaultCompletionItemKinds() {
74 CompletionItemKindBitset Defaults;
75 for (size_t I = CompletionItemKindMin;
76 I <= static_cast<size_t>(CompletionItemKind::Reference); ++I)
77 Defaults.set(I);
78 return Defaults;
79}
80
Ilya Biryukovafb55542017-05-16 14:40:30 +000081} // namespace
82
Sam McCalld1a7a372018-01-31 13:40:48 +000083void ClangdLSPServer::onInitialize(InitializeParams &Params) {
Simon Marchiabeed662018-10-16 15:55:03 +000084 if (Params.initializationOptions) {
85 const ClangdInitializationOptions &Opts = *Params.initializationOptions;
86
87 // Explicit compilation database path.
88 if (Opts.compilationDatabasePath.hasValue()) {
89 CDB.setCompileCommandsDir(Opts.compilationDatabasePath.getValue());
90 }
91
92 applyConfiguration(Opts.ParamsChange);
93 }
Simon Marchi88016782018-08-01 11:28:49 +000094
Ilya Biryukov7d60d202018-02-16 12:20:47 +000095 if (Params.rootUri && *Params.rootUri)
Ilya Biryukov652364b2018-09-26 05:48:29 +000096 Server->setRootPath(Params.rootUri->file());
Ilya Biryukov23bc73b2018-02-15 14:32:57 +000097 else if (Params.rootPath && !Params.rootPath->empty())
Ilya Biryukov652364b2018-09-26 05:48:29 +000098 Server->setRootPath(*Params.rootPath);
Ilya Biryukov23bc73b2018-02-15 14:32:57 +000099
100 CCOpts.EnableSnippets =
101 Params.capabilities.textDocument.completion.completionItem.snippetSupport;
Alex Lorenz8626d362018-08-10 17:25:07 +0000102 DiagOpts.EmbedFixesInDiagnostics =
103 Params.capabilities.textDocument.publishDiagnostics.clangdFixSupport;
Alex Lorenz0ce8a7a2018-08-22 20:30:06 +0000104 DiagOpts.SendDiagnosticCategory =
105 Params.capabilities.textDocument.publishDiagnostics.categorySupport;
Sam McCall20841d42018-10-16 16:29:41 +0000106 SupportsCodeAction =
107 Params.capabilities.textDocument.codeActionLiteralSupport;
Ilya Biryukov23bc73b2018-02-15 14:32:57 +0000108
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000109 if (Params.capabilities.workspace && Params.capabilities.workspace->symbol &&
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +0000110 Params.capabilities.workspace->symbol->symbolKind &&
111 Params.capabilities.workspace->symbol->symbolKind->valueSet) {
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000112 for (SymbolKind Kind :
113 *Params.capabilities.workspace->symbol->symbolKind->valueSet) {
114 SupportedSymbolKinds.set(static_cast<size_t>(Kind));
115 }
116 }
117
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +0000118 if (Params.capabilities.textDocument.completion.completionItemKind &&
119 Params.capabilities.textDocument.completion.completionItemKind->valueSet)
120 for (CompletionItemKind Kind : *Params.capabilities.textDocument.completion
121 .completionItemKind->valueSet)
122 SupportedCompletionItemKinds.set(static_cast<size_t>(Kind));
123
Sam McCalld20d7982018-07-09 14:25:59 +0000124 reply(json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000125 {{"capabilities",
Sam McCalld20d7982018-07-09 14:25:59 +0000126 json::Object{
Simon Marchi98082622018-03-26 14:41:40 +0000127 {"textDocumentSync", (int)TextDocumentSyncKind::Incremental},
Sam McCall0930ab02017-11-07 15:49:35 +0000128 {"documentFormattingProvider", true},
129 {"documentRangeFormattingProvider", true},
130 {"documentOnTypeFormattingProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000131 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000132 {"firstTriggerCharacter", "}"},
133 {"moreTriggerCharacter", {}},
134 }},
135 {"codeActionProvider", true},
136 {"completionProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000137 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000138 {"resolveProvider", false},
139 {"triggerCharacters", {".", ">", ":"}},
140 }},
141 {"signatureHelpProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000142 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000143 {"triggerCharacters", {"(", ","}},
144 }},
145 {"definitionProvider", true},
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000146 {"documentHighlightProvider", true},
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000147 {"hoverProvider", true},
Haojian Wu345099c2017-11-09 11:30:04 +0000148 {"renameProvider", true},
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000149 {"documentSymbolProvider", true},
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000150 {"workspaceSymbolProvider", true},
Sam McCall1ad142f2018-09-05 11:53:07 +0000151 {"referencesProvider", true},
Sam McCall0930ab02017-11-07 15:49:35 +0000152 {"executeCommandProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000153 json::Object{
Eric Liu2c190532018-05-15 15:23:53 +0000154 {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
Sam McCall0930ab02017-11-07 15:49:35 +0000155 }},
156 }}}});
Ilya Biryukovafb55542017-05-16 14:40:30 +0000157}
158
Sam McCalld1a7a372018-01-31 13:40:48 +0000159void ClangdLSPServer::onShutdown(ShutdownParams &Params) {
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000160 // Do essentially nothing, just say we're ready to exit.
161 ShutdownRequestReceived = true;
Sam McCalld1a7a372018-01-31 13:40:48 +0000162 reply(nullptr);
Sam McCall8a5dded2017-10-12 13:29:58 +0000163}
Ilya Biryukovafb55542017-05-16 14:40:30 +0000164
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000165void ClangdLSPServer::onExit(ExitParams &Params) {
166 // No work to do.
167 // JSONRPCDispatcher shuts down the transport after this notification.
168}
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000169
Sam McCalld1a7a372018-01-31 13:40:48 +0000170void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000171 PathRef File = Params.textDocument.uri.file();
Alex Lorenzf8087862018-08-01 17:39:29 +0000172 if (Params.metadata && !Params.metadata->extraFlags.empty())
173 CDB.setExtraFlagsForFile(File, std::move(Params.metadata->extraFlags));
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000174
Simon Marchi9569fd52018-03-16 14:30:42 +0000175 std::string &Contents = Params.textDocument.text;
176
Simon Marchi98082622018-03-26 14:41:40 +0000177 DraftMgr.addDraft(File, Contents);
Ilya Biryukov652364b2018-09-26 05:48:29 +0000178 Server->addDocument(File, Contents, WantDiagnostics::Yes);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000179}
180
Sam McCalld1a7a372018-01-31 13:40:48 +0000181void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) {
Eric Liu51fed182018-02-22 18:40:39 +0000182 auto WantDiags = WantDiagnostics::Auto;
183 if (Params.wantDiagnostics.hasValue())
184 WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes
185 : WantDiagnostics::No;
Simon Marchi9569fd52018-03-16 14:30:42 +0000186
187 PathRef File = Params.textDocument.uri.file();
Simon Marchi98082622018-03-26 14:41:40 +0000188 llvm::Expected<std::string> Contents =
189 DraftMgr.updateDraft(File, Params.contentChanges);
190 if (!Contents) {
191 // If this fails, we are most likely going to be not in sync anymore with
192 // the client. It is better to remove the draft and let further operations
193 // fail rather than giving wrong results.
194 DraftMgr.removeDraft(File);
Ilya Biryukov652364b2018-09-26 05:48:29 +0000195 Server->removeDocument(File);
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000196 CDB.invalidate(File);
Sam McCallbed58852018-07-11 10:35:11 +0000197 elog("Failed to update {0}: {1}", File, Contents.takeError());
Simon Marchi98082622018-03-26 14:41:40 +0000198 return;
199 }
Simon Marchi9569fd52018-03-16 14:30:42 +0000200
Ilya Biryukov652364b2018-09-26 05:48:29 +0000201 Server->addDocument(File, *Contents, WantDiags);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000202}
203
Sam McCalld1a7a372018-01-31 13:40:48 +0000204void ClangdLSPServer::onFileEvent(DidChangeWatchedFilesParams &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000205 Server->onFileEvent(Params);
Marc-Andre Laperlebf114242017-10-02 18:00:37 +0000206}
207
Sam McCalld1a7a372018-01-31 13:40:48 +0000208void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) {
Eric Liuc5105f92018-02-16 14:15:55 +0000209 auto ApplyEdit = [](WorkspaceEdit WE) {
210 ApplyWorkspaceEditParams Edit;
211 Edit.edit = std::move(WE);
212 // We don't need the response so id == 1 is OK.
213 // Ideally, we would wait for the response and if there is no error, we
214 // would reply success/failure to the original RPC.
215 call("workspace/applyEdit", Edit);
216 };
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000217 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
218 Params.workspaceEdit) {
219 // The flow for "apply-fix" :
220 // 1. We publish a diagnostic, including fixits
221 // 2. The user clicks on the diagnostic, the editor asks us for code actions
222 // 3. We send code actions, with the fixit embedded as context
223 // 4. The user selects the fixit, the editor asks us to apply it
224 // 5. We unwrap the changes and send them back to the editor
225 // 6. The editor applies the changes (applyEdit), and sends us a reply (but
226 // we ignore it)
227
Sam McCalld1a7a372018-01-31 13:40:48 +0000228 reply("Fix applied.");
Eric Liuc5105f92018-02-16 14:15:55 +0000229 ApplyEdit(*Params.workspaceEdit);
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000230 } else {
231 // We should not get here because ExecuteCommandParams would not have
232 // parsed in the first place and this handler should not be called. But if
233 // more commands are added, this will be here has a safe guard.
Ilya Biryukov940901e2017-12-13 12:51:22 +0000234 replyError(
Sam McCalld1a7a372018-01-31 13:40:48 +0000235 ErrorCode::InvalidParams,
Haojian Wu2375c922017-11-07 10:21:02 +0000236 llvm::formatv("Unsupported command \"{0}\".", Params.command).str());
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000237 }
238}
239
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000240void ClangdLSPServer::onWorkspaceSymbol(WorkspaceSymbolParams &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000241 Server->workspaceSymbols(
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000242 Params.query, CCOpts.Limit,
243 [this](llvm::Expected<std::vector<SymbolInformation>> Items) {
244 if (!Items)
245 return replyError(ErrorCode::InternalError,
246 llvm::toString(Items.takeError()));
247 for (auto &Sym : *Items)
248 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
249
Sam McCalld20d7982018-07-09 14:25:59 +0000250 reply(json::Array(*Items));
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000251 });
252}
253
Sam McCalld1a7a372018-01-31 13:40:48 +0000254void ClangdLSPServer::onRename(RenameParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000255 Path File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000256 llvm::Optional<std::string> Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000257 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000258 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000259 "onRename called for non-added file");
260
Ilya Biryukov652364b2018-09-26 05:48:29 +0000261 Server->rename(
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000262 File, Params.position, Params.newName,
263 [File, Code,
264 Params](llvm::Expected<std::vector<tooling::Replacement>> Replacements) {
265 if (!Replacements)
266 return replyError(ErrorCode::InternalError,
267 llvm::toString(Replacements.takeError()));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000268
Eric Liu9133ecd2018-05-11 12:12:08 +0000269 // Turn the replacements into the format specified by the Language
270 // Server Protocol. Fuse them into one big JSON array.
271 std::vector<TextEdit> Edits;
272 for (const auto &R : *Replacements)
273 Edits.push_back(replacementToEdit(*Code, R));
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000274 WorkspaceEdit WE;
275 WE.changes = {{Params.textDocument.uri.uri(), Edits}};
276 reply(WE);
277 });
Haojian Wu345099c2017-11-09 11:30:04 +0000278}
279
Sam McCalld1a7a372018-01-31 13:40:48 +0000280void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000281 PathRef File = Params.textDocument.uri.file();
282 DraftMgr.removeDraft(File);
Ilya Biryukov652364b2018-09-26 05:48:29 +0000283 Server->removeDocument(File);
Alex Lorenzf8087862018-08-01 17:39:29 +0000284 CDB.invalidate(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000285}
286
Sam McCall4db732a2017-09-30 10:08:52 +0000287void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCalld1a7a372018-01-31 13:40:48 +0000288 DocumentOnTypeFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000289 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000290 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000291 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000292 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000293 "onDocumentOnTypeFormatting called for non-added file");
294
Ilya Biryukov652364b2018-09-26 05:48:29 +0000295 auto ReplacementsOrError = Server->formatOnType(*Code, File, Params.position);
Raoul Wols212bcf82017-12-12 20:25:06 +0000296 if (ReplacementsOrError)
Sam McCalld20d7982018-07-09 14:25:59 +0000297 reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000298 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000299 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000300 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000301}
302
Sam McCall4db732a2017-09-30 10:08:52 +0000303void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCalld1a7a372018-01-31 13:40:48 +0000304 DocumentRangeFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000305 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000306 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000307 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000308 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000309 "onDocumentRangeFormatting called for non-added file");
310
Ilya Biryukov652364b2018-09-26 05:48:29 +0000311 auto ReplacementsOrError = Server->formatRange(*Code, File, Params.range);
Raoul Wols212bcf82017-12-12 20:25:06 +0000312 if (ReplacementsOrError)
Sam McCalld20d7982018-07-09 14:25:59 +0000313 reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000314 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000315 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000316 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000317}
318
Sam McCalld1a7a372018-01-31 13:40:48 +0000319void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000320 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000321 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000322 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000323 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000324 "onDocumentFormatting called for non-added file");
325
Ilya Biryukov652364b2018-09-26 05:48:29 +0000326 auto ReplacementsOrError = Server->formatFile(*Code, File);
Raoul Wols212bcf82017-12-12 20:25:06 +0000327 if (ReplacementsOrError)
Sam McCalld20d7982018-07-09 14:25:59 +0000328 reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000329 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000330 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000331 llvm::toString(ReplacementsOrError.takeError()));
Sam McCall4db732a2017-09-30 10:08:52 +0000332}
333
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000334void ClangdLSPServer::onDocumentSymbol(DocumentSymbolParams &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000335 Server->documentSymbols(
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000336 Params.textDocument.uri.file(),
337 [this](llvm::Expected<std::vector<SymbolInformation>> Items) {
338 if (!Items)
339 return replyError(ErrorCode::InvalidParams,
340 llvm::toString(Items.takeError()));
341 for (auto &Sym : *Items)
342 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
Sam McCalld20d7982018-07-09 14:25:59 +0000343 reply(json::Array(*Items));
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000344 });
345}
346
Sam McCall20841d42018-10-16 16:29:41 +0000347static Optional<Command> asCommand(const CodeAction &Action) {
348 Command Cmd;
349 if (Action.command && Action.edit)
350 return llvm::None; // Not representable. (We never emit these anyway).
351 if (Action.command) {
352 Cmd = *Action.command;
353 } else if (Action.edit) {
354 Cmd.command = Command::CLANGD_APPLY_FIX_COMMAND;
355 Cmd.workspaceEdit = *Action.edit;
356 } else {
357 return llvm::None;
358 }
359 Cmd.title = Action.title;
360 if (Action.kind && *Action.kind == CodeAction::QUICKFIX_KIND)
361 Cmd.title = "Apply fix: " + Cmd.title;
362 return Cmd;
363}
364
Sam McCalld1a7a372018-01-31 13:40:48 +0000365void ClangdLSPServer::onCodeAction(CodeActionParams &Params) {
Sam McCall20841d42018-10-16 16:29:41 +0000366 // We provide a code action for Fixes on the specified diagnostics.
367 if (!DraftMgr.getDraft(Params.textDocument.uri.file()))
Sam McCalld1a7a372018-01-31 13:40:48 +0000368 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000369 "onCodeAction called for non-added file");
370
Sam McCall20841d42018-10-16 16:29:41 +0000371 std::vector<CodeAction> Actions;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000372 for (Diagnostic &D : Params.context.diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000373 for (auto &F : getFixes(Params.textDocument.uri.file(), D)) {
Sam McCall20841d42018-10-16 16:29:41 +0000374 Actions.emplace_back();
375 Actions.back().title = F.Message;
376 Actions.back().kind = CodeAction::QUICKFIX_KIND;
377 Actions.back().diagnostics = {D};
378 Actions.back().edit.emplace();
379 Actions.back().edit->changes.emplace();
380 (*Actions.back().edit->changes)[Params.textDocument.uri.uri()] = {
381 F.Edits.begin(), F.Edits.end()};
Sam McCalldd0566b2017-11-06 15:40:30 +0000382 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000383 }
Sam McCall20841d42018-10-16 16:29:41 +0000384
385 if (SupportsCodeAction)
386 reply(json::Array(Actions));
387 else {
388 std::vector<Command> Commands;
389 for (const auto &Action : Actions)
390 if (auto Command = asCommand(Action))
391 Commands.push_back(std::move(*Command));
392 reply(json::Array(Commands));
393 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000394}
395
Sam McCalld1a7a372018-01-31 13:40:48 +0000396void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000397 Server->codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts,
398 [this](llvm::Expected<CodeCompleteResult> List) {
399 if (!List)
400 return replyError(List.takeError());
401 CompletionList LSPList;
402 LSPList.isIncomplete = List->HasMore;
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +0000403 for (const auto &R : List->Completions) {
404 CompletionItem C = R.render(CCOpts);
405 C.kind = adjustKindToCapability(
406 C.kind, SupportedCompletionItemKinds);
407 LSPList.items.push_back(std::move(C));
408 }
Ilya Biryukov652364b2018-09-26 05:48:29 +0000409 return reply(std::move(LSPList));
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000410 });
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000411}
412
Ilya Biryukov652364b2018-09-26 05:48:29 +0000413void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) {
414 Server->signatureHelp(Params.textDocument.uri.file(), Params.position,
415 [](llvm::Expected<SignatureHelp> SignatureHelp) {
416 if (!SignatureHelp)
417 return replyError(
418 ErrorCode::InvalidParams,
419 llvm::toString(SignatureHelp.takeError()));
420 reply(*SignatureHelp);
421 });
422}
423
Sam McCalld1a7a372018-01-31 13:40:48 +0000424void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000425 Server->findDefinitions(Params.textDocument.uri.file(), Params.position,
426 [](llvm::Expected<std::vector<Location>> Items) {
427 if (!Items)
428 return replyError(
429 ErrorCode::InvalidParams,
430 llvm::toString(Items.takeError()));
431 reply(json::Array(*Items));
432 });
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000433}
434
Sam McCalld1a7a372018-01-31 13:40:48 +0000435void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000436 llvm::Optional<Path> Result = Server->switchSourceHeader(Params.uri.file());
Sam McCalld1a7a372018-01-31 13:40:48 +0000437 reply(Result ? URI::createFile(*Result).toString() : "");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000438}
439
Sam McCalld1a7a372018-01-31 13:40:48 +0000440void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000441 Server->findDocumentHighlights(
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000442 Params.textDocument.uri.file(), Params.position,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000443 [](llvm::Expected<std::vector<DocumentHighlight>> Highlights) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000444 if (!Highlights)
445 return replyError(ErrorCode::InternalError,
446 llvm::toString(Highlights.takeError()));
Sam McCalld20d7982018-07-09 14:25:59 +0000447 reply(json::Array(*Highlights));
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000448 });
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000449}
450
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000451void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000452 Server->findHover(Params.textDocument.uri.file(), Params.position,
453 [](llvm::Expected<llvm::Optional<Hover>> H) {
454 if (!H) {
455 replyError(ErrorCode::InternalError,
456 llvm::toString(H.takeError()));
457 return;
458 }
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000459
Ilya Biryukov652364b2018-09-26 05:48:29 +0000460 reply(*H);
461 });
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000462}
463
Simon Marchi88016782018-08-01 11:28:49 +0000464void ClangdLSPServer::applyConfiguration(
Simon Marchiabeed662018-10-16 15:55:03 +0000465 const ClangdConfigurationParamsChange &Params) {
466 // Per-file update to the compilation database.
467 if (Params.compilationDatabaseChanges) {
468 const auto &CompileCommandUpdates = *Params.compilationDatabaseChanges;
Alex Lorenzf8087862018-08-01 17:39:29 +0000469 bool ShouldReparseOpenFiles = false;
470 for (auto &Entry : CompileCommandUpdates) {
471 /// The opened files need to be reparsed only when some existing
472 /// entries are changed.
473 PathRef File = Entry.first;
474 if (!CDB.setCompilationCommandForFile(
475 File, tooling::CompileCommand(
476 std::move(Entry.second.workingDirectory), File,
477 std::move(Entry.second.compilationCommand),
478 /*Output=*/"")))
479 ShouldReparseOpenFiles = true;
480 }
481 if (ShouldReparseOpenFiles)
482 reparseOpenedFiles();
483 }
Simon Marchi5178f922018-02-22 14:00:39 +0000484}
485
Simon Marchi88016782018-08-01 11:28:49 +0000486// FIXME: This function needs to be properly tested.
487void ClangdLSPServer::onChangeConfiguration(
488 DidChangeConfigurationParams &Params) {
489 applyConfiguration(Params.settings);
490}
491
Sam McCall1ad142f2018-09-05 11:53:07 +0000492void ClangdLSPServer::onReference(ReferenceParams &Params) {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000493 Server->findReferences(Params.textDocument.uri.file(), Params.position,
494 [](llvm::Expected<std::vector<Location>> Locations) {
495 if (!Locations)
496 return replyError(
497 ErrorCode::InternalError,
498 llvm::toString(Locations.takeError()));
499 reply(llvm::json::Array(*Locations));
500 });
Sam McCall1ad142f2018-09-05 11:53:07 +0000501}
502
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000503ClangdLSPServer::ClangdLSPServer(class Transport &Transp,
Sam McCalladccab62017-11-23 16:58:22 +0000504 const clangd::CodeCompleteOptions &CCOpts,
Eric Liubfac8f72017-12-19 18:00:37 +0000505 llvm::Optional<Path> CompileCommandsDir,
Alex Lorenzf8087862018-08-01 17:39:29 +0000506 bool ShouldUseInMemoryCDB,
Sam McCall7363a2f2018-03-05 17:28:54 +0000507 const ClangdServer::Options &Opts)
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000508 : Transp(Transp),
509 CDB(ShouldUseInMemoryCDB ? CompilationDB::makeInMemory()
510 : CompilationDB::makeDirectoryBased(
511 std::move(CompileCommandsDir))),
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000512 CCOpts(CCOpts), SupportedSymbolKinds(defaultSymbolKinds()),
Kadir Cetinkaya133d46f2018-09-27 17:13:07 +0000513 SupportedCompletionItemKinds(defaultCompletionItemKinds()),
Ilya Biryukov652364b2018-09-26 05:48:29 +0000514 Server(new ClangdServer(CDB.getCDB(), FSProvider, /*DiagConsumer=*/*this,
515 Opts)) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000516
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000517bool ClangdLSPServer::run() {
Ilya Biryukov652364b2018-09-26 05:48:29 +0000518 assert(Server);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000519
Ilya Biryukovafb55542017-05-16 14:40:30 +0000520 // Set up JSONRPCDispatcher.
Sam McCalld20d7982018-07-09 14:25:59 +0000521 JSONRPCDispatcher Dispatcher([](const json::Value &Params) {
Sam McCalld1a7a372018-01-31 13:40:48 +0000522 replyError(ErrorCode::MethodNotFound, "method not found");
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000523 return true;
Ilya Biryukov940901e2017-12-13 12:51:22 +0000524 });
Simon Marchi6e8eb9d2018-03-07 21:47:25 +0000525 registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000526
Ilya Biryukovafb55542017-05-16 14:40:30 +0000527 // Run the Language Server loop.
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000528 bool CleanExit = true;
529 if (auto Err = Dispatcher.runLanguageServerLoop(Transp)) {
530 elog("Transport error: {0}", std::move(Err));
531 CleanExit = false;
532 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000533
Ilya Biryukov652364b2018-09-26 05:48:29 +0000534 // Destroy ClangdServer to ensure all worker threads finish.
535 Server.reset();
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000536
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000537 return CleanExit && ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000538}
539
Ilya Biryukov71028b82018-03-12 15:28:22 +0000540std::vector<Fix> ClangdLSPServer::getFixes(StringRef File,
541 const clangd::Diagnostic &D) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000542 std::lock_guard<std::mutex> Lock(FixItsMutex);
543 auto DiagToFixItsIter = FixItsMap.find(File);
544 if (DiagToFixItsIter == FixItsMap.end())
545 return {};
546
547 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
548 auto FixItsIter = DiagToFixItsMap.find(D);
549 if (FixItsIter == DiagToFixItsMap.end())
550 return {};
551
552 return FixItsIter->second;
553}
554
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000555void ClangdLSPServer::onDiagnosticsReady(PathRef File,
556 std::vector<Diag> Diagnostics) {
Sam McCalld20d7982018-07-09 14:25:59 +0000557 json::Array DiagnosticsJSON;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000558
559 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000560 for (auto &Diag : Diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000561 toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) {
Alex Lorenz8626d362018-08-10 17:25:07 +0000562 json::Object LSPDiag({
Ilya Biryukov71028b82018-03-12 15:28:22 +0000563 {"range", Diag.range},
564 {"severity", Diag.severity},
565 {"message", Diag.message},
566 });
Alex Lorenz8626d362018-08-10 17:25:07 +0000567 // LSP extension: embed the fixes in the diagnostic.
568 if (DiagOpts.EmbedFixesInDiagnostics && !Fixes.empty()) {
569 json::Array ClangdFixes;
570 for (const auto &Fix : Fixes) {
571 WorkspaceEdit WE;
572 URIForFile URI{File};
573 WE.changes = {{URI.uri(), std::vector<TextEdit>(Fix.Edits.begin(),
574 Fix.Edits.end())}};
575 ClangdFixes.push_back(
576 json::Object{{"edit", toJSON(WE)}, {"title", Fix.Message}});
577 }
578 LSPDiag["clangd_fixes"] = std::move(ClangdFixes);
579 }
Alex Lorenz0ce8a7a2018-08-22 20:30:06 +0000580 if (DiagOpts.SendDiagnosticCategory && !Diag.category.empty())
Alex Lorenz37146432018-08-14 22:21:40 +0000581 LSPDiag["category"] = Diag.category;
Alex Lorenz8626d362018-08-10 17:25:07 +0000582 DiagnosticsJSON.push_back(std::move(LSPDiag));
Ilya Biryukov71028b82018-03-12 15:28:22 +0000583
584 auto &FixItsForDiagnostic = LocalFixIts[Diag];
Kirill Bobyrev4a5ff882018-10-07 14:49:41 +0000585 llvm::copy(Fixes, std::back_inserter(FixItsForDiagnostic));
Sam McCalldd0566b2017-11-06 15:40:30 +0000586 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000587 }
588
589 // Cache FixIts
590 {
591 // FIXME(ibiryukov): should be deleted when documents are removed
592 std::lock_guard<std::mutex> Lock(FixItsMutex);
593 FixItsMap[File] = LocalFixIts;
594 }
595
596 // Publish diagnostics.
Sam McCalldc8f3cf2018-10-17 07:32:05 +0000597 Transp.notify("textDocument/publishDiagnostics",
598 json::Object{
599 {"uri", URIForFile{File}},
600 {"diagnostics", std::move(DiagnosticsJSON)},
601 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000602}
Simon Marchi9569fd52018-03-16 14:30:42 +0000603
604void ClangdLSPServer::reparseOpenedFiles() {
605 for (const Path &FilePath : DraftMgr.getActiveFiles())
Ilya Biryukov652364b2018-09-26 05:48:29 +0000606 Server->addDocument(FilePath, *DraftMgr.getDraft(FilePath),
607 WantDiagnostics::Auto);
Simon Marchi9569fd52018-03-16 14:30:42 +0000608}
Alex Lorenzf8087862018-08-01 17:39:29 +0000609
610ClangdLSPServer::CompilationDB ClangdLSPServer::CompilationDB::makeInMemory() {
611 return CompilationDB(llvm::make_unique<InMemoryCompilationDb>(), nullptr,
612 /*IsDirectoryBased=*/false);
613}
614
615ClangdLSPServer::CompilationDB
616ClangdLSPServer::CompilationDB::makeDirectoryBased(
617 llvm::Optional<Path> CompileCommandsDir) {
618 auto CDB = llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(
619 std::move(CompileCommandsDir));
620 auto CachingCDB = llvm::make_unique<CachingCompilationDb>(*CDB);
621 return CompilationDB(std::move(CDB), std::move(CachingCDB),
622 /*IsDirectoryBased=*/true);
623}
624
625void ClangdLSPServer::CompilationDB::invalidate(PathRef File) {
626 if (!IsDirectoryBased)
627 static_cast<InMemoryCompilationDb *>(CDB.get())->invalidate(File);
628 else
629 CachingCDB->invalidate(File);
630}
631
632bool ClangdLSPServer::CompilationDB::setCompilationCommandForFile(
633 PathRef File, tooling::CompileCommand CompilationCommand) {
634 if (IsDirectoryBased) {
635 elog("Trying to set compile command for {0} while using directory-based "
636 "compilation database",
637 File);
638 return false;
639 }
640 return static_cast<InMemoryCompilationDb *>(CDB.get())
641 ->setCompilationCommandForFile(File, std::move(CompilationCommand));
642}
643
644void ClangdLSPServer::CompilationDB::setExtraFlagsForFile(
645 PathRef File, std::vector<std::string> ExtraFlags) {
646 if (!IsDirectoryBased) {
647 elog("Trying to set extra flags for {0} while using in-memory compilation "
648 "database",
649 File);
650 return;
651 }
652 static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
653 ->setExtraFlagsForFile(File, std::move(ExtraFlags));
654 CachingCDB->invalidate(File);
655}
656
657void ClangdLSPServer::CompilationDB::setCompileCommandsDir(Path P) {
658 if (!IsDirectoryBased) {
659 elog("Trying to set compile commands dir while using in-memory compilation "
660 "database");
661 return;
662 }
663 static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
664 ->setCompileCommandsDir(P);
665 CachingCDB->clear();
666}
667
668GlobalCompilationDatabase &ClangdLSPServer::CompilationDB::getCDB() {
669 if (CachingCDB)
670 return *CachingCDB;
671 return *CDB;
672}