blob: ca95fd8fdefc210f1c3e51b4d6baa4c969167903 [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) {
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +000059 // Do essentially nothing, just say we're ready to exit.
60 ShutdownRequestReceived = true;
61 C.reply("null");
Sam McCall8a5dded2017-10-12 13:29:58 +000062}
Ilya Biryukovafb55542017-05-16 14:40:30 +000063
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +000064void ClangdLSPServer::onExit(Ctx C, ExitParams &Params) { IsDone = true; }
65
Sam McCall8a5dded2017-10-12 13:29:58 +000066void ClangdLSPServer::onDocumentDidOpen(Ctx C,
67 DidOpenTextDocumentParams &Params) {
Krasimir Georgievc2a16a32017-07-06 08:44:54 +000068 if (Params.metadata && !Params.metadata->extraFlags.empty())
Sam McCall4db732a2017-09-30 10:08:52 +000069 CDB.setExtraFlagsForFile(Params.textDocument.uri.file,
70 std::move(Params.metadata->extraFlags));
71 Server.addDocument(Params.textDocument.uri.file, Params.textDocument.text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000072}
73
Sam McCall8a5dded2017-10-12 13:29:58 +000074void ClangdLSPServer::onDocumentDidChange(Ctx C,
75 DidChangeTextDocumentParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +000076 // We only support full syncing right now.
Sam McCall4db732a2017-09-30 10:08:52 +000077 Server.addDocument(Params.textDocument.uri.file,
78 Params.contentChanges[0].text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000079}
80
Sam McCall8a5dded2017-10-12 13:29:58 +000081void ClangdLSPServer::onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) {
Marc-Andre Laperlebf114242017-10-02 18:00:37 +000082 Server.onFileEvent(Params);
83}
84
Sam McCall8a5dded2017-10-12 13:29:58 +000085void ClangdLSPServer::onDocumentDidClose(Ctx C,
86 DidCloseTextDocumentParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +000087 Server.removeDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +000088}
89
Sam McCall4db732a2017-09-30 10:08:52 +000090void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCall8a5dded2017-10-12 13:29:58 +000091 Ctx C, DocumentOnTypeFormattingParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +000092 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +000093 std::string Code = Server.getDocument(File);
94 std::string Edits =
95 replacementsToEdits(Code, Server.formatOnType(File, Params.position));
Sam McCall8a5dded2017-10-12 13:29:58 +000096 C.reply("[" + Edits + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +000097}
98
Sam McCall4db732a2017-09-30 10:08:52 +000099void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCall8a5dded2017-10-12 13:29:58 +0000100 Ctx C, DocumentRangeFormattingParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000101 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +0000102 std::string Code = Server.getDocument(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000103 std::string Edits =
Sam McCall4db732a2017-09-30 10:08:52 +0000104 replacementsToEdits(Code, Server.formatRange(File, Params.range));
Sam McCall8a5dded2017-10-12 13:29:58 +0000105 C.reply("[" + Edits + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000106}
107
Sam McCall8a5dded2017-10-12 13:29:58 +0000108void ClangdLSPServer::onDocumentFormatting(Ctx C,
109 DocumentFormattingParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000110 auto File = Params.textDocument.uri.file;
111 std::string Code = Server.getDocument(File);
112 std::string Edits = replacementsToEdits(Code, Server.formatFile(File));
Sam McCall8a5dded2017-10-12 13:29:58 +0000113 C.reply("[" + Edits + "]");
Sam McCall4db732a2017-09-30 10:08:52 +0000114}
115
Sam McCall8a5dded2017-10-12 13:29:58 +0000116void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000117 // We provide a code action for each diagnostic at the requested location
118 // which has FixIts available.
Sam McCall4db732a2017-09-30 10:08:52 +0000119 std::string Code = Server.getDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000120 std::string Commands;
121 for (Diagnostic &D : Params.context.diagnostics) {
122 std::vector<clang::tooling::Replacement> Fixes =
Sam McCall4db732a2017-09-30 10:08:52 +0000123 getFixIts(Params.textDocument.uri.file, D);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000124 std::string Edits = replacementsToEdits(Code, Fixes);
125
126 if (!Edits.empty())
127 Commands +=
128 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
129 R"('", "command": "clangd.applyFix", "arguments": [")" +
130 llvm::yaml::escape(Params.textDocument.uri.uri) +
131 R"(", [)" + Edits +
132 R"(]]},)";
133 }
134 if (!Commands.empty())
135 Commands.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000136 C.reply("[" + Commands + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000137}
138
Sam McCall8a5dded2017-10-12 13:29:58 +0000139void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000140 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000141 .codeComplete(Params.textDocument.uri.file,
142 Position{Params.position.line,
143 Params.position.character})
Ilya Biryukovdcd21692017-10-05 17:04:13 +0000144 .get() // FIXME(ibiryukov): This could be made async if we
145 // had an API that would allow to attach callbacks to
146 // futures returned by ClangdServer.
Ilya Biryukov574b7532017-08-02 09:08:39 +0000147 .Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000148
149 std::string Completions;
150 for (const auto &Item : Items) {
151 Completions += CompletionItem::unparse(Item);
152 Completions += ",";
153 }
154 if (!Completions.empty())
155 Completions.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000156 C.reply("[" + Completions + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000157}
158
Sam McCall8a5dded2017-10-12 13:29:58 +0000159void ClangdLSPServer::onSignatureHelp(Ctx C,
160 TextDocumentPositionParams &Params) {
161 C.reply(SignatureHelp::unparse(
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000162 Server
163 .signatureHelp(
164 Params.textDocument.uri.file,
165 Position{Params.position.line, Params.position.character})
Sam McCall8a5dded2017-10-12 13:29:58 +0000166 .Value));
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000167}
168
Sam McCall8a5dded2017-10-12 13:29:58 +0000169void ClangdLSPServer::onGoToDefinition(Ctx C,
170 TextDocumentPositionParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000171 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000172 .findDefinitions(Params.textDocument.uri.file,
173 Position{Params.position.line,
174 Params.position.character})
175 .Value;
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000176
177 std::string Locations;
178 for (const auto &Item : Items) {
179 Locations += Location::unparse(Item);
180 Locations += ",";
181 }
182 if (!Locations.empty())
183 Locations.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000184 C.reply("[" + Locations + "]");
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000185}
186
Sam McCall8a5dded2017-10-12 13:29:58 +0000187void ClangdLSPServer::onSwitchSourceHeader(Ctx C,
188 TextDocumentIdentifier &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000189 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000190 std::string ResultUri;
Sam McCall8a5dded2017-10-12 13:29:58 +0000191 C.reply(Result ? URI::unparse(URI::fromFile(*Result)) : R"("")");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000192}
193
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000194ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000195 bool SnippetCompletions,
Ilya Biryukov0c1ca6b2017-10-02 15:13:20 +0000196 llvm::Optional<StringRef> ResourceDir,
197 llvm::Optional<Path> CompileCommandsDir)
198 : Out(Out), CDB(/*Logger=*/Out, std::move(CompileCommandsDir)),
Sam McCall4db732a2017-09-30 10:08:52 +0000199 Server(CDB, /*DiagConsumer=*/*this, FSProvider, AsyncThreadsCount,
Ilya Biryukovb080cb12017-10-23 14:46:48 +0000200 clangd::CodeCompleteOptions(
201 /*EnableSnippetsAndCodePatterns=*/SnippetCompletions),
202 /*Logger=*/Out, ResourceDir) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000203
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000204bool ClangdLSPServer::run(std::istream &In) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000205 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.
Sam McCall8a5dded2017-10-12 13:29:58 +0000208 JSONRPCDispatcher Dispatcher(
209 [](RequestContext Ctx, llvm::yaml::MappingNode *Params) {
210 Ctx.replyError(-32601, "method not found");
211 });
Sam McCall4db732a2017-09-30 10:08:52 +0000212 registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000213
Ilya Biryukovafb55542017-05-16 14:40:30 +0000214 // Run the Language Server loop.
215 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
216
217 // Make sure IsDone is set to true after this method exits to ensure assertion
218 // at the start of the method fires if it's ever executed again.
219 IsDone = true;
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000220
221 return ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000222}
223
224std::vector<clang::tooling::Replacement>
225ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
226 std::lock_guard<std::mutex> Lock(FixItsMutex);
227 auto DiagToFixItsIter = FixItsMap.find(File);
228 if (DiagToFixItsIter == FixItsMap.end())
229 return {};
230
231 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
232 auto FixItsIter = DiagToFixItsMap.find(D);
233 if (FixItsIter == DiagToFixItsMap.end())
234 return {};
235
236 return FixItsIter->second;
237}
238
Sam McCall4db732a2017-09-30 10:08:52 +0000239void ClangdLSPServer::onDiagnosticsReady(
240 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000241 std::string DiagnosticsJSON;
242
243 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCall4db732a2017-09-30 10:08:52 +0000244 for (auto &DiagWithFixes : Diagnostics.Value) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000245 auto Diag = DiagWithFixes.Diag;
246 DiagnosticsJSON +=
247 R"({"range":)" + Range::unparse(Diag.range) +
248 R"(,"severity":)" + std::to_string(Diag.severity) +
249 R"(,"message":")" + llvm::yaml::escape(Diag.message) +
250 R"("},)";
251
252 // We convert to Replacements to become independent of the SourceManager.
253 auto &FixItsForDiagnostic = LocalFixIts[Diag];
254 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
255 std::back_inserter(FixItsForDiagnostic));
256 }
257
258 // Cache FixIts
259 {
260 // FIXME(ibiryukov): should be deleted when documents are removed
261 std::lock_guard<std::mutex> Lock(FixItsMutex);
262 FixItsMap[File] = LocalFixIts;
263 }
264
265 // Publish diagnostics.
266 if (!DiagnosticsJSON.empty())
267 DiagnosticsJSON.pop_back(); // Drop trailing comma.
268 Out.writeMessage(
269 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
270 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
271 R"(]}})");
272}