Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 5c119e40 authored by Mark Nauwelaerts's avatar Mark Nauwelaerts

lspclient: load server configuration from external json configuration file

parent 3b636300
......@@ -51,12 +51,21 @@ LSPClientConfigPage::LSPClientConfigPage(QWidget *parent, LSPClientPlugin *plugi
m_complDoc = new QCheckBox(i18n("Show selected completion documentation"));
top->addWidget(m_complDoc);
layout->addWidget(outlineBox);
outlineBox = new QGroupBox(i18n("Server Configuration"), this);
top = new QVBoxLayout(outlineBox);
m_configPath = new KUrlRequester(this);
top->addWidget(m_configPath);
layout->addWidget(outlineBox);
layout->addStretch(1);
reset();
for (const auto & cb : {m_symbolDetails, m_symbolExpand, m_symbolSort, m_symbolTree, m_complDoc})
connect(cb, &QCheckBox::toggled, this, &LSPClientConfigPage::changed);
connect(m_configPath, &KUrlRequester::textChanged, this, &LSPClientConfigPage::changed);
connect(m_configPath, &KUrlRequester::urlSelected, this, &LSPClientConfigPage::changed);
}
QString LSPClientConfigPage::name() const
......@@ -83,6 +92,8 @@ void LSPClientConfigPage::apply()
m_plugin->m_complDoc = m_complDoc->isChecked();
m_plugin->m_configPath = m_configPath->url();
m_plugin->writeConfig();
}
......@@ -94,6 +105,8 @@ void LSPClientConfigPage::reset()
m_symbolSort->setChecked(m_plugin->m_symbolSort);
m_complDoc->setChecked(m_plugin->m_complDoc);
m_configPath->setUrl(m_plugin->m_configPath);
}
void LSPClientConfigPage::defaults()
......
......@@ -53,6 +53,7 @@ class LSPClientConfigPage : public KTextEditor::ConfigPage
QCheckBox* m_symbolTree;
QCheckBox* m_symbolSort;
QCheckBox* m_complDoc;
KUrlRequester *m_configPath;
LSPClientPlugin *m_plugin;
};
......
......@@ -68,14 +68,7 @@ void LSPClientPlugin::readConfig()
m_symbolExpand = config.readEntry(QStringLiteral("SymbolExpand"), true);
m_symbolSort = config.readEntry(QStringLiteral("SymbolSort"), false);
m_complDoc = config.readEntry(QStringLiteral("CompletionDocumentation"), true);
// TODO properly read/write from/to config
m_serverCmds = QMap<QString, QString> {
{ QStringLiteral("Python"),
QStringLiteral("python3 -m pyls --check-parent-process") },
{ QStringLiteral("C"), QStringLiteral("clangd -log=verbose --background-index") },
{ QStringLiteral("C++"), QStringLiteral("clangd -log=verbose --background-index") }
};
m_configPath = config.readEntry(QStringLiteral("ServerConfiguration"), QUrl());
emit update();
}
......@@ -88,6 +81,7 @@ void LSPClientPlugin::writeConfig() const
config.writeEntry(QStringLiteral("SymbolExpand"), m_symbolExpand);
config.writeEntry(QStringLiteral("SymbolSort"), m_symbolSort);
config.writeEntry(QStringLiteral("CompletionDocumentation"), m_complDoc);
config.writeEntry(QStringLiteral("ServerConfiguration"), m_configPath);
emit update();
}
......
......@@ -27,8 +27,6 @@
#include <KTextEditor/Plugin>
class KDirWatch;
class LSPClientPlugin : public KTextEditor::Plugin
{
Q_OBJECT
......@@ -51,8 +49,9 @@ class LSPClientPlugin : public KTextEditor::Plugin
bool m_symbolTree;
bool m_symbolSort;
bool m_complDoc;
QMap<QString, QString> m_serverCmds;
QUrl m_configPath;
private:
Q_SIGNALS:
// signal settings update
void update() const;
......
......@@ -156,6 +156,8 @@ class LSPClientServer::LSPClientServerPrivate
QStringList m_server;
// workspace root to pass along
QUrl m_root;
// user provided init
QJsonValue m_init;
// server process
QProcess m_sproc;
// server declared capabilites
......@@ -170,8 +172,9 @@ class LSPClientServer::LSPClientServerPrivate
QHash<int, GenericReplyHandler> m_handlers;
public:
LSPClientServerPrivate(LSPClientServer * _q, const QStringList & server, const QUrl & root)
: q(_q), m_server(server), m_root(root)
LSPClientServerPrivate(LSPClientServer * _q, const QStringList & server,
const QUrl & root, const QJsonValue & init)
: q(_q), m_server(server), m_root(root), m_init(init)
{
// setup async reading
QObject::connect(&m_sproc, &QProcess::readyRead, mem_fun(&self_type::read, this));
......@@ -415,7 +418,8 @@ private:
{ QStringLiteral("processId"), QCoreApplication::applicationPid() },
{ QStringLiteral("rootPath"), m_root.path() },
{ QStringLiteral("rootUri"), m_root.toString() },
{ QStringLiteral("capabilities"), capabilities }
{ QStringLiteral("capabilities"), capabilities },
{ QStringLiteral("initializationOptions"), m_init }
};
//
write(init_request(QStringLiteral("initialize"), params),
......@@ -757,8 +761,9 @@ static GenericReplyHandler make_handler(const ReplyHandler<ReplyType> & h,
}
LSPClientServer::LSPClientServer(const QStringList & server, const QUrl & root)
: d(new LSPClientServerPrivate(this, server, root))
LSPClientServer::LSPClientServer(const QStringList & server, const QUrl & root,
const QJsonValue & init)
: d(new LSPClientServerPrivate(this, server, root, init))
{}
LSPClientServer::~LSPClientServer()
......
......@@ -26,6 +26,7 @@
#include <QList>
#include <QVector>
#include <QPointer>
#include <QJsonValue>
#include <functional>
......@@ -274,7 +275,8 @@ public:
}
};
LSPClientServer(const QStringList & server, const QUrl & root);
LSPClientServer(const QStringList & server, const QUrl & root,
const QJsonValue & init = QJsonValue());
~LSPClientServer();
// server management
......
......@@ -29,6 +29,33 @@
#include <QTimer>
#include <QEventLoop>
#include <QDir>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonDocument>
// local helper;
// recursively merge top json top onto bottom json
static QJsonObject
merge(const QJsonObject & bottom, const QJsonObject & top)
{
QJsonObject result;
for (auto item = top.begin(); item != top.end(); item++) {
const auto & key = item.key();
if (item.value().isObject()) {
result.insert(key, merge(bottom.value(key).toObject(), item.value().toObject()));
} else {
result.insert(key, item.value());
}
}
// parts only in bottom
for (auto item = bottom.begin(); item != bottom.end(); item++) {
if (!result.contains(item.key())) {
result.insert(item.key(), item.value());
}
}
return result;
}
// helper class to sync document changes to LSP server
class LSPClientServerManagerImpl : public LSPClientServerManager
......@@ -47,6 +74,8 @@ class LSPClientServerManagerImpl : public LSPClientServerManager
LSPClientPlugin *m_plugin;
KTextEditor::MainWindow *m_mainWindow;
// merged default and user config
QJsonObject m_serverConfig;
// root -> (mode -> server)
QMap<QUrl, QMap<QString, QSharedPointer<LSPClientServer>>> m_servers;
QHash<KTextEditor::Document*, DocumentInfo> m_docs;
......@@ -56,7 +85,10 @@ class LSPClientServerManagerImpl : public LSPClientServerManager
public:
LSPClientServerManagerImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin)
: m_plugin(plugin) , m_mainWindow(mainWin)
{}
{
connect(plugin, &LSPClientPlugin::update, this, &self_type::updateServerConfig);
QTimer::singleShot(100, this, &self_type::updateServerConfig);
}
~LSPClientServerManagerImpl()
{
......@@ -224,17 +256,67 @@ private:
_findServer(KTextEditor::Document *document)
{
QObject *projectView = m_mainWindow->pluginView(QStringLiteral("kateprojectplugin"));
const auto projectRoot = projectView ? projectView->property("projectBaseDir").toString() : QString();
const auto root = QUrl::fromLocalFile(projectRoot);
const auto projectBase = QDir(projectView ? projectView->property("projectBaseDir").toString() : QString());
const auto& projectMap = projectView ? projectView->property("projectMap").toMap() : QVariantMap();
auto mode = document->highlightingMode();
qCInfo(LSPCLIENT) << "mode" << mode;
// merge with project specific
auto projectConfig = QJsonDocument::fromVariant(projectMap).object().value(QStringLiteral("lspclient")).toObject();
auto serverConfig = merge(m_serverConfig, projectConfig);
// locate server config
QJsonValue config;
QSet<QString> used;
while (true) {
qCInfo(LSPCLIENT) << "mode " << mode;
used << mode;
config = serverConfig.value(QStringLiteral("servers")).toObject().value(mode);
if (config.isObject()) {
const auto & base = config.toObject().value(QStringLiteral("use")).toString();
// basic cycle detection
if (!base.isEmpty() && !used.contains(base)) {
mode = base;
continue;
}
}
break;
}
if (!config.isObject())
return nullptr;
// merge global settings
serverConfig = merge(serverConfig.value(QStringLiteral("global")).toObject(), config.toObject());
QString rootpath;
auto rootv = serverConfig.value(QStringLiteral("root"));
if (rootv.isString()) {
auto sroot = rootv.toString();
if (QDir::isAbsolutePath(sroot)) {
rootpath = sroot;
} else if (!projectBase.isEmpty()) {
rootpath = QDir(projectBase).absoluteFilePath(sroot);
}
}
if (rootpath.isEmpty()) {
rootpath = QDir::homePath();
}
auto root = QUrl::fromLocalFile(rootpath);
auto server = m_servers.value(root).value(mode);
if (!server) {
auto servercmd = m_plugin->m_serverCmds.value(mode);
if (servercmd.length() > 0) {
auto cmdline = servercmd.split(QStringLiteral(" "));
server.reset(new LSPClientServer(cmdline, root));
QStringList cmdline;
auto vcmdline = serverConfig.value(QStringLiteral("command"));
if (vcmdline.isString()) {
cmdline = vcmdline.toString().split(QLatin1Char(' '));
} else {
for (const auto& c : vcmdline.toArray()) {
cmdline.push_back(c.toString());
}
}
if (cmdline.length() > 0) {
auto&& init = serverConfig.value(QStringLiteral("initializationOptions"));
server.reset(new LSPClientServer(cmdline, root, init));
m_servers[root][mode] = server;
connect(server.get(), &LSPClientServer::stateChanged,
this, &self_type::onStateChanged, Qt::UniqueConnection);
......@@ -247,6 +329,55 @@ private:
return (server && server->state() == LSPClientServer::State::Running) ? server : nullptr;
}
void updateServerConfig()
{
// default configuration
auto makeServerConfig = [] (const QString & cmdline) {
return QJsonObject {
{ QStringLiteral("command"), cmdline }
};
};
static auto defaultConfig = QJsonObject {
{ QStringLiteral("servers"),
QJsonObject {
{ QStringLiteral("Python"),
makeServerConfig(QStringLiteral("python3 -m pyls --check-parent-process")) },
{ QStringLiteral("C"),
makeServerConfig(QStringLiteral("clangd -log=verbose --background-index")) },
{ QStringLiteral("C++"),
QJsonObject { { QStringLiteral("use"), QStringLiteral("C") } } }
}
}
};
m_serverConfig = defaultConfig;
// consider specified configuration
const auto& configPath = m_plugin->m_configPath.path();
if (!configPath.isEmpty()) {
QFile f(configPath);
if (f.open(QIODevice::ReadOnly)) {
auto data = f.readAll();
auto json = QJsonDocument::fromJson(data);
if (json.isObject()) {
m_serverConfig = merge(m_serverConfig, json.object());
} else {
showMessage(i18n("Failed to parse server configuration: %1", configPath),
KTextEditor::Message::Error);
}
} else {
showMessage(i18n("Failed to read server configuration: %1", configPath),
KTextEditor::Message::Error);
}
}
// we could (but do not) perform restartAll here;
// for now let's leave that up to user
// but maybe we do have a server now where not before, so let's signal
emit serverChanged();
}
void trackDocument(KTextEditor::Document *doc, QSharedPointer<LSPClientServer> server)
{
auto it = m_docs.find(doc);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment