blob: fde6b6fc4b3d11233034c904f8ecb3c7162d99a9 [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;
72
73private:
74 ClangdLSPServer &LangServer;
75};
76
77void ClangdLSPServer::LSPProtocolCallbacks::onInitialize(StringRef ID,
78 JSONOutput &Out) {
79 Out.writeMessage(
80 R"({"jsonrpc":"2.0","id":)" + ID +
81 R"(,"result":{"capabilities":{
82 "textDocumentSync": 1,
83 "documentFormattingProvider": true,
84 "documentRangeFormattingProvider": true,
85 "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
86 "codeActionProvider": true,
87 "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">"]}
88 }}})");
89}
90
91void ClangdLSPServer::LSPProtocolCallbacks::onShutdown(JSONOutput &Out) {
92 LangServer.IsDone = true;
93}
94
95void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidOpen(
96 DidOpenTextDocumentParams Params, JSONOutput &Out) {
97 LangServer.Server.addDocument(Params.textDocument.uri.file,
98 Params.textDocument.text);
99}
100
101void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidChange(
102 DidChangeTextDocumentParams Params, JSONOutput &Out) {
103 // We only support full syncing right now.
104 LangServer.Server.addDocument(Params.textDocument.uri.file,
105 Params.contentChanges[0].text);
106}
107
108void ClangdLSPServer::LSPProtocolCallbacks::onDocumentDidClose(
109 DidCloseTextDocumentParams Params, JSONOutput &Out) {
110 LangServer.Server.removeDocument(Params.textDocument.uri.file);
111}
112
113void ClangdLSPServer::LSPProtocolCallbacks::onDocumentOnTypeFormatting(
114 DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
115 auto File = Params.textDocument.uri.file;
116 std::string Code = LangServer.Server.getDocument(File);
117 std::string Edits = replacementsToEdits(
118 Code, LangServer.Server.formatOnType(File, Params.position));
119
120 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
121 R"(,"result":[)" + Edits + R"(]})");
122}
123
124void ClangdLSPServer::LSPProtocolCallbacks::onDocumentRangeFormatting(
125 DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
126 auto File = Params.textDocument.uri.file;
127 std::string Code = LangServer.Server.getDocument(File);
128 std::string Edits = replacementsToEdits(
129 Code, LangServer.Server.formatRange(File, Params.range));
130
131 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
132 R"(,"result":[)" + Edits + R"(]})");
133}
134
135void ClangdLSPServer::LSPProtocolCallbacks::onDocumentFormatting(
136 DocumentFormattingParams Params, StringRef ID, JSONOutput &Out) {
137 auto File = Params.textDocument.uri.file;
138 std::string Code = LangServer.Server.getDocument(File);
139 std::string Edits =
140 replacementsToEdits(Code, LangServer.Server.formatFile(File));
141
142 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
143 R"(,"result":[)" + Edits + R"(]})");
144}
145
146void ClangdLSPServer::LSPProtocolCallbacks::onCodeAction(
147 CodeActionParams Params, StringRef ID, JSONOutput &Out) {
148 // We provide a code action for each diagnostic at the requested location
149 // which has FixIts available.
150 std::string Code =
151 LangServer.Server.getDocument(Params.textDocument.uri.file);
152 std::string Commands;
153 for (Diagnostic &D : Params.context.diagnostics) {
154 std::vector<clang::tooling::Replacement> Fixes =
155 LangServer.getFixIts(Params.textDocument.uri.file, D);
156 std::string Edits = replacementsToEdits(Code, Fixes);
157
158 if (!Edits.empty())
159 Commands +=
160 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
161 R"('", "command": "clangd.applyFix", "arguments": [")" +
162 llvm::yaml::escape(Params.textDocument.uri.uri) +
163 R"(", [)" + Edits +
164 R"(]]},)";
165 }
166 if (!Commands.empty())
167 Commands.pop_back();
168
169 Out.writeMessage(
170 R"({"jsonrpc":"2.0","id":)" + ID.str() +
171 R"(, "result": [)" + Commands +
172 R"(]})");
173}
174
175void ClangdLSPServer::LSPProtocolCallbacks::onCompletion(
176 TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) {
177
178 auto Items = LangServer.Server.codeComplete(
179 Params.textDocument.uri.file,
Ilya Biryukov22602992017-05-30 15:11:02 +0000180 Position{Params.position.line, Params.position.character}).Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000181
182 std::string Completions;
183 for (const auto &Item : Items) {
184 Completions += CompletionItem::unparse(Item);
185 Completions += ",";
186 }
187 if (!Completions.empty())
188 Completions.pop_back();
189 Out.writeMessage(
190 R"({"jsonrpc":"2.0","id":)" + ID.str() +
191 R"(,"result":[)" + Completions + R"(]})");
192}
193
Ilya Biryukov38d79772017-05-16 09:38:59 +0000194ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously)
Ilya Biryukov103c9512017-06-13 15:59:43 +0000195 : Out(Out), DiagConsumer(*this),
196 Server(CDB, DiagConsumer, FSProvider, RunSynchronously) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000197
Ilya Biryukovafb55542017-05-16 14:40:30 +0000198void ClangdLSPServer::run(std::istream &In) {
199 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000200
Ilya Biryukovafb55542017-05-16 14:40:30 +0000201 // Set up JSONRPCDispatcher.
202 LSPProtocolCallbacks Callbacks(*this);
203 JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
204 regiterCallbackHandlers(Dispatcher, Out, Callbacks);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000205
Ilya Biryukovafb55542017-05-16 14:40:30 +0000206 // Run the Language Server loop.
207 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
208
209 // Make sure IsDone is set to true after this method exits to ensure assertion
210 // at the start of the method fires if it's ever executed again.
211 IsDone = true;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000212}
213
214std::vector<clang::tooling::Replacement>
215ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
216 std::lock_guard<std::mutex> Lock(FixItsMutex);
217 auto DiagToFixItsIter = FixItsMap.find(File);
218 if (DiagToFixItsIter == FixItsMap.end())
219 return {};
220
221 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
222 auto FixItsIter = DiagToFixItsMap.find(D);
223 if (FixItsIter == DiagToFixItsMap.end())
224 return {};
225
226 return FixItsIter->second;
227}
228
Ilya Biryukov38d79772017-05-16 09:38:59 +0000229void ClangdLSPServer::consumeDiagnostics(
230 PathRef File, std::vector<DiagWithFixIts> Diagnostics) {
231 std::string DiagnosticsJSON;
232
233 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
234 for (auto &DiagWithFixes : Diagnostics) {
235 auto Diag = DiagWithFixes.Diag;
236 DiagnosticsJSON +=
237 R"({"range":)" + Range::unparse(Diag.range) +
238 R"(,"severity":)" + std::to_string(Diag.severity) +
239 R"(,"message":")" + llvm::yaml::escape(Diag.message) +
240 R"("},)";
241
242 // We convert to Replacements to become independent of the SourceManager.
243 auto &FixItsForDiagnostic = LocalFixIts[Diag];
244 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
245 std::back_inserter(FixItsForDiagnostic));
246 }
247
248 // Cache FixIts
249 {
250 // FIXME(ibiryukov): should be deleted when documents are removed
251 std::lock_guard<std::mutex> Lock(FixItsMutex);
252 FixItsMap[File] = LocalFixIts;
253 }
254
255 // Publish diagnostics.
256 if (!DiagnosticsJSON.empty())
257 DiagnosticsJSON.pop_back(); // Drop trailing comma.
258 Out.writeMessage(
259 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
260 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
261 R"(]}})");
262}