Skip to content

Commit

Permalink
Lua LSP: Add callback if server fails to start
Browse files Browse the repository at this point in the history
Change-Id: I422baeffff96cf56a110cbf74716352c9104c5ef
Reviewed-by: David Schulz <david.schulz@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
  • Loading branch information
Maddimax committed May 16, 2024
1 parent 29822d1 commit 898203a
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 51 deletions.
73 changes: 55 additions & 18 deletions src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp
Original file line number Diff line number Diff line change
@@ -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 <coreplugin/editormanager/editormanager.h>

#include <languageclient/languageclientinterface.h>
#include <languageclient/languageclientmanager.h>
#include <languageclient/languageclientsettings.h>
#include <languageclient/languageclienttr.h>

#include <lua/bindings/inheritance.h>
#include <lua/luaengine.h>
Expand All @@ -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
Expand Down Expand Up @@ -135,6 +151,8 @@ class LuaClientSettings : public BaseSettings
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 };
Expand All @@ -154,6 +172,7 @@ class LuaClientWrapper : public QObject
BaseSettings::StartBehavior m_startBehavior = BaseSettings::RequiresFile;

std::optional<sol::protected_function> m_onInstanceStart;
std::optional<sol::protected_function> m_startFailedCallback;
QMap<QString, sol::protected_function> m_messageCallbacks;

QList<Client *> m_clients;
Expand Down Expand Up @@ -190,6 +209,8 @@ class LuaClientWrapper : public QObject
m_startBehavior = startBehaviorFromString(
options.get_or<QString>("startBehavior", "AlwaysOn"));

m_startFailedCallback = options.get<sol::protected_function>("onStartFailed");

QString transportType = options.get_or<QString>("transport", "stdio");
if (transportType == "stdio")
m_transportType = TransportType::StdIO;
Expand Down Expand Up @@ -231,35 +252,45 @@ class LuaClientWrapper : public QObject
// get<sol::optional<>> because on MSVC, get_or(..., nullptr) fails to compile
m_aspects = options.get<sol::optional<AspectContainer *>>("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();

if (!result.valid()) {
qWarning() << "Error calling instance start callback:"
<< (result.get<sol::error>().what());
}

m_clients.push_back(c);
updateMessageCallbacks();
}
}
auto luaClient = qobject_cast<LuaClient *>(c);
if (luaClient && luaClient->m_settingsId == m_settingsTypeId && m_onInstanceStart) {
QTC_CHECK(::Lua::LuaEngine::void_safe_call(*m_onInstanceStart, c));

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<LuaClient *>(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
Expand Down Expand Up @@ -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())
Expand Down
1 change: 1 addition & 0 deletions src/plugins/lua/meta/lsp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
63 changes: 36 additions & 27 deletions src/plugins/lualsp/lualsp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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()
Expand All @@ -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
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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

Expand Down
6 changes: 0 additions & 6 deletions src/plugins/lualsp/lualsp/lualsp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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]]

0 comments on commit 898203a

Please sign in to comment.