diff --git a/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp b/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp index bcd9ade763f..900ce75b30e 100644 --- a/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp +++ b/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp @@ -1,9 +1,12 @@ // Copyright (C) 2024 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#include + #include #include #include +#include #include #include @@ -27,6 +30,19 @@ namespace LanguageClient::Lua { static void registerLuaApi(); +class LuaClient : public LanguageClient::Client +{ + Q_OBJECT + +public: + Utils::Id m_settingsId; + + LuaClient(BaseClientInterface *interface, Utils::Id settingsId) + : LanguageClient::Client(interface) + , m_settingsId(settingsId) + {} +}; + class LuaLanguageClientPlugin final : public ExtensionSystem::IPlugin { Q_OBJECT @@ -135,6 +151,8 @@ public: BaseSettings *copy() const override { return new LuaClientSettings(*this); } protected: + Client *createClient(BaseClientInterface *interface) const final; + BaseClientInterface *createInterface(ProjectExplorer::Project *project) const override; }; enum class TransportType { StdIO, LocalSocket }; @@ -154,6 +172,7 @@ public: BaseSettings::StartBehavior m_startBehavior = BaseSettings::RequiresFile; std::optional m_onInstanceStart; + std::optional m_startFailedCallback; QMap m_messageCallbacks; QList m_clients; @@ -190,6 +209,8 @@ public: m_startBehavior = startBehaviorFromString( options.get_or("startBehavior", "AlwaysOn")); + m_startFailedCallback = options.get("onStartFailed"); + QString transportType = options.get_or("transport", "stdio"); if (transportType == "stdio") m_transportType = TransportType::StdIO; @@ -231,35 +252,45 @@ public: // get> because on MSVC, get_or(..., nullptr) fails to compile m_aspects = options.get>("settings").value_or(nullptr); + if (m_aspects) { + connect(m_aspects, &AspectContainer::applied, this, [this] { + updateOptions(); + LanguageClientManager::applySettings(); + }); + } + connect( LanguageClientManager::instance(), &LanguageClientManager::clientInitialized, this, [this](Client *c) { - if (m_onInstanceStart) { - if (auto settings = LanguageClientManager::settingForClient(c)) { - if (settings->m_settingsTypeId == m_settingsTypeId) { - auto result = m_onInstanceStart->call(); + auto luaClient = qobject_cast(c); + if (luaClient && luaClient->m_settingsId == m_settingsTypeId && m_onInstanceStart) { + QTC_CHECK(::Lua::LuaEngine::void_safe_call(*m_onInstanceStart, c)); - if (!result.valid()) { - qWarning() << "Error calling instance start callback:" - << (result.get().what()); - } - - m_clients.push_back(c); - updateMessageCallbacks(); - } - } + m_clients.push_back(c); + updateMessageCallbacks(); } }); + connect( LanguageClientManager::instance(), &LanguageClientManager::clientRemoved, this, - [this](Client *c) { - if (m_clients.contains(c)) - m_clients.removeOne(c); - }); + &LuaClientWrapper::onClientRemoved); + } + + void onClientRemoved(Client *c, bool unexpected) + { + auto luaClient = qobject_cast(c); + if (!luaClient || luaClient->m_settingsId != m_settingsTypeId) + return; + + if (m_clients.contains(c)) + m_clients.removeOne(c); + + if (unexpected && m_startFailedCallback) + sol::protected_function_result result = m_startFailedCallback->call(); } // TODO: Unregister Client settings from LanguageClientManager @@ -457,6 +488,12 @@ QWidget *LuaClientSettings::createSettingsWidget(QWidget *parent) const return new BaseSettingsWidget(this, parent); } +Client *LuaClientSettings::createClient(BaseClientInterface *interface) const +{ + Client *client = new LuaClient(interface, m_settingsTypeId); + return client; +} + BaseClientInterface *LuaClientSettings::createInterface(ProjectExplorer::Project *project) const { if (auto w = m_wrapper.lock()) diff --git a/src/plugins/lua/meta/lsp.lua b/src/plugins/lua/meta/lsp.lua index 65b1ef8361b..1c404c167f6 100644 --- a/src/plugins/lua/meta/lsp.lua +++ b/src/plugins/lua/meta/lsp.lua @@ -11,6 +11,7 @@ local lsp = {} ---@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 settings? AspectContainer +---@field onStartFailed? function This callback is called when client failed to start. local ClientOptions = {} ---@class LanguageFilter diff --git a/src/plugins/lualsp/lualsp/init.lua b/src/plugins/lualsp/lualsp/init.lua index 75162c95c00..06d34f86ccb 100644 --- a/src/plugins/lualsp/lualsp/init.lua +++ b/src/plugins/lualsp/lualsp/init.lua @@ -24,6 +24,30 @@ local function createCommand() return cmd end +local function installServer() + print("Lua Language Server not found, installing ...") + local cmds = { + mac = "brew install lua-language-server", + windows = "winget install lua-language-server", + linux = "sudo apt install lua-language-server" + } + + if a.wait(Process.runInTerminal(cmds[Utils.HostOsInfo.os])) == 0 then + print("Lua Language Server installed!") + local binary = a.wait(Utils.FilePath.fromUserInput("lua-language-server"):searchInPath()) + if binary:isExecutableFile() == false then + mm.writeFlashing("Lua Language Server was installed, but I could not find it in your PATH") + return false + end + + Settings.binary.value = binary:toUserOutput() + Settings:apply() + return true + end + + mm.writeFlashing("Lua Language Server installation failed!") + return false +end local function setupClient() Client = LSP.Client.create({ name = 'Lua Language Server', @@ -35,6 +59,11 @@ local function setupClient() }, settings = Settings, startBehavior = "RequiresFile", + onStartFailed = function(ask) + a.sync(function() + installServer() + end)() + end }) Client.on_instance_start = function() @@ -46,24 +75,6 @@ local function setupClient() end) end -local function installServer() - print("Lua Language Server not found, installing ...") - local cmds = { - mac = "brew install lua-language-server", - windows = "winget install lua-language-server", - linux = "sudo apt install lua-language-server" - } - if a.wait(Process.runInTerminal(cmds[Utils.HostOsInfo.os])) == 0 then - print("Lua Language Server installed!") - Settings.binary.defaultPath = Utils.FilePath.fromUserInput("lua-language-server"):resolveSymlinks() - Settings:apply() - return true - end - - print("Lua Language Server installation failed!") - return false -end - local function using(tbl) local result = _G for k, v in pairs(tbl) do result[k] = v end @@ -112,8 +123,14 @@ local function setupAspect() labelText = "Binary:", toolTip = "The path to the lua-language-server binary.", expectedKind = S.Kind.ExistingCommand, - defaultPath = Utils.FilePath.fromUserInput("lua-language-server"):resolveSymlinks(), + defaultPath = Utils.FilePath.fromUserInput("lua-language-server"), }) + -- Search for the binary in the PATH + local serverPath = Settings.binary.defaultPath + local absolute = a.wait(serverPath:searchInPath()):resolveSymlinks() + if absolute:isExecutableFile() == true then + Settings.binary.defaultPath = absolute + end Settings.developMode = S.BoolAspect.create({ settingsKey = "LuaCopilot.DevelopMode", displayName = "Enable Develop Mode", @@ -143,15 +160,7 @@ local function setupAspect() return Settings end local function setup(parameters) - print("Setting up Lua Language Server ...") setupAspect() - local serverPath = Utils.FilePath.fromUserInput("lua-language-server") - local absolute = a.wait(serverPath:searchInPath()):resolveSymlinks() - if absolute:isExecutableFile() == true then - Settings.binary.defaultPath = absolute - else - installServer() - end setupClient() end diff --git a/src/plugins/lualsp/lualsp/lualsp.lua b/src/plugins/lualsp/lualsp/lualsp.lua index ba2de874144..843d18189e4 100644 --- a/src/plugins/lualsp/lualsp/lualsp.lua +++ b/src/plugins/lualsp/lualsp/lualsp.lua @@ -21,10 +21,4 @@ It will try to install it if it is not found. setup = function() require 'init'.setup() end, - hooks = { - editors = { - documentOpened = function(doc) print("documentOpened", doc) end, - documentClosed = function(doc) print("documentClosed", doc) end, - } - } } --[[@as QtcPlugin]]