From b0262115dda064d2cba888aa7d35cf3de766868b Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Fri, 19 Jul 2024 14:20:50 +0200 Subject: [PATCH] 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 --- .../lualanguageclient/lualanguageclient.cpp | 107 ++++++++++++++---- src/plugins/lua/meta/lsp.lua | 20 +++- 2 files changed, 101 insertions(+), 26 deletions(-) diff --git a/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp b/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp index 1d226feee97..afed4078a34 100644 --- a/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp +++ b/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -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 { - if (callback.valid()) { - return std::make_unique(messageValue.toObject(), callback); - } - - return std::make_unique(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 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::function wrap = async["wrap"]; + sol::table result = lua.create_table(); auto wrapperClass = result.new_usertype( @@ -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 { - auto luaClient = std::make_shared(options); - auto client = new LuaClientSettings(luaClient); + auto luaClientWrapper = std::make_shared(options); + auto clientSettings = new LuaClientSettings(luaClientWrapper); // The order is important! // First restore the settings ... const QList 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()) + .get(); + return result; }); } diff --git a/src/plugins/lua/meta/lsp.lua b/src/plugins/lua/meta/lsp.lua index ee89df7b520..7c97ec2954e 100644 --- a/src/plugins/lua/meta/lsp.lua +++ b/src/plugins/lua/meta/lsp.lua @@ -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.