Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
5
6#include <QtLanguageServer/private/qlspnotifysignals_p.h>
7#include <QtJsonRpc/private/qjsonrpcprotocol_p_p.h>
8
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
38QLanguageServer::QLanguageServer(const QJsonRpcTransport::DataHandler &h, QObject *parent)
39 : QObject(*new QLanguageServerPrivate(h), parent)
40{
41 Q_D(QLanguageServer);
42 registerMethods(*d->protocol.typedRpc());
43 d->notifySignals.registerHandlers(&d->protocol);
44}
45
46QLanguageServerProtocol *QLanguageServer::protocol()
47{
48 Q_D(QLanguageServer);
49 return &d->protocol;
50}
51
53{
54 const Q_D(QLanguageServer);
55 QMutexLocker l(&d->mutex);
56 return d->runStatus;
57}
58
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) {
71 return;
72 }
74
75 registerHandlers(&d->protocol);
76 for (auto module : d->modules)
77 module->registerHandlers(this, &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) {
87 return;
88 }
90}
91
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(serverModule->name())) {
102 d->modules.insert(serverModule->name(), serverModule);
103 qCWarning(lspServerLog) << "Duplicate add of QLanguageServerModule named"
104 << serverModule->name() << ", overwriting.";
105 } else {
106 d->modules.insert(serverModule->name(), serverModule);
107 }
108 }
109 }
110 if (rStatus != RunStatus::NotSetup) {
111 qCWarning(lspServerLog) << "Called QLanguageServer::addServerModule after setup";
113 return;
114 }
115}
116
118{
119 const Q_D(QLanguageServer);
120 QMutexLocker l(&d->mutex);
121 return d->modules.value(n);
122}
123
125{
126 Q_D(QLanguageServer);
127 return &d->notifySignals;
128}
129
130void QLanguageServer::registerMethods(QJsonRpc::TypedRpc &typedRpc)
131{
132 typedRpc.installMessagePreprocessor(
133 [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()
151 QLspSpecification::Requests::InitializeMethod)) {
152 return QJsonRpcProtocol::Processing::Continue;
153 } else if (!doc.isNull()
154 && doc.object()[u"method"].toString()
156 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(id, QRequestInProgress {});
171 }
172 return QJsonRpcProtocol::Processing::Continue;
173 }
174 if (rState == RunStatus::NotSetup || rState == RunStatus::DidSetup)
175 responder(QJsonRpcProtocol::MessageHandler::error(
176 int(QLspSpecification::ErrorCodes::ServerNotInitialized),
177 u"Request on non initialized Language Server (runStatus %1): %2"_s
178 .arg(int(rState))
179 .arg(QString::fromUtf8(doc.toJson()))));
180 else
181 responder(QJsonRpcProtocol::MessageHandler::error(
182 int(QLspSpecification::ErrorCodes::InvalidRequest),
183 u"Method called on stopping Language Server (runStatus %1)"_s.arg(
184 int(rState))));
185 return QJsonRpcProtocol::Processing::Stop;
186 });
187 typedRpc.installOnCloseAction([this](QJsonRpc::TypedResponse::Status,
188 const QJsonRpc::IdType &id, QJsonRpc::TypedRpc &) {
189 Q_D(QLanguageServer);
190 QJsonValue idValue = QTypedJson::toJsonValue(id);
191 bool lastReq;
192 {
193 QMutexLocker l(&d->mutex);
194 d->requestsInProgress.remove(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(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
230{
231 protocol()->receiveData(d);
232}
233
234void QLanguageServer::registerHandlers(QLanguageServerProtocol *protocol)
235{
236 QObject::connect(notifySignals(), &QLspNotifySignals::receivedCancelNotification, this,
237 [this](const QLspSpecification::Notifications::CancelParamsType &params) {
238 Q_D(QLanguageServer);
239 QJsonValue id = QTypedJson::toJsonValue(params.id);
240 QMutexLocker l(&d->mutex);
241 if (d->requestsInProgress.contains(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 [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 int(QLspSpecification::ErrorCodes::InvalidRequest),
265 u"Initialization request received on non setup language server"_s
266 .toUtf8());
267 else
268 response.sendErrorResponse(
269 int(QLspSpecification::ErrorCodes::InvalidRequest),
270 u"Received multiple initialization requests"_s.toUtf8());
272 return;
273 }
275 d->clientInfo = params;
276 setupCapabilities(d->clientInfo, d->serverInfo);
277 {
278 QMutexLocker l(&d->mutex);
279 d->runStatus = RunStatus::DidInitialize;
280 }
282 response.sendResponse(d->serverInfo);
283 });
284
285 QObject::connect(notifySignals(), &QLspNotifySignals::receivedInitializedNotification, this,
286 [this](const QLspSpecification::Notifications::InitializedParamsType &) {
287 Q_D(QLanguageServer);
288 {
289 QMutexLocker l(&d->mutex);
290 d->clientInitialized = true;
291 }
293 });
294
295 protocol->registerShutdownRequestHandler(
296 [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)
316 else if (shouldExecuteShutdown)
317 executeShutdown();
318 });
319
320 QObject::connect(notifySignals(), &QLspNotifySignals::receivedExitNotification, this,
321 [this](const QLspSpecification::Notifications::ExitParamsType &) {
324 else
325 emit exit();
326 });
327}
328
329void QLanguageServer::executeShutdown()
330{
331 RunStatus rStatus = runStatus();
332 if (rStatus != RunStatus::Stopping) {
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)
349 else
350 shutdownResponse.sendResponse(nullptr);
351}
352
353bool QLanguageServer::isRequestCanceled(const QJsonRpc::IdType &id) const
354{
355 const Q_D(QLanguageServer);
356 QJsonValue idVal = QTypedJson::toJsonValue(id);
357 QMutexLocker l(&d->mutex);
358 return d->requestsInProgress.value(idVal).canceled || d->runStatus != RunStatus::DidInitialize;
359}
360
362{
363 switch (runStatus()) {
368 return false;
373 break;
374 }
375 return true;
376}
377
\inmodule QtCore
Definition qbytearray.h:57
\inmodule QtCore\reentrant
bool isNull() const
returns true if this document is null.
QByteArray toJson(JsonFormat format=Indented) const
QJsonObject object() const
Returns the QJsonObject contained in the document.
bool isObject() const
Returns true if the document contains an object.
\inmodule QtCore\reentrant
Definition qjsonvalue.h:25
Implements a server for the language server protocol.
void registerHandlers(QLanguageServerProtocol *protocol)
void receiveData(const QByteArray &d)
void clientInitialized(QLanguageServer *server)
QLanguageServerProtocol * protocol()
const QLspSpecification::InitializeParams & clientInfo() const
void lifecycleError()
QLanguageServer(const QJsonRpcTransport::DataHandler &h, QObject *parent=nullptr)
void setupCapabilities(const QLspSpecification::InitializeParams &clientInfo, QLspSpecification::InitializeResult &serverInfo)
bool isRequestCanceled(const QJsonRpc::IdType &id) const
QLspNotifySignals * notifySignals()
void runStatusChanged(RunStatus)
const QLspSpecification::InitializeResult & serverInfo() const
void addServerModule(QLanguageServerModule *serverModule)
QLanguageServerModule * moduleByName(const QString &n) const
\inmodule QtCore
Definition qmutex.h:313
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromUtf8(QByteArrayView utf8)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:6018
Combined button and popup list for selecting options.
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
GLenum GLuint id
[7]
GLfloat n
GLfloat GLfloat GLfloat GLfloat h
void ** params
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
SSL_CTX int void * arg
#define emit
\inmodule QtCore\reentrant
QString errorString() const
\variable QJsonParseError::error