From 05b1d9cf68d385d23f9b2a38d213041882d14c8c Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Mon, 22 Jul 2024 16:32:38 +0200 Subject: [PATCH] LuaLSP: Make init Options dynamic Allows a LSP plugin to dynamically configure the initializationOptions Change-Id: Idc5d6526a732512b2e4e7394fb1c8b5749cde25d Reviewed-by: Christian Stenger --- .../lualanguageclient/lualanguageclient.cpp | 72 ++++++++++++++----- src/plugins/lua/meta/lsp.lua | 2 +- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp b/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp index 2898e835e9a..faef1f0bf59 100644 --- a/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp +++ b/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp @@ -177,8 +177,10 @@ class LuaClientWrapper; class LuaClientSettings : public BaseSettings { std::weak_ptr m_wrapper; + QObject guard; public: + LuaClientSettings(const LuaClientSettings &wrapper); LuaClientSettings(const std::weak_ptr &wrapper); ~LuaClientSettings() override = default; @@ -200,9 +202,11 @@ enum class TransportType { StdIO, LocalSocket }; class LuaClientWrapper : public QObject { + Q_OBJECT public: TransportType m_transportType{TransportType::StdIO}; std::function(CommandLine &)> m_cmdLineCallback; + std::function(QString &)> m_initOptionsCallback; AspectContainer *m_aspects{nullptr}; QString m_name; Utils::Id m_settingsTypeId; @@ -242,6 +246,23 @@ public: return cmdFromTable(res.get()); }); + m_initOptionsCallback = addValue( + options, + "initializationOptions", + m_initializationOptions, + [](const sol::protected_function_result &res) -> expected_str { + if (res.get_type(0) == sol::type::table) + return ::Lua::LuaEngine::toJsonString(res.get()); + else if (res.get_type(0) == sol::type::string) + return res.get(0); + return make_unexpected(QString("init callback did not return a table or string")); + }); + + if (auto initOptionsTable = options.get>("initializationOptions")) + m_initializationOptions = ::Lua::LuaEngine::toJsonString(*initOptionsTable); + else if (auto initOptionsString = options.get>("initializationOptions")) + m_initializationOptions = *initOptionsString; + m_name = options.get("name"); m_settingsTypeId = Utils::Id::fromString(QString("Lua_%1").arg(m_name)); m_serverName = options.get_or("serverName", ""); @@ -273,22 +294,6 @@ public: m_languageFilter.mimeTypes.push_back(v.as()); } - auto initOptionsTable = options.get>("initializationOptions"); - if (initOptionsTable) { - QJsonValue json = ::Lua::LuaEngine::toJson(*initOptionsTable); - QJsonDocument doc; - if (json.isArray()) { - doc.setArray(json.toArray()); - m_initializationOptions = QString::fromUtf8(doc.toJson()); - } else if (json.isObject()) { - doc.setObject(json.toObject()); - m_initializationOptions = QString::fromUtf8(doc.toJson()); - } - } - auto initOptionsString = options.get>("initializationOptions"); - if (initOptionsString) - m_initializationOptions = *initOptionsString; - // get> because on MSVC, get_or(..., nullptr) fails to compile m_aspects = options.get>("settings").value_or(nullptr); @@ -461,6 +466,16 @@ public: if (!result) qWarning() << "Error applying option callback:" << result.error(); } + if (m_initOptionsCallback) { + expected_str result = m_initOptionsCallback(m_initializationOptions); + if (!result) + qWarning() << "Error applying init option callback:" << result.error(); + + // Right now there is only one option that needs to be mirrored to the LSP Settings, + // so we only emit optionsChanged() here. If another setting should need to be dynamic + // optionsChanged() needs to be called for it as well, but only once per updateOptions() + emit optionsChanged(); + } } static CommandLine cmdFromTable(const sol::table &tbl) @@ -528,8 +543,23 @@ public: } return nullptr; } + +signals: + void optionsChanged(); }; +LuaClientSettings::LuaClientSettings(const LuaClientSettings &other) + : BaseSettings::BaseSettings(other) + , m_wrapper(other.m_wrapper) +{ + if (auto w = m_wrapper.lock()) { + QObject::connect(w.get(), &LuaClientWrapper::optionsChanged, &guard, [this] { + if (auto w = m_wrapper.lock()) + m_initializationOptions = w->m_initializationOptions; + }); + } +} + LuaClientSettings::LuaClientSettings(const std::weak_ptr &wrapper) : m_wrapper(wrapper) { @@ -539,6 +569,10 @@ LuaClientSettings::LuaClientSettings(const std::weak_ptr &wrap m_languageFilter = w->m_languageFilter; m_initializationOptions = w->m_initializationOptions; m_startBehavior = w->m_startBehavior; + QObject::connect(w.get(), &LuaClientWrapper::optionsChanged, &guard, [this] { + if (auto w = m_wrapper.lock()) + m_initializationOptions = w->m_initializationOptions; + }); } } @@ -548,7 +582,8 @@ bool LuaClientSettings::applyFromSettingsWidget(QWidget *widget) if (auto w = m_wrapper.lock()) { w->m_name = m_name; - w->m_initializationOptions = m_initializationOptions; + if (!w->m_initOptionsCallback) + w->m_initializationOptions = m_initializationOptions; w->m_languageFilter = m_languageFilter; w->m_startBehavior = m_startBehavior; w->applySettings(); @@ -570,7 +605,8 @@ void LuaClientSettings::fromMap(const Utils::Store &map) BaseSettings::fromMap(map); if (auto w = m_wrapper.lock()) { w->m_name = m_name; - w->m_initializationOptions = m_initializationOptions; + if (!w->m_initOptionsCallback) + w->m_initializationOptions = m_initializationOptions; w->m_languageFilter = m_languageFilter; w->m_startBehavior = m_startBehavior; w->fromMap(map); diff --git a/src/plugins/lua/meta/lsp.lua b/src/plugins/lua/meta/lsp.lua index 7c97ec2954e..73b664cf761 100644 --- a/src/plugins/lua/meta/lsp.lua +++ b/src/plugins/lua/meta/lsp.lua @@ -10,7 +10,7 @@ local lsp = {} ---@field serverName? string The socket path when transport == "localsocket". ---@field languageFilter LanguageFilter The language filter deciding which files to open with the language server. ---@field startBehavior? "AlwaysOn"|"RequiresFile"|"RequiresProject" ----@field initializationOptions? table|string The initialization options to pass to the language server, either a JSON string, or a table. +---@field initializationOptions? function|table|string The initialization options to pass to the language server, either a JSON string, a table, or a function that returns either. ---@field settings? AspectContainer ---@field onStartFailed? function This callback is called when client failed to start. local ClientOptions = {}