LSP: separate communication interface and client logic

Change-Id: I7d35fa634287b5f858c4a87aa10f99bf18d1ad07
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2019-01-31 08:46:23 +01:00
parent f7e1354ae5
commit eac1b6059c
10 changed files with 333 additions and 179 deletions

View File

@@ -176,7 +176,7 @@ bool BaseMessage::isValid() const
return contentLength >= 0;
}
QByteArray BaseMessage::toData()
QByteArray BaseMessage::toData() const
{
return header() + content;
}

View File

@@ -55,7 +55,7 @@ public:
bool isComplete() const;
bool isValid() const;
QByteArray toData();
QByteArray toData() const;
QByteArray mimeType;
QByteArray content;

View File

@@ -25,8 +25,9 @@
#include "baseclient.h"
#include "languageclientinterface.h"
#include "languageclientmanager.h"
#include "languageclient/languageclientutils.h"
#include "languageclientutils.h"
#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>
@@ -60,22 +61,23 @@ using namespace Utils;
namespace LanguageClient {
static Q_LOGGING_CATEGORY(LOGLSPCLIENT, "qtc.languageclient.client", QtWarningMsg);
static Q_LOGGING_CATEGORY(LOGLSPCLIENTV, "qtc.languageclient.messages", QtWarningMsg);
static Q_LOGGING_CATEGORY(LOGLSPCLIENTPARSE, "qtc.languageclient.parse", QtWarningMsg);
BaseClient::BaseClient()
BaseClient::BaseClient(BaseClientInterface *clientInterface)
: m_id(Core::Id::fromString(QUuid::createUuid().toString()))
, m_completionProvider(this)
, m_clientInterface(clientInterface)
{
m_buffer.open(QIODevice::ReadWrite | QIODevice::Append);
m_contentHandler.insert(JsonRpcMessageHandler::jsonRpcMimeType(),
&JsonRpcMessageHandler::parseContent);
QTC_ASSERT(clientInterface, return);
connect(clientInterface, &BaseClientInterface::messageReceived, this, &BaseClient::handleMessage);
connect(clientInterface, &BaseClientInterface::error, this, &BaseClient::setError);
connect(clientInterface, &BaseClientInterface::finished, this, &BaseClient::finished);
}
BaseClient::~BaseClient()
{
using namespace TextEditor;
m_buffer.close();
// FIXME: instead of replacing the completion provider in the text document store the
// completion provider as a prioritised list in the text document
for (TextDocument *document : m_resetCompletionProvider)
@@ -91,6 +93,7 @@ BaseClient::~BaseClient()
void BaseClient::initialize()
{
using namespace ProjectExplorer;
QTC_ASSERT(m_clientInterface, return);
QTC_ASSERT(m_state == Uninitialized, return);
qCDebug(LOGLSPCLIENT) << "initializing language server " << m_displayName;
auto initRequest = new InitializeRequest();
@@ -108,7 +111,7 @@ void BaseClient::initialize()
});
// directly send data otherwise the state check would fail;
initRequest->registerResponseHandler(&m_responseHandlers);
sendData(initRequest->toBaseMessage().toData());
m_clientInterface->sendMessage(initRequest->toBaseMessage());
m_state = InitializeRequested;
}
@@ -190,12 +193,13 @@ void BaseClient::openDocument(Core::IDocument *document)
void BaseClient::sendContent(const IContent &content)
{
QTC_ASSERT(m_clientInterface, return);
QTC_ASSERT(m_state == Initialized, return);
content.registerResponseHandler(&m_responseHandlers);
QString error;
if (!QTC_GUARD(content.isValid(&error)))
Core::MessageManager::write(error);
sendData(content.toBaseMessage().toData());
m_clientInterface->sendMessage(content.toBaseMessage());
}
void BaseClient::sendContent(const DocumentUri &uri, const IContent &content)
@@ -652,6 +656,11 @@ bool BaseClient::needsRestart(const BaseSettings *settings) const
|| m_languagFilter.filePattern != settings->m_languageFilter.filePattern;
}
bool BaseClient::start()
{
return m_clientInterface->start();
}
bool BaseClient::reset()
{
if (!m_restartsLeft)
@@ -659,9 +668,7 @@ bool BaseClient::reset()
--m_restartsLeft;
m_state = Uninitialized;
m_responseHandlers.clear();
m_buffer.close();
m_buffer.setData(nullptr);
m_buffer.open(QIODevice::ReadWrite | QIODevice::Append);
m_clientInterface->resetBuffer();
m_openedDocument.clear();
m_serverCapabilities = ServerCapabilities();
m_dynamicCapabilities.reset();
@@ -674,6 +681,24 @@ void BaseClient::setError(const QString &message)
m_state = Error;
}
void BaseClient::handleMessage(const BaseMessage &message)
{
if (auto handler = m_contentHandler[message.mimeType]) {
QString parseError;
handler(message.content, message.codec, parseError,
[this](MessageId id, const QByteArray &content, QTextCodec *codec){
this->handleResponse(id, content, codec);
},
[this](const QString &method, MessageId id, const IContent *content){
this->handleMethod(method, id, content);
});
if (!parseError.isEmpty())
log(parseError);
} else {
log(tr("Cannot handle content of type: %1").arg(QLatin1String(message.mimeType)));
}
}
void BaseClient::log(const QString &message, Core::MessageManager::PrintToOutputPaneFlag flag)
{
Core::MessageManager::write(QString("LanguageClient %1: %2").arg(name(), message), flag);
@@ -841,6 +866,7 @@ void BaseClient::intializeCallback(const InitializeRequest::Response &initRespon
void BaseClient::shutDownCallback(const ShutdownRequest::Response &shutdownResponse)
{
QTC_ASSERT(m_state == ShutdownRequested, return);
QTC_ASSERT(m_clientInterface, return);
optional<ShutdownRequest::Response::Error> errorValue = shutdownResponse.error();
if (errorValue.has_value()) {
ShutdownRequest::Response::Error error = errorValue.value();
@@ -848,7 +874,7 @@ void BaseClient::shutDownCallback(const ShutdownRequest::Response &shutdownRespo
return;
}
// directly send data otherwise the state check would fail;
sendData(ExitNotification().toBaseMessage().toData());
m_clientInterface->sendMessage(ExitNotification().toBaseMessage());
qCDebug(LOGLSPCLIENT) << "language server " << m_displayName << " shutdown";
m_state = Shutdown;
}
@@ -872,118 +898,4 @@ bool BaseClient::sendWorkspceFolderChanges() const
return false;
}
void BaseClient::parseData(const QByteArray &data)
{
const qint64 preWritePosition = m_buffer.pos();
qCDebug(LOGLSPCLIENTPARSE) << "parse buffer pos: " << preWritePosition;
qCDebug(LOGLSPCLIENTPARSE) << " data: " << data;
if (!m_buffer.atEnd())
m_buffer.seek(preWritePosition + m_buffer.bytesAvailable());
m_buffer.write(data);
m_buffer.seek(preWritePosition);
while (!m_buffer.atEnd()) {
QString parseError;
BaseMessage::parse(&m_buffer, parseError, m_currentMessage);
qCDebug(LOGLSPCLIENTPARSE) << " complete: " << m_currentMessage.isComplete();
qCDebug(LOGLSPCLIENTPARSE) << " length: " << m_currentMessage.contentLength;
qCDebug(LOGLSPCLIENTPARSE) << " content: " << m_currentMessage.content;
if (!parseError.isEmpty())
log(parseError);
if (!m_currentMessage.isComplete())
break;
if (auto handler = m_contentHandler[m_currentMessage.mimeType]){
QString parseError;
handler(m_currentMessage.content, m_currentMessage.codec, parseError,
[this](MessageId id, const QByteArray &content, QTextCodec *codec){
this->handleResponse(id, content, codec);
},
[this](const QString &method, MessageId id, const IContent *content){
this->handleMethod(method, id, content);
});
if (!parseError.isEmpty())
log(parseError);
} else {
log(tr("Cannot handle content of type: %1").arg(QLatin1String(m_currentMessage.mimeType)));
}
m_currentMessage = BaseMessage();
}
if (m_buffer.atEnd()) {
m_buffer.close();
m_buffer.setData(nullptr);
m_buffer.open(QIODevice::ReadWrite | QIODevice::Append);
}
}
StdIOClient::StdIOClient(const QString &executable, const QString &arguments)
: m_executable(executable)
, m_arguments(arguments)
{
connect(&m_process, &QProcess::readyReadStandardError,
this, &StdIOClient::readError);
connect(&m_process, &QProcess::readyReadStandardOutput,
this, &StdIOClient::readOutput);
connect(&m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
this, &StdIOClient::onProcessFinished);
m_process.setArguments(Utils::QtcProcess::splitArgs(m_arguments));
m_process.setProgram(m_executable);
}
StdIOClient::~StdIOClient()
{
Utils::SynchronousProcess::stopProcess(m_process);
}
bool StdIOClient::needsRestart(const StdIOSettings *settings)
{
return m_executable != settings->m_executable || m_arguments != settings->m_arguments;
}
bool StdIOClient::start()
{
m_process.start();
if (!m_process.waitForStarted() || m_process.state() != QProcess::Running) {
setError(m_process.errorString());
return false;
}
return true;
}
void StdIOClient::setWorkingDirectory(const QString &workingDirectory)
{
m_process.setWorkingDirectory(workingDirectory);
}
void StdIOClient::sendData(const QByteArray &data)
{
if (m_process.state() != QProcess::Running) {
log(tr("Cannot send data to unstarted server %1").arg(m_process.program()));
return;
}
qCDebug(LOGLSPCLIENTV) << "StdIOClient send data:";
qCDebug(LOGLSPCLIENTV).noquote() << data;
m_process.write(data);
}
void StdIOClient::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
if (exitStatus == QProcess::CrashExit)
setError(tr("Crashed with exit code %1: %2").arg(exitCode, m_process.error()));
emit finished();
}
void StdIOClient::readError()
{
qCDebug(LOGLSPCLIENTV) << "StdIOClient std err:\n";
qCDebug(LOGLSPCLIENTV).noquote() << m_process.readAllStandardError();
}
void StdIOClient::readOutput()
{
const QByteArray &out = m_process.readAllStandardOutput();
qDebug(LOGLSPCLIENTV) << "StdIOClient std out:\n";
qDebug(LOGLSPCLIENTV).noquote() << out;
parseData(out);
}
} // namespace LanguageClient

View File

@@ -56,12 +56,14 @@ namespace TextEditor
namespace LanguageClient {
class BaseClientInterface;
class BaseClient : public QObject
{
Q_OBJECT
public:
BaseClient();
explicit BaseClient(BaseClientInterface *clientInterface); // takes ownership
~BaseClient() override;
BaseClient(const BaseClient &) = delete;
@@ -124,8 +126,8 @@ public:
bool needsRestart(const BaseSettings *) const;
virtual bool start() { return true; }
virtual bool reset();
bool start();
bool reset();
void log(const QString &message,
Core::MessageManager::PrintToOutputPaneFlag flag = Core::MessageManager::NoModeSwitch);
@@ -143,8 +145,7 @@ signals:
protected:
void setError(const QString &message);
virtual void sendData(const QByteArray &data) = 0;
void parseData(const QByteArray &data);
void handleMessage(const LanguageServerProtocol::BaseMessage &message);
private:
void handleResponse(const LanguageServerProtocol::MessageId &id, const QByteArray &content,
@@ -168,7 +169,6 @@ private:
State m_state = Uninitialized;
QHash<LanguageServerProtocol::MessageId, LanguageServerProtocol::ResponseHandler> m_responseHandlers;
QHash<QByteArray, ContentHandler> m_contentHandler;
QBuffer m_buffer;
QString m_displayName;
LanguageFilter m_languagFilter;
QList<Utils::FileName> m_openedDocument;
@@ -177,41 +177,9 @@ private:
DynamicCapabilities m_dynamicCapabilities;
LanguageClientCompletionAssistProvider m_completionProvider;
QSet<TextEditor::TextDocument *> m_resetCompletionProvider;
LanguageServerProtocol::BaseMessage m_currentMessage;
QHash<LanguageServerProtocol::DocumentUri, LanguageServerProtocol::MessageId> m_highlightRequests;
int m_restartsLeft = 5;
};
class StdIOClient : public BaseClient
{
Q_OBJECT
public:
StdIOClient(const QString &executable, const QString &arguments);
~StdIOClient() override;
StdIOClient() = delete;
StdIOClient(const StdIOClient &) = delete;
StdIOClient(StdIOClient &&) = delete;
StdIOClient &operator=(const StdIOClient &) = delete;
StdIOClient &operator=(StdIOClient &&) = delete;
bool needsRestart(const StdIOSettings *settings);
bool start() override;
void setWorkingDirectory(const QString &workingDirectory);
protected:
void sendData(const QByteArray &data) final;
QProcess m_process;
private:
void readError();
void readOutput();
void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
const QString m_executable;
const QString m_arguments;
QScopedPointer<BaseClientInterface> m_clientInterface;
};
} // namespace LanguageClient

View File

@@ -7,6 +7,7 @@ HEADERS += \
dynamiccapabilities.h \
languageclient_global.h \
languageclientcodeassist.h \
languageclientinterface.h \
languageclientmanager.h \
languageclientoutline.h \
languageclientplugin.h \
@@ -18,6 +19,7 @@ SOURCES += \
baseclient.cpp \
dynamiccapabilities.cpp \
languageclientcodeassist.cpp \
languageclientinterface.cpp \
languageclientmanager.cpp \
languageclientoutline.cpp \
languageclientplugin.cpp \

View File

@@ -22,6 +22,8 @@ QtcPlugin {
"languageclient_global.h",
"languageclientcodeassist.cpp",
"languageclientcodeassist.h",
"languageclientinterface.cpp",
"languageclientinterface.h",
"languageclientmanager.cpp",
"languageclientmanager.h",
"languageclientoutline.cpp",

View File

@@ -0,0 +1,165 @@
/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "languageclientinterface.h"
#include "languageclientsettings.h"
#include <utils/qtcprocess.h>
#include <utils/synchronousprocess.h>
#include <QLoggingCategory>
using namespace LanguageServerProtocol;
static Q_LOGGING_CATEGORY(LOGLSPCLIENTV, "qtc.languageclient.messages", QtWarningMsg);
static Q_LOGGING_CATEGORY(LOGLSPCLIENTPARSE, "qtc.languageclient.parse", QtWarningMsg);
namespace LanguageClient {
BaseClientInterface::BaseClientInterface()
{
m_buffer.open(QIODevice::ReadWrite | QIODevice::Append);
}
BaseClientInterface::~BaseClientInterface()
{
m_buffer.close();
}
void BaseClientInterface::sendMessage(const BaseMessage &message)
{
sendData(message.toData());
}
void BaseClientInterface::resetBuffer()
{
m_buffer.close();
m_buffer.setData(nullptr);
m_buffer.open(QIODevice::ReadWrite | QIODevice::Append);
}
void BaseClientInterface::parseData(const QByteArray &data)
{
const qint64 preWritePosition = m_buffer.pos();
qCDebug(LOGLSPCLIENTPARSE) << "parse buffer pos: " << preWritePosition;
qCDebug(LOGLSPCLIENTPARSE) << " data: " << data;
if (!m_buffer.atEnd())
m_buffer.seek(preWritePosition + m_buffer.bytesAvailable());
m_buffer.write(data);
m_buffer.seek(preWritePosition);
while (!m_buffer.atEnd()) {
QString parseError;
BaseMessage::parse(&m_buffer, parseError, m_currentMessage);
qCDebug(LOGLSPCLIENTPARSE) << " complete: " << m_currentMessage.isComplete();
qCDebug(LOGLSPCLIENTPARSE) << " length: " << m_currentMessage.contentLength;
qCDebug(LOGLSPCLIENTPARSE) << " content: " << m_currentMessage.content;
if (!parseError.isEmpty())
emit error(parseError);
if (!m_currentMessage.isComplete())
break;
emit messageReceived(m_currentMessage);
m_currentMessage = BaseMessage();
}
if (m_buffer.atEnd()) {
m_buffer.close();
m_buffer.setData(nullptr);
m_buffer.open(QIODevice::ReadWrite | QIODevice::Append);
}
}
StdIOClientInterface::StdIOClientInterface(const QString &executable, const QString &arguments)
: m_executable(executable)
, m_arguments(arguments)
{
connect(&m_process, &QProcess::readyReadStandardError,
this, &StdIOClientInterface::readError);
connect(&m_process, &QProcess::readyReadStandardOutput,
this, &StdIOClientInterface::readOutput);
connect(&m_process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &StdIOClientInterface::onProcessFinished);
m_process.setArguments(Utils::QtcProcess::splitArgs(m_arguments));
m_process.setProgram(m_executable);
}
StdIOClientInterface::~StdIOClientInterface()
{
Utils::SynchronousProcess::stopProcess(m_process);
}
bool StdIOClientInterface::needsRestart(const StdIOSettings *settings)
{
return m_executable != settings->m_executable || m_arguments != settings->m_arguments;
}
bool StdIOClientInterface::start()
{
m_process.start();
if (!m_process.waitForStarted() || m_process.state() != QProcess::Running) {
emit error(m_process.errorString());
return false;
}
return true;
}
void StdIOClientInterface::setWorkingDirectory(const QString &workingDirectory)
{
m_process.setWorkingDirectory(workingDirectory);
}
void StdIOClientInterface::sendData(const QByteArray &data)
{
if (m_process.state() != QProcess::Running) {
emit error(tr("Cannot send data to unstarted server %1").arg(m_process.program()));
return;
}
qCDebug(LOGLSPCLIENTV) << "StdIOClient send data:";
qCDebug(LOGLSPCLIENTV).noquote() << data;
m_process.write(data);
}
void StdIOClientInterface::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
if (exitStatus == QProcess::CrashExit)
emit error(tr("Crashed with exit code %1: %2").arg(exitCode, m_process.error()));
emit finished();
}
void StdIOClientInterface::readError()
{
qCDebug(LOGLSPCLIENTV) << "StdIOClient std err:\n";
qCDebug(LOGLSPCLIENTV).noquote() << m_process.readAllStandardError();
}
void StdIOClientInterface::readOutput()
{
const QByteArray &out = m_process.readAllStandardOutput();
qCDebug(LOGLSPCLIENTV) << "StdIOClient std out:\n";
qCDebug(LOGLSPCLIENTV).noquote() << out;
parseData(out);
}
} // namespace LanguageClient

View File

@@ -0,0 +1,96 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#pragma once
#include <languageserverprotocol/basemessage.h>
#include <QBuffer>
#include <QProcess>
namespace LanguageClient {
class StdIOSettings;
class BaseClientInterface : public QObject
{
Q_OBJECT
public:
BaseClientInterface();
virtual ~BaseClientInterface();
void sendMessage(const LanguageServerProtocol::BaseMessage &message);
virtual bool start() { return true; }
void resetBuffer();
signals:
void messageReceived(LanguageServerProtocol::BaseMessage message);
void finished();
void error(const QString &message);
protected:
virtual void sendData(const QByteArray &data) = 0;
void parseData(const QByteArray &data);
private:
QBuffer m_buffer;
LanguageServerProtocol::BaseMessage m_currentMessage;
};
class StdIOClientInterface : public BaseClientInterface
{
Q_OBJECT
public:
StdIOClientInterface(const QString &executable, const QString &arguments);
~StdIOClientInterface() override;
StdIOClientInterface() = delete;
StdIOClientInterface(const StdIOClientInterface &) = delete;
StdIOClientInterface(StdIOClientInterface &&) = delete;
StdIOClientInterface &operator=(const StdIOClientInterface &) = delete;
StdIOClientInterface &operator=(StdIOClientInterface &&) = delete;
bool needsRestart(const StdIOSettings *settings);
bool start() override;
void setWorkingDirectory(const QString &workingDirectory);
protected:
void sendData(const QByteArray &data) final;
QProcess m_process;
private:
void readError();
void readOutput();
void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
const QString m_executable;
const QString m_arguments;
};
} // namespace LanguageClient

View File

@@ -28,6 +28,7 @@
#include "baseclient.h"
#include "languageclientmanager.h"
#include "languageclient_global.h"
#include "languageclientinterface.h"
#include <coreplugin/icore.h>
#include <utils/algorithm.h>
@@ -399,6 +400,13 @@ bool BaseSettings::isValid() const
BaseClient *BaseSettings::createClient() const
{
BaseClientInterface *interface = createInterface();
if (QTC_GUARD(interface)) {
auto *client = new BaseClient(interface);
client->setName(m_name);
client->setSupportedLanguage(m_languageFilter);
return client;
}
return nullptr;
}
@@ -467,8 +475,8 @@ bool StdIOSettings::needsRestart() const
{
if (BaseSettings::needsRestart())
return true;
if (auto stdIOClient = qobject_cast<StdIOClient *>(m_client))
return stdIOClient->needsRestart(this);
if (auto stdIOInterface = qobject_cast<StdIOClientInterface *>(m_client))
return stdIOInterface->needsRestart(this);
return false;
}
@@ -477,14 +485,6 @@ bool StdIOSettings::isValid() const
return BaseSettings::isValid() && !m_executable.isEmpty();
}
BaseClient *StdIOSettings::createClient() const
{
auto client = new StdIOClient(m_executable, m_arguments);
client->setName(m_name);
client->setSupportedLanguage(m_languageFilter);
return client;
}
QVariantMap StdIOSettings::toMap() const
{
QVariantMap map = BaseSettings::toMap();
@@ -500,6 +500,11 @@ void StdIOSettings::fromMap(const QVariantMap &map)
m_arguments = map[argumentsKey].toString();
}
BaseClientInterface *StdIOSettings::createInterface() const
{
return new StdIOClientInterface(m_executable, m_arguments);
}
BaseSettingsWidget::BaseSettingsWidget(const BaseSettings *settings, QWidget *parent)
: QWidget(parent)
, m_name(new QLineEdit(settings->m_name, this))

