blob: 1fd0e95aef4856779213ce582ffe0b49706ee945 [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
54 void onInitialize(StringRef ID, JSONOutput &Out) override;
55 void onShutdown(JSONOutput &Out) override;
56 void onDocumentDidOpen(DidOpenTextDocumentParams Params,
57 JSONOutput &Out) override;
58 void onDocumentDidChange(DidChangeTextDocumentParams Params,
59 JSONOutput &Out) override;
60 void onDocumentDidClose(DidCloseTextDocumentParams Params,
61 JSONOutput &Out) override;
62 void onDocumentOnTypeFormatting(DocumentOnTypeFormattingParams Params,
63 StringRef ID, JSONOutput &Out) override;
64 void onDocumentRangeFormatting(DocumentRangeFormattingParams Params,
65 StringRef ID, JSONOutput &Out) override;
66 void onDocumentFormatting(DocumentFormattingParams Params, StringRef ID,
67 JSONOutput &Out) override;
68 void onCodeAction(CodeActionParams Params, StringRef ID,
69 JSONOutput &Out) override;
70 void onCompletion(TextDocumentPositionParams Params, StringRef ID,
71 JSONOutput &Out) override;
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +000072 void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID,
73 JSONOutput &Out) override;
Ilya Biryukovafb55542017-05-16 14:40:30 +000074
75private:
76 ClangdLSPServer &LangServer;
77};
78
79void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID,
80 JSONOutput &Out) {
81 Out.writeMessage(
82 R"({"jsonrpc":"2.0","id":)" + ID +
83 R"(,"result":{"capabilities":{
84 "textDocumentSync": 1,
85 "documentFormattingProvider": true,
86 "documentRangeFormattingProvider": true,
87 "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
88 "codeActionProvider": true,
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +000089 "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]},
90 "definitionProvider": true
Ilya Biryukovafb55542017-05-16 14:40:30 +000091 }}})");
92}
93
94void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) {
95 LangServer.IsDone = true;
96}
97
98void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidOpen(
99 DidOpenTextDocumentParams Params, JSONOutput &Out) {
100 LangServer.Server.addDocument(Params.textDocument.uri.file,
101 Params.textDocument.text);
102}
103
104void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange(
105 DidChangeTextDocumentParams Params, JSONOutput &Out) {
106 // We only support full syncing right now.
107 LangServer.Server.addDocument(Params.textDocument.uri.file,
108 Params.contentChanges[0].text);
109}
110
111void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose(
112 DidCloseTextDocumentParams Params, JSONOutput &Out) {
113 LangServer.Server.removeDocument(Params.textDocument.uri.file);
114}
115
116void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting(
117 DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
118 auto File = Params.textDocument.uri.file;
119 std::string Code = LangServer.Server.getDocument(File);
120 std::string Edits = replacementsToEdits(
121 Code, LangServer.Server.formatOnType(File, Params.position));
122
123 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
124 R"(,"result":[)" + Edits + R"(]})");
125}
126
127void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting(
128 DocumentRangeFormattingParams 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.formatRange(File, Params.range));
133
134 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
135 R"(,"result":[)" + Edits + R"(]})");
136}
137
138void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting(
139 DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) {
140 auto File = Params.textDocument.uri.file;
141 std::string Code = LangServer.Server.getDocument(File);
142 std::string Edits =
143 replacementsToEdits(Code, LangServer.Server.formatFile(File));
144
145 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
146 R"(,"result":[)" + Edits + R"(]})");
147}
148
149void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction(
150 CodeActionParams Params, StringRef ID, JSONOutput &Out) {
151 // We provide a code action for each diagnostic at the requested location
152 // which has FixIts available.
153 std::string Code =
154 LangServer.Server.getDocument(Params.textDocument.uri.file);
155 std::string Commands;
156 for (Diagnostic &D : Params.context.diagnostics) {
157 std::vector<clang::tooling::Replacement> Fixes =
158 LangServer.getFixIts(Params.textDocument.uri.file, D);
159 std::string Edits = replacementsToEdits(Code, Fixes);
160
161 if (!Edits.empty())
162 Commands +=
163 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
164 R"('", "command": "clangd.applyFix", "arguments": [")" +
165 llvm::yaml::escape(Params.textDocument.uri.uri) +
166 R"(", [)" + Edits +
167 R"(]]},)";
168 }
169 if (!Commands.empty())
170 Commands.pop_back();
171
172 Out.writeMessage(
173 R"({"jsonrpc":"2.0","id":)" + ID.str() +
174 R"(, "result": [)" + Commands +
175 R"(]})");
176}
177
178void ClangdLSPServer::LSPProtocolCallbacks::onCompletion(
179 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
180
181 auto Items = LangServer.Server.codeComplete(
182 Params.textDocument.uri.file,
Ilya Biryukov22602992017-05-30 15:11:02 +0000183 Position{Params.position.line, Params.position.character}).Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000184
185 std::string Completions;
186 for (const auto &Item : Items) {
187 Completions += CompletionItem::unparse(Item);
188 Completions += ",";
189 }
190 if (!Completions.empty())
191 Completions.pop_back();
192 Out.writeMessage(
193 R"({"jsonrpc":"2.0","id":)" + ID.str() +
194 R"(,"result":[)" + Completions + R"(]})");
195}
196
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000197void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition(
198 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
199
200 auto Items = LangServer.Server.findDefinitions(
201 Params.textDocument.uri.file,
202 Position{Params.position.line, Params.position.character}).Value;
203
204 std::string Locations;
205 for (const auto &Item : Items) {
206 Locations += Location::unparse(Item);
207 Locations += ",";
208 }
209 if (!Locations.empty())
210 Locations.pop_back();
211 Out.writeMessage(
212 R"({"jsonrpc":"2.0","id":)" + ID.str() +
213 R"(,"result":[)" + Locations + R"(]})");
214}
215
Ilya Biryukov38d79772017-05-16 09:38:59 +0000216ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously)
Ilya Biryukov103c9512017-06-13 15:59:43 +0000217 : Out(Out), DiagConsumer(*this),
218 Server(CDB, DiagConsumer, FSProvider, RunSynchronously) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000219
Ilya Biryukovafb55542017-05-16 14:40:30 +0000220void ClangdLSPServer::run(std::istream &In) {
221 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000222
Ilya Biryukovafb55542017-05-16 14:40:30 +0000223 // Set up JSONRPCDispatcher.
224 LSPProtocolCallbacks Callbacks(*this);
225 JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
226 regiterCallbackHandlers(Dispatcher, Out, Callbacks);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000227
Ilya Biryukovafb55542017-05-16 14:40:30 +0000228 // Run the Language Server loop.
229 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
230
231 // Make sure IsDone is set to true after this method exits to ensure assertion
232 // at the start of the method fires if it's ever executed again.
233 IsDone = true;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000234}
235
236std::vector<clang::tooling::Replacement>
237ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
238 std::lock_guard<std::mutex> Lock(FixItsMutex);
239 auto DiagToFixItsIter = FixItsMap.find(File);
240 if (DiagToFixItsIter == FixItsMap.end())
241 return {};
242
243 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
244 auto FixItsIter = DiagToFixItsMap.find(D);
245 if (FixItsIter == DiagToFixItsMap.end())
246 return {};
247
248 return FixItsIter->second;
249}
250
Ilya Biryukov38d79772017-05-16 09:38:59 +0000251void ClangdLSPServer::consumeDiagnostics(
252 PathRef File, std::vector<DiagWithFixIts> Diagnostics) {
253 std::string DiagnosticsJSON;
254
255 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
256 for (auto &DiagWithFixes : Diagnostics) {
257 auto Diag = DiagWithFixes.Diag;
258 DiagnosticsJSON +=
259 R"({"range":)" + Range::unparse(Diag.range) +
260 R"(,"severity":)" + std::to_string(Diag.severity) +
261 R"(,"message":")" + llvm::yaml::escape(Diag.message) +
262 R"("},)";
263
264 // We convert to Replacements to become independent of the SourceManager.
265 auto &FixItsForDiagnostic = LocalFixIts[Diag];
266 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
267 std::back_inserter(FixItsForDiagnostic));
268 }
269
270 // Cache FixIts
271 {
272 // FIXME(ibiryukov): should be deleted when documents are removed
273 std::lock_guard<std::mutex> Lock(FixItsMutex);
274 FixItsMap[File] = LocalFixIts;
275 }
276
277 // Publish diagnostics.
278 if (!DiagnosticsJSON.empty())
279 DiagnosticsJSON.pop_back(); // Drop trailing comma.
280 Out.writeMessage(
281 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
282 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
283 R"(]}})");
284}