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 Initialized.
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 registerHandlers(&d->protocol);
46}
47
48QLanguageServerProtocol *QLanguageServer::protocol()
49{
50 Q_D(QLanguageServer);
51 return &d->protocol;
52}
53
54QLanguageServer::RunStatus QLanguageServer::runStatus() const
55{
56 const Q_D(QLanguageServer);
57 QMutexLocker l(&d->mutex);
58 return d->runStatus;
59}
60
61void QLanguageServer::registerModule(QLanguageServerModule *serverModule)
62{
63 Q_D(QLanguageServer);
64 Q_ASSERT(serverModule);
65 serverModule->registerHandlers(this, &d->protocol);
66 serverModule->setupCapabilities(d->serverInfo.capabilities);
67}
68
69QLspNotifySignals *QLanguageServer::notifySignals()
70{
71 Q_D(QLanguageServer);
72 return &d->notifySignals;
73}
74
75void QLanguageServer::registerMethods(QJsonRpc::TypedRpc &typedRpc)
76{
77 typedRpc.installMessagePreprocessor(
78 [this](const QJsonDocument &doc, const QJsonParseError &err,
79 const QJsonRpcProtocol::Handler<QJsonRpcProtocol::Response> &responder) {
80 Q_D(QLanguageServer);
81 if (!doc.isObject()) {
82 qCWarning(lspServerLog)
83 << "non object jsonrpc message" << doc << err.errorString();
84 return QJsonRpcProtocol::Processing::Stop;
85 }
86 bool sendErrorResponse = false;
87 RunStatus rState = RunStatus::NotInitialized;
88 QJsonValue id = doc.object()[u"id"];
89 {
90 QMutexLocker l(&d->mutex);
91 // the normal case is d->runStatus == RunStatus::Initialized
92 if (d->runStatus != RunStatus::Initialized) {
93 if (d->runStatus == RunStatus::NotInitialized && !doc.isNull()
94 && doc.object()[u"method"].toString()
95 == QString::fromUtf8(
96 QLspSpecification::Requests::InitializeMethod)) {
97 return QJsonRpcProtocol::Processing::Continue;
98 } else if (!doc.isNull()
99 && doc.object()[u"method"].toString()
100 == QString::fromUtf8(
101 QLspSpecification::Notifications::ExitMethod)) {
102 return QJsonRpcProtocol::Processing::Continue;
103 }
104 if (id.isString() || id.isDouble()) {
105 sendErrorResponse = true;
106 rState = d->runStatus;
107 } else {
108 return QJsonRpcProtocol::Processing::Stop;
109 }
110 }
111 }
112 if (!sendErrorResponse) {
113 if (id.isString() || id.isDouble()) {
114 QMutexLocker l(&d->mutex);
115 d->requestsInProgress.insert(id, QRequestInProgress {});
116 }
117 return QJsonRpcProtocol::Processing::Continue;
118 }
119 if (rState == RunStatus::NotInitialized)
120 responder(QJsonRpcProtocol::MessageHandler::error(
121 int(QLspSpecification::ErrorCodes::ServerNotInitialized),
122 u"Request on non initialized Language Server (runStatus %1): %2"_s
123 .arg(int(rState))
124 .arg(QString::fromUtf8(doc.toJson()))));
125 else
126 responder(QJsonRpcProtocol::MessageHandler::error(
127 int(QLspSpecification::ErrorCodes::InvalidRequest),
128 u"Method called on stopping Language Server (runStatus %1)"_s.arg(
129 int(rState))));
130 return QJsonRpcProtocol::Processing::Stop;
131 });
132 typedRpc.installOnCloseAction([this](QJsonRpc::TypedResponse::Status,
133 const QJsonRpc::IdType &id, QJsonRpc::TypedRpc &) {
134 Q_D(QLanguageServer);
135 QJsonValue idValue = QTypedJson::toJsonValue(id);
136 bool lastReq;
137 {
138 QMutexLocker l(&d->mutex);
139 d->requestsInProgress.remove(idValue);
140 lastReq = d->runStatus == RunStatus::WaitPending && d->requestsInProgress.size() <= 1;
141 if (lastReq)
142 d->runStatus = RunStatus::Stopping;
143 }
144 if (lastReq)
145 executeShutdown();
146 });
147}
148
149const QLspSpecification::InitializeParams &QLanguageServer::clientInfo() const
150{
151 const Q_D(QLanguageServer);
152
153 if (int(runStatus()) < int(RunStatus::Initialized))
154 qCWarning(lspServerLog) << "asked for Language Server clientInfo before initialization";
155 return d->clientInfo;
156}
157
158void QLanguageServer::receiveData(const QByteArray &data, bool isEndOfMessage)
159{
160 if (!data.isEmpty())
161 protocol()->receiveData(data);
162
163 const Q_D(QLanguageServer);
164 // read next message if not shutting down
165 if (isEndOfMessage && d->runStatus != RunStatus::Stopped)
166 emit readNextMessage();
167}
168
169void QLanguageServer::registerHandlers(QLanguageServerProtocol *protocol)
170{
171 QObject::connect(notifySignals(), &QLspNotifySignals::receivedCancelNotification, this,
172 [this](const QLspSpecification::Notifications::CancelParamsType &params) {
173 Q_D(QLanguageServer);
174 QJsonValue id = QTypedJson::toJsonValue(params.id);
175 QMutexLocker l(&d->mutex);
176 if (d->requestsInProgress.contains(id))
177 d->requestsInProgress[id].canceled = true;
178 else
179 qCWarning(lspServerLog)
180 << "Ignoring cancellation of non in progress request" << id;
181 });
182
183 protocol->registerInitializeRequestHandler(
184 [this](const QByteArray &,
185 const QLspSpecification::Requests::InitializeParamsType &params,
186 QLspSpecification::Responses::InitializeResponseType &&response) {
187 Q_D(QLanguageServer);
188 {
189 QMutexLocker l(&d->mutex);
190 if (d->runStatus == RunStatus::Initialized) {
191 response.sendErrorResponse(
192 int(QLspSpecification::ErrorCodes::InvalidRequest),
193 u"Received multiple initialization requests"_s.toUtf8());
194 }
195 }
196
197 qCDebug(lspServerLog) << "init";
198 d->clientInfo = params;
199 {
200 QMutexLocker l(&d->mutex);
201 d->runStatus = RunStatus::Initialized;
202 }
203 response.sendResponse(d->serverInfo);
204 });
205
206 QObject::connect(notifySignals(), &QLspNotifySignals::receivedInitializedNotification, this,
207 [this](const QLspSpecification::Notifications::InitializedParamsType &) {
208 emit clientInitialized(this);
209 });
210
211 protocol->registerShutdownRequestHandler(
212 [this](const QByteArray &, const QLspSpecification::Requests::ShutdownParamsType &,
213 QLspSpecification::Responses::ShutdownResponseType &&response) {
214 Q_D(QLanguageServer);
215 RunStatus rStatus;
216 bool shouldExecuteShutdown = false;
217 {
218 QMutexLocker l(&d->mutex);
219 rStatus = d->runStatus;
220 if (rStatus == RunStatus::Initialized) {
221 d->shutdownResponse = std::move(response);
222 if (d->requestsInProgress.size() <= 1) {
223 d->runStatus = RunStatus::Stopping;
224 shouldExecuteShutdown = true;
225 } else {
226 d->runStatus = RunStatus::WaitPending;
227 }
228 }
229 }
230 if (rStatus != RunStatus::Initialized)
231 emit lifecycleError();
232 else if (shouldExecuteShutdown)
233 executeShutdown();
234 });
235
236 QObject::connect(notifySignals(), &QLspNotifySignals::receivedExitNotification, this,
237 [this](const QLspSpecification::Notifications::ExitParamsType &) {
238 Q_D(QLanguageServer);
239 QMutexLocker l(&d->mutex);
240 RunStatus runStatus = d->runStatus;
241 if (runStatus != RunStatus::WaitingForExit) {
242 emit lifecycleError();
243 return;
244 }
245 d->runStatus = RunStatus::Stopped;
246 emit exit();
247 });
248}
249
250void QLanguageServer::executeShutdown()
251{
252 RunStatus rStatus = runStatus();
253 if (rStatus != RunStatus::Stopping) {
254 emit lifecycleError();
255 return;
256 }
257 QLspSpecification::Responses::ShutdownResponseType shutdownResponse;
258 {
259 Q_D(QLanguageServer);
260 QMutexLocker l(&d->mutex);
261 rStatus = d->runStatus;
262 if (rStatus == RunStatus::Stopping) {
263 shutdownResponse = std::move(d->shutdownResponse);
264 d->runStatus = RunStatus::WaitingForExit;
265 }
266 }
267 if (rStatus != RunStatus::Stopping)
268 emit lifecycleError();
269 else
270 shutdownResponse.sendResponse(nullptr);
271}
272
273QT_END_NAMESPACE
QLanguageServerPrivate(const QJsonRpcTransport::DataHandler &h)
Combined button and popup list for selecting options.