blob: 61dbd6689daced1430b588823eacf18133e94bda [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;
21
Ilya Biryukovafb55542017-05-16 14:40:30 +000022namespace {
23
Eric Liu5740ff52018-01-31 16:26:27 +000024/// \brief Supports a test URI scheme with relaxed constraints for lit tests.
25/// The path in a test URI will be combined with a platform-specific fake
26/// directory to form an absolute path. For example, test:///a.cpp is resolved
27/// C:\clangd-test\a.cpp on Windows and /clangd-test/a.cpp on Unix.
28class TestScheme : public URIScheme {
29public:
30 llvm::Expected<std::string>
31 getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
32 llvm::StringRef /*HintPath*/) const override {
33 using namespace llvm::sys;
34 // Still require "/" in body to mimic file scheme, as we want lengths of an
35 // equivalent URI in both schemes to be the same.
36 if (!Body.startswith("/"))
37 return llvm::make_error<llvm::StringError>(
38 "Expect URI body to be an absolute path starting with '/': " + Body,
39 llvm::inconvertibleErrorCode());
40 Body = Body.ltrim('/');
41#ifdef LLVM_ON_WIN32
42 constexpr char TestDir[] = "C:\\clangd-test";
43#else
44 constexpr char TestDir[] = "/clangd-test";
45#endif
46 llvm::SmallVector<char, 16> Path(Body.begin(), Body.end());
47 path::native(Path);
48 auto Err = fs::make_absolute(TestDir, Path);
Eric Liucda25262018-02-01 12:44:52 +000049 if (Err)
50 llvm_unreachable("Failed to make absolute path in test scheme.");
Eric Liu5740ff52018-01-31 16:26:27 +000051 return std::string(Path.begin(), Path.end());
52 }
53
54 llvm::Expected<URI>
55 uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
56 llvm_unreachable("Clangd must never create a test URI.");
57 }
58};
59
60static URISchemeRegistry::Add<TestScheme>
61 X("test", "Test scheme for clangd lit tests.");
62
Raoul Wols212bcf82017-12-12 20:25:06 +000063TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R) {
64 Range ReplacementRange = {
65 offsetToPosition(Code, R.getOffset()),
66 offsetToPosition(Code, R.getOffset() + R.getLength())};
67 return {ReplacementRange, R.getReplacementText()};
68}
69
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000070std::vector<TextEdit>
Ilya Biryukovafb55542017-05-16 14:40:30 +000071replacementsToEdits(StringRef Code,
72 const std::vector<tooling::Replacement> &Replacements) {
73 // Turn the replacements into the format specified by the Language Server
Sam McCalldd0566b2017-11-06 15:40:30 +000074 // Protocol. Fuse them into one big JSON array.
75 std::vector<TextEdit> Edits;
Raoul Wols212bcf82017-12-12 20:25:06 +000076 for (const auto &R : Replacements)
77 Edits.push_back(replacementToEdit(Code, R));
78 return Edits;
79}
80
81std::vector<TextEdit> replacementsToEdits(StringRef Code,
82 const tooling::Replacements &Repls) {
83 std::vector<TextEdit> Edits;
84 for (const auto &R : Repls)
85 Edits.push_back(replacementToEdit(Code, R));
Ilya Biryukovafb55542017-05-16 14:40:30 +000086 return Edits;
87}
88
89} // namespace
90
Sam McCalld1a7a372018-01-31 13:40:48 +000091void ClangdLSPServer::onInitialize(InitializeParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +000092 if (Params.rootUri && *Params.rootUri)
93 Server.setRootPath(Params.rootUri->file());
Ilya Biryukov23bc73b2018-02-15 14:32:57 +000094 else if (Params.rootPath && !Params.rootPath->empty())
95 Server.setRootPath(*Params.rootPath);
96
97 CCOpts.EnableSnippets =
98 Params.capabilities.textDocument.completion.completionItem.snippetSupport;
99
Sam McCalld1a7a372018-01-31 13:40:48 +0000100 reply(json::obj{
Sam McCall0930ab02017-11-07 15:49:35 +0000101 {{"capabilities",
102 json::obj{
Simon Marchi98082622018-03-26 14:41:40 +0000103 {"textDocumentSync", (int)TextDocumentSyncKind::Incremental},
Sam McCall0930ab02017-11-07 15:49:35 +0000104 {"documentFormattingProvider", true},
105 {"documentRangeFormattingProvider", true},
106 {"documentOnTypeFormattingProvider",
107 json::obj{
108 {"firstTriggerCharacter", "}"},
109 {"moreTriggerCharacter", {}},
110 }},
111 {"codeActionProvider", true},
112 {"completionProvider",
113 json::obj{
114 {"resolveProvider", false},
115 {"triggerCharacters", {".", ">", ":"}},
116 }},
117 {"signatureHelpProvider",
118 json::obj{
119 {"triggerCharacters", {"(", ","}},
120 }},
121 {"definitionProvider", true},
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000122 {"documentHighlightProvider", true},
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000123 {"hoverProvider", true},
Haojian Wu345099c2017-11-09 11:30:04 +0000124 {"renameProvider", true},
Sam McCall0930ab02017-11-07 15:49:35 +0000125 {"executeCommandProvider",
126 json::obj{
Eric Liuc5105f92018-02-16 14:15:55 +0000127 {"commands",
128 {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND,
129 ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE}},
Sam McCall0930ab02017-11-07 15:49:35 +0000130 }},
131 }}}});
Ilya Biryukovafb55542017-05-16 14:40:30 +0000132}
133
Sam McCalld1a7a372018-01-31 13:40:48 +0000134void ClangdLSPServer::onShutdown(ShutdownParams &Params) {
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000135 // Do essentially nothing, just say we're ready to exit.
136 ShutdownRequestReceived = true;
Sam McCalld1a7a372018-01-31 13:40:48 +0000137 reply(nullptr);
Sam McCall8a5dded2017-10-12 13:29:58 +0000138}
Ilya Biryukovafb55542017-05-16 14:40:30 +0000139
Sam McCalld1a7a372018-01-31 13:40:48 +0000140void ClangdLSPServer::onExit(ExitParams &Params) { IsDone = true; }
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000141
Sam McCalld1a7a372018-01-31 13:40:48 +0000142void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000143 if (Params.metadata && !Params.metadata->extraFlags.empty())
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000144 CDB.setExtraFlagsForFile(Params.textDocument.uri.file(),
Sam McCall4db732a2017-09-30 10:08:52 +0000145 std::move(Params.metadata->extraFlags));
Simon Marchi9569fd52018-03-16 14:30:42 +0000146
147 PathRef File = Params.textDocument.uri.file();
148 std::string &Contents = Params.textDocument.text;
149
Simon Marchi98082622018-03-26 14:41:40 +0000150 DraftMgr.addDraft(File, Contents);
Simon Marchi9569fd52018-03-16 14:30:42 +0000151 Server.addDocument(File, Contents, WantDiagnostics::Yes);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000152}
153
Sam McCalld1a7a372018-01-31 13:40:48 +0000154void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) {
Eric Liu51fed182018-02-22 18:40:39 +0000155 auto WantDiags = WantDiagnostics::Auto;
156 if (Params.wantDiagnostics.hasValue())
157 WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes
158 : WantDiagnostics::No;
Simon Marchi9569fd52018-03-16 14:30:42 +0000159
160 PathRef File = Params.textDocument.uri.file();
Simon Marchi98082622018-03-26 14:41:40 +0000161 llvm::Expected<std::string> Contents =
162 DraftMgr.updateDraft(File, Params.contentChanges);
163 if (!Contents) {
164 // If this fails, we are most likely going to be not in sync anymore with
165 // the client. It is better to remove the draft and let further operations
166 // fail rather than giving wrong results.
167 DraftMgr.removeDraft(File);
168 Server.removeDocument(File);
169 log(llvm::toString(Contents.takeError()));
170 return;
171 }
Simon Marchi9569fd52018-03-16 14:30:42 +0000172
Simon Marchi98082622018-03-26 14:41:40 +0000173 Server.addDocument(File, *Contents, WantDiags);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000174}
175
Sam McCalld1a7a372018-01-31 13:40:48 +0000176void ClangdLSPServer::onFileEvent(DidChangeWatchedFilesParams &Params) {
Marc-Andre Laperlebf114242017-10-02 18:00:37 +0000177 Server.onFileEvent(Params);
178}
179
Sam McCalld1a7a372018-01-31 13:40:48 +0000180void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) {
Eric Liuc5105f92018-02-16 14:15:55 +0000181 auto ApplyEdit = [](WorkspaceEdit WE) {
182 ApplyWorkspaceEditParams Edit;
183 Edit.edit = std::move(WE);
184 // We don't need the response so id == 1 is OK.
185 // Ideally, we would wait for the response and if there is no error, we
186 // would reply success/failure to the original RPC.
187 call("workspace/applyEdit", Edit);
188 };
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000189 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
190 Params.workspaceEdit) {
191 // The flow for "apply-fix" :
192 // 1. We publish a diagnostic, including fixits
193 // 2. The user clicks on the diagnostic, the editor asks us for code actions
194 // 3. We send code actions, with the fixit embedded as context
195 // 4. The user selects the fixit, the editor asks us to apply it
196 // 5. We unwrap the changes and send them back to the editor
197 // 6. The editor applies the changes (applyEdit), and sends us a reply (but
198 // we ignore it)
199
Sam McCalld1a7a372018-01-31 13:40:48 +0000200 reply("Fix applied.");
Eric Liuc5105f92018-02-16 14:15:55 +0000201 ApplyEdit(*Params.workspaceEdit);
202 } else if (Params.command ==
203 ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) {
204 auto &FileURI = Params.includeInsertion->textDocument.uri;
Simon Marchi9569fd52018-03-16 14:30:42 +0000205 auto Code = DraftMgr.getDraft(FileURI.file());
Eric Liuc5105f92018-02-16 14:15:55 +0000206 if (!Code)
207 return replyError(ErrorCode::InvalidParams,
208 ("command " +
209 ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE +
210 " called on non-added file " + FileURI.file())
211 .str());
Eric Liu6c8e8582018-02-26 08:32:13 +0000212 llvm::StringRef DeclaringHeader = Params.includeInsertion->declaringHeader;
213 if (DeclaringHeader.empty())
214 return replyError(
215 ErrorCode::InvalidParams,
216 "declaringHeader must be provided for include insertion.");
217 llvm::StringRef PreferredHeader = Params.includeInsertion->preferredHeader;
218 auto Replaces = Server.insertInclude(
219 FileURI.file(), *Code, DeclaringHeader,
220 PreferredHeader.empty() ? DeclaringHeader : PreferredHeader);
Eric Liuc5105f92018-02-16 14:15:55 +0000221 if (!Replaces) {
222 std::string ErrMsg =
223 ("Failed to generate include insertion edits for adding " +
Eric Liu6c8e8582018-02-26 08:32:13 +0000224 DeclaringHeader + " (" + PreferredHeader + ") into " +
225 FileURI.file())
Eric Liuc5105f92018-02-16 14:15:55 +0000226 .str();
227 log(ErrMsg + ":" + llvm::toString(Replaces.takeError()));
228 replyError(ErrorCode::InternalError, ErrMsg);
229 return;
230 }
231 auto Edits = replacementsToEdits(*Code, *Replaces);
232 WorkspaceEdit WE;
233 WE.changes = {{FileURI.uri(), Edits}};
234
Eric Liu6c8e8582018-02-26 08:32:13 +0000235 reply(("Inserted header " + DeclaringHeader + " (" + PreferredHeader + ")")
236 .str());
Eric Liuc5105f92018-02-16 14:15:55 +0000237 ApplyEdit(std::move(WE));
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000238 } else {
239 // We should not get here because ExecuteCommandParams would not have
240 // parsed in the first place and this handler should not be called. But if
241 // more commands are added, this will be here has a safe guard.
Ilya Biryukov940901e2017-12-13 12:51:22 +0000242 replyError(
Sam McCalld1a7a372018-01-31 13:40:48 +0000243 ErrorCode::InvalidParams,
Haojian Wu2375c922017-11-07 10:21:02 +0000244 llvm::formatv("Unsupported command \"{0}\".", Params.command).str());
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000245 }
246}
247
Sam McCalld1a7a372018-01-31 13:40:48 +0000248void ClangdLSPServer::onRename(RenameParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000249 Path File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000250 llvm::Optional<std::string> Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000251 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000252 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000253 "onRename called for non-added file");
254
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000255 Server.rename(
256 File, Params.position, Params.newName,
257 [File, Code,
258 Params](llvm::Expected<std::vector<tooling::Replacement>> Replacements) {
259 if (!Replacements)
260 return replyError(ErrorCode::InternalError,
261 llvm::toString(Replacements.takeError()));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000262
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000263 std::vector<TextEdit> Edits = replacementsToEdits(*Code, *Replacements);
264 WorkspaceEdit WE;
265 WE.changes = {{Params.textDocument.uri.uri(), Edits}};
266 reply(WE);
267 });
Haojian Wu345099c2017-11-09 11:30:04 +0000268}
269
Sam McCalld1a7a372018-01-31 13:40:48 +0000270void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) {
Simon Marchi9569fd52018-03-16 14:30:42 +0000271 PathRef File = Params.textDocument.uri.file();
272 DraftMgr.removeDraft(File);
273 Server.removeDocument(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000274}
275
Sam McCall4db732a2017-09-30 10:08:52 +0000276void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCalld1a7a372018-01-31 13:40:48 +0000277 DocumentOnTypeFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000278 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000279 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000280 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000281 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000282 "onDocumentOnTypeFormatting called for non-added file");
283
284 auto ReplacementsOrError = Server.formatOnType(*Code, File, Params.position);
Raoul Wols212bcf82017-12-12 20:25:06 +0000285 if (ReplacementsOrError)
Sam McCalld1a7a372018-01-31 13:40:48 +0000286 reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000287 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000288 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000289 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000290}
291
Sam McCall4db732a2017-09-30 10:08:52 +0000292void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCalld1a7a372018-01-31 13:40:48 +0000293 DocumentRangeFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000294 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000295 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000296 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000297 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000298 "onDocumentRangeFormatting called for non-added file");
299
300 auto ReplacementsOrError = Server.formatRange(*Code, File, Params.range);
Raoul Wols212bcf82017-12-12 20:25:06 +0000301 if (ReplacementsOrError)
Sam McCalld1a7a372018-01-31 13:40:48 +0000302 reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000303 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000304 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000305 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000306}
307
Sam McCalld1a7a372018-01-31 13:40:48 +0000308void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000309 auto File = Params.textDocument.uri.file();
Simon Marchi9569fd52018-03-16 14:30:42 +0000310 auto Code = DraftMgr.getDraft(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000311 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000312 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000313 "onDocumentFormatting called for non-added file");
314
315 auto ReplacementsOrError = Server.formatFile(*Code, File);
Raoul Wols212bcf82017-12-12 20:25:06 +0000316 if (ReplacementsOrError)
Sam McCalld1a7a372018-01-31 13:40:48 +0000317 reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000318 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000319 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000320 llvm::toString(ReplacementsOrError.takeError()));
Sam McCall4db732a2017-09-30 10:08:52 +0000321}
322
Sam McCalld1a7a372018-01-31 13:40:48 +0000323void ClangdLSPServer::onCodeAction(CodeActionParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000324 // We provide a code action for each diagnostic at the requested location
325 // which has FixIts available.
Simon Marchi9569fd52018-03-16 14:30:42 +0000326 auto Code = DraftMgr.getDraft(Params.textDocument.uri.file());
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000327 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000328 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000329 "onCodeAction called for non-added file");
330
Sam McCalldd0566b2017-11-06 15:40:30 +0000331 json::ary Commands;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000332 for (Diagnostic &D : Params.context.diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000333 for (auto &F : getFixes(Params.textDocument.uri.file(), D)) {
Sam McCalldd0566b2017-11-06 15:40:30 +0000334 WorkspaceEdit WE;
Ilya Biryukov71028b82018-03-12 15:28:22 +0000335 std::vector<TextEdit> Edits(F.Edits.begin(), F.Edits.end());
Eric Liu78ed91a72018-01-29 15:37:46 +0000336 WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}};
Sam McCalldd0566b2017-11-06 15:40:30 +0000337 Commands.push_back(json::obj{
Ilya Biryukov71028b82018-03-12 15:28:22 +0000338 {"title", llvm::formatv("Apply fix: {0}", F.Message)},
Sam McCalldd0566b2017-11-06 15:40:30 +0000339 {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND},
340 {"arguments", {WE}},
341 });
342 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000343 }
Sam McCalld1a7a372018-01-31 13:40:48 +0000344 reply(std::move(Commands));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000345}
346
Sam McCalld1a7a372018-01-31 13:40:48 +0000347void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000348 Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000349 [](llvm::Expected<CompletionList> List) {
350 if (!List)
351 return replyError(ErrorCode::InvalidParams,
352 llvm::toString(List.takeError()));
353 reply(*List);
354 });
Ilya Biryukovafb55542017-05-16 14:40:30 +0000355}
356
Sam McCalld1a7a372018-01-31 13:40:48 +0000357void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000358 Server.signatureHelp(Params.textDocument.uri.file(), Params.position,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000359 [](llvm::Expected<SignatureHelp> SignatureHelp) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000360 if (!SignatureHelp)
361 return replyError(
362 ErrorCode::InvalidParams,
363 llvm::toString(SignatureHelp.takeError()));
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000364 reply(*SignatureHelp);
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000365 });
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000366}
367
Sam McCalld1a7a372018-01-31 13:40:48 +0000368void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000369 Server.findDefinitions(
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000370 Params.textDocument.uri.file(), Params.position,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000371 [](llvm::Expected<std::vector<Location>> Items) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000372 if (!Items)
373 return replyError(ErrorCode::InvalidParams,
374 llvm::toString(Items.takeError()));
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000375 reply(json::ary(*Items));
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000376 });
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000377}
378
Sam McCalld1a7a372018-01-31 13:40:48 +0000379void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000380 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file());
Sam McCalld1a7a372018-01-31 13:40:48 +0000381 reply(Result ? URI::createFile(*Result).toString() : "");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000382}
383
Sam McCalld1a7a372018-01-31 13:40:48 +0000384void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000385 Server.findDocumentHighlights(
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000386 Params.textDocument.uri.file(), Params.position,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000387 [](llvm::Expected<std::vector<DocumentHighlight>> Highlights) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000388 if (!Highlights)
389 return replyError(ErrorCode::InternalError,
390 llvm::toString(Highlights.takeError()));
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000391 reply(json::ary(*Highlights));
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000392 });
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000393}
394
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000395void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) {
396 Server.findHover(Params.textDocument.uri.file(), Params.position,
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000397 [](llvm::Expected<Hover> H) {
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000398 if (!H) {
399 replyError(ErrorCode::InternalError,
400 llvm::toString(H.takeError()));
401 return;
402 }
403
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000404 reply(*H);
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000405 });
406}
407
Simon Marchi5178f922018-02-22 14:00:39 +0000408// FIXME: This function needs to be properly tested.
409void ClangdLSPServer::onChangeConfiguration(
410 DidChangeConfigurationParams &Params) {
411 ClangdConfigurationParamsChange &Settings = Params.settings;
412
413 // Compilation database change.
414 if (Settings.compilationDatabasePath.hasValue()) {
415 CDB.setCompileCommandsDir(Settings.compilationDatabasePath.getValue());
Simon Marchi9569fd52018-03-16 14:30:42 +0000416 reparseOpenedFiles();
Simon Marchi5178f922018-02-22 14:00:39 +0000417 }
418}
419
Sam McCall7363a2f2018-03-05 17:28:54 +0000420ClangdLSPServer::ClangdLSPServer(JSONOutput &Out,
Sam McCalladccab62017-11-23 16:58:22 +0000421 const clangd::CodeCompleteOptions &CCOpts,
Eric Liubfac8f72017-12-19 18:00:37 +0000422 llvm::Optional<Path> CompileCommandsDir,
Sam McCall7363a2f2018-03-05 17:28:54 +0000423 const ClangdServer::Options &Opts)
Ilya Biryukov940901e2017-12-13 12:51:22 +0000424 : Out(Out), CDB(std::move(CompileCommandsDir)), CCOpts(CCOpts),
Sam McCall7363a2f2018-03-05 17:28:54 +0000425 Server(CDB, FSProvider, /*DiagConsumer=*/*this, Opts) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000426
Sam McCall5ed599e2018-02-06 10:47:30 +0000427bool ClangdLSPServer::run(std::istream &In, JSONStreamStyle InputStyle) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000428 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000429
Ilya Biryukovafb55542017-05-16 14:40:30 +0000430 // Set up JSONRPCDispatcher.
Sam McCalld1a7a372018-01-31 13:40:48 +0000431 JSONRPCDispatcher Dispatcher([](const json::Expr &Params) {
432 replyError(ErrorCode::MethodNotFound, "method not found");
Ilya Biryukov940901e2017-12-13 12:51:22 +0000433 });
Simon Marchi6e8eb9d2018-03-07 21:47:25 +0000434 registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000435
Ilya Biryukovafb55542017-05-16 14:40:30 +0000436 // Run the Language Server loop.
Sam McCall5ed599e2018-02-06 10:47:30 +0000437 runLanguageServerLoop(In, Out, InputStyle, Dispatcher, IsDone);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000438
439 // Make sure IsDone is set to true after this method exits to ensure assertion
440 // at the start of the method fires if it's ever executed again.
441 IsDone = true;
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000442
443 return ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000444}
445
Ilya Biryukov71028b82018-03-12 15:28:22 +0000446std::vector<Fix> ClangdLSPServer::getFixes(StringRef File,
447 const clangd::Diagnostic &D) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000448 std::lock_guard<std::mutex> Lock(FixItsMutex);
449 auto DiagToFixItsIter = FixItsMap.find(File);
450 if (DiagToFixItsIter == FixItsMap.end())
451 return {};
452
453 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
454 auto FixItsIter = DiagToFixItsMap.find(D);
455 if (FixItsIter == DiagToFixItsMap.end())
456 return {};
457
458 return FixItsIter->second;
459}
460
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000461void ClangdLSPServer::onDiagnosticsReady(PathRef File,
462 std::vector<Diag> Diagnostics) {
Sam McCalldd0566b2017-11-06 15:40:30 +0000463 json::ary DiagnosticsJSON;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000464
465 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCalla7bb0cc2018-03-12 23:22:35 +0000466 for (auto &Diag : Diagnostics) {
Ilya Biryukov71028b82018-03-12 15:28:22 +0000467 toLSPDiags(Diag, [&](clangd::Diagnostic Diag, llvm::ArrayRef<Fix> Fixes) {
468 DiagnosticsJSON.push_back(json::obj{
469 {"range", Diag.range},
470 {"severity", Diag.severity},
471 {"message", Diag.message},
472 });
473
474 auto &FixItsForDiagnostic = LocalFixIts[Diag];
475 std::copy(Fixes.begin(), Fixes.end(),
476 std::back_inserter(FixItsForDiagnostic));
Sam McCalldd0566b2017-11-06 15:40:30 +0000477 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000478 }
479
480 // Cache FixIts
481 {
482 // FIXME(ibiryukov): should be deleted when documents are removed
483 std::lock_guard<std::mutex> Lock(FixItsMutex);
484 FixItsMap[File] = LocalFixIts;
485 }
486
487 // Publish diagnostics.
Sam McCalldd0566b2017-11-06 15:40:30 +0000488 Out.writeMessage(json::obj{
489 {"jsonrpc", "2.0"},
490 {"method", "textDocument/publishDiagnostics"},
491 {"params",
492 json::obj{
Eric Liu78ed91a72018-01-29 15:37:46 +0000493 {"uri", URIForFile{File}},
Sam McCalldd0566b2017-11-06 15:40:30 +0000494 {"diagnostics", std::move(DiagnosticsJSON)},
495 }},
496 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000497}
Simon Marchi9569fd52018-03-16 14:30:42 +0000498
499void ClangdLSPServer::reparseOpenedFiles() {
500 for (const Path &FilePath : DraftMgr.getActiveFiles())
501 Server.addDocument(FilePath, *DraftMgr.getDraft(FilePath),
502 WantDiagnostics::Auto,
503 /*SkipCache=*/true);
504}