Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qlanguageserver.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
7#include <QtLanguageServer/private/qlspnotifysignals_p.h>
8#include <QtJsonRpc/private/qjsonrpcprotocol_p_p.h>
9
11
12Q_LOGGING_CATEGORY(lspServerLog, "qt.languageserver.server")
13
14using namespace QLspSpecification;
15using namespace Qt::StringLiterals;
16
17QLanguageServerPrivate::QLanguageServerPrivate(const QJsonRpcTransport::DataHandler &h)
18 : protocol(h)
19{
20}
21
22/*!
23\internal
24\class QLanguageServer
25\brief Implements a server for the language server protocol
26
27QLanguageServer is a class that uses the QLanguageServerProtocol to
28provide a server implementation.
29It handles the lifecycle management, and can be extended via
30QLanguageServerModule subclasses.
31
32The language server keeps a strictly monotonically increasing runState that can be queried
33from any thread (and is thus mutex gated), the normal run state is DidInitialize.
34
35The language server also keeps track of the task canceled by the client (or implicitly when
36shutting down, and isRequestCanceled can be called from any thread.
37*/
38
39QLanguageServer::QLanguageServer(const QJsonRpcTransport::DataHandler &h, QObject *parent)
40 : QObject(*new QLanguageServerPrivate(h), parent)
41{
42 Q_D(QLanguageServer);
43 registerMethods(*d->protocol.typedRpc());
44 d->notifySignals.registerHandlers(&d->protocol);
45}
46
47QLanguageServerProtocol *QLanguageServer::protocol()
48{
49 Q_D(QLanguageServer);
50 return &d->protocol;
51}
52
54{
55 const Q_D(QLanguageServer);
56 QMutexLocker l(&d->mutex);
57 return d->runStatus;
58}
59
61{
62 Q_D(QLanguageServer);
63 RunStatus rStatus;
64 {
65 QMutexLocker l(&d->mutex);
66 rStatus = d->runStatus;
67 if (rStatus == RunStatus::NotSetup)
68 d->runStatus = RunStatus::SettingUp;
69 }
70 if (rStatus != RunStatus::NotSetup) {
71 emit lifecycleError();
72 return;
73 }
74 emit runStatusChanged(RunStatus::SettingUp);
75
76 registerHandlers(&d->protocol);
77 for (auto module : d->modules)
78 module->registerHandlers(this, &d->protocol);
79
80 {
81 QMutexLocker l(&d->mutex);
82 rStatus = d->runStatus;
83 if (rStatus == RunStatus::SettingUp)
84 d->runStatus = RunStatus::DidSetup;
85 }
86 if (rStatus != RunStatus::SettingUp) {
87 emit lifecycleError();
88 return;
89 }
90 emit runStatusChanged(RunStatus::DidSetup);
91}
92
94{
95 Q_D(QLanguageServer);
96 Q_ASSERT(serverModule);
97 RunStatus rStatus;
98 {
99 QMutexLocker l(&d->mutex);
100 rStatus = d->runStatus;
101 if (rStatus == RunStatus::NotSetup) {
102 if (d->modules.contains(serverModule->name())) {
103 d->modules.insert(serverModule->name(), serverModule);
104 qCWarning(lspServerLog) << "Duplicate add of QLanguageServerModule named"
105 << serverModule->name() << ", overwriting.";
106 } else {
107 d->modules.insert(serverModule->name(), serverModule);
108 }
109 }
110 }
111 if (rStatus != RunStatus::NotSetup) {
112 qCWarning(lspServerLog) << "Called QLanguageServer::addServerModule after setup";
113 emit lifecycleError();
114 return;
115 }
116}
117
119{
120 const Q_D(QLanguageServer);
121 QMutexLocker l(&d->mutex);
122 return d->modules.value(n);
123}
124
126{
127 Q_D(QLanguageServer);
128 return &d->notifySignals;
129}
130
131void QLanguageServer::registerMethods(QJsonRpc::TypedRpc &typedRpc)
132{
133 typedRpc.installMessagePreprocessor(
134 [this](const QJsonDocument &doc, const QJsonParseError &err,
135 const QJsonRpcProtocol::Handler<QJsonRpcProtocol::Response> &responder) {
136 Q_D(QLanguageServer);
137 if (!doc.isObject()) {
138 qCWarning(lspServerLog)
139 << "non object jsonrpc message" << doc << err.errorString();
140 return QJsonRpcProtocol::Processing::Stop;
141 }
142 bool sendErrorResponse = false;
143 RunStatus rState;
144 QJsonValue id = doc.object()[u"id"];
145 {
146 QMutexLocker l(&d->mutex);
147 // the normal case is d->runStatus == RunStatus::DidInitialize
148 if (d->runStatus != RunStatus::DidInitialize) {
149 if (d->runStatus == RunStatus::DidSetup && !doc.isNull()
150 && doc.object()[u"method"].toString()
151 == QString::fromUtf8(
152 QLspSpecification::Requests::InitializeMethod)) {
153 return QJsonRpcProtocol::Processing::Continue;
154 } else if (!doc.isNull()
155 && doc.object()[u"method"].toString()
156 == QString::fromUtf8(
157 QLspSpecification::Notifications::ExitMethod)) {
158 return QJsonRpcProtocol::Processing::Continue;
159 }
160 if (id.isString() || id.isDouble()) {
161 sendErrorResponse = true;
162 rState = d->runStatus;
163 } else {
164 return QJsonRpcProtocol::Processing::Stop;
165 }
166 }
167 }
168 if (!sendErrorResponse) {
169 if (id.isString() || id.isDouble()) {
170 QMutexLocker l(&d->mutex);
171 d->requestsInProgress.insert(id, QRequestInProgress {});
172 }
173 return QJsonRpcProtocol::Processing::Continue;
174 }
175 if (rState == RunStatus::NotSetup || rState == RunStatus::DidSetup)
176 responder(QJsonRpcProtocol::MessageHandler::error(
177 int(QLspSpecification::ErrorCodes::ServerNotInitialized),
178 u"Request on non initialized Language Server (runStatus %1): %2"_s
179 .arg(int(rState))
180 .arg(QString::fromUtf8(doc.toJson()))));
181 else
182 responder(QJsonRpcProtocol::MessageHandler::error(
183 int(QLspSpecification::ErrorCodes::InvalidRequest),
184 u"Method called on stopping Language Server (runStatus %1)"_s.arg(
185 int(rState))));
186 return QJsonRpcProtocol::Processing::Stop;
187 });
188 typedRpc.installOnCloseAction([this](QJsonRpc::TypedResponse::Status,
189 const QJsonRpc::IdType &id, QJsonRpc::TypedRpc &) {
190 Q_D(QLanguageServer);
191 QJsonValue idValue = QTypedJson::toJsonValue(id);
192 bool lastReq;
193 {
194 QMutexLocker l(&d->mutex);
195 d->requestsInProgress.remove(idValue);
196 lastReq = d->runStatus == RunStatus::WaitPending && d->requestsInProgress.size() <= 1;
197 if (lastReq)
198 d->runStatus = RunStatus::Stopping;
199 }
200 if (lastReq)
201 executeShutdown();
202 });
203}
204
205void QLanguageServer::setupCapabilities(const QLspSpecification::InitializeParams &clientInfo,
206 QLspSpecification::InitializeResult &serverInfo)
207{
208 Q_D(QLanguageServer);
209 for (auto module : std::as_const(d->modules))
210 module->setupCapabilities(clientInfo, serverInfo);
211}
212
214{
215 const Q_D(QLanguageServer);
216
218 qCWarning(lspServerLog) << "asked for Language Server clientInfo before initialization";
219 return d->clientInfo;
220}
221
223{
224 const Q_D(QLanguageServer);
226 qCWarning(lspServerLog) << "asked for Language Server serverInfo before initialization";
227 return d->serverInfo;
228}
229
230void QLanguageServer::receiveData(const QByteArray &data, bool isEndOfMessage)
231{
232 if (!data.isEmpty())
233 protocol()->receiveData(data);
234
235 const Q_D(QLanguageServer);
236 // read next message if not shutting down
237 if (isEndOfMessage && d->runStatus != RunStatus::Stopped)
238 emit readNextMessage();
239}
240
241void QLanguageServer::registerHandlers(QLanguageServerProtocol *protocol)
242{
243 QObject::connect(notifySignals(), &QLspNotifySignals::receivedCancelNotification, this,
244 [this](const QLspSpecification::Notifications::CancelParamsType &params) {
245 Q_D(QLanguageServer);
246 QJsonValue id = QTypedJson::toJsonValue(params.id);
247 QMutexLocker l(&d->mutex);
248 if (d->requestsInProgress.contains(id))
249 d->requestsInProgress[id].canceled = true;
250 else
251 qCWarning(lspServerLog)
252 << "Ignoring cancellation of non in progress request" << id;
253 });
254
255 protocol->registerInitializeRequestHandler(
256 [this](const QByteArray &,
257 const QLspSpecification::Requests::InitializeParamsType &params,
258 QLspSpecification::Responses::InitializeResponseType &&response) {
259 qCDebug(lspServerLog) << "init";
260 Q_D(QLanguageServer);
261 RunStatus rStatus;
262 {
263 QMutexLocker l(&d->mutex);
264 rStatus = d->runStatus;
265 if (rStatus == RunStatus::DidSetup)
266 d->runStatus = RunStatus::Initializing;
267 }
268 if (rStatus != RunStatus::DidSetup) {
269 if (rStatus == RunStatus::NotSetup || rStatus == RunStatus::SettingUp)
270 response.sendErrorResponse(
271 int(QLspSpecification::ErrorCodes::InvalidRequest),
272 u"Initialization request received on non setup language server"_s
273 .toUtf8());
274 else
275 response.sendErrorResponse(
276 int(QLspSpecification::ErrorCodes::InvalidRequest),
277 u"Received multiple initialization requests"_s.toUtf8());
278 emit lifecycleError();
279 return;
280 }
281 emit runStatusChanged(RunStatus::Initializing);
282 d->clientInfo = params;
283 setupCapabilities(d->clientInfo, d->serverInfo);
284 {
285 QMutexLocker l(&d->mutex);
286 d->runStatus = RunStatus::DidInitialize;
287 }
288 emit runStatusChanged(RunStatus::DidInitialize);
289 response.sendResponse(d->serverInfo);
290 });
291
292 QObject::connect(notifySignals(), &QLspNotifySignals::receivedInitializedNotification, this,
293 [this](const QLspSpecification::Notifications::InitializedParamsType &) {
294 Q_D(QLanguageServer);
295 {
296 QMutexLocker l(&d->mutex);
297 d->clientInitialized = true;
298 }
299 emit clientInitialized(this);
300 });
301
302 protocol->registerShutdownRequestHandler(
303 [this](const QByteArray &, const QLspSpecification::Requests::ShutdownParamsType &,
304 QLspSpecification::Responses::ShutdownResponseType &&response) {
305 Q_D(QLanguageServer);
306 RunStatus rStatus;
307 bool shouldExecuteShutdown = false;
308 {
309 QMutexLocker l(&d->mutex);
310 rStatus = d->runStatus;
311 if (rStatus == RunStatus::DidInitialize) {
312 d->shutdownResponse = std::move(response);
313 if (d->requestsInProgress.size() <= 1) {
314 d->runStatus = RunStatus::Stopping;
315 shouldExecuteShutdown = true;
316 } else {
317 d->runStatus = RunStatus::WaitPending;
318 }
319 }
320 }
321 if (rStatus != RunStatus::DidInitialize)
322 emit lifecycleError();
323 else if (shouldExecuteShutdown)
324 executeShutdown();
325 });
326
327 QObject::connect(notifySignals(), &QLspNotifySignals::receivedExitNotification, this,
328 [this](const QLspSpecification::Notifications::ExitParamsType &) {
329 Q_D(QLanguageServer);
330 QMutexLocker l(&d->mutex);
331 RunStatus runStatus = d->runStatus;
332 if (runStatus != RunStatus::WaitingForExit) {
333 emit lifecycleError();
334 return;
335 }
336 d->runStatus = RunStatus::Stopped;
337 emit exit();
338 });
339}
340
341void QLanguageServer::executeShutdown()
342{
343 RunStatus rStatus = runStatus();
344 if (rStatus != RunStatus::Stopping) {
345 emit lifecycleError();
346 return;
347 }
348 emit shutdown();
349 QLspSpecification::Responses::ShutdownResponseType shutdownResponse;
350 {
351 Q_D(QLanguageServer);
352 QMutexLocker l(&d->mutex);
353 rStatus = d->runStatus;
354 if (rStatus == RunStatus::Stopping) {
355 shutdownResponse = std::move(d->shutdownResponse);
356 d->runStatus = RunStatus::WaitingForExit;
357 }
358 }
359 if (rStatus != RunStatus::Stopping)
360 emit lifecycleError();
361 else
362 shutdownResponse.sendResponse(nullptr);
363}
364
365bool QLanguageServer::isRequestCanceled(const QJsonRpc::IdType &id) const
366{
367 const Q_D(QLanguageServer);
368 QJsonValue idVal = QTypedJson::toJsonValue(id);
369 QMutexLocker l(&d->mutex);
370 return d->requestsInProgress.value(idVal).canceled || d->runStatus != RunStatus::DidInitialize;
371}
372
374{
375 switch (runStatus()) {
380 return false;
386 break;
387 }
388 return true;
389}
390
391QT_END_NAMESPACE
QLanguageServerPrivate(const QJsonRpcTransport::DataHandler &h)
Implements a server for the language server protocol.
void registerHandlers(QLanguageServerProtocol *protocol)
const QLspSpecification::InitializeParams & clientInfo() const
void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo, QLspSpecification::InitializeResult &serverInfo)
bool isRequestCanceled(const QJsonRpc::IdType &id) const
QLspNotifySignals * notifySignals()
const QLspSpecification::InitializeResult & serverInfo() const
void addServerModule(QLanguageServerModule *serverModule)
QLanguageServerModule * moduleByName(const QString &n) const
bool isInitialized() const
RunStatus runStatus() const