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