blob: 80ee47cab7cb0c163833d9e9f3f916067f5b88dd [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"
Ilya Biryukovafb55542017-05-16 14:40:30 +000012#include "ProtocolHandlers.h"
Ilya Biryukov38d79772017-05-16 09:38:59 +000013
14using namespace clang::clangd;
15using namespace clang;
16
Ilya Biryukovafb55542017-05-16 14:40:30 +000017namespace {
18
19std::string
20replacementsToEdits(StringRef Code,
21 const std::vector<tooling::Replacement> &Replacements) {
22 // Turn the replacements into the format specified by the Language Server
23 // Protocol. Fuse them into one big JSON array.
24 std::string Edits;
25 for (auto &R : Replacements) {
26 Range ReplacementRange = {
27 offsetToPosition(Code, R.getOffset()),
28 offsetToPosition(Code, R.getOffset() + R.getLength())};
29 TextEdit TE = {ReplacementRange, R.getReplacementText()};
30 Edits += TextEdit::unparse(TE);
31 Edits += ',';
32 }
33 if (!Edits.empty())
34 Edits.pop_back();
35
36 return Edits;
37}
38
39} // namespace
40
Ilya Biryukov103c9512017-06-13 15:59:43 +000041ClangdLSPServer::LSPDiagnosticsConsumer::LSPDiagnosticsConsumer(
42 ClangdLSPServer &Server)
43 : Server(Server) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +000044
Ilya Biryukov103c9512017-06-13 15:59:43 +000045void ClangdLSPServer::LSPDiagnosticsConsumer::onDiagnosticsReady(
46 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
47 Server.consumeDiagnostics(File, Diagnostics.Value);
48}
Ilya Biryukov38d79772017-05-16 09:38:59 +000049
Ilya Biryukovafb55542017-05-16 14:40:30 +000050class ClangdLSPServer::LSPProtocolCallbacks : public ProtocolCallbacks {
51public:
52 LSPProtocolCallbacks(ClangdLSPServer &LangServer) : LangServer(LangServer) {}
53
Marc-Andre Laperle37de9712017-09-27 15:31:17 +000054 void onInitialize(StringRef ID, InitializeParams IP,
55 JSONOutput &Out) override;
Ilya Biryukovafb55542017-05-16 14:40:30 +000056 void onShutdown(JSONOutput &Out) override;
57 void onDocumentDidOpen(DidOpenTextDocumentParams Params,
58 JSONOutput &Out) override;
59 void onDocumentDidChange(DidChangeTextDocumentParams Params,
60 JSONOutput &Out) override;
61 void onDocumentDidClose(DidCloseTextDocumentParams Params,
62 JSONOutput &Out) override;
63 void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params,
64 StringRef ID, JSONOutput &Out) override;
65 void onDocumentRangeFormatting(DocumentRangeFormattingParams Params,
66 StringRef ID, JSONOutput &Out) override;
67 void onDocumentFormatting(DocumentFormattingParams Params, StringRef ID,
68 JSONOutput &Out) override;
69 void onCodeAction(CodeActionParams Params, StringRef ID,
70 JSONOutput &Out) override;
71 void onCompletion(TextDocumentPositionParams Params, StringRef ID,
72 JSONOutput &Out) override;
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +000073 void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID,
Ilya Biryukov574b7532017-08-02 09:08:39 +000074 JSONOutput &Out) override;
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +000075 void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID,
76 JSONOutput &Out) override;
Ilya Biryukovafb55542017-05-16 14:40:30 +000077
78private:
79 ClangdLSPServer &LangServer;
80};
81
82void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID,
Marc-Andre Laperle37de9712017-09-27 15:31:17 +000083 InitializeParams IP,
Ilya Biryukovafb55542017-05-16 14:40:30 +000084 JSONOutput &Out) {
85 Out.writeMessage(
86 R"({"jsonrpc":"2.0","id":)" + ID +
87 R"(,"result":{"capabilities":{
88 "textDocumentSync": 1,
89 "documentFormattingProvider": true,
90 "documentRangeFormattingProvider": true,
91 "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
92 "codeActionProvider": true,
Ilya Biryukova2e7ca92017-07-31 09:27:52 +000093 "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +000094 "definitionProvider": true
Ilya Biryukovafb55542017-05-16 14:40:30 +000095 }}})");
Marc-Andre Laperle37de9712017-09-27 15:31:17 +000096 if (IP.rootUri && !IP.rootUri->file.empty())
97 LangServer.Server.setRootPath(IP.rootUri->file);
98 else if (IP.rootPath && !IP.rootPath->empty())
99 LangServer.Server.setRootPath(*IP.rootPath);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000100}
101
102void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) {
103 LangServer.IsDone = true;
104}
105
106void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidOpen(
107 DidOpenTextDocumentParams Params, JSONOutput &Out) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000108 if (Params.metadata && !Params.metadata->extraFlags.empty())
109 LangServer.CDB.setExtraFlagsForFile(Params.textDocument.uri.file,
110 std::move(Params.metadata->extraFlags));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000111 LangServer.Server.addDocument(Params.textDocument.uri.file,
112 Params.textDocument.text);
113}
114
115void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange(
116 DidChangeTextDocumentParams Params, JSONOutput &Out) {
117 // We only support full syncing right now.
118 LangServer.Server.addDocument(Params.textDocument.uri.file,
119 Params.contentChanges[0].text);
120}
121
122void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose(
123 DidCloseTextDocumentParams Params, JSONOutput &Out) {
124 LangServer.Server.removeDocument(Params.textDocument.uri.file);
125}
126
127void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting(
128 DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
129 auto File = Params.textDocument.uri.file;
130 std::string Code = LangServer.Server.getDocument(File);
131 std::string Edits = replacementsToEdits(
132 Code, LangServer.Server.formatOnType(File, Params.position));
133
134 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
135 R"(,"result":[)" + Edits + R"(]})");
136}
137
138void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting(
139 DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
140 auto File = Params.textDocument.uri.file;
141 std::string Code = LangServer.Server.getDocument(File);
142 std::string Edits = replacementsToEdits(
143 Code, LangServer.Server.formatRange(File, Params.range));
144
145 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
146 R"(,"result":[)" + Edits + R"(]})");
147}
148
149void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting(
150 DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) {
151 auto File = Params.textDocument.uri.file;
152 std::string Code = LangServer.Server.getDocument(File);
153 std::string Edits =
154 replacementsToEdits(Code, LangServer.Server.formatFile(File));
155
156 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
157 R"(,"result":[)" + Edits + R"(]})");
158}
159
160void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction(
161 CodeActionParams Params, StringRef ID, JSONOutput &Out) {
162 // We provide a code action for each diagnostic at the requested location
163 // which has FixIts available.
164 std::string Code =
165 LangServer.Server.getDocument(Params.textDocument.uri.file);
166 std::string Commands;
167 for (Diagnostic &D : Params.context.diagnostics) {
168 std::vector<clang::tooling::Replacement> Fixes =
169 LangServer.getFixIts(Params.textDocument.uri.file, D);
170 std::string Edits = replacementsToEdits(Code, Fixes);
171
172 if (!Edits.empty())
173 Commands +=
174 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
175 R"('", "command": "clangd.applyFix", "arguments": [")" +
176 llvm::yaml::escape(Params.textDocument.uri.uri) +
177 R"(", [)" + Edits +
178 R"(]]},)";
179 }
180 if (!Commands.empty())
181 Commands.pop_back();
182
183 Out.writeMessage(
184 R"({"jsonrpc":"2.0","id":)" + ID.str() +
185 R"(, "result": [)" + Commands +
186 R"(]})");
187}
188
189void ClangdLSPServer::LSPProtocolCallbacks::onCompletion(
190 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
191
Ilya Biryukov574b7532017-08-02 09:08:39 +0000192 auto Items = LangServer.Server
193 .codeComplete(Params.textDocument.uri.file,
194 Position{Params.position.line,
195 Params.position.character})
196 .Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000197
198 std::string Completions;
199 for (const auto &Item : Items) {
200 Completions += CompletionItem::unparse(Item);
201 Completions += ",";
202 }
203 if (!Completions.empty())
204 Completions.pop_back();
205 Out.writeMessage(
206 R"({"jsonrpc":"2.0","id":)" + ID.str() +
207 R"(,"result":[)" + Completions + R"(]})");
208}
209
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000210void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition(
211 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
212
Ilya Biryukov574b7532017-08-02 09:08:39 +0000213 auto Items = LangServer.Server
214 .findDefinitions(Params.textDocument.uri.file,
215 Position{Params.position.line,
216 Params.position.character})
217 .Value;
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000218
219 std::string Locations;
220 for (const auto &Item : Items) {
221 Locations += Location::unparse(Item);
222 Locations += ",";
223 }
224 if (!Locations.empty())
225 Locations.pop_back();
226 Out.writeMessage(
227 R"({"jsonrpc":"2.0","id":)" + ID.str() +
228 R"(,"result":[)" + Locations + R"(]})");
229}
230
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000231void ClangdLSPServer::LSPProtocolCallbacks::onSwitchSourceHeader(
232 TextDocumentIdentifier Params, StringRef ID, JSONOutput &Out) {
233 llvm::Optional<Path> Result =
234 LangServer.Server.switchSourceHeader(Params.uri.file);
235 std::string ResultUri;
236 if (Result)
237 ResultUri = URI::unparse(URI::fromFile(*Result));
238 else
239 ResultUri = "\"\"";
240
241 Out.writeMessage(
242 R"({"jsonrpc":"2.0","id":)" + ID.str() +
243 R"(,"result":)" + ResultUri + R"(})");
244}
245
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000246ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000247 bool SnippetCompletions,
Krasimir Georgiev0dcb48e2017-07-19 15:43:35 +0000248 llvm::Optional<StringRef> ResourceDir)
Ilya Biryukove5128f72017-09-20 07:24:15 +0000249 : Out(Out), CDB(/*Logger=*/Out), DiagConsumer(*this),
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000250 Server(CDB, DiagConsumer, FSProvider, AsyncThreadsCount,
Ilya Biryukove5128f72017-09-20 07:24:15 +0000251 SnippetCompletions, /*Logger=*/Out, ResourceDir) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000252
Ilya Biryukovafb55542017-05-16 14:40:30 +0000253void ClangdLSPServer::run(std::istream &In) {
254 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000255
Ilya Biryukovafb55542017-05-16 14:40:30 +0000256 // Set up JSONRPCDispatcher.
257 LSPProtocolCallbacks Callbacks(*this);
258 JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
259 regiterCallbackHandlers(Dispatcher, Out, Callbacks);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000260
Ilya Biryukovafb55542017-05-16 14:40:30 +0000261 // Run the Language Server loop.
262 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
263
264 // Make sure IsDone is set to true after this method exits to ensure assertion
265 // at the start of the method fires if it's ever executed again.
266 IsDone = true;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000267}
268
269std::vector<clang::tooling::Replacement>
270ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
271 std::lock_guard<std::mutex> Lock(FixItsMutex);
272 auto DiagToFixItsIter = FixItsMap.find(File);
273 if (DiagToFixItsIter == FixItsMap.end())
274 return {};
275
276 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
277 auto FixItsIter = DiagToFixItsMap.find(D);
278 if (FixItsIter == DiagToFixItsMap.end())
279 return {};
280
281 return FixItsIter->second;
282}
283
Ilya Biryukov38d79772017-05-16 09:38:59 +0000284void ClangdLSPServer::consumeDiagnostics(
285 PathRef File, std::vector<DiagWithFixIts> Diagnostics) {
286 std::string DiagnosticsJSON;
287
288 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
289 for (auto &DiagWithFixes : Diagnostics) {
290 auto Diag = DiagWithFixes.Diag;
291 DiagnosticsJSON +=
292 R"({"range":)" + Range::unparse(Diag.range) +
293 R"(,"severity":)" + std::to_string(Diag.severity) +
294 R"(,"message":")" + llvm::yaml::escape(Diag.message) +
295 R"("},)";
296
297 // We convert to Replacements to become independent of the SourceManager.
298 auto &FixItsForDiagnostic = LocalFixIts[Diag];
299 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
300 std::back_inserter(FixItsForDiagnostic));
301 }
302
303 // Cache FixIts
304 {
305 // FIXME(ibiryukov): should be deleted when documents are removed
306 std::lock_guard<std::mutex> Lock(FixItsMutex);
307 FixItsMap[File] = LocalFixIts;
308 }
309
310 // Publish diagnostics.
311 if (!DiagnosticsJSON.empty())
312 DiagnosticsJSON.pop_back(); // Drop trailing comma.
313 Out.writeMessage(
314 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
315 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
316 R"(]}})");
317}