blob: 160a3320b5698dbaa2bcec90cea1539d52e623b9 [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,
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
184 auto Items = LangServer.Server.codeComplete(
185 Params.textDocument.uri.file,
Ilya Biryukov22602992017-05-30 15:11:02 +0000186 Position{Params.position.line, Params.position.character}).Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000187
188 std::string Completions;
189 for (const auto &Item : Items) {
190 Completions += CompletionItem::unparse(Item);
191 Completions += ",";
192 }
193 if (!Completions.empty())
194 Completions.pop_back();
195 Out.writeMessage(
196 R"({"jsonrpc":"2.0","id":)" + ID.str() +
197 R"(,"result":[)" + Completions + R"(]})");
198}
199
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000200void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition(
201 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
202
203 auto Items = LangServer.Server.findDefinitions(
204 Params.textDocument.uri.file,
205 Position{Params.position.line, Params.position.character}).Value;
206
207 std::string Locations;
208 for (const auto &Item : Items) {
209 Locations += Location::unparse(Item);
210 Locations += ",";
211 }
212 if (!Locations.empty())
213 Locations.pop_back();
214 Out.writeMessage(
215 R"({"jsonrpc":"2.0","id":)" + ID.str() +
216 R"(,"result":[)" + Locations + R"(]})");
217}
218
Krasimir Georgiev0dcb48e2017-07-19 15:43:35 +0000219ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously,
220 llvm::Optional<StringRef> ResourceDir)
Ilya Biryukov103c9512017-06-13 15:59:43 +0000221 : Out(Out), DiagConsumer(*this),
Krasimir Georgiev0dcb48e2017-07-19 15:43:35 +0000222 Server(CDB, DiagConsumer, FSProvider, RunSynchronously, ResourceDir) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000223
Ilya Biryukovafb55542017-05-16 14:40:30 +0000224void ClangdLSPServer::run(std::istream &In) {
225 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000226
Ilya Biryukovafb55542017-05-16 14:40:30 +0000227 // Set up JSONRPCDispatcher.
228 LSPProtocolCallbacks Callbacks(*this);
229 JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
230 regiterCallbackHandlers(Dispatcher, Out, Callbacks);
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
Ilya Biryukov38d79772017-05-16 09:38:59 +0000255void ClangdLSPServer::consumeDiagnostics(
256 PathRef File, std::vector<DiagWithFixIts> Diagnostics) {
257 std::string DiagnosticsJSON;
258
259 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
260 for (auto &DiagWithFixes : Diagnostics) {
261 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}