blob: e87c9fd9f4b0ee8ed68f7b8bbbed2f65a43c126b [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;
Ilya Biryukovafb55542017-05-16 14:40:30 +000075
76private:
77 ClangdLSPServer &LangServer;
78};
79
80void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID,
Marc-Andre Laperle37de9712017-09-27 15:31:17 +000081 InitializeParams IP,
Ilya Biryukovafb55542017-05-16 14:40:30 +000082 JSONOutput &Out) {
83 Out.writeMessage(
84 R"({"jsonrpc":"2.0","id":)" + ID +
85 R"(,"result":{"capabilities":{
86 "textDocumentSync": 1,
87 "documentFormattingProvider": true,
88 "documentRangeFormattingProvider": true,
89 "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
90 "codeActionProvider": true,
Ilya Biryukova2e7ca92017-07-31 09:27:52 +000091 "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +000092 "definitionProvider": true
Ilya Biryukovafb55542017-05-16 14:40:30 +000093 }}})");
Marc-Andre Laperle37de9712017-09-27 15:31:17 +000094 if (IP.rootUri && !IP.rootUri->file.empty())
95 LangServer.Server.setRootPath(IP.rootUri->file);
96 else if (IP.rootPath && !IP.rootPath->empty())
97 LangServer.Server.setRootPath(*IP.rootPath);
Ilya Biryukovafb55542017-05-16 14:40:30 +000098}
99
100void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) {
101 LangServer.IsDone = true;
102}
103
104void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidOpen(
105 DidOpenTextDocumentParams Params, JSONOutput &Out) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000106 if (Params.metadata && !Params.metadata->extraFlags.empty())
107 LangServer.CDB.setExtraFlagsForFile(Params.textDocument.uri.file,
108 std::move(Params.metadata->extraFlags));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000109 LangServer.Server.addDocument(Params.textDocument.uri.file,
110 Params.textDocument.text);
111}
112
113void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange(
114 DidChangeTextDocumentParams Params, JSONOutput &Out) {
115 // We only support full syncing right now.
116 LangServer.Server.addDocument(Params.textDocument.uri.file,
117 Params.contentChanges[0].text);
118}
119
120void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose(
121 DidCloseTextDocumentParams Params, JSONOutput &Out) {
122 LangServer.Server.removeDocument(Params.textDocument.uri.file);
123}
124
125void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting(
126 DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
127 auto File = Params.textDocument.uri.file;
128 std::string Code = LangServer.Server.getDocument(File);
129 std::string Edits = replacementsToEdits(
130 Code, LangServer.Server.formatOnType(File, Params.position));
131
132 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
133 R"(,"result":[)" + Edits + R"(]})");
134}
135
136void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting(
137 DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
138 auto File = Params.textDocument.uri.file;
139 std::string Code = LangServer.Server.getDocument(File);
140 std::string Edits = replacementsToEdits(
141 Code, LangServer.Server.formatRange(File, Params.range));
142
143 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
144 R"(,"result":[)" + Edits + R"(]})");
145}
146
147void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting(
148 DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) {
149 auto File = Params.textDocument.uri.file;
150 std::string Code = LangServer.Server.getDocument(File);
151 std::string Edits =
152 replacementsToEdits(Code, LangServer.Server.formatFile(File));
153
154 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
155 R"(,"result":[)" + Edits + R"(]})");
156}
157
158void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction(
159 CodeActionParams Params, StringRef ID, JSONOutput &Out) {
160 // We provide a code action for each diagnostic at the requested location
161 // which has FixIts available.
162 std::string Code =
163 LangServer.Server.getDocument(Params.textDocument.uri.file);
164 std::string Commands;
165 for (Diagnostic &D : Params.context.diagnostics) {
166 std::vector<clang::tooling::Replacement> Fixes =
167 LangServer.getFixIts(Params.textDocument.uri.file, D);
168 std::string Edits = replacementsToEdits(Code, Fixes);
169
170 if (!Edits.empty())
171 Commands +=
172 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
173 R"('", "command": "clangd.applyFix", "arguments": [")" +
174 llvm::yaml::escape(Params.textDocument.uri.uri) +
175 R"(", [)" + Edits +
176 R"(]]},)";
177 }
178 if (!Commands.empty())
179 Commands.pop_back();
180
181 Out.writeMessage(
182 R"({"jsonrpc":"2.0","id":)" + ID.str() +
183 R"(, "result": [)" + Commands +
184 R"(]})");
185}
186
187void ClangdLSPServer::LSPProtocolCallbacks::onCompletion(
188 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
189
Ilya Biryukov574b7532017-08-02 09:08:39 +0000190 auto Items = LangServer.Server
191 .codeComplete(Params.textDocument.uri.file,
192 Position{Params.position.line,
193 Params.position.character})
194 .Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000195
196 std::string Completions;
197 for (const auto &Item : Items) {
198 Completions += CompletionItem::unparse(Item);
199 Completions += ",";
200 }
201 if (!Completions.empty())
202 Completions.pop_back();
203 Out.writeMessage(
204 R"({"jsonrpc":"2.0","id":)" + ID.str() +
205 R"(,"result":[)" + Completions + R"(]})");
206}
207
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000208void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition(
209 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
210
Ilya Biryukov574b7532017-08-02 09:08:39 +0000211 auto Items = LangServer.Server
212 .findDefinitions(Params.textDocument.uri.file,
213 Position{Params.position.line,
214 Params.position.character})
215 .Value;
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000216
217 std::string Locations;
218 for (const auto &Item : Items) {
219 Locations += Location::unparse(Item);
220 Locations += ",";
221 }
222 if (!Locations.empty())
223 Locations.pop_back();
224 Out.writeMessage(
225 R"({"jsonrpc":"2.0","id":)" + ID.str() +
226 R"(,"result":[)" + Locations + R"(]})");
227}
228
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000229ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000230 bool SnippetCompletions,
Krasimir Georgiev0dcb48e2017-07-19 15:43:35 +0000231 llvm::Optional<StringRef> ResourceDir)
Ilya Biryukove5128f72017-09-20 07:24:15 +0000232 : Out(Out), CDB(/*Logger=*/Out), DiagConsumer(*this),
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000233 Server(CDB, DiagConsumer, FSProvider, AsyncThreadsCount,
Ilya Biryukove5128f72017-09-20 07:24:15 +0000234 SnippetCompletions, /*Logger=*/Out, ResourceDir) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000235
Ilya Biryukovafb55542017-05-16 14:40:30 +0000236void ClangdLSPServer::run(std::istream &In) {
237 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000238
Ilya Biryukovafb55542017-05-16 14:40:30 +0000239 // Set up JSONRPCDispatcher.
240 LSPProtocolCallbacks Callbacks(*this);
241 JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
242 regiterCallbackHandlers(Dispatcher, Out, Callbacks);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000243
Ilya Biryukovafb55542017-05-16 14:40:30 +0000244 // Run the Language Server loop.
245 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
246
247 // Make sure IsDone is set to true after this method exits to ensure assertion
248 // at the start of the method fires if it's ever executed again.
249 IsDone = true;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000250}
251
252std::vector<clang::tooling::Replacement>
253ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
254 std::lock_guard<std::mutex> Lock(FixItsMutex);
255 auto DiagToFixItsIter = FixItsMap.find(File);
256 if (DiagToFixItsIter == FixItsMap.end())
257 return {};
258
259 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
260 auto FixItsIter = DiagToFixItsMap.find(D);
261 if (FixItsIter == DiagToFixItsMap.end())
262 return {};
263
264 return FixItsIter->second;
265}
266
Ilya Biryukov38d79772017-05-16 09:38:59 +0000267void ClangdLSPServer::consumeDiagnostics(
268 PathRef File, std::vector<DiagWithFixIts> Diagnostics) {
269 std::string DiagnosticsJSON;
270
271 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
272 for (auto &DiagWithFixes : Diagnostics) {
273 auto Diag = DiagWithFixes.Diag;
274 DiagnosticsJSON +=
275 R"({"range":)" + Range::unparse(Diag.range) +
276 R"(,"severity":)" + std::to_string(Diag.severity) +
277 R"(,"message":")" + llvm::yaml::escape(Diag.message) +
278 R"("},)";
279
280 // We convert to Replacements to become independent of the SourceManager.
281 auto &FixItsForDiagnostic = LocalFixIts[Diag];
282 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
283 std::back_inserter(FixItsForDiagnostic));
284 }
285
286 // Cache FixIts
287 {
288 // FIXME(ibiryukov): should be deleted when documents are removed
289 std::lock_guard<std::mutex> Lock(FixItsMutex);
290 FixItsMap[File] = LocalFixIts;
291 }
292
293 // Publish diagnostics.
294 if (!DiagnosticsJSON.empty())
295 DiagnosticsJSON.pop_back(); // Drop trailing comma.
296 Out.writeMessage(
297 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
298 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
299 R"(]}})");
300}