diff --git a/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp b/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp index f872c8d07c2..1d226feee97 100644 --- a/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp +++ b/src/plugins/languageclient/lualanguageclient/lualanguageclient.cpp @@ -26,6 +26,43 @@ using namespace Core; using namespace TextEditor; using namespace ProjectExplorer; +namespace { + +class RequestWithResponse : public LanguageServerProtocol::JsonRpcMessage +{ + sol::function m_callback; + LanguageServerProtocol::MessageId m_id; + +public: + RequestWithResponse(const QJsonObject &obj, const sol::function &cb) + : LanguageServerProtocol::JsonRpcMessage(obj) + , m_callback(cb) + { + m_id = LanguageServerProtocol::MessageId(obj["id"]); + } + + std::optional responseHandler() const override + { + if (!m_id.isValid()) + qWarning() << "Invalid 'id' in request:" << toJsonObject(); + return std::nullopt; + + return LanguageServerProtocol::ResponseHandler{ + m_id, [callback = m_callback](const JsonRpcMessage &msg) { + if (!callback.valid()) { + qWarning() << "Invalid Lua callback"; + return; + } + + auto result = ::Lua::LuaEngine::void_safe_call( + callback, ::Lua::LuaEngine::toTable(callback.lua_state(), msg.toJsonObject())); + QTC_CHECK_EXPECTED(result); + }}; + } +}; + +} // anonymous namespace + namespace LanguageClient::Lua { static void registerLuaApi(); @@ -73,14 +110,16 @@ public: } m_process = new Process; m_process->setProcessMode(ProcessMode::Writer); - connect(m_process, - &Process::readyReadStandardError, - this, - &LuaLocalSocketClientInterface::readError); - connect(m_process, - &Process::readyReadStandardOutput, - this, - &LuaLocalSocketClientInterface::readOutput); + connect( + m_process, + &Process::readyReadStandardError, + this, + &LuaLocalSocketClientInterface::readError); + connect( + m_process, + &Process::readyReadStandardOutput, + this, + &LuaLocalSocketClientInterface::readOutput); connect(m_process, &Process::started, this, [this]() { this->LocalSocketClientInterface::startImpl(); emit started(); @@ -348,15 +387,24 @@ public: } } - void sendMessage(const sol::table &message) + void sendMessage(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"); - const LanguageServerProtocol::JsonRpcMessage jsonrpcmessage(messageValue.toObject()); + + 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())) { if (c) - c->sendMessage(jsonrpcmessage); + c->sendMessage(*request); } } @@ -545,6 +593,27 @@ static void registerLuaApi() LanguageClientSettings::registerClientType(type); return luaClient; + }, + "documentVersion", + [](const Utils::FilePath &path) -> int { + auto client = LanguageClientManager::clientForFilePath(path); + if (!client) { + qWarning() << "documentVersion(). No client for file path:" << path; + return -1; + } + + return client->documentVersion(path); + }, + + "hostPathToServerUri", + [](const Utils::FilePath &path) -> QString { + auto client = LanguageClientManager::clientForFilePath(path); + if (!client) { + qWarning() << "hostPathToServerUri(). No client for file path:" << path; + return {}; + } + + return client->hostPathToServerUri(path).toString(); }); return result; diff --git a/src/plugins/lua/bindings/utils.cpp b/src/plugins/lua/bindings/utils.cpp index 6d50869725d..8b5895f34c7 100644 --- a/src/plugins/lua/bindings/utils.cpp +++ b/src/plugins/lua/bindings/utils.cpp @@ -9,6 +9,7 @@ #include #include +#include using namespace Utils; @@ -25,41 +26,43 @@ void addUtilsModule() sol::table utils = lua.create_table(); - utils.set_function("waitms_cb", [guard = pluginSpec->connectionGuard.get()](int ms, const sol::function &cb) { - QTimer::singleShot(ms, guard, [cb]() { cb(); }); - }); + utils.set_function( + "waitms_cb", + [guard = pluginSpec->connectionGuard.get()](int ms, const sol::function &cb) { + QTimer::singleShot(ms, guard, [cb]() { cb(); }); + }); - auto dirEntries_cb = - [&futureSync, guard = pluginSpec->connectionGuard.get()]( - const FilePath &p, const sol::table &options, const sol::function &cb) { - const QStringList nameFilters = options.get_or("nameFilters", {}); - QDir::Filters fileFilters - = (QDir::Filters) options.get_or("fileFilters", QDir::NoFilter); - QDirIterator::IteratorFlags flags - = (QDirIterator::IteratorFlags) - options.get_or("flags", QDirIterator::NoIteratorFlags); + auto dirEntries_cb = [&futureSync, guard = pluginSpec->connectionGuard.get()]( + const FilePath &p, + const sol::table &options, + const sol::function &cb) { + const QStringList nameFilters = options.get_or("nameFilters", {}); + QDir::Filters fileFilters + = (QDir::Filters) options.get_or("fileFilters", QDir::NoFilter); + QDirIterator::IteratorFlags flags + = (QDirIterator::IteratorFlags) + options.get_or("flags", QDirIterator::NoIteratorFlags); - FileFilter filter(nameFilters, fileFilters, flags); + FileFilter filter(nameFilters, fileFilters, flags); - QFuture future = Utils::asyncRun( - [p, filter](QPromise &promise) { - p.iterateDirectory( - [&promise](const FilePath &item) { - if (promise.isCanceled()) - return IterationPolicy::Stop; + QFuture future = Utils::asyncRun([p, filter](QPromise &promise) { + p.iterateDirectory( + [&promise](const FilePath &item) { + if (promise.isCanceled()) + return IterationPolicy::Stop; - promise.addResult(item); - return IterationPolicy::Continue; - }, - filter); - }); + promise.addResult(item); + return IterationPolicy::Continue; + }, + filter); + }); - futureSync.addFuture(future); + futureSync.addFuture(future); - Utils::onFinished(future, guard, [cb](const QFuture &future) { - cb(future.results()); - }); - }; + Utils::onFinished(future, guard, [cb](const QFuture &future) { + cb(future.results()); + }); + }; auto searchInPath_cb = [&futureSync, guard = pluginSpec->connectionGuard @@ -77,6 +80,8 @@ void addUtilsModule() utils.set_function("__dirEntries_cb__", dirEntries_cb); utils.set_function("__searchInPath_cb__", searchInPath_cb); + utils.set_function("createUuid", []() { return QUuid::createUuid().toString(); }); + sol::function wrap = async["wrap"].get(); utils["waitms"] = wrap(utils["waitms_cb"]); diff --git a/src/plugins/lua/meta/lsp.lua b/src/plugins/lua/meta/lsp.lua index 785f1bfd169..ee89df7b520 100644 --- a/src/plugins/lua/meta/lsp.lua +++ b/src/plugins/lua/meta/lsp.lua @@ -32,6 +32,14 @@ function lsp.Client:registerMessage(msg, callback) end ---Sends a message to the language server. function lsp.Client:sendMessage(msg, callback) end +---@param filePath FilePath to get the version of. +---@return int 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. +function lsp.Client:hostPathToServerUri(filePath) end + ---Creates a new Language Client. ---@param options ClientOptions ---@return Client diff --git a/src/plugins/lua/meta/utils.lua b/src/plugins/lua/meta/utils.lua index c5df6247fe5..cc8ebc08e2c 100644 --- a/src/plugins/lua/meta/utils.lua +++ b/src/plugins/lua/meta/utils.lua @@ -14,6 +14,10 @@ function utils.waitms(ms) end ---@param callback function The callback to call. function utils.waitms_cb(ms, callback) end +---Creates a UUID. +---@return QString Arbitrary UUID string. +function utils.createUuid() end + ---@class FilePath utils.FilePath = {}