blob: 64849dec88daf9ec7cd77c00a2afe54a035043d1 [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 McCall8a5dded2017-10-12 13:29:58 +000040void ClangdLSPServer::onInitialize(Ctx C, InitializeParams &Params) {
41 C.reply(
42 R"({"capabilities":{
Ilya Biryukovafb55542017-05-16 14:40:30 +000043 "textDocumentSync": 1,
44 "documentFormattingProvider": true,
45 "documentRangeFormattingProvider": true,
46 "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
47 "codeActionProvider": true,
Ilya Biryukova2e7ca92017-07-31 09:27:52 +000048 "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +000049 "signatureHelpProvider": {"triggerCharacters": ["(",","]},
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +000050 "definitionProvider": true
Sam McCall8a5dded2017-10-12 13:29:58 +000051 }})");
52 if (Params.rootUri && !Params.rootUri->file.empty())
53 Server.setRootPath(Params.rootUri->file);
54 else if (Params.rootPath && !Params.rootPath->empty())
55 Server.setRootPath(*Params.rootPath);
Ilya Biryukovafb55542017-05-16 14:40:30 +000056}
57
Sam McCall8a5dded2017-10-12 13:29:58 +000058void ClangdLSPServer::onShutdown(Ctx C, ShutdownParams &Params) {
59 IsDone = true;
60}
Ilya Biryukovafb55542017-05-16 14:40:30 +000061
Sam McCall8a5dded2017-10-12 13:29:58 +000062void ClangdLSPServer::onDocumentDidOpen(Ctx C,
63 DidOpenTextDocumentParams &Params) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000064 if (Params.metadata && !Params.metadata->extraFlags.empty())
Sam McCall4db732a2017-09-30 10:08:52 +000065 CDB.setExtraFlagsForFile(Params.textDocument.uri.file,
66 std::move(Params.metadata->extraFlags));
67 Server.addDocument(Params.textDocument.uri.file, Params.textDocument.text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000068}
69
Sam McCall8a5dded2017-10-12 13:29:58 +000070void ClangdLSPServer::onDocumentDidChange(Ctx C,
71 DidChangeTextDocumentParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +000072 // We only support full syncing right now.
Sam McCall4db732a2017-09-30 10:08:52 +000073 Server.addDocument(Params.textDocument.uri.file,
74 Params.contentChanges[0].text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000075}
76
Sam McCall8a5dded2017-10-12 13:29:58 +000077void ClangdLSPServer::onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) {
Marc-Andre Laperlebf114242017-10-02 18:00:37 +000078 Server.onFileEvent(Params);
79}
80
Sam McCall8a5dded2017-10-12 13:29:58 +000081void ClangdLSPServer::onDocumentDidClose(Ctx C,
82 DidCloseTextDocumentParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +000083 Server.removeDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +000084}
85
Sam McCall4db732a2017-09-30 10:08:52 +000086void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCall8a5dded2017-10-12 13:29:58 +000087 Ctx C, DocumentOnTypeFormattingParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +000088 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +000089 std::string Code = Server.getDocument(File);
90 std::string Edits =
91 replacementsToEdits(Code, Server.formatOnType(File, Params.position));
Sam McCall8a5dded2017-10-12 13:29:58 +000092 C.reply("[" + Edits + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +000093}
94
Sam McCall4db732a2017-09-30 10:08:52 +000095void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCall8a5dded2017-10-12 13:29:58 +000096 Ctx C, DocumentRangeFormattingParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +000097 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +000098 std::string Code = Server.getDocument(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +000099 std::string Edits =
Sam McCall4db732a2017-09-30 10:08:52 +0000100 replacementsToEdits(Code, Server.formatRange(File, Params.range));
Sam McCall8a5dded2017-10-12 13:29:58 +0000101 C.reply("[" + Edits + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000102}
103
Sam McCall8a5dded2017-10-12 13:29:58 +0000104void ClangdLSPServer::onDocumentFormatting(Ctx C,
105 DocumentFormattingParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000106 auto File = Params.textDocument.uri.file;
107 std::string Code = Server.getDocument(File);
108 std::string Edits = replacementsToEdits(Code, Server.formatFile(File));
Sam McCall8a5dded2017-10-12 13:29:58 +0000109 C.reply("[" + Edits + "]");
Sam McCall4db732a2017-09-30 10:08:52 +0000110}
111
Sam McCall8a5dded2017-10-12 13:29:58 +0000112void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000113 // We provide a code action for each diagnostic at the requested location
114 // which has FixIts available.
Sam McCall4db732a2017-09-30 10:08:52 +0000115 std::string Code = Server.getDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000116 std::string Commands;
117 for (Diagnostic &D : Params.context.diagnostics) {
118 std::vector<clang::tooling::Replacement> Fixes =
Sam McCall4db732a2017-09-30 10:08:52 +0000119 getFixIts(Params.textDocument.uri.file, D);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000120 std::string Edits = replacementsToEdits(Code, Fixes);
121
122 if (!Edits.empty())
123 Commands +=
124 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
125 R"('", "command": "clangd.applyFix", "arguments": [")" +
126 llvm::yaml::escape(Params.textDocument.uri.uri) +
127 R"(", [)" + Edits +
128 R"(]]},)";
129 }
130 if (!Commands.empty())
131 Commands.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000132 C.reply("[" + Commands + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000133}
134
Sam McCall8a5dded2017-10-12 13:29:58 +0000135void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000136 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000137 .codeComplete(Params.textDocument.uri.file,
138 Position{Params.position.line,
139 Params.position.character})
Ilya Biryukovdcd21692017-10-05 17:04:13 +0000140 .get() // FIXME(ibiryukov): This could be made async if we
141 // had an API that would allow to attach callbacks to
142 // futures returned by ClangdServer.
Ilya Biryukov574b7532017-08-02 09:08:39 +0000143 .Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000144
145 std::string Completions;
146 for (const auto &Item : Items) {
147 Completions += CompletionItem::unparse(Item);
148 Completions += ",";
149 }
150 if (!Completions.empty())
151 Completions.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000152 C.reply("[" + Completions + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000153}
154
Sam McCall8a5dded2017-10-12 13:29:58 +0000155void ClangdLSPServer::onSignatureHelp(Ctx C,
156 TextDocumentPositionParams &Params) {
157 C.reply(SignatureHelp::unparse(
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000158 Server
159 .signatureHelp(
160 Params.textDocument.uri.file,
161 Position{Params.position.line, Params.position.character})
Sam McCall8a5dded2017-10-12 13:29:58 +0000162 .Value));
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000163}
164
Sam McCall8a5dded2017-10-12 13:29:58 +0000165void ClangdLSPServer::onGoToDefinition(Ctx C,
166 TextDocumentPositionParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000167 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000168 .findDefinitions(Params.textDocument.uri.file,
169 Position{Params.position.line,
170 Params.position.character})
171 .Value;
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000172
173 std::string Locations;
174 for (const auto &Item : Items) {
175 Locations += Location::unparse(Item);
176 Locations += ",";
177 }
178 if (!Locations.empty())
179 Locations.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000180 C.reply("[" + Locations + "]");
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000181}
182
Sam McCall8a5dded2017-10-12 13:29:58 +0000183void ClangdLSPServer::onSwitchSourceHeader(Ctx C,
184 TextDocumentIdentifier &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000185 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000186 std::string ResultUri;
Sam McCall8a5dded2017-10-12 13:29:58 +0000187 C.reply(Result ? URI::unparse(URI::fromFile(*Result)) : R"("")");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000188}
189
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000190ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000191 bool SnippetCompletions,
Ilya Biryukov0c1ca6b2017-10-02 15:13:20 +0000192 llvm::Optional<StringRef> ResourceDir,
193 llvm::Optional<Path> CompileCommandsDir)
194 : Out(Out), CDB(/*Logger=*/Out, std::move(CompileCommandsDir)),
Sam McCall4db732a2017-09-30 10:08:52 +0000195 Server(CDB, /*DiagConsumer=*/*this, FSProvider, AsyncThreadsCount,
Ilya Biryukove5128f72017-09-20 07:24:15 +0000196 SnippetCompletions, /*Logger=*/Out, ResourceDir) {}
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.
Sam McCall8a5dded2017-10-12 13:29:58 +0000202 JSONRPCDispatcher Dispatcher(
203 [](RequestContext Ctx, llvm::yaml::MappingNode *Params) {
204 Ctx.replyError(-32601, "method not found");
205 });
Sam McCall4db732a2017-09-30 10:08:52 +0000206 registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000207
Ilya Biryukovafb55542017-05-16 14:40:30 +0000208 // Run the Language Server loop.
209 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
210
211 // Make sure IsDone is set to true after this method exits to ensure assertion
212 // at the start of the method fires if it's ever executed again.
213 IsDone = true;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000214}
215
216std::vector<clang::tooling::Replacement>
217ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
218 std::lock_guard<std::mutex> Lock(FixItsMutex);
219 auto DiagToFixItsIter = FixItsMap.find(File);
220 if (DiagToFixItsIter == FixItsMap.end())
221 return {};
222
223 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
224 auto FixItsIter = DiagToFixItsMap.find(D);
225 if (FixItsIter == DiagToFixItsMap.end())
226 return {};
227
228 return FixItsIter->second;
229}
230
Sam McCall4db732a2017-09-30 10:08:52 +0000231void ClangdLSPServer::onDiagnosticsReady(
232 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000233 std::string DiagnosticsJSON;
234
235 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCall4db732a2017-09-30 10:08:52 +0000236 for (auto &DiagWithFixes : Diagnostics.Value) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000237 auto Diag = DiagWithFixes.Diag;
238 DiagnosticsJSON +=
239 R"({"range":)" + Range::unparse(Diag.range) +
240 R"(,"severity":)" + std::to_string(Diag.severity) +
241 R"(,"message":")" + llvm::yaml::escape(Diag.message) +
242 R"("},)";
243
244 // We convert to Replacements to become independent of the SourceManager.
245 auto &FixItsForDiagnostic = LocalFixIts[Diag];
246 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
247 std::back_inserter(FixItsForDiagnostic));
248 }
249
250 // Cache FixIts
251 {
252 // FIXME(ibiryukov): should be deleted when documents are removed
253 std::lock_guard<std::mutex> Lock(FixItsMutex);
254 FixItsMap[File] = LocalFixIts;
255 }
256
257 // Publish diagnostics.
258 if (!DiagnosticsJSON.empty())
259 DiagnosticsJSON.pop_back(); // Drop trailing comma.
260 Out.writeMessage(
261 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
262 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
263 R"(]}})");
264}