blob: b02891ba145eaf02146fadbeaf05e4a5d0d8fb26 [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,
Ilya Biryukov574b7532017-08-02 09:08:39 +000073 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,
Ilya Biryukova2e7ca92017-07-31 09:27:52 +000089 "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +000090 "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) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +0000100 if (Params.metadata && !Params.metadata->extraFlags.empty())
101 LangServer.CDB.setExtraFlagsForFile(Params.textDocument.uri.file,
102 std::move(Params.metadata->extraFlags));
Ilya Biryukovafb55542017-05-16 14:40:30 +0000103 LangServer.Server.addDocument(Params.textDocument.uri.file,
104 Params.textDocument.text);
105}
106
107void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange(
108 DidChangeTextDocumentParams Params, JSONOutput &Out) {
109 // We only support full syncing right now.
110 LangServer.Server.addDocument(Params.textDocument.uri.file,
111 Params.contentChanges[0].text);
112}
113
114void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose(
115 DidCloseTextDocumentParams Params, JSONOutput &Out) {
116 LangServer.Server.removeDocument(Params.textDocument.uri.file);
117}
118
119void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting(
120 DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
121 auto File = Params.textDocument.uri.file;
122 std::string Code = LangServer.Server.getDocument(File);
123 std::string Edits = replacementsToEdits(
124 Code, LangServer.Server.formatOnType(File, Params.position));
125
126 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
127 R"(,"result":[)" + Edits + R"(]})");
128}
129
130void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting(
131 DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
132 auto File = Params.textDocument.uri.file;
133 std::string Code = LangServer.Server.getDocument(File);
134 std::string Edits = replacementsToEdits(
135 Code, LangServer.Server.formatRange(File, Params.range));
136
137 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
138 R"(,"result":[)" + Edits + R"(]})");
139}
140
141void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting(
142 DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) {
143 auto File = Params.textDocument.uri.file;
144 std::string Code = LangServer.Server.getDocument(File);
145 std::string Edits =
146 replacementsToEdits(Code, LangServer.Server.formatFile(File));
147
148 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
149 R"(,"result":[)" + Edits + R"(]})");
150}
151
152void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction(
153 CodeActionParams Params, StringRef ID, JSONOutput &Out) {
154 // We provide a code action for each diagnostic at the requested location
155 // which has FixIts available.
156 std::string Code =
157 LangServer.Server.getDocument(Params.textDocument.uri.file);
158 std::string Commands;
159 for (Diagnostic &D : Params.context.diagnostics) {
160 std::vector<clang::tooling::Replacement> Fixes =
161 LangServer.getFixIts(Params.textDocument.uri.file, D);
162 std::string Edits = replacementsToEdits(Code, Fixes);
163
164 if (!Edits.empty())
165 Commands +=
166 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
167 R"('", "command": "clangd.applyFix", "arguments": [")" +
168 llvm::yaml::escape(Params.textDocument.uri.uri) +
169 R"(", [)" + Edits +
170 R"(]]},)";
171 }
172 if (!Commands.empty())
173 Commands.pop_back();
174
175 Out.writeMessage(
176 R"({"jsonrpc":"2.0","id":)" + ID.str() +
177 R"(, "result": [)" + Commands +
178 R"(]})");
179}
180
181void ClangdLSPServer::LSPProtocolCallbacks::onCompletion(
182 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
183
Ilya Biryukov574b7532017-08-02 09:08:39 +0000184 auto Items = LangServer.Server
185 .codeComplete(Params.textDocument.uri.file,
186 Position{Params.position.line,
187 Params.position.character})
188 .Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000189
190 std::string Completions;
191 for (const auto &Item : Items) {
192 Completions += CompletionItem::unparse(Item);
193 Completions += ",";
194 }
195 if (!Completions.empty())
196 Completions.pop_back();
197 Out.writeMessage(
198 R"({"jsonrpc":"2.0","id":)" + ID.str() +
199 R"(,"result":[)" + Completions + R"(]})");
200}
201
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000202void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition(
203 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
204
Ilya Biryukov574b7532017-08-02 09:08:39 +0000205 auto Items = LangServer.Server
206 .findDefinitions(Params.textDocument.uri.file,
207 Position{Params.position.line,
208 Params.position.character})
209 .Value;
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000210
211 std::string Locations;
212 for (const auto &Item : Items) {
213 Locations += Location::unparse(Item);
214 Locations += ",";
215 }
216 if (!Locations.empty())
217 Locations.pop_back();
218 Out.writeMessage(
219 R"({"jsonrpc":"2.0","id":)" + ID.str() +
220 R"(,"result":[)" + Locations + R"(]})");
221}
222
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000223ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000224 bool SnippetCompletions,
Krasimir Georgiev0dcb48e2017-07-19 15:43:35 +0000225 llvm::Optional<StringRef> ResourceDir)
Ilya Biryukove5128f72017-09-20 07:24:15 +0000226 : Out(Out), CDB(/*Logger=*/Out), DiagConsumer(*this),
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000227 Server(CDB, DiagConsumer, FSProvider, AsyncThreadsCount,
Ilya Biryukove5128f72017-09-20 07:24:15 +0000228 SnippetCompletions, /*Logger=*/Out, ResourceDir) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000229
Ilya Biryukovafb55542017-05-16 14:40:30 +0000230void ClangdLSPServer::run(std::istream &In) {
231 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000232
Ilya Biryukovafb55542017-05-16 14:40:30 +0000233 // Set up JSONRPCDispatcher.
234 LSPProtocolCallbacks Callbacks(*this);
235 JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
236 regiterCallbackHandlers(Dispatcher, Out, Callbacks);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000237
Ilya Biryukovafb55542017-05-16 14:40:30 +0000238 // Run the Language Server loop.
239 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
240
241 // Make sure IsDone is set to true after this method exits to ensure assertion
242 // at the start of the method fires if it's ever executed again.
243 IsDone = true;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000244}
245
246std::vector<clang::tooling::Replacement>
247ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
248 std::lock_guard<std::mutex> Lock(FixItsMutex);
249 auto DiagToFixItsIter = FixItsMap.find(File);
250 if (DiagToFixItsIter == FixItsMap.end())
251 return {};
252
253 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
254 auto FixItsIter = DiagToFixItsMap.find(D);
255 if (FixItsIter == DiagToFixItsMap.end())
256 return {};
257
258 return FixItsIter->second;
259}
260
Ilya Biryukov38d79772017-05-16 09:38:59 +0000261void ClangdLSPServer::consumeDiagnostics(
262 PathRef File, std::vector<DiagWithFixIts> Diagnostics) {
263 std::string DiagnosticsJSON;
264
265 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
266 for (auto &DiagWithFixes : Diagnostics) {
267 auto Diag = DiagWithFixes.Diag;
268 DiagnosticsJSON +=
269 R"({"range":)" + Range::unparse(Diag.range) +
270 R"(,"severity":)" + std::to_string(Diag.severity) +
271 R"(,"message":")" + llvm::yaml::escape(Diag.message) +
272 R"("},)";
273
274 // We convert to Replacements to become independent of the SourceManager.
275 auto &FixItsForDiagnostic = LocalFixIts[Diag];
276 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
277 std::back_inserter(FixItsForDiagnostic));
278 }
279
280 // Cache FixIts
281 {
282 // FIXME(ibiryukov): should be deleted when documents are removed
283 std::lock_guard<std::mutex> Lock(FixItsMutex);
284 FixItsMap[File] = LocalFixIts;
285 }
286
287 // Publish diagnostics.
288 if (!DiagnosticsJSON.empty())
289 DiagnosticsJSON.pop_back(); // Drop trailing comma.
290 Out.writeMessage(
291 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
292 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
293 R"(]}})");
294}