forked from qt-creator/qt-creator
Lua LSP: Add callback if server fails to start
Change-Id: I422baeffff96cf56a110cbf74716352c9104c5ef Reviewed-by: David Schulz <david.schulz@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
// Copyright (C) 2024 The Qt Company Ltd.
|
// Copyright (C) 2024 The Qt Company Ltd.
|
||||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
// 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/languageclientinterface.h>
|
||||||
#include <languageclient/languageclientmanager.h>
|
#include <languageclient/languageclientmanager.h>
|
||||||
#include <languageclient/languageclientsettings.h>
|
#include <languageclient/languageclientsettings.h>
|
||||||
|
#include <languageclient/languageclienttr.h>
|
||||||
|
|
||||||
#include <lua/bindings/inheritance.h>
|
#include <lua/bindings/inheritance.h>
|
||||||
#include <lua/luaengine.h>
|
#include <lua/luaengine.h>
|
||||||
@@ -27,6 +30,19 @@ namespace LanguageClient::Lua {
|
|||||||
|
|
||||||
static void registerLuaApi();
|
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
|
class LuaLanguageClientPlugin final : public ExtensionSystem::IPlugin
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -135,6 +151,8 @@ public:
|
|||||||
BaseSettings *copy() const override { return new LuaClientSettings(*this); }
|
BaseSettings *copy() const override { return new LuaClientSettings(*this); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
Client *createClient(BaseClientInterface *interface) const final;
|
||||||
|
|
||||||
BaseClientInterface *createInterface(ProjectExplorer::Project *project) const override;
|
BaseClientInterface *createInterface(ProjectExplorer::Project *project) const override;
|
||||||
};
|
};
|
||||||
enum class TransportType { StdIO, LocalSocket };
|
enum class TransportType { StdIO, LocalSocket };
|
||||||
@@ -154,6 +172,7 @@ public:
|
|||||||
BaseSettings::StartBehavior m_startBehavior = BaseSettings::RequiresFile;
|
BaseSettings::StartBehavior m_startBehavior = BaseSettings::RequiresFile;
|
||||||
|
|
||||||
std::optional<sol::protected_function> m_onInstanceStart;
|
std::optional<sol::protected_function> m_onInstanceStart;
|
||||||
|
std::optional<sol::protected_function> m_startFailedCallback;
|
||||||
QMap<QString, sol::protected_function> m_messageCallbacks;
|
QMap<QString, sol::protected_function> m_messageCallbacks;
|
||||||
|
|
||||||
QList<Client *> m_clients;
|
QList<Client *> m_clients;
|
||||||
@@ -190,6 +209,8 @@ public:
|
|||||||
m_startBehavior = startBehaviorFromString(
|
m_startBehavior = startBehaviorFromString(
|
||||||
options.get_or<QString>("startBehavior", "AlwaysOn"));
|
options.get_or<QString>("startBehavior", "AlwaysOn"));
|
||||||
|
|
||||||
|
m_startFailedCallback = options.get<sol::protected_function>("onStartFailed");
|
||||||
|
|
||||||
QString transportType = options.get_or<QString>("transport", "stdio");
|
QString transportType = options.get_or<QString>("transport", "stdio");
|
||||||
if (transportType == "stdio")
|
if (transportType == "stdio")
|
||||||
m_transportType = TransportType::StdIO;
|
m_transportType = TransportType::StdIO;
|
||||||
@@ -231,35 +252,45 @@ public:
|
|||||||
// get<sol::optional<>> because on MSVC, get_or(..., nullptr) fails to compile
|
// get<sol::optional<>> because on MSVC, get_or(..., nullptr) fails to compile
|
||||||
m_aspects = options.get<sol::optional<AspectContainer *>>("settings").value_or(nullptr);
|
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(
|
connect(
|
||||||
LanguageClientManager::instance(),
|
LanguageClientManager::instance(),
|
||||||
&LanguageClientManager::clientInitialized,
|
&LanguageClientManager::clientInitialized,
|
||||||
this,
|
this,
|
||||||
[this](Client *c) {
|
[this](Client *c) {
|
||||||
if (m_onInstanceStart) {
|
auto luaClient = qobject_cast<LuaClient *>(c);
|
||||||
if (auto settings = LanguageClientManager::settingForClient(c)) {
|
if (luaClient && luaClient->m_settingsId == m_settingsTypeId && m_onInstanceStart) {
|
||||||
if (settings->m_settingsTypeId == m_settingsTypeId) {
|
QTC_CHECK(::Lua::LuaEngine::void_safe_call(*m_onInstanceStart, c));
|
||||||
auto result = m_onInstanceStart->call();
|
|
||||||
|
|
||||||
if (!result.valid()) {
|
m_clients.push_back(c);
|
||||||
qWarning() << "Error calling instance start callback:"
|
updateMessageCallbacks();
|
||||||
<< (result.get<sol::error>().what());
|
|
||||||
}
|
|
||||||
|
|
||||||
m_clients.push_back(c);
|
|
||||||
updateMessageCallbacks();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
LanguageClientManager::instance(),
|
LanguageClientManager::instance(),
|
||||||
&LanguageClientManager::clientRemoved,
|
&LanguageClientManager::clientRemoved,
|
||||||
this,
|
this,
|
||||||
[this](Client *c) {
|
&LuaClientWrapper::onClientRemoved);
|
||||||
if (m_clients.contains(c))
|
}
|
||||||
m_clients.removeOne(c);
|
|
||||||
});
|
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
|
// TODO: Unregister Client settings from LanguageClientManager
|
||||||
@@ -457,6 +488,12 @@ QWidget *LuaClientSettings::createSettingsWidget(QWidget *parent) const
|
|||||||
return new BaseSettingsWidget(this, parent);
|
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
|
BaseClientInterface *LuaClientSettings::createInterface(ProjectExplorer::Project *project) const
|
||||||
{
|
{
|
||||||
if (auto w = m_wrapper.lock())
|
if (auto w = m_wrapper.lock())
|
||||||
|
@@ -11,6 +11,7 @@ local lsp = {}
|
|||||||
---@field startBehavior? "AlwaysOn"|"RequiresFile"|"RequiresProject"
|
---@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? table|string The initialization options to pass to the language server, either a json string, or a table
|
||||||
---@field settings? AspectContainer
|
---@field settings? AspectContainer
|
||||||
|
---@field onStartFailed? function This callback is called when client failed to start.
|
||||||
local ClientOptions = {}
|
local ClientOptions = {}
|
||||||
|
|
||||||
---@class LanguageFilter
|
---@class LanguageFilter
|
||||||
|
@@ -24,6 +24,30 @@ local function createCommand()
|
|||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
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!")
|
||||||
|
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()
|
local function setupClient()
|
||||||
Client = LSP.Client.create({
|
Client = LSP.Client.create({
|
||||||
name = 'Lua Language Server',
|
name = 'Lua Language Server',
|
||||||
@@ -35,6 +59,11 @@ local function setupClient()
|
|||||||
},
|
},
|
||||||
settings = Settings,
|
settings = Settings,
|
||||||
startBehavior = "RequiresFile",
|
startBehavior = "RequiresFile",
|
||||||
|
onStartFailed = function(ask)
|
||||||
|
a.sync(function()
|
||||||
|
installServer()
|
||||||
|
end)()
|
||||||
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
Client.on_instance_start = function()
|
Client.on_instance_start = function()
|
||||||
@@ -46,24 +75,6 @@ local function setupClient()
|
|||||||
end)
|
end)
|
||||||
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 function using(tbl)
|
||||||
local result = _G
|
local result = _G
|
||||||
for k, v in pairs(tbl) do result[k] = v end
|
for k, v in pairs(tbl) do result[k] = v end
|
||||||
@@ -112,8 +123,14 @@ local function setupAspect()
|
|||||||
labelText = "Binary:",
|
labelText = "Binary:",
|
||||||
toolTip = "The path to the lua-language-server binary.",
|
toolTip = "The path to the lua-language-server binary.",
|
||||||
expectedKind = S.Kind.ExistingCommand,
|
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({
|
Settings.developMode = S.BoolAspect.create({
|
||||||
settingsKey = "LuaCopilot.DevelopMode",
|
settingsKey = "LuaCopilot.DevelopMode",
|
||||||
displayName = "Enable Develop Mode",
|
displayName = "Enable Develop Mode",
|
||||||
@@ -143,15 +160,7 @@ local function setupAspect()
|
|||||||
return Settings
|
return Settings
|
||||||
end
|
end
|
||||||
local function setup(parameters)
|
local function setup(parameters)
|
||||||
print("Setting up Lua Language Server ...")
|
|
||||||
setupAspect()
|
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()
|
setupClient()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -21,10 +21,4 @@ It will try to install it if it is not found.
|
|||||||
setup = function()
|
setup = function()
|
||||||
require 'init'.setup()
|
require 'init'.setup()
|
||||||
end,
|
end,
|
||||||
hooks = {
|
|
||||||
editors = {
|
|
||||||
documentOpened = function(doc) print("documentOpened", doc) end,
|
|
||||||
documentClosed = function(doc) print("documentClosed", doc) end,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} --[[@as QtcPlugin]]
|
} --[[@as QtcPlugin]]
|
||||||
|
Reference in New Issue
Block a user