LanguageClient: Move the interface out of the gui thread

Change-Id: Iec34f5a0ca3f7f8e2306d3c8a50c2155b5b96807
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
This commit is contained in:
David Schulz
2022-05-11 14:01:49 +02:00
parent 296edb321a
commit 0422233af4
7 changed files with 102 additions and 45 deletions

View File

@@ -354,7 +354,7 @@ public:
constexpr static const char methodName[] = "$/cancelRequest"; constexpr static const char methodName[] = "$/cancelRequest";
}; };
} // namespace LanguageClient } // namespace LanguageServerProtocol
template <typename Error> template <typename Error>
inline QDebug operator<<(QDebug stream, inline QDebug operator<<(QDebug stream,
@@ -363,3 +363,5 @@ inline QDebug operator<<(QDebug stream,
stream.nospace() << error.toString(); stream.nospace() << error.toString();
return stream; return stream;
} }
Q_DECLARE_METATYPE(LanguageServerProtocol::JsonRpcMessage)

View File

@@ -62,7 +62,6 @@
#include <utils/mimeutils.h> #include <utils/mimeutils.h>
#include <utils/qtcprocess.h> #include <utils/qtcprocess.h>
#include <QDebug> #include <QDebug>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QMessageBox> #include <QMessageBox>
@@ -71,6 +70,7 @@
#include <QTextBlock> #include <QTextBlock>
#include <QTextCursor> #include <QTextCursor>
#include <QTextDocument> #include <QTextDocument>
#include <QThread>
#include <QTimer> #include <QTimer>
using namespace LanguageServerProtocol; using namespace LanguageServerProtocol;
@@ -80,9 +80,56 @@ namespace LanguageClient {
static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client", QtWarningMsg); static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client", QtWarningMsg);
class InterfaceController : public QObject
{
Q_OBJECT
public:
InterfaceController(BaseClientInterface *interface)
: m_interface(interface)
{
using Interface = BaseClientInterface;
interface->moveToThread(&m_thread);
connect(interface, &Interface::contentReceived, this, &InterfaceController::contentReceived);
connect(interface, &Interface::error, this, &InterfaceController::error);
connect(interface, &Interface::finished, this, &InterfaceController::finished);
connect(interface, &Interface::started, this, &InterfaceController::started);
m_thread.start();
}
~InterfaceController()
{
m_interface->deleteLater();
m_thread.quit();
m_thread.wait();
}
void start()
{
QMetaObject::invokeMethod(m_interface, &BaseClientInterface::start);
}
void sendContent(const JsonRpcMessage message)
{
QMetaObject::invokeMethod(m_interface, [=]() { m_interface->sendContent(message); });
}
void resetBuffer()
{
QMetaObject::invokeMethod(m_interface, &BaseClientInterface::resetBuffer);
}
signals:
void contentReceived(const JsonRpcMessage &message);
void started();
void error(const QString &message);
void finished();
private:
BaseClientInterface *m_interface;
QThread m_thread;
};
Client::Client(BaseClientInterface *clientInterface) Client::Client(BaseClientInterface *clientInterface)
: m_id(Utils::Id::fromString(QUuid::createUuid().toString())) : m_id(Utils::Id::fromString(QUuid::createUuid().toString()))
, m_clientInterface(clientInterface) , m_clientInterface(new InterfaceController(clientInterface))
, m_documentSymbolCache(this) , m_documentSymbolCache(this)
, m_hoverHandler(this) , m_hoverHandler(this)
, m_symbolSupport(this) , m_symbolSupport(this)
@@ -105,9 +152,12 @@ Client::Client(BaseClientInterface *clientInterface)
this, &Client::projectClosed); this, &Client::projectClosed);
QTC_ASSERT(clientInterface, return); QTC_ASSERT(clientInterface, return);
connect(clientInterface, &BaseClientInterface::contentReceived, this, &Client::handleContent); connect(m_clientInterface, &InterfaceController::contentReceived, this, &Client::handleContent);
connect(clientInterface, &BaseClientInterface::error, this, &Client::setError); connect(m_clientInterface, &InterfaceController::error, this, &Client::setError);
connect(clientInterface, &BaseClientInterface::finished, this, &Client::finished); connect(m_clientInterface, &InterfaceController::finished, this, &Client::finished);
connect(m_clientInterface, &InterfaceController::started, this, [this]() {
LanguageClientManager::clientStarted(this);
});
connect(Core::EditorManager::instance(), connect(Core::EditorManager::instance(),
&Core::EditorManager::documentClosed, &Core::EditorManager::documentClosed,
this, this,
@@ -159,9 +209,10 @@ Client::~Client()
m_documentHighlightsTimer.clear(); m_documentHighlightsTimer.clear();
updateEditorToolBar(m_openedDocument.keys()); updateEditorToolBar(m_openedDocument.keys());
// do not handle messages while shutting down // do not handle messages while shutting down
disconnect(m_clientInterface.data(), &BaseClientInterface::contentReceived, disconnect(m_clientInterface, &InterfaceController::contentReceived,
this, &Client::handleContent); this, &Client::handleContent);
delete m_diagnosticManager; delete m_diagnosticManager;
delete m_clientInterface;
} }
static ClientCapabilities generateClientCapabilities() static ClientCapabilities generateClientCapabilities()
@@ -1170,10 +1221,7 @@ bool Client::supportsDocumentSymbols(const TextEditor::TextDocument *doc) const
void Client::start() void Client::start()
{ {
LanguageClientManager::addClient(this); LanguageClientManager::addClient(this);
if (m_clientInterface->start()) m_clientInterface->start();
LanguageClientManager::clientStarted(this);
else
LanguageClientManager::clientFinished(this);
} }
bool Client::reset() bool Client::reset()
@@ -1541,8 +1589,8 @@ void Client::sendContentNow(const IContent &content)
LanguageClientManager::logJsonRpcMessage(LspLogMessage::ClientMessage, LanguageClientManager::logJsonRpcMessage(LspLogMessage::ClientMessage,
name(), name(),
static_cast<const JsonRpcMessage &>(content)); static_cast<const JsonRpcMessage &>(content));
m_clientInterface->sendContent(static_cast<const JsonRpcMessage &>(content));
} }
m_clientInterface->sendContent(content);
} }
bool Client::documentUpdatePostponed(const Utils::FilePath &fileName) const bool Client::documentUpdatePostponed(const Utils::FilePath &fileName) const
@@ -1694,3 +1742,5 @@ QTextCursor Client::adjustedCursorForHighlighting(const QTextCursor &cursor,
} }
} // namespace LanguageClient } // namespace LanguageClient
#include <client.moc>

