blob: 887c75a3b76a28629160e5f3e8017a7e656a1ee5 [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"
12
13using namespace clang::clangd;
14using namespace clang;
15
Ilya Biryukovafb55542017-05-16 14:40:30 +000016namespace {
17
18std::string
19replacementsToEdits(StringRef Code,
20 const std::vector<tooling::Replacement> &Replacements) {
21 // Turn the replacements into the format specified by the Language Server
22 // Protocol. Fuse them into one big JSON array.
23 std::string Edits;
24 for (auto &R : Replacements) {
25 Range ReplacementRange = {
26 offsetToPosition(Code, R.getOffset()),
27 offsetToPosition(Code, R.getOffset() + R.getLength())};
28 TextEdit TE = {ReplacementRange, R.getReplacementText()};
29 Edits += TextEdit::unparse(TE);
30 Edits += ',';
31 }
32 if (!Edits.empty())
33 Edits.pop_back();
34
35 return Edits;
36}
37
38} // namespace
39
Sam McCall4db732a2017-09-30 10:08:52 +000040void ClangdLSPServer::onInitialize(StringRef ID, InitializeParams IP,
41 JSONOutput &Out) {
Ilya Biryukovafb55542017-05-16 14:40:30 +000042 Out.writeMessage(
43 R"({"jsonrpc":"2.0","id":)" + ID +
44 R"(,"result":{"capabilities":{
45 "textDocumentSync": 1,
46 "documentFormattingProvider": true,
47 "documentRangeFormattingProvider": true,
48 "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
49 "codeActionProvider": true,
Ilya Biryukova2e7ca92017-07-31 09:27:52 +000050 "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +000051 "signatureHelpProvider": {"triggerCharacters": ["(",","]},
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +000052 "definitionProvider": true
Ilya Biryukovafb55542017-05-16 14:40:30 +000053 }}})");
Marc-Andre Laperle37de9712017-09-27 15:31:17 +000054 if (IP.rootUri && !IP.rootUri->file.empty())
Sam McCall4db732a2017-09-30 10:08:52 +000055 Server.setRootPath(IP.rootUri->file);
Marc-Andre Laperle37de9712017-09-27 15:31:17 +000056 else if (IP.rootPath && !IP.rootPath->empty())
Sam McCall4db732a2017-09-30 10:08:52 +000057 Server.setRootPath(*IP.rootPath);
Ilya Biryukovafb55542017-05-16 14:40:30 +000058}
59
Sam McCall4db732a2017-09-30 10:08:52 +000060void ClangdLSPServer::onShutdown(JSONOutput &Out) { IsDone = true; }
Ilya Biryukovafb55542017-05-16 14:40:30 +000061
Sam McCall4db732a2017-09-30 10:08:52 +000062void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams Params,
63 JSONOutput &Out) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000064 if (Params.metadata && !Params.metadata->extraFlags.empty())
Sam McCall4db732a2017-09-30 10:08:52 +000065 CDB.setExtraFlagsForFile(Params.textDocument.uri.file,
66 std::move(Params.metadata->extraFlags));
67 Server.addDocument(Params.textDocument.uri.file, Params.textDocument.text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000068}
69
Sam McCall4db732a2017-09-30 10:08:52 +000070void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams Params,
71 JSONOutput &Out) {
Ilya Biryukovafb55542017-05-16 14:40:30 +000072 // We only support full syncing right now.
Sam McCall4db732a2017-09-30 10:08:52 +000073 Server.addDocument(Params.textDocument.uri.file,
74 Params.contentChanges[0].text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000075}
76
Marc-Andre Laperlebf114242017-10-02 18:00:37 +000077void ClangdLSPServer::onFileEvent(const DidChangeWatchedFilesParams &Params) {
78 Server.onFileEvent(Params);
79}
80
Sam McCall4db732a2017-09-30 10:08:52 +000081void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams Params,
82 JSONOutput &Out) {
83 Server.removeDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +000084}
85
Sam McCall4db732a2017-09-30 10:08:52 +000086void ClangdLSPServer::onDocumentOnTypeFormatting(
Ilya Biryukovafb55542017-05-16 14:40:30 +000087 DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
88 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +000089 std::string Code = Server.getDocument(File);
90 std::string Edits =
91 replacementsToEdits(Code, Server.formatOnType(File, Params.position));
Ilya Biryukovafb55542017-05-16 14:40:30 +000092
93 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
94 R"(,"result":[)" + Edits + R"(]})");
95}
96
Sam McCall4db732a2017-09-30 10:08:52 +000097void ClangdLSPServer::onDocumentRangeFormatting(
Ilya Biryukovafb55542017-05-16 14:40:30 +000098 DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
99 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +0000100 std::string Code = Server.getDocument(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000101 std::string Edits =
Sam McCall4db732a2017-09-30 10:08:52 +0000102 replacementsToEdits(Code, Server.formatRange(File, Params.range));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000103
104 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
105 R"(,"result":[)" + Edits + R"(]})");
106}
107
Sam McCall4db732a2017-09-30 10:08:52 +0000108void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams Params,
109 StringRef ID, JSONOutput &Out) {
110 auto File = Params.textDocument.uri.file;
111 std::string Code = Server.getDocument(File);
112 std::string Edits = replacementsToEdits(Code, Server.formatFile(File));
113
114 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
115 R"(,"result":[)" + Edits + R"(]})");
116}
117
118void ClangdLSPServer::onCodeAction(CodeActionParams Params, StringRef ID,
119 JSONOutput &Out) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000120 // We provide a code action for each diagnostic at the requested location
121 // which has FixIts available.
Sam McCall4db732a2017-09-30 10:08:52 +0000122 std::string Code = Server.getDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000123 std::string Commands;
124 for (Diagnostic &D : Params.context.diagnostics) {
125 std::vector<clang::tooling::Replacement> Fixes =
Sam McCall4db732a2017-09-30 10:08:52 +0000126 getFixIts(Params.textDocument.uri.file, D);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000127 std::string Edits = replacementsToEdits(Code, Fixes);
128
129 if (!Edits.empty())
130 Commands +=
131 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
132 R"('", "command": "clangd.applyFix", "arguments": [")" +
133 llvm::yaml::escape(Params.textDocument.uri.uri) +
134 R"(", [)" + Edits +
135 R"(]]},)";
136 }
137 if (!Commands.empty())
138 Commands.pop_back();
139
140 Out.writeMessage(
141 R"({"jsonrpc":"2.0","id":)" + ID.str() +
142 R"(, "result": [)" + Commands +
143 R"(]})");
144}
145
Sam McCall4db732a2017-09-30 10:08:52 +0000146void ClangdLSPServer::onCompletion(TextDocumentPositionParams Params,
147 StringRef ID, JSONOutput &Out) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000148
Sam McCall4db732a2017-09-30 10:08:52 +0000149 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000150 .codeComplete(Params.textDocument.uri.file,
151 Position{Params.position.line,
152 Params.position.character})
Ilya Biryukovdcd21692017-10-05 17:04:13 +0000153 .get() // FIXME(ibiryukov): This could be made async if we
154 // had an API that would allow to attach callbacks to
155 // futures returned by ClangdServer.
Ilya Biryukov574b7532017-08-02 09:08:39 +0000156 .Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000157
158 std::string Completions;
159 for (const auto &Item : Items) {
160 Completions += CompletionItem::unparse(Item);
161 Completions += ",";
162 }
163 if (!Completions.empty())
164 Completions.pop_back();
165 Out.writeMessage(
166 R"({"jsonrpc":"2.0","id":)" + ID.str() +
167 R"(,"result":[)" + Completions + R"(]})");
168}
169
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000170void ClangdLSPServer::onSignatureHelp(TextDocumentPositionParams Params,
171 StringRef ID, JSONOutput &Out) {
172 const auto SigHelp = SignatureHelp::unparse(
173 Server
174 .signatureHelp(
175 Params.textDocument.uri.file,
176 Position{Params.position.line, Params.position.character})
177 .Value);
178 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() + R"(,"result":)" +
179 SigHelp + "}");
180}
181
Sam McCall4db732a2017-09-30 10:08:52 +0000182void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams Params,
183 StringRef ID, JSONOutput &Out) {
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000184
Sam McCall4db732a2017-09-30 10:08:52 +0000185 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000186 .findDefinitions(Params.textDocument.uri.file,
187 Position{Params.position.line,
188 Params.position.character})
189 .Value;
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000190
191 std::string Locations;
192 for (const auto &Item : Items) {
193 Locations += Location::unparse(Item);
194 Locations += ",";
195 }
196 if (!Locations.empty())
197 Locations.pop_back();
198 Out.writeMessage(
199 R"({"jsonrpc":"2.0","id":)" + ID.str() +
200 R"(,"result":[)" + Locations + R"(]})");
201}
202
Sam McCall4db732a2017-09-30 10:08:52 +0000203void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier Params,
204 StringRef ID, JSONOutput &Out) {
205 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000206 std::string ResultUri;
207 if (Result)
208 ResultUri = URI::unparse(URI::fromFile(*Result));
209 else
210 ResultUri = "\"\"";
211
212 Out.writeMessage(
213 R"({"jsonrpc":"2.0","id":)" + ID.str() +
214 R"(,"result":)" + ResultUri + R"(})");
215}
216
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000217ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000218 bool SnippetCompletions,
Ilya Biryukov0c1ca6b2017-10-02 15:13:20 +0000219 llvm::Optional<StringRef> ResourceDir,
220 llvm::Optional<Path> CompileCommandsDir)
221 : Out(Out), CDB(/*Logger=*/Out, std::move(CompileCommandsDir)),
Sam McCall4db732a2017-09-30 10:08:52 +0000222 Server(CDB, /*DiagConsumer=*/*this, FSProvider, AsyncThreadsCount,
Ilya Biryukove5128f72017-09-20 07:24:15 +0000223 SnippetCompletions, /*Logger=*/Out, ResourceDir) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000224
Ilya Biryukovafb55542017-05-16 14:40:30 +0000225void ClangdLSPServer::run(std::istream &In) {
226 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000227
Ilya Biryukovafb55542017-05-16 14:40:30 +0000228 // Set up JSONRPCDispatcher.
Ilya Biryukovafb55542017-05-16 14:40:30 +0000229 JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
Sam McCall4db732a2017-09-30 10:08:52 +0000230 registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000231
Ilya Biryukovafb55542017-05-16 14:40:30 +0000232 // Run the Language Server loop.
233 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
234
235 // Make sure IsDone is set to true after this method exits to ensure assertion
236 // at the start of the method fires if it's ever executed again.
237 IsDone = true;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000238}
239
240std::vector<clang::tooling::Replacement>
241ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
242 std::lock_guard<std::mutex> Lock(FixItsMutex);
243 auto DiagToFixItsIter = FixItsMap.find(File);
244 if (DiagToFixItsIter == FixItsMap.end())
245 return {};
246
247 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
248 auto FixItsIter = DiagToFixItsMap.find(D);
249 if (FixItsIter == DiagToFixItsMap.end())
250 return {};
251
252 return FixItsIter->second;
253}
254
Sam McCall4db732a2017-09-30 10:08:52 +0000255void ClangdLSPServer::onDiagnosticsReady(
256 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000257 std::string DiagnosticsJSON;
258
259 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCall4db732a2017-09-30 10:08:52 +0000260 for (auto &DiagWithFixes : Diagnostics.Value) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000261 auto Diag = DiagWithFixes.Diag;
262 DiagnosticsJSON +=
263 R"({"range":)" + Range::unparse(Diag.range) +
264 R"(,"severity":)" + std::to_string(Diag.severity) +
265 R"(,"message":")" + llvm::yaml::escape(Diag.message) +
266 R"("},)";
267
268 // We convert to Replacements to become independent of the SourceManager.
269 auto &FixItsForDiagnostic = LocalFixIts[Diag];
270 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
271 std::back_inserter(FixItsForDiagnostic));
272 }
273
274 // Cache FixIts
275 {
276 // FIXME(ibiryukov): should be deleted when documents are removed
277 std::lock_guard<std::mutex> Lock(FixItsMutex);
278 FixItsMap[File] = LocalFixIts;
279 }
280
281 // Publish diagnostics.
282 if (!DiagnosticsJSON.empty())
283 DiagnosticsJSON.pop_back(); // Drop trailing comma.
284 Out.writeMessage(
285 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
286 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
287 R"(]}})");
288}