blob: 99353e6c5ca092ef78dff7eecf70ce250a5e6d51 [file] [log] [blame]
Ilya Biryukov38d79772017-05-16 09:38:59 +00001//===--- ClangdLSPServer.cpp - LSP server ------------------------*- C++-*-===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===---------------------------------------------------------------------===//
9
10#include "ClangdLSPServer.h"
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"
Simon Marchi9569fd52018-03-16 14:30:42 +000015#include "llvm/Support/Errc.h"
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000016#include "llvm/Support/FormatVariadic.h"
Eric Liu5740ff52018-01-31 16:26:27 +000017#include "llvm/Support/Path.h"
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000018
Ilya Biryukov38d79772017-05-16 09:38:59 +000019using namespace clang::clangd;
20using namespace clang;
Sam McCalld20d7982018-07-09 14:25:59 +000021using namespace llvm;
Ilya Biryukov38d79772017-05-16 09:38:59 +000022
Ilya Biryukovafb55542017-05-16 14:40:30 +000023namespace {
24
Eric Liu5740ff52018-01-31 16:26:27 +000025/// \brief Supports a test URI scheme with relaxed constraints for lit tests.
26/// The path in a test URI will be combined with a platform-specific fake
27/// directory to form an absolute path. For example, test:///a.cpp is resolved
28/// C:\clangd-test\a.cpp on Windows and /clangd-test/a.cpp on Unix.
29class TestScheme : public URIScheme {
30public:
31 llvm::Expected<std::string>
32 getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
33 llvm::StringRef /*HintPath*/) const override {
34 using namespace llvm::sys;
35 // Still require "/" in body to mimic file scheme, as we want lengths of an
36 // equivalent URI in both schemes to be the same.
37 if (!Body.startswith("/"))
38 return llvm::make_error<llvm::StringError>(
39 "Expect URI body to be an absolute path starting with '/': " + Body,
40 llvm::inconvertibleErrorCode());
41 Body = Body.ltrim('/');
Nico Weber0da22902018-04-10 13:14:03 +000042#ifdef _WIN32
Eric Liu5740ff52018-01-31 16:26:27 +000043 constexpr char TestDir[] = "C:\\clangd-test";
44#else
45 constexpr char TestDir[] = "/clangd-test";
46#endif
47 llvm::SmallVector<char, 16> Path(Body.begin(), Body.end());
48 path::native(Path);
49 auto Err = fs::make_absolute(TestDir, Path);
Eric Liucda25262018-02-01 12:44:52 +000050 if (Err)
51 llvm_unreachable("Failed to make absolute path in test scheme.");
Eric Liu5740ff52018-01-31 16:26:27 +000052 return std::string(Path.begin(), Path.end());
53 }
54
55 llvm::Expected<URI>
56 uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
57 llvm_unreachable("Clangd must never create a test URI.");
58 }
59};
60
61static URISchemeRegistry::Add<TestScheme>
62 X("test", "Test scheme for clangd lit tests.");
63
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000064SymbolKindBitset defaultSymbolKinds() {
65 SymbolKindBitset Defaults;
66 for (size_t I = SymbolKindMin; I <= static_cast<size_t>(SymbolKind::Array);
67 ++I)
68 Defaults.set(I);
69 return Defaults;
70}
71
Ilya Biryukovafb55542017-05-16 14:40:30 +000072} // namespace
73
Sam McCalld1a7a372018-01-31 13:40:48 +000074void ClangdLSPServer::onInitialize(InitializeParams &Params) {
Simon Marchi88016782018-08-01 11:28:49 +000075 if (Params.initializationOptions)
76 applyConfiguration(*Params.initializationOptions);
77
Ilya Biryukov7d60d202018-02-16 12:20:47 +000078 if (Params.rootUri && *Params.rootUri)
79 Server.setRootPath(Params.rootUri->file());
Ilya Biryukov23bc73b2018-02-15 14:32:57 +000080 else if (Params.rootPath && !Params.rootPath->empty())
81 Server.setRootPath(*Params.rootPath);
82
83 CCOpts.EnableSnippets =
84 Params.capabilities.textDocument.completion.completionItem.snippetSupport;
85
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +000086 if (Params.capabilities.workspace && Params.capabilities.workspace->symbol &&
87 Params.capabilities.workspace->symbol->symbolKind) {
88 for (SymbolKind Kind :
89 *Params.capabilities.workspace->symbol->symbolKind->valueSet) {
90 SupportedSymbolKinds.set(static_cast<size_t>(Kind));
91 }
92 }
93
Sam McCalld20d7982018-07-09 14:25:59 +000094 reply(json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +000095 {{"capabilities",
Sam McCalld20d7982018-07-09 14:25:59 +000096 json::Object{
Simon Marchi98082622018-03-26 14:41:40 +000097 {"textDocumentSync", (int)TextDocumentSyncKind::Incremental},
Sam McCall0930ab02017-11-07 15:49:35 +000098 {"documentFormattingProvider", true},
99 {"documentRangeFormattingProvider", true},
100 {"documentOnTypeFormattingProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000101 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000102 {"firstTriggerCharacter", "}"},
103 {"moreTriggerCharacter", {}},
104 }},
105 {"codeActionProvider", true},
106 {"completionProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000107 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000108 {"resolveProvider", false},
109 {"triggerCharacters", {".", ">", ":"}},
110 }},
111 {"signatureHelpProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000112 json::Object{
Sam McCall0930ab02017-11-07 15:49:35 +0000113 {"triggerCharacters", {"(", ","}},
114 }},
115 {"definitionProvider", true},
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000116 {"documentHighlightProvider", true},
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000117 {"hoverProvider", true},
Haojian Wu345099c2017-11-09 11:30:04 +0000118 {"renameProvider", true},
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000119 {"documentSymbolProvider", true},
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000120 {"workspaceSymbolProvider", true},
Sam McCall0930ab02017-11-07 15:49:35 +0000121 {"executeCommandProvider",
Sam McCalld20d7982018-07-09 14:25:59 +0000122 json::Object{
Eric Liu2c190532018-05-15 15:23:53 +0000123 {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
Sam McCall0930ab02017-11-07 15:49:35 +0000124 }},
125 }}}});
Ilya Biryukovafb55542017-05-16 14:40:30 +0000126}
127
Sam McCalld1a7a372018-01-31 13:40:48 +0000128void ClangdLSPServer::onShutdown(ShutdownParams &Params) {
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000129 // Do essentially nothing, just say we're ready to exit.
130 ShutdownRequestReceived = true;
Sam McCalld1a7a372018-01-31 13:40:48 +0000131 reply(nullptr);
Sam McCall8a5dded2017-10-12 13:29:58 +0000132}
Ilya Biryukovafb55542017-05-16 14:40:30 +0000133
Sam McCalld1a7a372018-01-31 13:40:48 +0000134void ClangdLSPServer::onExit(ExitParams &Params) { IsDone = true; }
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000135
Sam McCalld1a7a372018-01-31 13:40:48 +0000136void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000137 PathRef File = Params.textDocument.uri.file();
Alex Lorenzf8087862018-08-01 17:39:29 +0000138 if (Params.metadata && !Params.metadata->extraFlags.empty())
139 CDB.setExtraFlagsForFile(File, std::move(Params.metadata->extraFlags));
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000140
Simon Marchi9569fd52018-03-16 14:30:42 +0000141 std::string &Contents = Params.textDocument.text;
142
Simon Marchi98082622018-03-26 14:41:40 +0000143 DraftMgr.addDraft(File, Contents);
Simon Marchi9569fd52018-03-16 14:30:42 +0000144 Server.addDocument(File, Contents, WantDiagnostics::Yes);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000145}
146
Sam McCalld1a7a372018-01-31 13:40:48 +0000147void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) {
Eric Liu51fed182018-02-22 18:40:39 +0000148 auto WantDiags = WantDiagnostics::Auto;
149 if (Params.wantDiagnostics.hasValue())
150 WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes
151 : WantDiagnostics::No;
Simon Marchi9569fd52018-03-16 14:30:42 +0000152
153 PathRef File = Params.textDocument.uri.file();
Simon Marchi98082622018-03-26 14:41:40 +0000154 llvm::Expected<std::string> Contents =
155 DraftMgr.updateDraft(File, Params.contentChanges);
156 if (!Contents) {
157 // If this fails, we are most likely going to be not in sync anymore with
158 // the client. It is better to remove the draft and let further operations
159 // fail rather than giving wrong results.
160 DraftMgr.removeDraft(File);
161 Server.removeDocument(File);
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000162 CDB.invalidate(File);
Sam McCallbed58852018-07-11 10:35:11 +0000163 elog("Failed to update {0}: {1}", File, Contents.takeError());
Simon Marchi98082622018-03-26 14:41:40 +0000164 return;
165 }
Simon Marchi9569fd52018-03-16 14:30:42 +0000166
Simon Marchi98082622018-03-26 14:41:40 +0000167 Server.addDocument(File, *Contents, WantDiags);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000168}
169
Sam McCalld1a7a372018-01-31 13:40:48 +0000170void ClangdLSPServer::onFileEvent(DidChangeWatchedFilesParams &Params) {
Marc-Andre Laperlebf114242017-10-02 18:00:37 +0000171 Server.onFileEvent(Params);
172}
173
Sam McCalld1a7a372018-01-31 13:40:48 +0000174void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) {
Eric Liuc5105f92018-02-16 14:15:55 +0000175 auto ApplyEdit = [](WorkspaceEdit WE) {
176 ApplyWorkspaceEditParams Edit;
177 Edit.edit = std::move(WE);
178 // We don't need the response so id == 1 is OK.
179 // Ideally, we would wait for the response and if there is no error, we
180 // would reply success/failure to the original RPC.
181 call("workspace/applyEdit", Edit);
182 };
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000183 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
184 Params.workspaceEdit) {
185 // The flow for "apply-fix" :
186 // 1. We publish a diagnostic, including fixits
187 // 2. The user clicks on the diagnostic, the editor asks us for code actions
188 // 3. We send code actions, with the fixit embedded as context
189 // 4. The user selects the fixit, the editor asks us to apply it
190 // 5. We unwrap the changes and send them back to the editor
191 // 6. The editor applies the changes (applyEdit), and sends us a reply (but
192 // we ignore it)
193
Sam McCalld1a7a372018-01-31 13:40:48 +0000194 reply("Fix applied.");
Eric Liuc5105f92018-02-16 14:15:55 +0000195 ApplyEdit(*Params.workspaceEdit);
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000196 } else {
197 // We should not get here because ExecuteCommandParams would not have
198 // parsed in the first place and this handler should not be called. But if
199 // more commands are added, this will be here has a safe guard.
Ilya Biryukov940901e2017-12-13 12:51:22 +0000200 replyError(
Sam McCalld1a7a372018-01-31 13:40:48 +0000201 ErrorCode::InvalidParams,
Haojian Wu2375c922017-11-07 10:21:02 +0000202 llvm::formatv("Unsupported command \"{0}\".", Params.command).str());
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000203 }
204}
205
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000206void ClangdLSPServer::onWorkspaceSymbol(WorkspaceSymbolParams &Params) {
207 Server.workspaceSymbols(
208 Params.query, CCOpts.Limit,
209 [this](llvm::Expected<std::vector<SymbolInformation>> Items) {
210 if (!Items)
211 return replyError(ErrorCode::InternalError,
212 llvm::toString(Items.takeError()));
213 for (auto &Sym : *Items)
214 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
215
Sam McCalld20d7982018-07-09 14:25:59 +0000216 reply(json::Array(*Items));
Marc-Andre Laperleb387b6e2018-04-23 20:00:52 +0000217 });
218}
219
Sam McCalld1a7a372018-01-31 13:40:48 +0000220void ClangdLSPServer::onRename(RenameParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000221 Path File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000222 llvm::Optional<std::string> Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000223 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000224 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000225 "onRename called for non-added file");
226
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000227 Server.rename(
228 File, Params.position, Params.newName,
229 [File, Code,
230 Params](llvm::Expected<std::vector<tooling::Replacement>> Replacements) {
231 if (!Replacements)
232 return replyError(ErrorCode::InternalError,
233 llvm::toString(Replacements.takeError()));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000234
Eric Liu9133ecd2018-05-11 12:12:08 +0000235 // Turn the replacements into the format specified by the Language
236 // Server Protocol. Fuse them into one big JSON array.
237 std::vector<TextEdit> Edits;
238 for (const auto &R : *Replacements)
239 Edits.push_back(replacementToEdit(*Code, R));
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000240 WorkspaceEdit WE;
241 WE.changes = {{Params.textDocument.uri.uri(), Edits}};
242 reply(WE);
243 });
Haojian Wu345099c2017-11-09 11:30:04 +0000244}
245
Sam McCalld1a7a372018-01-31 13:40:48 +0000246void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000247 PathRef File = Params.textDocument.uri.file();
248 DraftMgr.removeDraft(File);
249 Server.removeDocument(File);
Alex Lorenzf8087862018-08-01 17:39:29 +0000250 CDB.invalidate(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000251}
252
Sam McCall4db732a2017-09-30 10:08:52 +0000253void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCalld1a7a372018-01-31 13:40:48 +0000254 DocumentOnTypeFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000255 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000256 auto 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 "onDocumentOnTypeFormatting called for non-added file");
260
261 auto ReplacementsOrError = Server.formatOnType(*Code, File, Params.position);
Raoul Wols212bcf82017-12-12 20:25:06 +0000262 if (ReplacementsOrError)
Sam McCalld20d7982018-07-09 14:25:59 +0000263 reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000264 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000265 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000266 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000267}
268
Sam McCall4db732a2017-09-30 10:08:52 +0000269void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCalld1a7a372018-01-31 13:40:48 +0000270 DocumentRangeFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000271 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000272 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000273 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000274 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000275 "onDocumentRangeFormatting called for non-added file");
276
277 auto ReplacementsOrError = Server.formatRange(*Code, File, Params.range);
Raoul Wols212bcf82017-12-12 20:25:06 +0000278 if (ReplacementsOrError)
Sam McCalld20d7982018-07-09 14:25:59 +0000279 reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000280 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000281 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000282 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000283}
284
Sam McCalld1a7a372018-01-31 13:40:48 +0000285void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000286 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000287 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000288 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000289 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000290 "onDocumentFormatting called for non-added file");
291
292 auto ReplacementsOrError = Server.formatFile(*Code, File);
Raoul Wols212bcf82017-12-12 20:25:06 +0000293 if (ReplacementsOrError)
Sam McCalld20d7982018-07-09 14:25:59 +0000294 reply(json::Array(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000295 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000296 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000297 llvm::toString(ReplacementsOrError.takeError()));
Sam McCall4db732a2017-09-30 10:08:52 +0000298}
299
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000300void ClangdLSPServer::onDocumentSymbol(DocumentSymbolParams &Params) {
301 Server.documentSymbols(
302 Params.textDocument.uri.file(),
303 [this](llvm::Expected<std::vector<SymbolInformation>> Items) {
304 if (!Items)
305 return replyError(ErrorCode::InvalidParams,
306 llvm::toString(Items.takeError()));
307 for (auto &Sym : *Items)
308 Sym.kind = adjustKindToCapability(Sym.kind, SupportedSymbolKinds);
Sam McCalld20d7982018-07-09 14:25:59 +0000309 reply(json::Array(*Items));
Marc-Andre Laperle1be69702018-07-05 19:35:01 +0000310 });
311}
312
Sam McCalld1a7a372018-01-31 13:40:48 +0000313void ClangdLSPServer::onCodeAction(CodeActionParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000314 // We provide a code action for each diagnostic at the requested location
315 // which has FixIts available.
Simon Marchi9569fd52018-03-16 14:30:42 +0000316 auto Code = DraftMgr.getDraft(Params.textDocument.uri.file());
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000317 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000318 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000319 "onCodeAction called for non-added file");
320
Sam McCalld20d7982018-07-09 14:25:59 +0000321 json::Array Commands;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000322 for (Diagnostic &D : Params.context.diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000323 for (auto &F : getFixes(Params.textDocument.uri.file(), D)) {
Sam McCalldd0566b2017-11-06 15:40:30 +0000324 WorkspaceEdit WE;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000325 std::vector<TextEdit> Edits(F.Edits.begin(), F.Edits.end());
Eric Liu78ed91a72018-01-29 15:37:46 +0000326 WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}};
Sam McCalld20d7982018-07-09 14:25:59 +0000327 Commands.push_back(json::Object{
Ilya Biryukov71028b82018-03-12 15:28:22 +0000328 {"title", llvm::formatv("Apply fix: {0}", F.Message)},
Sam McCalldd0566b2017-11-06 15:40:30 +0000329 {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND},
330 {"arguments", {WE}},
331 });
332 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000333 }
Sam McCalld1a7a372018-01-31 13:40:48 +0000334 reply(std::move(Commands));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000335}
336
Sam McCalld1a7a372018-01-31 13:40:48 +0000337void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000338 Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts,
Sam McCalle746a2b2018-07-02 11:13:16 +0000339 [this](llvm::Expected<CodeCompleteResult> List) {
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000340 if (!List)
341 return replyError(ErrorCode::InvalidParams,
342 llvm::toString(List.takeError()));
Sam McCalle746a2b2018-07-02 11:13:16 +0000343 CompletionList LSPList;
344 LSPList.isIncomplete = List->HasMore;
345 for (const auto &R : List->Completions)
346 LSPList.items.push_back(R.render(CCOpts));
347 reply(std::move(LSPList));
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000348 });
Ilya Biryukovafb55542017-05-16 14:40:30 +0000349}
350
Sam McCalld1a7a372018-01-31 13:40:48 +0000351void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000352 Server.signatureHelp(Params.textDocument.uri.file(), Params.position,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000353 [](llvm::Expected<SignatureHelp> SignatureHelp) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000354 if (!SignatureHelp)
355 return replyError(
356 ErrorCode::InvalidParams,
357 llvm::toString(SignatureHelp.takeError()));
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000358 reply(*SignatureHelp);
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000359 });
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000360}
361
Sam McCalld1a7a372018-01-31 13:40:48 +0000362void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000363 Server.findDefinitions(
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000364 Params.textDocument.uri.file(), Params.position,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000365 [](llvm::Expected<std::vector<Location>> Items) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000366 if (!Items)
367 return replyError(ErrorCode::InvalidParams,
368 llvm::toString(Items.takeError()));
Sam McCalld20d7982018-07-09 14:25:59 +0000369 reply(json::Array(*Items));
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000370 });
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000371}
372
Sam McCalld1a7a372018-01-31 13:40:48 +0000373void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000374 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file());
Sam McCalld1a7a372018-01-31 13:40:48 +0000375 reply(Result ? URI::createFile(*Result).toString() : "");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000376}
377
Sam McCalld1a7a372018-01-31 13:40:48 +0000378void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000379 Server.findDocumentHighlights(
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000380 Params.textDocument.uri.file(), Params.position,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000381 [](llvm::Expected<std::vector<DocumentHighlight>> Highlights) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000382 if (!Highlights)
383 return replyError(ErrorCode::InternalError,
384 llvm::toString(Highlights.takeError()));
Sam McCalld20d7982018-07-09 14:25:59 +0000385 reply(json::Array(*Highlights));
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000386 });
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000387}
388
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000389void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) {
390 Server.findHover(Params.textDocument.uri.file(), Params.position,
Sam McCall682cfe72018-06-04 10:37:16 +0000391 [](llvm::Expected<llvm::Optional<Hover>> H) {
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000392 if (!H) {
393 replyError(ErrorCode::InternalError,
394 llvm::toString(H.takeError()));
395 return;
396 }
397
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000398 reply(*H);
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000399 });
400}
401
Simon Marchi88016782018-08-01 11:28:49 +0000402void ClangdLSPServer::applyConfiguration(
403 const ClangdConfigurationParamsChange &Settings) {
Simon Marchi5178f922018-02-22 14:00:39 +0000404 // Compilation database change.
405 if (Settings.compilationDatabasePath.hasValue()) {
Alex Lorenzf8087862018-08-01 17:39:29 +0000406 CDB.setCompileCommandsDir(Settings.compilationDatabasePath.getValue());
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000407
Simon Marchi9569fd52018-03-16 14:30:42 +0000408 reparseOpenedFiles();
Simon Marchi5178f922018-02-22 14:00:39 +0000409 }
Alex Lorenzf8087862018-08-01 17:39:29 +0000410
411 // Update to the compilation database.
412 if (Settings.compilationDatabaseChanges) {
413 const auto &CompileCommandUpdates = *Settings.compilationDatabaseChanges;
414 bool ShouldReparseOpenFiles = false;
415 for (auto &Entry : CompileCommandUpdates) {
416 /// The opened files need to be reparsed only when some existing
417 /// entries are changed.
418 PathRef File = Entry.first;
419 if (!CDB.setCompilationCommandForFile(
420 File, tooling::CompileCommand(
421 std::move(Entry.second.workingDirectory), File,
422 std::move(Entry.second.compilationCommand),
423 /*Output=*/"")))
424 ShouldReparseOpenFiles = true;
425 }
426 if (ShouldReparseOpenFiles)
427 reparseOpenedFiles();
428 }
Simon Marchi5178f922018-02-22 14:00:39 +0000429}
430
Simon Marchi88016782018-08-01 11:28:49 +0000431// FIXME: This function needs to be properly tested.
432void ClangdLSPServer::onChangeConfiguration(
433 DidChangeConfigurationParams &Params) {
434 applyConfiguration(Params.settings);
435}
436
Sam McCall7363a2f2018-03-05 17:28:54 +0000437ClangdLSPServer::ClangdLSPServer(JSONOutput &Out,
Sam McCalladccab62017-11-23 16:58:22 +0000438 const clangd::CodeCompleteOptions &CCOpts,
Eric Liubfac8f72017-12-19 18:00:37 +0000439 llvm::Optional<Path> CompileCommandsDir,
Alex Lorenzf8087862018-08-01 17:39:29 +0000440 bool ShouldUseInMemoryCDB,
Sam McCall7363a2f2018-03-05 17:28:54 +0000441 const ClangdServer::Options &Opts)
Alex Lorenzf8087862018-08-01 17:39:29 +0000442 : Out(Out), CDB(ShouldUseInMemoryCDB ? CompilationDB::makeInMemory()
443 : CompilationDB::makeDirectoryBased(
444 std::move(CompileCommandsDir))),
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000445 CCOpts(CCOpts), SupportedSymbolKinds(defaultSymbolKinds()),
Alex Lorenzf8087862018-08-01 17:39:29 +0000446 Server(CDB.getCDB(), FSProvider, /*DiagConsumer=*/*this, Opts) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000447
Sam McCall27a07cf2018-06-05 09:34:46 +0000448bool ClangdLSPServer::run(std::FILE *In, JSONStreamStyle InputStyle) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000449 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000450
Ilya Biryukovafb55542017-05-16 14:40:30 +0000451 // Set up JSONRPCDispatcher.
Sam McCalld20d7982018-07-09 14:25:59 +0000452 JSONRPCDispatcher Dispatcher([](const json::Value &Params) {
Sam McCalld1a7a372018-01-31 13:40:48 +0000453 replyError(ErrorCode::MethodNotFound, "method not found");
Ilya Biryukov940901e2017-12-13 12:51:22 +0000454 });
Simon Marchi6e8eb9d2018-03-07 21:47:25 +0000455 registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000456
Ilya Biryukovafb55542017-05-16 14:40:30 +0000457 // Run the Language Server loop.
Sam McCall5ed599e2018-02-06 10:47:30 +0000458 runLanguageServerLoop(In, Out, InputStyle, Dispatcher, IsDone);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000459
460 // Make sure IsDone is set to true after this method exits to ensure assertion
461 // at the start of the method fires if it's ever executed again.
462 IsDone = true;
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000463
464 return ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000465}
466
Ilya Biryukov71028b82018-03-12 15:28:22 +0000467std::vector<Fix> ClangdLSPServer::getFixes(StringRef File,
468 const clangd::Diagnostic &D) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000469 std::lock_guard<std::mutex> Lock(FixItsMutex);
470 auto DiagToFixItsIter = FixItsMap.find(File);
471 if (DiagToFixItsIter == FixItsMap.end())
472 return {};
473
474 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
475 auto FixItsIter = DiagToFixItsMap.find(D);
476 if (FixItsIter == DiagToFixItsMap.end())
477 return {};
478
479 return FixItsIter->second;
480}
481
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000482void ClangdLSPServer::onDiagnosticsReady(PathRef File,
483 std::vector<Diag> Diagnostics) {
Sam McCalld20d7982018-07-09 14:25:59 +0000484 json::Array DiagnosticsJSON;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000485
486 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000487 for (auto &Diag : Diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000488 toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) {
Sam McCalld20d7982018-07-09 14:25:59 +0000489 DiagnosticsJSON.push_back(json::Object{
Ilya Biryukov71028b82018-03-12 15:28:22 +0000490 {"range", Diag.range},
491 {"severity", Diag.severity},
492 {"message", Diag.message},
493 });
494
495 auto &FixItsForDiagnostic = LocalFixIts[Diag];
496 std::copy(Fixes.begin(), Fixes.end(),
497 std::back_inserter(FixItsForDiagnostic));
Sam McCalldd0566b2017-11-06 15:40:30 +0000498 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000499 }
500
501 // Cache FixIts
502 {
503 // FIXME(ibiryukov): should be deleted when documents are removed
504 std::lock_guard<std::mutex> Lock(FixItsMutex);
505 FixItsMap[File] = LocalFixIts;
506 }
507
508 // Publish diagnostics.
Sam McCalld20d7982018-07-09 14:25:59 +0000509 Out.writeMessage(json::Object{
Sam McCalldd0566b2017-11-06 15:40:30 +0000510 {"jsonrpc", "2.0"},
511 {"method", "textDocument/publishDiagnostics"},
512 {"params",
Sam McCalld20d7982018-07-09 14:25:59 +0000513 json::Object{
Eric Liu78ed91a72018-01-29 15:37:46 +0000514 {"uri", URIForFile{File}},
Sam McCalldd0566b2017-11-06 15:40:30 +0000515 {"diagnostics", std::move(DiagnosticsJSON)},
516 }},
517 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000518}
Simon Marchi9569fd52018-03-16 14:30:42 +0000519
520void ClangdLSPServer::reparseOpenedFiles() {
521 for (const Path &FilePath : DraftMgr.getActiveFiles())
522 Server.addDocument(FilePath, *DraftMgr.getDraft(FilePath),
Ilya Biryukovb10ef472018-06-13 09:20:41 +0000523 WantDiagnostics::Auto);
Simon Marchi9569fd52018-03-16 14:30:42 +0000524}
Alex Lorenzf8087862018-08-01 17:39:29 +0000525
526ClangdLSPServer::CompilationDB ClangdLSPServer::CompilationDB::makeInMemory() {
527 return CompilationDB(llvm::make_unique<InMemoryCompilationDb>(), nullptr,
528 /*IsDirectoryBased=*/false);
529}
530
531ClangdLSPServer::CompilationDB
532ClangdLSPServer::CompilationDB::makeDirectoryBased(
533 llvm::Optional<Path> CompileCommandsDir) {
534 auto CDB = llvm::make_unique<DirectoryBasedGlobalCompilationDatabase>(
535 std::move(CompileCommandsDir));
536 auto CachingCDB = llvm::make_unique<CachingCompilationDb>(*CDB);
537 return CompilationDB(std::move(CDB), std::move(CachingCDB),
538 /*IsDirectoryBased=*/true);
539}
540
541void ClangdLSPServer::CompilationDB::invalidate(PathRef File) {
542 if (!IsDirectoryBased)
543 static_cast<InMemoryCompilationDb *>(CDB.get())->invalidate(File);
544 else
545 CachingCDB->invalidate(File);
546}
547
548bool ClangdLSPServer::CompilationDB::setCompilationCommandForFile(
549 PathRef File, tooling::CompileCommand CompilationCommand) {
550 if (IsDirectoryBased) {
551 elog("Trying to set compile command for {0} while using directory-based "
552 "compilation database",
553 File);
554 return false;
555 }
556 return static_cast<InMemoryCompilationDb *>(CDB.get())
557 ->setCompilationCommandForFile(File, std::move(CompilationCommand));
558}
559
560void ClangdLSPServer::CompilationDB::setExtraFlagsForFile(
561 PathRef File, std::vector<std::string> ExtraFlags) {
562 if (!IsDirectoryBased) {
563 elog("Trying to set extra flags for {0} while using in-memory compilation "
564 "database",
565 File);
566 return;
567 }
568 static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
569 ->setExtraFlagsForFile(File, std::move(ExtraFlags));
570 CachingCDB->invalidate(File);
571}
572
573void ClangdLSPServer::CompilationDB::setCompileCommandsDir(Path P) {
574 if (!IsDirectoryBased) {
575 elog("Trying to set compile commands dir while using in-memory compilation "
576 "database");
577 return;
578 }
579 static_cast<DirectoryBasedGlobalCompilationDatabase *>(CDB.get())
580 ->setCompileCommandsDir(P);
581 CachingCDB->clear();
582}
583
584GlobalCompilationDatabase &ClangdLSPServer::CompilationDB::getCDB() {
585 if (CachingCDB)
586 return *CachingCDB;
587 return *CDB;
588}