LuaLS: Add sendMessageForDocument

The original "sendMessage" function sends a message to each client.
To send a message to a specific Language client the sendMessageForDocument
and sendMessageForDocumentWithId functions are added.

As a fix the way that settings are copied into the Lua wrapper and the id used
to find clients was changed.

Change-Id: I577ee1f9da983c80e4ef00b8e86ccb2bfe369314
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2024-07-19 14:20:50 +02:00
parent 68d00db242
commit b0262115dd
2 changed files with 101 additions and 26 deletions

View File

@@ -15,6 +15,7 @@
#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectmanager.h>
#include <utils/commandline.h>
#include <utils/layoutbuilder.h>
@@ -204,6 +205,7 @@ public:
AspectContainer *m_aspects{nullptr};
QString m_name;
Utils::Id m_settingsTypeId;
QString m_clientSettingsId;
QString m_initializationOptions;
CommandLine m_cmdLine;
QString m_serverName;
@@ -364,7 +366,7 @@ public:
void updateMessageCallbacks()
{
for (Client *c : LanguageClientManager::clientsForSettingId(m_settingsTypeId.toString())) {
for (Client *c : LanguageClientManager::clientsForSettingId(m_clientSettingsId)) {
if (!c)
continue;
for (const auto &[msg, func] : m_messageCallbacks.asKeyValueRange()) {
@@ -387,27 +389,70 @@ public:
}
}
void sendMessage(const sol::table &message, const sol::function &callback)
void sendMessage(const sol::table &message)
{
const QJsonValue messageValue = ::Lua::LuaEngine::toJson(message);
if (!messageValue.isObject())
throw sol::error("Message is not an object");
auto make_request = [&]() -> std::unique_ptr<LanguageServerProtocol::JsonRpcMessage> {
if (callback.valid()) {
return std::make_unique<RequestWithResponse>(messageValue.toObject(), callback);
}
return std::make_unique<LanguageServerProtocol::JsonRpcMessage>(messageValue.toObject());
};
auto const request = make_request();
for (Client *c : LanguageClientManager::clientsForSettingId(m_settingsTypeId.toString())) {
const LanguageServerProtocol::JsonRpcMessage request(messageValue.toObject());
for (Client *c : LanguageClientManager::clientsForSettingId(m_clientSettingsId)) {
if (c)
c->sendMessage(*request);
c->sendMessage(request);
}
}
QList<Client *> clientsForDocument(Core::IDocument *document)
{
if (m_startBehavior == BaseSettings::RequiresProject) {
Project *project = ProjectManager::projectForFile(document->filePath());
const auto clients = LanguageClientManager::clientsForSettingId(m_clientSettingsId);
return Utils::filtered(clients, [project](Client *c) {
return c && c->project() == project;
});
}
return LanguageClientManager::clientsForSettingId(m_clientSettingsId);
}
void sendMessageForDocument(Core::IDocument *document, const sol::table &message)
{
const QJsonValue messageValue = ::Lua::LuaEngine::toJson(message);
if (!messageValue.isObject())
throw sol::error("Message is not an object");
const LanguageServerProtocol::JsonRpcMessage request(messageValue.toObject());
auto clients = clientsForDocument(document);
QTC_CHECK(clients.size() == 1);
for (Client *c : clients) {
if (c)
c->sendMessage(request);
}
}
void sendMessageWithIdForDocument_cb(
TextEditor::TextDocument *document, const sol::table &message, const sol::function callback)
{
const QJsonValue messageValue = ::Lua::LuaEngine::toJson(message);
if (!messageValue.isObject())
throw sol::error("Message is not an object");
QJsonObject obj = messageValue.toObject();
obj["id"] = QUuid::createUuid().toString();
const RequestWithResponse request{obj, callback};
auto clients = clientsForDocument(document);
QTC_ASSERT(clients.size() != 0, throw sol::error("No client for document found"));
QTC_ASSERT(clients.size() == 1, throw sol::error("Multiple clients for document found"));
QTC_ASSERT(clients.front(), throw sol::error("Client is null"));
clients.front()->sendMessage(request);
}
void updateOptions()
{
if (m_cmdLineCallback) {
@@ -500,8 +545,13 @@ bool LuaClientSettings::applyFromSettingsWidget(QWidget *widget)
{
BaseSettings::applyFromSettingsWidget(widget);
if (auto w = m_wrapper.lock())
if (auto w = m_wrapper.lock()) {
w->m_name = m_name;
w->m_initializationOptions = m_initializationOptions;
w->m_languageFilter = m_languageFilter;
w->m_startBehavior = m_startBehavior;
w->applySettings();
}
return true;
}
@@ -553,6 +603,9 @@ BaseClientInterface *LuaClientSettings::createInterface(ProjectExplorer::Project
static void registerLuaApi()
{
::Lua::LuaEngine::registerProvider("LSP", [](sol::state_view lua) -> sol::object {
sol::table async = lua.script("return require('async')", "_process_").get<sol::table>();
sol::function wrap = async["wrap"];
sol::table result = lua.create_table();
auto wrapperClass = result.new_usertype<LuaClientWrapper>(
@@ -569,30 +622,36 @@ static void registerLuaApi()
&LuaClientWrapper::registerMessageCallback,
"sendMessage",
&LuaClientWrapper::sendMessage,
"sendMessageForDocument",
&LuaClientWrapper::sendMessageForDocument,
"sendMessageWithIdForDocument_cb",
&LuaClientWrapper::sendMessageWithIdForDocument_cb,
"create",
[](const sol::table &options) -> std::shared_ptr<LuaClientWrapper> {
auto luaClient = std::make_shared<LuaClientWrapper>(options);
auto client = new LuaClientSettings(luaClient);
auto luaClientWrapper = std::make_shared<LuaClientWrapper>(options);
auto clientSettings = new LuaClientSettings(luaClientWrapper);
// The order is important!
// First restore the settings ...
const QList<Utils::Store> savedSettings
= LanguageClientSettings::storesBySettingsType(luaClient->m_settingsTypeId);
= LanguageClientSettings::storesBySettingsType(
luaClientWrapper->m_settingsTypeId);
if (!savedSettings.isEmpty())
client->fromMap(savedSettings.first());
clientSettings->fromMap(savedSettings.first());
// ... then register the settings.
LanguageClientManager::registerClientSettings(client);
LanguageClientManager::registerClientSettings(clientSettings);
luaClientWrapper->m_clientSettingsId = clientSettings->m_id;
// and the client type.
ClientType type;
type.id = client->m_settingsTypeId;
type.name = luaClient->m_name;
type.id = clientSettings->m_settingsTypeId;
type.name = luaClientWrapper->m_name;
type.userAddable = false;
LanguageClientSettings::registerClientType(type);
return luaClient;
return luaClientWrapper;
},
"documentVersion",
[](const Utils::FilePath &path) -> int {
@@ -616,6 +675,10 @@ static void registerLuaApi()
return client->hostPathToServerUri(path).toString();
});
wrapperClass["sendMessageWithIdForDocument"]
= wrap(wrapperClass["sendMessageWithIdForDocument_cb"].get<sol::function>())
.get<sol::function>();
return result;
});
}

View File

@@ -1,7 +1,8 @@
---@meta LSP
local lsp = {}
---@module "TextEditor"
---@class ClientOptions
---@field name string The name under which to register the language server.
---@field cmd function|string[] The command to start the language server, or a function returning a string[].
@@ -30,14 +31,25 @@ function lsp.Client:registerMessage(msg, callback) end
---@param msg table the message to send.
---Sends a message to the language server.
function lsp.Client:sendMessage(msg, callback) end
function lsp.Client:sendMessage(msg) end
---Sends a message to the language server for a specific document.
---@param document TextDocument The document for which to send the message
---@param msg table The message to send.
function lsp.Client:sendMessageForDocument(document, msg) end
---@async
---Sends a message with an auto generated unique id to the language server for a specific document. Use a.wait(...) to wait for the response.
---@param document TextDocument The document for which to send the message
---@param msg table The message to send.
function lsp.Client:sendMessageWithIdForDocument(document, msg) end
---@param filePath FilePath to get the version of.
---@return int Returns -1 on error, otherwise current document version.
---@return integer Returns -1 on error, otherwise current document version.
function lsp.Client:documentVersion(filePath) end
---
---@param filePath table file path to get the uri of.
---@return QString Returns empty string on error, otherwise the server URI string.
---@return string Returns empty string on error, otherwise the server URI string.
function lsp.Client:hostPathToServerUri(filePath) end
---Creates a new Language Client.