blob: 135cbf49c417e8646fecb23bd248d5c015b37b23 [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"
12
13using namespace clang::clangd;
14using namespace clang;
15
Ilya Biryukovafb55542017-05-16 14:40:30 +000016namespace {
17
18std::string
19replacementsToEdits(StringRef Code,
20 const std::vector<tooling::Replacement> &Replacements) {
21 // Turn the replacements into the format specified by the Language Server
22 // Protocol. Fuse them into one big JSON array.
23 std::string Edits;
24 for (auto &R : Replacements) {
25 Range ReplacementRange = {
26 offsetToPosition(Code, R.getOffset()),
27 offsetToPosition(Code, R.getOffset() + R.getLength())};
28 TextEdit TE = {ReplacementRange, R.getReplacementText()};
29 Edits += TextEdit::unparse(TE);
30 Edits += ',';
31 }
32 if (!Edits.empty())
33 Edits.pop_back();
34
35 return Edits;
36}
37
38} // namespace
39
Sam McCall4db732a2017-09-30 10:08:52 +000040void ClangdLSPServer::onInitialize(StringRef ID, InitializeParams IP,
41 JSONOutput &Out) {
Ilya Biryukovafb55542017-05-16 14:40:30 +000042 Out.writeMessage(
43 R"({"jsonrpc":"2.0","id":)" + ID +
44 R"(,"result":{"capabilities":{
45 "textDocumentSync": 1,
46 "documentFormattingProvider": true,
47 "documentRangeFormattingProvider": true,
48 "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
49 "codeActionProvider": true,
Ilya Biryukova2e7ca92017-07-31 09:27:52 +000050 "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +000051 "definitionProvider": true
Ilya Biryukovafb55542017-05-16 14:40:30 +000052 }}})");
Marc-Andre Laperle37de9712017-09-27 15:31:17 +000053 if (IP.rootUri && !IP.rootUri->file.empty())
Sam McCall4db732a2017-09-30 10:08:52 +000054 Server.setRootPath(IP.rootUri->file);
Marc-Andre Laperle37de9712017-09-27 15:31:17 +000055 else if (IP.rootPath && !IP.rootPath->empty())
Sam McCall4db732a2017-09-30 10:08:52 +000056 Server.setRootPath(*IP.rootPath);
Ilya Biryukovafb55542017-05-16 14:40:30 +000057}
58
Sam McCall4db732a2017-09-30 10:08:52 +000059void ClangdLSPServer::onShutdown(JSONOutput &Out) { IsDone = true; }
Ilya Biryukovafb55542017-05-16 14:40:30 +000060
Sam McCall4db732a2017-09-30 10:08:52 +000061void ClangdLSPServer::onDocumentDidOpen(DidOpenTextDocumentParams Params,
62 JSONOutput &Out) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000063 if (Params.metadata && !Params.metadata->extraFlags.empty())
Sam McCall4db732a2017-09-30 10:08:52 +000064 CDB.setExtraFlagsForFile(Params.textDocument.uri.file,
65 std::move(Params.metadata->extraFlags));
66 Server.addDocument(Params.textDocument.uri.file, Params.textDocument.text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000067}
68
Sam McCall4db732a2017-09-30 10:08:52 +000069void ClangdLSPServer::onDocumentDidChange(DidChangeTextDocumentParams Params,
70 JSONOutput &Out) {
Ilya Biryukovafb55542017-05-16 14:40:30 +000071 // We only support full syncing right now.
Sam McCall4db732a2017-09-30 10:08:52 +000072 Server.addDocument(Params.textDocument.uri.file,
73 Params.contentChanges[0].text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000074}
75
Sam McCall4db732a2017-09-30 10:08:52 +000076void ClangdLSPServer::onDocumentDidClose(DidCloseTextDocumentParams Params,
77 JSONOutput &Out) {
78 Server.removeDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +000079}
80
Sam McCall4db732a2017-09-30 10:08:52 +000081void ClangdLSPServer::onDocumentOnTypeFormatting(
Ilya Biryukovafb55542017-05-16 14:40:30 +000082 DocumentOnTypeFormattingParams Params, StringRef ID, JSONOutput &Out) {
83 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +000084 std::string Code = Server.getDocument(File);
85 std::string Edits =
86 replacementsToEdits(Code, Server.formatOnType(File, Params.position));
Ilya Biryukovafb55542017-05-16 14:40:30 +000087
88 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
89 R"(,"result":[)" + Edits + R"(]})");
90}
91
Sam McCall4db732a2017-09-30 10:08:52 +000092void ClangdLSPServer::onDocumentRangeFormatting(
Ilya Biryukovafb55542017-05-16 14:40:30 +000093 DocumentRangeFormattingParams Params, StringRef ID, JSONOutput &Out) {
94 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +000095 std::string Code = Server.getDocument(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +000096 std::string Edits =
Sam McCall4db732a2017-09-30 10:08:52 +000097 replacementsToEdits(Code, Server.formatRange(File, Params.range));
Ilya Biryukovafb55542017-05-16 14:40:30 +000098
99 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
100 R"(,"result":[)" + Edits + R"(]})");
101}
102
Sam McCall4db732a2017-09-30 10:08:52 +0000103void ClangdLSPServer::onDocumentFormatting(DocumentFormattingParams Params,
104 StringRef ID, JSONOutput &Out) {
105 auto File = Params.textDocument.uri.file;
106 std::string Code = Server.getDocument(File);
107 std::string Edits = replacementsToEdits(Code, Server.formatFile(File));
108
109 Out.writeMessage(R"({"jsonrpc":"2.0","id":)" + ID.str() +
110 R"(,"result":[)" + Edits + R"(]})");
111}
112
113void ClangdLSPServer::onCodeAction(CodeActionParams Params, StringRef ID,
114 JSONOutput &Out) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000115 // We provide a code action for each diagnostic at the requested location
116 // which has FixIts available.
Sam McCall4db732a2017-09-30 10:08:52 +0000117 std::string Code = Server.getDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000118 std::string Commands;
119 for (Diagnostic &D : Params.context.diagnostics) {
120 std::vector<clang::tooling::Replacement> Fixes =
Sam McCall4db732a2017-09-30 10:08:52 +0000121 getFixIts(Params.textDocument.uri.file, D);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000122 std::string Edits = replacementsToEdits(Code, Fixes);
123
124 if (!Edits.empty())
125 Commands +=
126 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
127 R"('", "command": "clangd.applyFix", "arguments": [")" +
128 llvm::yaml::escape(Params.textDocument.uri.uri) +
129 R"(", [)" + Edits +
130 R"(]]},)";
131 }
132 if (!Commands.empty())
133 Commands.pop_back();
134
135 Out.writeMessage(
136 R"({"jsonrpc":"2.0","id":)" + ID.str() +
137 R"(, "result": [)" + Commands +
138 R"(]})");
139}
140
Sam McCall4db732a2017-09-30 10:08:52 +0000141void ClangdLSPServer::onCompletion(TextDocumentPositionParams Params,
142 StringRef ID, JSONOutput &Out) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000143
Sam McCall4db732a2017-09-30 10:08:52 +0000144 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000145 .codeComplete(Params.textDocument.uri.file,
146 Position{Params.position.line,
147 Params.position.character})
148 .Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000149
150 std::string Completions;
151 for (const auto &Item : Items) {
152 Completions += CompletionItem::unparse(Item);
153 Completions += ",";
154 }
155 if (!Completions.empty())
156 Completions.pop_back();
157 Out.writeMessage(
158 R"({"jsonrpc":"2.0","id":)" + ID.str() +
159 R"(,"result":[)" + Completions + R"(]})");
160}
161
Sam McCall4db732a2017-09-30 10:08:52 +0000162void ClangdLSPServer::onGoToDefinition(TextDocumentPositionParams Params,
163 StringRef ID, JSONOutput &Out) {
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000164
Sam McCall4db732a2017-09-30 10:08:52 +0000165 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000166 .findDefinitions(Params.textDocument.uri.file,
167 Position{Params.position.line,
168 Params.position.character})
169 .Value;
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000170
171 std::string Locations;
172 for (const auto &Item : Items) {
173 Locations += Location::unparse(Item);
174 Locations += ",";
175 }
176 if (!Locations.empty())
177 Locations.pop_back();
178 Out.writeMessage(
179 R"({"jsonrpc":"2.0","id":)" + ID.str() +
180 R"(,"result":[)" + Locations + R"(]})");
181}
182
Sam McCall4db732a2017-09-30 10:08:52 +0000183void ClangdLSPServer::onSwitchSourceHeader(TextDocumentIdentifier Params,
184 StringRef ID, JSONOutput &Out) {
185 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000186 std::string ResultUri;
187 if (Result)
188 ResultUri = URI::unparse(URI::fromFile(*Result));
189 else
190 ResultUri = "\"\"";
191
192 Out.writeMessage(
193 R"({"jsonrpc":"2.0","id":)" + ID.str() +
194 R"(,"result":)" + ResultUri + R"(})");
195}
196
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000197ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000198 bool SnippetCompletions,
Krasimir Georgiev0dcb48e2017-07-19 15:43:35 +0000199 llvm::Optional<StringRef> ResourceDir)
Sam McCall4db732a2017-09-30 10:08:52 +0000200 : Out(Out), CDB(/*Logger=*/Out),
201 Server(CDB, /*DiagConsumer=*/*this, FSProvider, AsyncThreadsCount,
Ilya Biryukove5128f72017-09-20 07:24:15 +0000202 SnippetCompletions, /*Logger=*/Out, ResourceDir) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000203
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.
Ilya Biryukovafb55542017-05-16 14:40:30 +0000208 JSONRPCDispatcher Dispatcher(llvm::make_unique<Handler>(Out));
Sam McCall4db732a2017-09-30 10:08:52 +0000209 registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000210
Ilya Biryukovafb55542017-05-16 14:40:30 +0000211 // Run the Language Server loop.
212 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
213
214 // Make sure IsDone is set to true after this method exits to ensure assertion
215 // at the start of the method fires if it's ever executed again.
216 IsDone = true;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000217}
218
219std::vector<clang::tooling::Replacement>
220ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
221 std::lock_guard<std::mutex> Lock(FixItsMutex);
222 auto DiagToFixItsIter = FixItsMap.find(File);
223 if (DiagToFixItsIter == FixItsMap.end())
224 return {};
225
226 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
227 auto FixItsIter = DiagToFixItsMap.find(D);
228 if (FixItsIter == DiagToFixItsMap.end())
229 return {};
230
231 return FixItsIter->second;
232}
233
Sam McCall4db732a2017-09-30 10:08:52 +0000234void ClangdLSPServer::onDiagnosticsReady(
235 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000236 std::string DiagnosticsJSON;
237
238 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCall4db732a2017-09-30 10:08:52 +0000239 for (auto &DiagWithFixes : Diagnostics.Value) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000240 auto Diag = DiagWithFixes.Diag;
241 DiagnosticsJSON +=
242 R"({"range":)" + Range::unparse(Diag.range) +
243 R"(,"severity":)" + std::to_string(Diag.severity) +
244 R"(,"message":")" + llvm::yaml::escape(Diag.message) +
245 R"("},)";
246
247 // We convert to Replacements to become independent of the SourceManager.
248 auto &FixItsForDiagnostic = LocalFixIts[Diag];
249 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
250 std::back_inserter(FixItsForDiagnostic));
251 }
252
253 // Cache FixIts
254 {
255 // FIXME(ibiryukov): should be deleted when documents are removed
256 std::lock_guard<std::mutex> Lock(FixItsMutex);
257 FixItsMap[File] = LocalFixIts;
258 }
259
260 // Publish diagnostics.
261 if (!DiagnosticsJSON.empty())
262 DiagnosticsJSON.pop_back(); // Drop trailing comma.
263 Out.writeMessage(
264 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
265 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
266 R"(]}})");
267}