View File

@@ -80,6 +80,7 @@ QT_END_NAMESPACE
namespace LanguageClient { namespace LanguageClient {
class BaseClientInterface; class BaseClientInterface;
class InterfaceController;
class LANGUAGECLIENT_EXPORT Client : public QObject class LANGUAGECLIENT_EXPORT Client : public QObject
{ {
@@ -300,7 +301,7 @@ private:
QMap<TextEditor::TextDocument *, AssistProviders> m_resetAssistProvider; QMap<TextEditor::TextDocument *, AssistProviders> m_resetAssistProvider;
QHash<TextEditor::TextEditorWidget *, LanguageServerProtocol::MessageId> m_highlightRequests; QHash<TextEditor::TextEditorWidget *, LanguageServerProtocol::MessageId> m_highlightRequests;
int m_restartsLeft = 5; int m_restartsLeft = 5;
QScopedPointer<BaseClientInterface> m_clientInterface; InterfaceController *m_clientInterface = nullptr;
DiagnosticManager *m_diagnosticManager = nullptr; DiagnosticManager *m_diagnosticManager = nullptr;
DocumentSymbolCache m_documentSymbolCache; DocumentSymbolCache m_documentSymbolCache;
HoverHandler m_hoverHandler; HoverHandler m_hoverHandler;

View File

@@ -99,72 +99,72 @@ void BaseClientInterface::parseCurrentMessage()
m_currentMessage = BaseMessage(); m_currentMessage = BaseMessage();
} }
StdIOClientInterface::StdIOClientInterface() StdIOClientInterface::StdIOClientInterface() {}
{
m_process.setProcessMode(ProcessMode::Writer);
connect(&m_process, &QtcProcess::readyReadStandardError,
this, &StdIOClientInterface::readError);
connect(&m_process, &QtcProcess::readyReadStandardOutput,
this, &StdIOClientInterface::readOutput);
connect(&m_process, &QtcProcess::finished,
this, &StdIOClientInterface::onProcessFinished);
}
StdIOClientInterface::~StdIOClientInterface() StdIOClientInterface::~StdIOClientInterface()
{ {
m_process.stopProcess(); if (m_process)
m_process->stopProcess();
delete m_process;
} }
bool StdIOClientInterface::start() void StdIOClientInterface::startImpl()
{ {
m_process.start(); m_process = new Utils::QtcProcess;
if (!m_process.waitForStarted() || m_process.state() != QProcess::Running) { m_process->setProcessMode(ProcessMode::Writer);
emit error(m_process.errorString()); connect(m_process, &QtcProcess::readyReadStandardError,
return false; this, &StdIOClientInterface::readError);
} connect(m_process, &QtcProcess::readyReadStandardOutput,
return true; this, &StdIOClientInterface::readOutput);
connect(m_process, &QtcProcess::finished, this, &StdIOClientInterface::onProcessFinished);
connect(m_process, &QtcProcess::started, this, &StdIOClientInterface::started);
m_process->setCommand(m_cmd);
m_process->setWorkingDirectory(m_workingDirectory);
m_process->start();
} }
void StdIOClientInterface::setCommandLine(const CommandLine &cmd) void StdIOClientInterface::setCommandLine(const CommandLine &cmd)
{ {
m_process.setCommand(cmd); m_cmd = cmd;
} }
void StdIOClientInterface::setWorkingDirectory(const FilePath &workingDirectory) void StdIOClientInterface::setWorkingDirectory(const FilePath &workingDirectory)
{ {
m_process.setWorkingDirectory(workingDirectory); m_workingDirectory = workingDirectory;
} }
void StdIOClientInterface::sendData(const QByteArray &data) void StdIOClientInterface::sendData(const QByteArray &data)
{ {
if (m_process.state() != QProcess::Running) { if (!m_process || m_process->state() != QProcess::Running) {
emit error(tr("Cannot send data to unstarted server %1") emit error(tr("Cannot send data to unstarted server %1")
.arg(m_process.commandLine().toUserOutput())); .arg(m_cmd.toUserOutput()));
return; return;
} }
qCDebug(LOGLSPCLIENTV) << "StdIOClient send data:"; qCDebug(LOGLSPCLIENTV) << "StdIOClient send data:";
qCDebug(LOGLSPCLIENTV).noquote() << data; qCDebug(LOGLSPCLIENTV).noquote() << data;
m_process.writeRaw(data); m_process->writeRaw(data);
} }
void StdIOClientInterface::onProcessFinished() void StdIOClientInterface::onProcessFinished()
{ {
if (m_process.exitStatus() == QProcess::CrashExit) QTC_ASSERT(m_process, return);
if (m_process->exitStatus() == QProcess::CrashExit)
emit error(tr("Crashed with exit code %1: %2") emit error(tr("Crashed with exit code %1: %2")
.arg(m_process.exitCode()).arg(m_process.errorString())); .arg(m_process->exitCode()).arg(m_process->errorString()));
emit finished(); emit finished();
} }
void StdIOClientInterface::readError() void StdIOClientInterface::readError()
{ {
QTC_ASSERT(m_process, return);
qCDebug(LOGLSPCLIENTV) << "StdIOClient std err:\n"; qCDebug(LOGLSPCLIENTV) << "StdIOClient std err:\n";
qCDebug(LOGLSPCLIENTV).noquote() << m_process.readAllStandardError(); qCDebug(LOGLSPCLIENTV).noquote() << m_process->readAllStandardError();
} }
void StdIOClientInterface::readOutput() void StdIOClientInterface::readOutput()
{ {
const QByteArray &out = m_process.readAllStandardOutput(); QTC_ASSERT(m_process, return);
const QByteArray &out = m_process->readAllStandardOutput();
qCDebug(LOGLSPCLIENTV) << "StdIOClient std out:\n"; qCDebug(LOGLSPCLIENTV) << "StdIOClient std out:\n";
qCDebug(LOGLSPCLIENTV).noquote() << out; qCDebug(LOGLSPCLIENTV).noquote() << out;
parseData(out); parseData(out);

View File

@@ -46,7 +46,7 @@ public:
~BaseClientInterface() override; ~BaseClientInterface() override;
void sendContent(const LanguageServerProtocol::IContent &content); void sendContent(const LanguageServerProtocol::IContent &content);
virtual bool start() { return true; } void start() { startImpl(); }
void resetBuffer(); void resetBuffer();
@@ -54,8 +54,10 @@ signals:
void contentReceived(const LanguageServerProtocol::JsonRpcMessage message); void contentReceived(const LanguageServerProtocol::JsonRpcMessage message);
void finished(); void finished();
void error(const QString &message); void error(const QString &message);
void started();
protected: protected:
virtual void startImpl() { emit started(); }
virtual void sendData(const QByteArray &data) = 0; virtual void sendData(const QByteArray &data) = 0;
void parseData(const QByteArray &data); void parseData(const QByteArray &data);
virtual void parseCurrentMessage(); virtual void parseCurrentMessage();
@@ -77,7 +79,7 @@ public:
StdIOClientInterface &operator=(const StdIOClientInterface &) = delete; StdIOClientInterface &operator=(const StdIOClientInterface &) = delete;
StdIOClientInterface &operator=(StdIOClientInterface &&) = delete; StdIOClientInterface &operator=(StdIOClientInterface &&) = delete;
bool start() override; void startImpl() override;
// These functions only have an effect if they are called before start // These functions only have an effect if they are called before start
void setCommandLine(const Utils::CommandLine &cmd); void setCommandLine(const Utils::CommandLine &cmd);
@@ -85,7 +87,9 @@ public:
protected: protected:
void sendData(const QByteArray &data) final; void sendData(const QByteArray &data) final;
Utils::QtcProcess m_process; Utils::CommandLine m_cmd;
Utils::FilePath m_workingDirectory;
Utils::QtcProcess *m_process = nullptr;
private: private:
void readError(); void readError();

View File

@@ -41,6 +41,7 @@ static LanguageClientPlugin *m_instance = nullptr;
LanguageClientPlugin::LanguageClientPlugin() LanguageClientPlugin::LanguageClientPlugin()
{ {
m_instance = this; m_instance = this;
qRegisterMetaType<LanguageServerProtocol::JsonRpcMessage>();
} }
LanguageClientPlugin::~LanguageClientPlugin() LanguageClientPlugin::~LanguageClientPlugin()

View File

@@ -34,7 +34,6 @@ using namespace LanguageServerProtocol;
Q_DECLARE_METATYPE(QTextCodec *) Q_DECLARE_METATYPE(QTextCodec *)
Q_DECLARE_METATYPE(BaseMessage) Q_DECLARE_METATYPE(BaseMessage)
Q_DECLARE_METATYPE(JsonRpcMessage)
Q_DECLARE_METATYPE(DocumentUri) Q_DECLARE_METATYPE(DocumentUri)
Q_DECLARE_METATYPE(Range) Q_DECLARE_METATYPE(Range)