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
11Q_LOGGING_CATEGORY(lspServerLog, "qt.languageserver.server")
12
13using namespace QLspSpecification;
14using namespace Qt::StringLiterals;
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
229void QLanguageServer::receiveData(const QByteArray &data, bool isEndOfMessage)
230{
231 if (!data.isEmpty())
232 protocol()->receiveData(data);
233
234 const Q_D(QLanguageServer);
235 // read next message if not shutting down
236 if (isEndOfMessage && d->runStatus != RunStatus::Stopped)
238}
239
240void QLanguageServer::registerHandlers(QLanguageServerProtocol *protocol)
241{
242 QObject::connect(notifySignals(), &QLspNotifySignals::receivedCancelNotification, this,
243 [this](const QLspSpecification::Notifications::CancelParamsType &params) {
244 Q_D(QLanguageServer);
245 QJsonValue id = QTypedJson::toJsonValue(params.id);
246 QMutexLocker l(&d->mutex);
247 if (d->requestsInProgress.contains(id))
248 d->requestsInProgress[id].canceled = true;
249 else
250 qCWarning(lspServerLog)
251 << "Ignoring cancellation of non in progress request" << id;
252 });
253
254 protocol->registerInitializeRequestHandler(
255 [this](const QByteArray &,
256 const QLspSpecification::Requests::InitializeParamsType &params,
257 QLspSpecification::Responses::InitializeResponseType &&response) {
258 qCDebug(lspServerLog) << "init";
259 Q_D(QLanguageServer);
260 RunStatus rStatus;
261 {
262 QMutexLocker l(&d->mutex);
263 rStatus = d->runStatus;
264 if (rStatus == RunStatus::DidSetup)
265 d->runStatus = RunStatus::Initializing;
266 }
267 if (rStatus != RunStatus::DidSetup) {
268 if (rStatus == RunStatus::NotSetup || rStatus == RunStatus::SettingUp)
269 response.sendErrorResponse(
270 int(QLspSpecification::ErrorCodes::InvalidRequest),
271 u"Initialization request received on non setup language server"_s
272 .toUtf8());
273 else
274 response.sendErrorResponse(
275 int(QLspSpecification::ErrorCodes::InvalidRequest),
276 u"Received multiple initialization requests"_s.toUtf8());
278 return;
279 }
281 d->clientInfo = params;
282 setupCapabilities(d->clientInfo, d->serverInfo);
283 {
284 QMutexLocker l(&d->mutex);
285 d->runStatus = RunStatus::DidInitialize;
286 }
288 response.sendResponse(d->serverInfo);
289 });
290
291 QObject::connect(notifySignals(), &QLspNotifySignals::receivedInitializedNotification, this,
292 [this](const QLspSpecification::Notifications::InitializedParamsType &) {
293 Q_D(QLanguageServer);
294 {
295 QMutexLocker l(&d->mutex);
296 d->clientInitialized = true;
297 }
299 });
300
301 protocol->registerShutdownRequestHandler(
302 [this](const QByteArray &, const QLspSpecification::Requests::ShutdownParamsType &,
303 QLspSpecification::Responses::ShutdownResponseType &&response) {
304 Q_D(QLanguageServer);
305 RunStatus rStatus;
306 bool shouldExecuteShutdown = false;
307 {
308 QMutexLocker l(&d->mutex);
309 rStatus = d->runStatus;
310 if (rStatus == RunStatus::DidInitialize) {
311 d->shutdownResponse = std::move(response);
312 if (d->requestsInProgress.size() <= 1) {
313 d->runStatus = RunStatus::Stopping;
314 shouldExecuteShutdown = true;
315 } else {
316 d->runStatus = RunStatus::WaitPending;
317 }
318 }
319 }
320 if (rStatus != RunStatus::DidInitialize)
322 else if (shouldExecuteShutdown)
323 executeShutdown();
324 });
325
326 QObject::connect(notifySignals(), &QLspNotifySignals::receivedExitNotification, this,
327 [this](const QLspSpecification::Notifications::ExitParamsType &) {
330 else
331 emit exit();
332 });
333}
334
335void QLanguageServer::executeShutdown()
336{
337 RunStatus rStatus = runStatus();
338 if (rStatus != RunStatus::Stopping) {
340 return;
341 }
342 emit shutdown();
343 QLspSpecification::Responses::ShutdownResponseType shutdownResponse;
344 {
345 Q_D(QLanguageServer);
346 QMutexLocker l(&d->mutex);
347 rStatus = d->runStatus;
348 if (rStatus == RunStatus::Stopping) {
349 shutdownResponse = std::move(d->shutdownResponse);
350 d->runStatus = RunStatus::Stopped;
351 }
352 }
353 if (rStatus != RunStatus::Stopping)
355 else
356 shutdownResponse.sendResponse(nullptr);
357}
358
359bool QLanguageServer::isRequestCanceled(const QJsonRpc::IdType &id) const
360{
361 const Q_D(QLanguageServer);
362 QJsonValue idVal = QTypedJson::toJsonValue(id);
363 QMutexLocker l(&d->mutex);
364 return d->requestsInProgress.value(idVal).canceled || d->runStatus != RunStatus::DidInitialize;
365}
366
368{
369 switch (runStatus()) {
374 return false;
379 break;
380 }
381 return true;
382}
383
\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 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 receiveData(const QByteArray &d, bool isEndOfMessage)
void runStatusChanged(RunStatus)
const QLspSpecification::InitializeResult & serverInfo() const
void addServerModule(QLanguageServerModule *serverModule)
QLanguageServerModule * moduleByName(const QString &n) const
void readNextMessage()
\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:6028
Combined button and popup list for selecting options.
Definition qcompare.h:63
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
GLenum GLuint id
[7]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
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