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

source code of qtdeclarative/src/qmlls/qlanguageserver.cpp