Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qlanguageserver_p_p.h"
#include <QtLanguageServer/private/qlspnotifysignals_p.h>
#include <QtJsonRpc/private/qjsonrpcprotocol_p_p.h>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lspServerLog, "qt.languageserver.server")
using namespace QLspSpecification;
using namespace Qt::StringLiterals;
QLanguageServerPrivate::QLanguageServerPrivate(const QJsonRpcTransport::DataHandler &h)
: protocol(h)
{
}
/*!
\internal
\class QLanguageServer
\brief Implements a server for the language server protocol
QLanguageServer is a class that uses the QLanguageServerProtocol to
provide a server implementation.
It handles the lifecycle management, and can be extended via
QLanguageServerModule subclasses.
The language server keeps a strictly monotonically increasing runState that can be queried
from any thread (and is thus mutex gated), the normal run state is Initialized.
The language server also keeps track of the task canceled by the client (or implicitly when
shutting down, and isRequestCanceled can be called from any thread.
*/
QLanguageServer::QLanguageServer(const QJsonRpcTransport::DataHandler &h, QObject *parent)
: QObject(*new QLanguageServerPrivate(h), parent)
{
Q_D(QLanguageServer);
registerMethods(*d->protocol.typedRpc());
d->notifySignals.registerHandlers(&d->protocol);
registerHandlers(&d->protocol);
}
QLanguageServerProtocol *QLanguageServer::protocol()
{
Q_D(QLanguageServer);
return &d->protocol;
}
QLanguageServer::RunStatus QLanguageServer::runStatus() const
{
const Q_D(QLanguageServer);
QMutexLocker l(&d->mutex);
return d->runStatus;
}
void QLanguageServer::registerModule(QLanguageServerModule *serverModule)
{
Q_D(QLanguageServer);
Q_ASSERT(serverModule);
serverModule->registerHandlers(this, &d->protocol);
serverModule->setupCapabilities(d->serverInfo.capabilities);
}
QLspNotifySignals *QLanguageServer::notifySignals()
{
Q_D(QLanguageServer);
return &d->notifySignals;
}
void QLanguageServer::registerMethods(QJsonRpc::TypedRpc &typedRpc)
{
typedRpc.installMessagePreprocessor(
[this](const QJsonDocument &doc, const QJsonParseError &err,
const QJsonRpcProtocol::Handler<QJsonRpcProtocol::Response> &responder) {
Q_D(QLanguageServer);
if (!doc.isObject()) {
qCWarning(lspServerLog)
<< "non object jsonrpc message" << doc << err.errorString();
return QJsonRpcProtocol::Processing::Stop;
}
bool sendErrorResponse = false;
RunStatus rState = RunStatus::NotInitialized;
QJsonValue id = doc.object()[u"id"];
{
QMutexLocker l(&d->mutex);
// the normal case is d->runStatus == RunStatus::Initialized
if (d->runStatus != RunStatus::Initialized) {
if (d->runStatus == RunStatus::NotInitialized && !doc.isNull()
&& doc.object()[u"method"].toString()
== QString::fromUtf8(
QLspSpecification::Requests::InitializeMethod)) {
return QJsonRpcProtocol::Processing::Continue;
} else if (!doc.isNull()
&& doc.object()[u"method"].toString()
== QString::fromUtf8(
QLspSpecification::Notifications::ExitMethod)) {
return QJsonRpcProtocol::Processing::Continue;
}
if (id.isString() || id.isDouble()) {
sendErrorResponse = true;
rState = d->runStatus;
} else {
return QJsonRpcProtocol::Processing::Stop;
}
}
}
if (!sendErrorResponse)
return QJsonRpcProtocol::Processing::Continue;
if (rState == RunStatus::NotInitialized)
responder(QJsonRpcProtocol::MessageHandler::error(
int(QLspSpecification::ErrorCodes::ServerNotInitialized),
u"Request on non initialized Language Server (runStatus %1): %2"_s
.arg(int(rState))
.arg(QString::fromUtf8(doc.toJson()))));
else
responder(QJsonRpcProtocol::MessageHandler::error(
int(QLspSpecification::ErrorCodes::InvalidRequest),
u"Method called on stopping Language Server (runStatus %1)"_s.arg(
int(rState))));
return QJsonRpcProtocol::Processing::Stop;
});
}
const QLspSpecification::InitializeParams &QLanguageServer::clientInfo() const
{
const Q_D(QLanguageServer);
if (int(runStatus()) < int(RunStatus::Initialized))
qCWarning(lspServerLog) << "asked for Language Server clientInfo before initialization";
return d->clientInfo;
}
void QLanguageServer::receiveData(const QByteArray &data, bool isEndOfMessage)
{
if (!data.isEmpty())
protocol()->receiveData(data);
const Q_D(QLanguageServer);
// read next message if not shutting down
if (isEndOfMessage && d->runStatus != RunStatus::Stopped)
emit readNextMessage();
}
void QLanguageServer::registerHandlers(QLanguageServerProtocol *protocol)
{
protocol->registerInitializeRequestHandler(
[this](const QByteArray &,
const QLspSpecification::Requests::InitializeParamsType &params,
QLspSpecification::Responses::InitializeResponseType &&response) {
Q_D(QLanguageServer);
{
QMutexLocker l(&d->mutex);
if (d->runStatus == RunStatus::Initialized) {
response.sendErrorResponse(
int(QLspSpecification::ErrorCodes::InvalidRequest),
u"Received multiple initialization requests"_s.toUtf8());
}
}
qCDebug(lspServerLog) << "init";
d->clientInfo = params;
{
QMutexLocker l(&d->mutex);
d->runStatus = RunStatus::Initialized;
}
response.sendResponse(d->serverInfo);
});
QObject::connect(notifySignals(), &QLspNotifySignals::receivedInitializedNotification, this,
[this](const QLspSpecification::Notifications::InitializedParamsType &) {
emit clientInitialized(this);
});
protocol->registerShutdownRequestHandler(
[this](const QByteArray &, const QLspSpecification::Requests::ShutdownParamsType &,
QLspSpecification::Responses::ShutdownResponseType &&response) {
Q_D(QLanguageServer);
RunStatus rStatus;
bool shouldExecuteShutdown = false;
{
QMutexLocker l(&d->mutex);
rStatus = d->runStatus;
if (rStatus == RunStatus::Initialized) {
d->shutdownResponse = std::move(response);
d->runStatus = RunStatus::Stopping;
shouldExecuteShutdown = true;
}
}
if (rStatus != RunStatus::Initialized)
emit lifecycleError();
else if (shouldExecuteShutdown)
executeShutdown();
});
QObject::connect(notifySignals(), &QLspNotifySignals::receivedExitNotification, this,
[this](const QLspSpecification::Notifications::ExitParamsType &) {
Q_D(QLanguageServer);
QMutexLocker l(&d->mutex);
RunStatus runStatus = d->runStatus;
if (runStatus != RunStatus::WaitingForExit) {
emit lifecycleError();
return;
}
d->runStatus = RunStatus::Stopped;
emit exit();
});
}
void QLanguageServer::executeShutdown()
{
RunStatus rStatus = runStatus();
if (rStatus != RunStatus::Stopping) {
emit lifecycleError();
return;
}
QLspSpecification::Responses::ShutdownResponseType shutdownResponse;
{
Q_D(QLanguageServer);
QMutexLocker l(&d->mutex);
rStatus = d->runStatus;
if (rStatus == RunStatus::Stopping) {
shutdownResponse = std::move(d->shutdownResponse);
d->runStatus = RunStatus::WaitingForExit;
}
}
if (rStatus != RunStatus::Stopping)
emit lifecycleError();
else
shutdownResponse.sendResponse(nullptr);
}
QT_END_NAMESPACE