View File

@@ -44,6 +44,7 @@ namespace LanguageClient {
constexpr char noLanguageFilter[] = "No Filter";
class BaseClient;
class BaseClientInterface;
struct LanguageFilter
{
@@ -73,11 +74,13 @@ public:
virtual BaseSettings *copy() const { return new BaseSettings(*this); }
virtual bool needsRestart() const;
virtual bool isValid() const ;
virtual BaseClient *createClient() const;
BaseClient *createClient() const;
virtual QVariantMap toMap() const;
virtual void fromMap(const QVariantMap &map);
protected:
virtual BaseClientInterface *createInterface() const { return nullptr; }
BaseSettings(const BaseSettings &other) = default;
BaseSettings(BaseSettings &&other) = default;
BaseSettings &operator=(const BaseSettings &other) = default;
@@ -105,11 +108,12 @@ public:
BaseSettings *copy() const override { return new StdIOSettings(*this); }
bool needsRestart() const override;
bool isValid() const override;
BaseClient *createClient() const override;
QVariantMap toMap() const override;
void fromMap(const QVariantMap &map) override;
protected:
BaseClientInterface *createInterface() const override;
StdIOSettings(const StdIOSettings &other) = default;
StdIOSettings(StdIOSettings &&other) = default;
StdIOSettings &operator=(const StdIOSettings &other) = default;