blob: 77192ff2dfd589e714e910cdd50a211658561396 [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"
11#include "JSONRPCDispatcher.h"
Sam McCallb536a2a2017-12-19 12:23:48 +000012#include "SourceCode.h"
Eric Liu78ed91a72018-01-29 15:37:46 +000013#include "URI.h"
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000014#include "llvm/Support/FormatVariadic.h"
Eric Liu5740ff52018-01-31 16:26:27 +000015#include "llvm/Support/Path.h"
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000016
Ilya Biryukov38d79772017-05-16 09:38:59 +000017using namespace clang::clangd;
18using namespace clang;
19
Ilya Biryukovafb55542017-05-16 14:40:30 +000020namespace {
21
Eric Liu5740ff52018-01-31 16:26:27 +000022/// \brief Supports a test URI scheme with relaxed constraints for lit tests.
23/// The path in a test URI will be combined with a platform-specific fake
24/// directory to form an absolute path. For example, test:///a.cpp is resolved
25/// C:\clangd-test\a.cpp on Windows and /clangd-test/a.cpp on Unix.
26class TestScheme : public URIScheme {
27public:
28 llvm::Expected<std::string>
29 getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body,
30 llvm::StringRef /*HintPath*/) const override {
31 using namespace llvm::sys;
32 // Still require "/" in body to mimic file scheme, as we want lengths of an
33 // equivalent URI in both schemes to be the same.
34 if (!Body.startswith("/"))
35 return llvm::make_error<llvm::StringError>(
36 "Expect URI body to be an absolute path starting with '/': " + Body,
37 llvm::inconvertibleErrorCode());
38 Body = Body.ltrim('/');
39#ifdef LLVM_ON_WIN32
40 constexpr char TestDir[] = "C:\\clangd-test";
41#else
42 constexpr char TestDir[] = "/clangd-test";
43#endif
44 llvm::SmallVector<char, 16> Path(Body.begin(), Body.end());
45 path::native(Path);
46 auto Err = fs::make_absolute(TestDir, Path);
Eric Liucda25262018-02-01 12:44:52 +000047 if (Err)
48 llvm_unreachable("Failed to make absolute path in test scheme.");
Eric Liu5740ff52018-01-31 16:26:27 +000049 return std::string(Path.begin(), Path.end());
50 }
51
52 llvm::Expected<URI>
53 uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override {
54 llvm_unreachable("Clangd must never create a test URI.");
55 }
56};
57
58static URISchemeRegistry::Add<TestScheme>
59 X("test", "Test scheme for clangd lit tests.");
60
Raoul Wols212bcf82017-12-12 20:25:06 +000061TextEdit replacementToEdit(StringRef Code, const tooling::Replacement &R) {
62 Range ReplacementRange = {
63 offsetToPosition(Code, R.getOffset()),
64 offsetToPosition(Code, R.getOffset() + R.getLength())};
65 return {ReplacementRange, R.getReplacementText()};
66}
67
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +000068std::vector<TextEdit>
Ilya Biryukovafb55542017-05-16 14:40:30 +000069replacementsToEdits(StringRef Code,
70 const std::vector<tooling::Replacement> &Replacements) {
71 // Turn the replacements into the format specified by the Language Server
Sam McCalldd0566b2017-11-06 15:40:30 +000072 // Protocol. Fuse them into one big JSON array.
73 std::vector<TextEdit> Edits;
Raoul Wols212bcf82017-12-12 20:25:06 +000074 for (const auto &R : Replacements)
75 Edits.push_back(replacementToEdit(Code, R));
76 return Edits;
77}
78
79std::vector<TextEdit> replacementsToEdits(StringRef Code,
80 const tooling::Replacements &Repls) {
81 std::vector<TextEdit> Edits;
82 for (const auto &R : Repls)
83 Edits.push_back(replacementToEdit(Code, R));
Ilya Biryukovafb55542017-05-16 14:40:30 +000084 return Edits;
85}
86
87} // namespace
88
Sam McCalld1a7a372018-01-31 13:40:48 +000089void ClangdLSPServer::onInitialize(InitializeParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +000090 if (Params.rootUri && *Params.rootUri)
91 Server.setRootPath(Params.rootUri->file());
Ilya Biryukov23bc73b2018-02-15 14:32:57 +000092 else if (Params.rootPath && !Params.rootPath->empty())
93 Server.setRootPath(*Params.rootPath);
94
95 CCOpts.EnableSnippets =
96 Params.capabilities.textDocument.completion.completionItem.snippetSupport;
97
Sam McCalld1a7a372018-01-31 13:40:48 +000098 reply(json::obj{
Sam McCall0930ab02017-11-07 15:49:35 +000099 {{"capabilities",
100 json::obj{
101 {"textDocumentSync", 1},
102 {"documentFormattingProvider", true},
103 {"documentRangeFormattingProvider", true},
104 {"documentOnTypeFormattingProvider",
105 json::obj{
106 {"firstTriggerCharacter", "}"},
107 {"moreTriggerCharacter", {}},
108 }},
109 {"codeActionProvider", true},
110 {"completionProvider",
111 json::obj{
112 {"resolveProvider", false},
113 {"triggerCharacters", {".", ">", ":"}},
114 }},
115 {"signatureHelpProvider",
116 json::obj{
117 {"triggerCharacters", {"(", ","}},
118 }},
119 {"definitionProvider", true},
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000120 {"documentHighlightProvider", true},
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000121 {"hoverProvider", true},
Haojian Wu345099c2017-11-09 11:30:04 +0000122 {"renameProvider", true},
Sam McCall0930ab02017-11-07 15:49:35 +0000123 {"executeCommandProvider",
124 json::obj{
Eric Liuc5105f92018-02-16 14:15:55 +0000125 {"commands",
126 {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND,
127 ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE}},
Sam McCall0930ab02017-11-07 15:49:35 +0000128 }},
129 }}}});
Ilya Biryukovafb55542017-05-16 14:40:30 +0000130}
131
Sam McCalld1a7a372018-01-31 13:40:48 +0000132void ClangdLSPServer::onShutdown(ShutdownParams &Params) {
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000133 // Do essentially nothing, just say we're ready to exit.
134 ShutdownRequestReceived = true;
Sam McCalld1a7a372018-01-31 13:40:48 +0000135 reply(nullptr);
Sam McCall8a5dded2017-10-12 13:29:58 +0000136}
Ilya Biryukovafb55542017-05-16 14:40:30 +0000137
Sam McCalld1a7a372018-01-31 13:40:48 +0000138void ClangdLSPServer::onExit(ExitParams &Params) { IsDone = true; }
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000139
Sam McCalld1a7a372018-01-31 13:40:48 +0000140void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams &Params) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000141 if (Params.metadata && !Params.metadata->extraFlags.empty())
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000142 CDB.setExtraFlagsForFile(Params.textDocument.uri.file(),
Sam McCall4db732a2017-09-30 10:08:52 +0000143 std::move(Params.metadata->extraFlags));
Sam McCall568e17f2018-02-22 13:11:12 +0000144 Server.addDocument(Params.textDocument.uri.file(), Params.textDocument.text,
145 WantDiagnostics::Yes);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000146}
147
Sam McCalld1a7a372018-01-31 13:40:48 +0000148void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams &Params) {
Benjamin Kramerb560a9a2017-10-26 10:36:20 +0000149 if (Params.contentChanges.size() != 1)
Sam McCalld1a7a372018-01-31 13:40:48 +0000150 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000151 "can only apply one change at a time");
Eric Liu51fed182018-02-22 18:40:39 +0000152 auto WantDiags = WantDiagnostics::Auto;
153 if (Params.wantDiagnostics.hasValue())
154 WantDiags = Params.wantDiagnostics.getValue() ? WantDiagnostics::Yes
155 : WantDiagnostics::No;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000156 // We only support full syncing right now.
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000157 Server.addDocument(Params.textDocument.uri.file(),
Eric Liu51fed182018-02-22 18:40:39 +0000158 Params.contentChanges[0].text, WantDiags);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000159}
160
Sam McCalld1a7a372018-01-31 13:40:48 +0000161void ClangdLSPServer::onFileEvent(DidChangeWatchedFilesParams &Params) {
Marc-Andre Laperlebf114242017-10-02 18:00:37 +0000162 Server.onFileEvent(Params);
163}
164
Sam McCalld1a7a372018-01-31 13:40:48 +0000165void ClangdLSPServer::onCommand(ExecuteCommandParams &Params) {
Eric Liuc5105f92018-02-16 14:15:55 +0000166 auto ApplyEdit = [](WorkspaceEdit WE) {
167 ApplyWorkspaceEditParams Edit;
168 Edit.edit = std::move(WE);
169 // We don't need the response so id == 1 is OK.
170 // Ideally, we would wait for the response and if there is no error, we
171 // would reply success/failure to the original RPC.
172 call("workspace/applyEdit", Edit);
173 };
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000174 if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
175 Params.workspaceEdit) {
176 // The flow for "apply-fix" :
177 // 1. We publish a diagnostic, including fixits
178 // 2. The user clicks on the diagnostic, the editor asks us for code actions
179 // 3. We send code actions, with the fixit embedded as context
180 // 4. The user selects the fixit, the editor asks us to apply it
181 // 5. We unwrap the changes and send them back to the editor
182 // 6. The editor applies the changes (applyEdit), and sends us a reply (but
183 // we ignore it)
184
Sam McCalld1a7a372018-01-31 13:40:48 +0000185 reply("Fix applied.");
Eric Liuc5105f92018-02-16 14:15:55 +0000186 ApplyEdit(*Params.workspaceEdit);
187 } else if (Params.command ==
188 ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE) {
189 auto &FileURI = Params.includeInsertion->textDocument.uri;
190 auto Code = Server.getDocument(FileURI.file());
191 if (!Code)
192 return replyError(ErrorCode::InvalidParams,
193 ("command " +
194 ExecuteCommandParams::CLANGD_INSERT_HEADER_INCLUDE +
195 " called on non-added file " + FileURI.file())
196 .str());
Eric Liu6c8e8582018-02-26 08:32:13 +0000197 llvm::StringRef DeclaringHeader = Params.includeInsertion->declaringHeader;
198 if (DeclaringHeader.empty())
199 return replyError(
200 ErrorCode::InvalidParams,
201 "declaringHeader must be provided for include insertion.");
202 llvm::StringRef PreferredHeader = Params.includeInsertion->preferredHeader;
203 auto Replaces = Server.insertInclude(
204 FileURI.file(), *Code, DeclaringHeader,
205 PreferredHeader.empty() ? DeclaringHeader : PreferredHeader);
Eric Liuc5105f92018-02-16 14:15:55 +0000206 if (!Replaces) {
207 std::string ErrMsg =
208 ("Failed to generate include insertion edits for adding " +
Eric Liu6c8e8582018-02-26 08:32:13 +0000209 DeclaringHeader + " (" + PreferredHeader + ") into " +
210 FileURI.file())
Eric Liuc5105f92018-02-16 14:15:55 +0000211 .str();
212 log(ErrMsg + ":" + llvm::toString(Replaces.takeError()));
213 replyError(ErrorCode::InternalError, ErrMsg);
214 return;
215 }
216 auto Edits = replacementsToEdits(*Code, *Replaces);
217 WorkspaceEdit WE;
218 WE.changes = {{FileURI.uri(), Edits}};
219
Eric Liu6c8e8582018-02-26 08:32:13 +0000220 reply(("Inserted header " + DeclaringHeader + " (" + PreferredHeader + ")")
221 .str());
Eric Liuc5105f92018-02-16 14:15:55 +0000222 ApplyEdit(std::move(WE));
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000223 } else {
224 // We should not get here because ExecuteCommandParams would not have
225 // parsed in the first place and this handler should not be called. But if
226 // more commands are added, this will be here has a safe guard.
Ilya Biryukov940901e2017-12-13 12:51:22 +0000227 replyError(
Sam McCalld1a7a372018-01-31 13:40:48 +0000228 ErrorCode::InvalidParams,
Haojian Wu2375c922017-11-07 10:21:02 +0000229 llvm::formatv("Unsupported command \"{0}\".", Params.command).str());
Marc-Andre Laperlee7ec16a2017-11-03 13:39:15 +0000230 }
231}
232
Sam McCalld1a7a372018-01-31 13:40:48 +0000233void ClangdLSPServer::onRename(RenameParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000234 Path File = Params.textDocument.uri.file();
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000235 llvm::Optional<std::string> Code = Server.getDocument(File);
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000236 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000237 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000238 "onRename called for non-added file");
239
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000240 Server.rename(
241 File, Params.position, Params.newName,
242 [File, Code,
243 Params](llvm::Expected<std::vector<tooling::Replacement>> Replacements) {
244 if (!Replacements)
245 return replyError(ErrorCode::InternalError,
246 llvm::toString(Replacements.takeError()));
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000247
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000248 std::vector<TextEdit> Edits = replacementsToEdits(*Code, *Replacements);
249 WorkspaceEdit WE;
250 WE.changes = {{Params.textDocument.uri.uri(), Edits}};
251 reply(WE);
252 });
Haojian Wu345099c2017-11-09 11:30:04 +0000253}
254
Sam McCalld1a7a372018-01-31 13:40:48 +0000255void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000256 Server.removeDocument(Params.textDocument.uri.file());
Ilya Biryukovafb55542017-05-16 14:40:30 +0000257}
258
Sam McCall4db732a2017-09-30 10:08:52 +0000259void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCalld1a7a372018-01-31 13:40:48 +0000260 DocumentOnTypeFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000261 auto File = Params.textDocument.uri.file();
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000262 auto Code = Server.getDocument(File);
263 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000264 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000265 "onDocumentOnTypeFormatting called for non-added file");
266
267 auto ReplacementsOrError = Server.formatOnType(*Code, File, Params.position);
Raoul Wols212bcf82017-12-12 20:25:06 +0000268 if (ReplacementsOrError)
Sam McCalld1a7a372018-01-31 13:40:48 +0000269 reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000270 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000271 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000272 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000273}
274
Sam McCall4db732a2017-09-30 10:08:52 +0000275void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCalld1a7a372018-01-31 13:40:48 +0000276 DocumentRangeFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000277 auto File = Params.textDocument.uri.file();
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000278 auto Code = Server.getDocument(File);
279 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000280 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000281 "onDocumentRangeFormatting called for non-added file");
282
283 auto ReplacementsOrError = Server.formatRange(*Code, File, Params.range);
Raoul Wols212bcf82017-12-12 20:25:06 +0000284 if (ReplacementsOrError)
Sam McCalld1a7a372018-01-31 13:40:48 +0000285 reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000286 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000287 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000288 llvm::toString(ReplacementsOrError.takeError()));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000289}
290
Sam McCalld1a7a372018-01-31 13:40:48 +0000291void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000292 auto File = Params.textDocument.uri.file();
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000293 auto Code = Server.getDocument(File);
294 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000295 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000296 "onDocumentFormatting called for non-added file");
297
298 auto ReplacementsOrError = Server.formatFile(*Code, File);
Raoul Wols212bcf82017-12-12 20:25:06 +0000299 if (ReplacementsOrError)
Sam McCalld1a7a372018-01-31 13:40:48 +0000300 reply(json::ary(replacementsToEdits(*Code, ReplacementsOrError.get())));
Raoul Wols212bcf82017-12-12 20:25:06 +0000301 else
Sam McCalld1a7a372018-01-31 13:40:48 +0000302 replyError(ErrorCode::UnknownErrorCode,
Ilya Biryukov940901e2017-12-13 12:51:22 +0000303 llvm::toString(ReplacementsOrError.takeError()));
Sam McCall4db732a2017-09-30 10:08:52 +0000304}
305
Sam McCalld1a7a372018-01-31 13:40:48 +0000306void ClangdLSPServer::onCodeAction(CodeActionParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000307 // We provide a code action for each diagnostic at the requested location
308 // which has FixIts available.
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000309 auto Code = Server.getDocument(Params.textDocument.uri.file());
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000310 if (!Code)
Sam McCalld1a7a372018-01-31 13:40:48 +0000311 return replyError(ErrorCode::InvalidParams,
Ilya Biryukov261c72e2018-01-17 12:30:24 +0000312 "onCodeAction called for non-added file");
313
Sam McCalldd0566b2017-11-06 15:40:30 +0000314 json::ary Commands;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000315 for (Diagnostic &D : Params.context.diagnostics) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000316 auto Edits = getFixIts(Params.textDocument.uri.file(), D);
Sam McCalldd0566b2017-11-06 15:40:30 +0000317 if (!Edits.empty()) {
318 WorkspaceEdit WE;
Eric Liu78ed91a72018-01-29 15:37:46 +0000319 WE.changes = {{Params.textDocument.uri.uri(), std::move(Edits)}};
Sam McCalldd0566b2017-11-06 15:40:30 +0000320 Commands.push_back(json::obj{
321 {"title", llvm::formatv("Apply FixIt {0}", D.message)},
322 {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND},
323 {"arguments", {WE}},
324 });
325 }
Ilya Biryukovafb55542017-05-16 14:40:30 +0000326 }
Sam McCalld1a7a372018-01-31 13:40:48 +0000327 reply(std::move(Commands));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000328}
329
Sam McCalld1a7a372018-01-31 13:40:48 +0000330void ClangdLSPServer::onCompletion(TextDocumentPositionParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000331 Server.codeComplete(Params.textDocument.uri.file(), Params.position, CCOpts,
Sam McCalld1a7a372018-01-31 13:40:48 +0000332 [](Tagged<CompletionList> List) { reply(List.Value); });
Ilya Biryukovafb55542017-05-16 14:40:30 +0000333}
334
Sam McCalld1a7a372018-01-31 13:40:48 +0000335void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000336 Server.signatureHelp(Params.textDocument.uri.file(), Params.position,
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000337 [](llvm::Expected<Tagged<SignatureHelp>> SignatureHelp) {
338 if (!SignatureHelp)
339 return replyError(
340 ErrorCode::InvalidParams,
341 llvm::toString(SignatureHelp.takeError()));
342 reply(SignatureHelp->Value);
343 });
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000344}
345
Sam McCalld1a7a372018-01-31 13:40:48 +0000346void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams &Params) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000347 Server.findDefinitions(
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000348 Params.textDocument.uri.file(), Params.position,
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000349 [](llvm::Expected<Tagged<std::vector<Location>>> Items) {
350 if (!Items)
351 return replyError(ErrorCode::InvalidParams,
352 llvm::toString(Items.takeError()));
353 reply(json::ary(Items->Value));
354 });
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000355}
356
Sam McCalld1a7a372018-01-31 13:40:48 +0000357void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier &Params) {
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000358 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file());
Sam McCalld1a7a372018-01-31 13:40:48 +0000359 reply(Result ? URI::createFile(*Result).toString() : "");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000360}
361
Sam McCalld1a7a372018-01-31 13:40:48 +0000362void ClangdLSPServer::onDocumentHighlight(TextDocumentPositionParams &Params) {
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000363 Server.findDocumentHighlights(
Ilya Biryukov7d60d202018-02-16 12:20:47 +0000364 Params.textDocument.uri.file(), Params.position,
Ilya Biryukov2c5e8e82018-02-15 13:15:47 +0000365 [](llvm::Expected<Tagged<std::vector<DocumentHighlight>>> Highlights) {
366 if (!Highlights)
367 return replyError(ErrorCode::InternalError,
368 llvm::toString(Highlights.takeError()));
369 reply(json::ary(Highlights->Value));
370 });
Ilya Biryukov0e6a51f2017-12-12 12:27:47 +0000371}
372
Marc-Andre Laperle3e618ed2018-02-16 21:38:15 +0000373void ClangdLSPServer::onHover(TextDocumentPositionParams &Params) {
374 Server.findHover(Params.textDocument.uri.file(), Params.position,
375 [](llvm::Expected<Tagged<Hover>> H) {
376 if (!H) {
377 replyError(ErrorCode::InternalError,
378 llvm::toString(H.takeError()));
379 return;
380 }
381
382 reply(H->Value);
383 });
384}
385
Simon Marchi5178f922018-02-22 14:00:39 +0000386// FIXME: This function needs to be properly tested.
387void ClangdLSPServer::onChangeConfiguration(
388 DidChangeConfigurationParams &Params) {
389 ClangdConfigurationParamsChange &Settings = Params.settings;
390
391 // Compilation database change.
392 if (Settings.compilationDatabasePath.hasValue()) {
393 CDB.setCompileCommandsDir(Settings.compilationDatabasePath.getValue());
394 Server.reparseOpenedFiles();
395 }
396}
397
Sam McCall7363a2f2018-03-05 17:28:54 +0000398ClangdLSPServer::ClangdLSPServer(JSONOutput &Out,
Sam McCalladccab62017-11-23 16:58:22 +0000399 const clangd::CodeCompleteOptions &CCOpts,
Eric Liubfac8f72017-12-19 18:00:37 +0000400 llvm::Optional<Path> CompileCommandsDir,
Sam McCall7363a2f2018-03-05 17:28:54 +0000401 const ClangdServer::Options &Opts)
Ilya Biryukov940901e2017-12-13 12:51:22 +0000402 : Out(Out), CDB(std::move(CompileCommandsDir)), CCOpts(CCOpts),
Sam McCall7363a2f2018-03-05 17:28:54 +0000403 Server(CDB, FSProvider, /*DiagConsumer=*/*this, Opts) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000404
Sam McCall5ed599e2018-02-06 10:47:30 +0000405bool ClangdLSPServer::run(std::istream &In, JSONStreamStyle InputStyle) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000406 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000407
Ilya Biryukovafb55542017-05-16 14:40:30 +0000408 // Set up JSONRPCDispatcher.
Sam McCalld1a7a372018-01-31 13:40:48 +0000409 JSONRPCDispatcher Dispatcher([](const json::Expr &Params) {
410 replyError(ErrorCode::MethodNotFound, "method not found");
Ilya Biryukov940901e2017-12-13 12:51:22 +0000411 });
Simon Marchi6e8eb9d2018-03-07 21:47:25 +0000412 registerCallbackHandlers(Dispatcher, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000413
Ilya Biryukovafb55542017-05-16 14:40:30 +0000414 // Run the Language Server loop.
Sam McCall5ed599e2018-02-06 10:47:30 +0000415 runLanguageServerLoop(In, Out, InputStyle, Dispatcher, IsDone);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000416
417 // Make sure IsDone is set to true after this method exits to ensure assertion
418 // at the start of the method fires if it's ever executed again.
419 IsDone = true;
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000420
421 return ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000422}
423
Sam McCall8111d3b2017-12-13 08:48:42 +0000424std::vector<TextEdit> ClangdLSPServer::getFixIts(StringRef File,
425 const clangd::Diagnostic &D) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000426 std::lock_guard<std::mutex> Lock(FixItsMutex);
427 auto DiagToFixItsIter = FixItsMap.find(File);
428 if (DiagToFixItsIter == FixItsMap.end())
429 return {};
430
431 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
432 auto FixItsIter = DiagToFixItsMap.find(D);
433 if (FixItsIter == DiagToFixItsMap.end())
434 return {};
435
436 return FixItsIter->second;
437}
438
Sam McCall4db732a2017-09-30 10:08:52 +0000439void ClangdLSPServer::onDiagnosticsReady(
Sam McCalld1a7a372018-01-31 13:40:48 +0000440 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
Sam McCalldd0566b2017-11-06 15:40:30 +0000441 json::ary DiagnosticsJSON;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000442
443 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCall4db732a2017-09-30 10:08:52 +0000444 for (auto &DiagWithFixes : Diagnostics.Value) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000445 auto Diag = DiagWithFixes.Diag;
Sam McCalldd0566b2017-11-06 15:40:30 +0000446 DiagnosticsJSON.push_back(json::obj{
447 {"range", Diag.range},
448 {"severity", Diag.severity},
449 {"message", Diag.message},
450 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000451 // We convert to Replacements to become independent of the SourceManager.
452 auto &FixItsForDiagnostic = LocalFixIts[Diag];
453 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
454 std::back_inserter(FixItsForDiagnostic));
455 }
456
457 // Cache FixIts
458 {
459 // FIXME(ibiryukov): should be deleted when documents are removed
460 std::lock_guard<std::mutex> Lock(FixItsMutex);
461 FixItsMap[File] = LocalFixIts;
462 }
463
464 // Publish diagnostics.
Sam McCalldd0566b2017-11-06 15:40:30 +0000465 Out.writeMessage(json::obj{
466 {"jsonrpc", "2.0"},
467 {"method", "textDocument/publishDiagnostics"},
468 {"params",
469 json::obj{
Eric Liu78ed91a72018-01-29 15:37:46 +0000470 {"uri", URIForFile{File}},
Sam McCalldd0566b2017-11-06 15:40:30 +0000471 {"diagnostics", std::move(DiagnosticsJSON)},
472 }},
473 });
Ilya Biryukov38d79772017-05-16 09:38:59 +0000474}