blob: 3b68f2a6dc158619549d71bbae9ebd1a01bd2e4f [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) {
Benjamin Kramerb560a9a2017-10-26 10:36:20 +000076 if (Params.contentChanges.size() != 1)
77 return C.replyError(-32602, "can only apply one change at a time");
Ilya Biryukovafb55542017-05-16 14:40:30 +000078 // We only support full syncing right now.
Sam McCall4db732a2017-09-30 10:08:52 +000079 Server.addDocument(Params.textDocument.uri.file,
80 Params.contentChanges[0].text);
Ilya Biryukovafb55542017-05-16 14:40:30 +000081}
82
Sam McCall8a5dded2017-10-12 13:29:58 +000083void ClangdLSPServer::onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) {
Marc-Andre Laperlebf114242017-10-02 18:00:37 +000084 Server.onFileEvent(Params);
85}
86
Sam McCall8a5dded2017-10-12 13:29:58 +000087void ClangdLSPServer::onDocumentDidClose(Ctx C,
88 DidCloseTextDocumentParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +000089 Server.removeDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +000090}
91
Sam McCall4db732a2017-09-30 10:08:52 +000092void ClangdLSPServer::onDocumentOnTypeFormatting(
Sam McCall8a5dded2017-10-12 13:29:58 +000093 Ctx C, DocumentOnTypeFormattingParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +000094 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +000095 std::string Code = Server.getDocument(File);
96 std::string Edits =
97 replacementsToEdits(Code, Server.formatOnType(File, Params.position));
Sam McCall8a5dded2017-10-12 13:29:58 +000098 C.reply("[" + Edits + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +000099}
100
Sam McCall4db732a2017-09-30 10:08:52 +0000101void ClangdLSPServer::onDocumentRangeFormatting(
Sam McCall8a5dded2017-10-12 13:29:58 +0000102 Ctx C, DocumentRangeFormattingParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000103 auto File = Params.textDocument.uri.file;
Sam McCall4db732a2017-09-30 10:08:52 +0000104 std::string Code = Server.getDocument(File);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000105 std::string Edits =
Sam McCall4db732a2017-09-30 10:08:52 +0000106 replacementsToEdits(Code, Server.formatRange(File, Params.range));
Sam McCall8a5dded2017-10-12 13:29:58 +0000107 C.reply("[" + Edits + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000108}
109
Sam McCall8a5dded2017-10-12 13:29:58 +0000110void ClangdLSPServer::onDocumentFormatting(Ctx C,
111 DocumentFormattingParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000112 auto File = Params.textDocument.uri.file;
113 std::string Code = Server.getDocument(File);
114 std::string Edits = replacementsToEdits(Code, Server.formatFile(File));
Sam McCall8a5dded2017-10-12 13:29:58 +0000115 C.reply("[" + Edits + "]");
Sam McCall4db732a2017-09-30 10:08:52 +0000116}
117
Sam McCall8a5dded2017-10-12 13:29:58 +0000118void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000119 // We provide a code action for each diagnostic at the requested location
120 // which has FixIts available.
Sam McCall4db732a2017-09-30 10:08:52 +0000121 std::string Code = Server.getDocument(Params.textDocument.uri.file);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000122 std::string Commands;
123 for (Diagnostic &D : Params.context.diagnostics) {
124 std::vector<clang::tooling::Replacement> Fixes =
Sam McCall4db732a2017-09-30 10:08:52 +0000125 getFixIts(Params.textDocument.uri.file, D);
Ilya Biryukovafb55542017-05-16 14:40:30 +0000126 std::string Edits = replacementsToEdits(Code, Fixes);
127
128 if (!Edits.empty())
129 Commands +=
130 R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
131 R"('", "command": "clangd.applyFix", "arguments": [")" +
132 llvm::yaml::escape(Params.textDocument.uri.uri) +
133 R"(", [)" + Edits +
134 R"(]]},)";
135 }
136 if (!Commands.empty())
137 Commands.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000138 C.reply("[" + Commands + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000139}
140
Sam McCall8a5dded2017-10-12 13:29:58 +0000141void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000142 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000143 .codeComplete(Params.textDocument.uri.file,
144 Position{Params.position.line,
145 Params.position.character})
Ilya Biryukovdcd21692017-10-05 17:04:13 +0000146 .get() // FIXME(ibiryukov): This could be made async if we
147 // had an API that would allow to attach callbacks to
148 // futures returned by ClangdServer.
Ilya Biryukov574b7532017-08-02 09:08:39 +0000149 .Value;
Ilya Biryukovafb55542017-05-16 14:40:30 +0000150
151 std::string Completions;
152 for (const auto &Item : Items) {
153 Completions += CompletionItem::unparse(Item);
154 Completions += ",";
155 }
156 if (!Completions.empty())
157 Completions.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000158 C.reply("[" + Completions + "]");
Ilya Biryukovafb55542017-05-16 14:40:30 +0000159}
160
Sam McCall8a5dded2017-10-12 13:29:58 +0000161void ClangdLSPServer::onSignatureHelp(Ctx C,
162 TextDocumentPositionParams &Params) {
163 C.reply(SignatureHelp::unparse(
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000164 Server
165 .signatureHelp(
166 Params.textDocument.uri.file,
167 Position{Params.position.line, Params.position.character})
Sam McCall8a5dded2017-10-12 13:29:58 +0000168 .Value));
Ilya Biryukovd9bdfe02017-10-06 11:54:17 +0000169}
170
Sam McCall8a5dded2017-10-12 13:29:58 +0000171void ClangdLSPServer::onGoToDefinition(Ctx C,
172 TextDocumentPositionParams &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000173 auto Items = Server
Ilya Biryukov574b7532017-08-02 09:08:39 +0000174 .findDefinitions(Params.textDocument.uri.file,
175 Position{Params.position.line,
176 Params.position.character})
177 .Value;
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000178
179 std::string Locations;
180 for (const auto &Item : Items) {
181 Locations += Location::unparse(Item);
182 Locations += ",";
183 }
184 if (!Locations.empty())
185 Locations.pop_back();
Sam McCall8a5dded2017-10-12 13:29:58 +0000186 C.reply("[" + Locations + "]");
Marc-Andre Laperle2cbf0372017-06-28 16:12:10 +0000187}
188
Sam McCall8a5dded2017-10-12 13:29:58 +0000189void ClangdLSPServer::onSwitchSourceHeader(Ctx C,
190 TextDocumentIdentifier &Params) {
Sam McCall4db732a2017-09-30 10:08:52 +0000191 llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000192 std::string ResultUri;
Sam McCall8a5dded2017-10-12 13:29:58 +0000193 C.reply(Result ? URI::unparse(URI::fromFile(*Result)) : R"("")");
Marc-Andre Laperle6571b3e2017-09-28 03:14:40 +0000194}
195
Ilya Biryukovdb8b2d72017-08-14 08:45:47 +0000196ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
Ilya Biryukovb33c1572017-09-12 13:57:14 +0000197 bool SnippetCompletions,
Ilya Biryukov0c1ca6b2017-10-02 15:13:20 +0000198 llvm::Optional<StringRef> ResourceDir,
199 llvm::Optional<Path> CompileCommandsDir)
200 : Out(Out), CDB(/*Logger=*/Out, std::move(CompileCommandsDir)),
Sam McCall4db732a2017-09-30 10:08:52 +0000201 Server(CDB, /*DiagConsumer=*/*this, FSProvider, AsyncThreadsCount,
Ilya Biryukovb080cb12017-10-23 14:46:48 +0000202 clangd::CodeCompleteOptions(
203 /*EnableSnippetsAndCodePatterns=*/SnippetCompletions),
204 /*Logger=*/Out, ResourceDir) {}
Ilya Biryukov38d79772017-05-16 09:38:59 +0000205
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000206bool ClangdLSPServer::run(std::istream &In) {
Ilya Biryukovafb55542017-05-16 14:40:30 +0000207 assert(!IsDone && "Run was called before");
Ilya Biryukov38d79772017-05-16 09:38:59 +0000208
Ilya Biryukovafb55542017-05-16 14:40:30 +0000209 // Set up JSONRPCDispatcher.
Sam McCall8a5dded2017-10-12 13:29:58 +0000210 JSONRPCDispatcher Dispatcher(
211 [](RequestContext Ctx, llvm::yaml::MappingNode *Params) {
212 Ctx.replyError(-32601, "method not found");
213 });
Sam McCall4db732a2017-09-30 10:08:52 +0000214 registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this);
Ilya Biryukov38d79772017-05-16 09:38:59 +0000215
Ilya Biryukovafb55542017-05-16 14:40:30 +0000216 // Run the Language Server loop.
217 runLanguageServerLoop(In, Out, Dispatcher, IsDone);
218
219 // Make sure IsDone is set to true after this method exits to ensure assertion
220 // at the start of the method fires if it's ever executed again.
221 IsDone = true;
Ilya Biryukov0d9b8a32017-10-25 08:45:41 +0000222
223 return ShutdownRequestReceived;
Ilya Biryukov38d79772017-05-16 09:38:59 +0000224}
225
226std::vector<clang::tooling::Replacement>
227ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
228 std::lock_guard<std::mutex> Lock(FixItsMutex);
229 auto DiagToFixItsIter = FixItsMap.find(File);
230 if (DiagToFixItsIter == FixItsMap.end())
231 return {};
232
233 const auto &DiagToFixItsMap = DiagToFixItsIter->second;
234 auto FixItsIter = DiagToFixItsMap.find(D);
235 if (FixItsIter == DiagToFixItsMap.end())
236 return {};
237
238 return FixItsIter->second;
239}
240
Sam McCall4db732a2017-09-30 10:08:52 +0000241void ClangdLSPServer::onDiagnosticsReady(
242 PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000243 std::string DiagnosticsJSON;
244
245 DiagnosticToReplacementMap LocalFixIts; // Temporary storage
Sam McCall4db732a2017-09-30 10:08:52 +0000246 for (auto &DiagWithFixes : Diagnostics.Value) {
Ilya Biryukov38d79772017-05-16 09:38:59 +0000247 auto Diag = DiagWithFixes.Diag;
248 DiagnosticsJSON +=
249 R"({"range":)" + Range::unparse(Diag.range) +
250 R"(,"severity":)" + std::to_string(Diag.severity) +
251 R"(,"message":")" + llvm::yaml::escape(Diag.message) +
252 R"("},)";
253
254 // We convert to Replacements to become independent of the SourceManager.
255 auto &FixItsForDiagnostic = LocalFixIts[Diag];
256 std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
257 std::back_inserter(FixItsForDiagnostic));
258 }
259
260 // Cache FixIts
261 {
262 // FIXME(ibiryukov): should be deleted when documents are removed
263 std::lock_guard<std::mutex> Lock(FixItsMutex);
264 FixItsMap[File] = LocalFixIts;
265 }
266
267 // Publish diagnostics.
268 if (!DiagnosticsJSON.empty())
269 DiagnosticsJSON.pop_back(); // Drop trailing comma.
270 Out.writeMessage(
271 R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
272 URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
273 R"(]}})");
274}