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 return QJsonRpcProtocol::Processing::Continue;
114
115 if (rState == RunStatus::NotInitialized)
116 responder(QJsonRpcProtocol::MessageHandler::error(
117 int(QLspSpecification::ErrorCodes::ServerNotInitialized),
118 u"Request on non initialized Language Server (runStatus %1): %2"_s
119 .arg(int(rState))
120 .arg(QString::fromUtf8(doc.toJson()))));
121 else
122 responder(QJsonRpcProtocol::MessageHandler::error(
123 int(QLspSpecification::ErrorCodes::InvalidRequest),
124 u"Method called on stopping Language Server (runStatus %1)"_s.arg(
125 int(rState))));
126 return QJsonRpcProtocol::Processing::Stop;
127 });
128}
129
130const QLspSpecification::InitializeParams &QLanguageServer::clientInfo() const
131{
132 const Q_D(QLanguageServer);
133
134 if (int(runStatus()) < int(RunStatus::Initialized))
135 qCWarning(lspServerLog) << "asked for Language Server clientInfo before initialization";
136 return d->clientInfo;
137}
138
139void QLanguageServer::receiveData(const QByteArray &data, bool isEndOfMessage)
140{
141 if (!data.isEmpty())
142 protocol()->receiveData(data);
143
144 const Q_D(QLanguageServer);
145 // read next message if not shutting down
146 if (isEndOfMessage && d->runStatus != RunStatus::Stopped)
147 emit readNextMessage();
148}
149
150void QLanguageServer::registerHandlers(QLanguageServerProtocol *protocol)
151{
152 protocol->registerInitializeRequestHandler(
153 [this](const QByteArray &,
154 const QLspSpecification::Requests::InitializeParamsType &params,
155 QLspSpecification::Responses::InitializeResponseType &&response) {
156 Q_D(QLanguageServer);
157 {
158 QMutexLocker l(&d->mutex);
159 if (d->runStatus == RunStatus::Initialized) {
160 response.sendErrorResponse(
161 int(QLspSpecification::ErrorCodes::InvalidRequest),
162 u"Received multiple initialization requests"_s.toUtf8());
163 }
164 }
165
166 qCDebug(lspServerLog) << "init";
167 d->clientInfo = params;
168 {
169 QMutexLocker l(&d->mutex);
170 d->runStatus = RunStatus::Initialized;
171 }
172 response.sendResponse(d->serverInfo);
173 });
174
175 QObject::connect(notifySignals(), &QLspNotifySignals::receivedInitializedNotification, this,
176 [this](const QLspSpecification::Notifications::InitializedParamsType &) {
177 emit clientInitialized(this);
178 });
179
180 protocol->registerShutdownRequestHandler(
181 [this](const QByteArray &, const QLspSpecification::Requests::ShutdownParamsType &,
182 QLspSpecification::Responses::ShutdownResponseType &&response) {
183 Q_D(QLanguageServer);
184 RunStatus rStatus;
185 bool shouldExecuteShutdown = false;
186 {
187 QMutexLocker l(&d->mutex);
188 rStatus = d->runStatus;
189 if (rStatus == RunStatus::Initialized) {
190 d->shutdownResponse = std::move(response);
191 d->runStatus = RunStatus::Stopping;
192 shouldExecuteShutdown = true;
193 }
194 }
195 if (rStatus != RunStatus::Initialized)
196 emit lifecycleError();
197 else if (shouldExecuteShutdown)
198 executeShutdown();
199 });
200
201 QObject::connect(notifySignals(), &QLspNotifySignals::receivedExitNotification, this,
202 [this](const QLspSpecification::Notifications::ExitParamsType &) {
203 Q_D(QLanguageServer);
204 QMutexLocker l(&d->mutex);
205 RunStatus runStatus = d->runStatus;
206 if (runStatus != RunStatus::WaitingForExit) {
207 emit lifecycleError();
208 return;
209 }
210 d->runStatus = RunStatus::Stopped;
211 emit exit();
212 });
213}
214
215void QLanguageServer::executeShutdown()
216{
217 RunStatus rStatus = runStatus();
218 if (rStatus != RunStatus::Stopping) {
219 emit lifecycleError();
220 return;
221 }
222 QLspSpecification::Responses::ShutdownResponseType shutdownResponse;
223 {
224 Q_D(QLanguageServer);
225 QMutexLocker l(&d->mutex);
226 rStatus = d->runStatus;
227 if (rStatus == RunStatus::Stopping) {
228 shutdownResponse = std::move(d->shutdownResponse);
229 d->runStatus = RunStatus::WaitingForExit;
230 }
231 }
232 if (rStatus != RunStatus::Stopping)
233 emit lifecycleError();
234 else
235 shutdownResponse.sendResponse(nullptr);
236}
237
238QT_END_NAMESPACE
QLanguageServerPrivate(const QJsonRpcTransport::DataHandler &h)
Combined button and popup list for selecting options.