From 48a3adc8cf1cd0b91604d6f1f58d4e1627aff673 Mon Sep 17 00:00:00 2001 From: Mm2PL Date: Sun, 6 Oct 2024 16:06:46 +0200 Subject: [PATCH] Move events to sol including tab completion --- src/controllers/plugins/LuaAPI.cpp | 55 ++++++++------------ src/controllers/plugins/LuaAPI.hpp | 14 +++-- src/controllers/plugins/LuaUtilities.cpp | 17 ------ src/controllers/plugins/LuaUtilities.hpp | 2 - src/controllers/plugins/Plugin.cpp | 1 + src/controllers/plugins/Plugin.hpp | 31 +++-------- src/controllers/plugins/PluginController.cpp | 36 +++++++------ 7 files changed, 62 insertions(+), 94 deletions(-) diff --git a/src/controllers/plugins/LuaAPI.cpp b/src/controllers/plugins/LuaAPI.cpp index 678e59061..069fae3ce 100644 --- a/src/controllers/plugins/LuaAPI.cpp +++ b/src/controllers/plugins/LuaAPI.cpp @@ -3,19 +3,22 @@ # include "Application.hpp" # include "common/QLogging.hpp" -# include "controllers/commands/CommandController.hpp" # include "controllers/plugins/LuaUtilities.hpp" # include "controllers/plugins/PluginController.hpp" -# include "messages/MessageBuilder.hpp" -# include "providers/twitch/TwitchIrcServer.hpp" +# include "controllers/plugins/SolTypes.hpp" // for lua operations on QString{,List} for CompletionList # include # include # include # include +# include # include # include # include +# include +# include + +# include namespace { using namespace chatterino; @@ -61,38 +64,26 @@ QDebug qdebugStreamForLogLevel(lua::api::LogLevel lvl) // luaL_error is a c-style vararg function, this makes clang-tidy not dislike it so much namespace chatterino::lua::api { -int c2_register_callback(lua_State *L) +CompletionList::CompletionList(const sol::table &table) + : values(table.get("values")) + , hideOthers(table["hide_others"]) { - auto *pl = getApp()->getPlugins()->getPluginByStatePtr(L); - if (pl == nullptr) - { - luaL_error(L, "internal error: no plugin"); - return 0; - } - EventType evtType{}; - if (!lua::peek(L, &evtType, 1)) - { - luaL_error(L, "cannot get event name (1st arg of register_callback, " - "expected a string)"); - return 0; - } - if (lua_isnoneornil(L, 2)) - { - luaL_error(L, "missing argument for register_callback: function " - "\"pointer\""); - return 0; - } +} - auto typeName = magic_enum::enum_name(evtType); - std::string callbackSavedName; - callbackSavedName.reserve(5 + typeName.size()); - callbackSavedName += "c2cb-"; - callbackSavedName += typeName; - lua_setfield(L, LUA_REGISTRYINDEX, callbackSavedName.c_str()); +sol::table toTable(lua_State *L, const CompletionEvent &ev) +{ + return sol::state_view(L).create_table_with( + "query", ev.query, // + "full_text_content", ev.full_text_content, // + "cursor_position", ev.cursor_position, // + "is_first_word", ev.is_first_word // + ); +} - lua_pop(L, 2); - - return 0; +void c2_register_callback(Plugin *pl, EventType evtType, + sol::protected_function callback) +{ + pl->callbacks[evtType] = std::move(callback); } int c2_log(lua_State *L) diff --git a/src/controllers/plugins/LuaAPI.hpp b/src/controllers/plugins/LuaAPI.hpp index 8d8e7e945..7f8f0733d 100644 --- a/src/controllers/plugins/LuaAPI.hpp +++ b/src/controllers/plugins/LuaAPI.hpp @@ -1,13 +1,16 @@ #pragma once #ifdef CHATTERINO_HAVE_PLUGINS +# include "controllers/plugins/api/ChannelRef.hpp" +# include "controllers/plugins/Plugin.hpp" # include +# include # include +# include # include # include -# include struct lua_State; namespace chatterino::lua::api { @@ -39,10 +42,12 @@ enum class LogLevel { Debug, Info, Warning, Critical }; * @lua@class CompletionList */ struct CompletionList { + CompletionList(const sol::table &); + /** * @lua@field values string[] The completions */ - std::vector values{}; + QStringList values; /** * @lua@field hide_others boolean Whether other completions from Chatterino should be hidden/ignored. @@ -72,6 +77,8 @@ struct CompletionEvent { bool is_first_word{}; }; +sol::table toTable(lua_State *L, const CompletionEvent &ev); + /** * @includefile common/Channel.hpp * @includefile controllers/plugins/api/ChannelRef.hpp @@ -96,7 +103,8 @@ struct CompletionEvent { * @lua@param func fun(event: CompletionEvent): CompletionList The callback to be invoked. * @exposed c2.register_callback */ -int c2_register_callback(lua_State *L); +void c2_register_callback(Plugin *pl, EventType evtType, + sol::protected_function callback); /** * Writes a message to the Chatterino log. diff --git a/src/controllers/plugins/LuaUtilities.cpp b/src/controllers/plugins/LuaUtilities.cpp index 228f92f3e..e8e31b6fd 100644 --- a/src/controllers/plugins/LuaUtilities.cpp +++ b/src/controllers/plugins/LuaUtilities.cpp @@ -227,23 +227,6 @@ bool peek(lua_State *L, std::string *out, StackIdx idx) return true; } -bool peek(lua_State *L, api::CompletionList *out, StackIdx idx) -{ - StackGuard guard(L); - int typ = lua_getfield(L, idx, "values"); - if (typ != LUA_TTABLE) - { - lua_pop(L, 1); - return false; - } - if (!lua::pop(L, &out->values, -1)) - { - return false; - } - lua_getfield(L, idx, "hide_others"); - return lua::pop(L, &out->hideOthers); -} - QString toString(lua_State *L, StackIdx idx) { size_t len{}; diff --git a/src/controllers/plugins/LuaUtilities.hpp b/src/controllers/plugins/LuaUtilities.hpp index 73d57dea2..c033611b5 100644 --- a/src/controllers/plugins/LuaUtilities.hpp +++ b/src/controllers/plugins/LuaUtilities.hpp @@ -25,7 +25,6 @@ struct CommandContext; namespace chatterino::lua { namespace api { - struct CompletionList; struct CompletionEvent; } // namespace api @@ -73,7 +72,6 @@ bool peek(lua_State *L, double *out, StackIdx idx = -1); bool peek(lua_State *L, QString *out, StackIdx idx = -1); bool peek(lua_State *L, QByteArray *out, StackIdx idx = -1); bool peek(lua_State *L, std::string *out, StackIdx idx = -1); -bool peek(lua_State *L, api::CompletionList *out, StackIdx idx = -1); /** * @brief Converts Lua object at stack index idx to a string. diff --git a/src/controllers/plugins/Plugin.cpp b/src/controllers/plugins/Plugin.cpp index f530f5057..22c9ef987 100644 --- a/src/controllers/plugins/Plugin.cpp +++ b/src/controllers/plugins/Plugin.cpp @@ -231,6 +231,7 @@ Plugin::~Plugin() { // clearing this after the state is gone is not safe to do this->ownedCommands.clear(); + this->callbacks.clear(); lua_close(this->state_); } } diff --git a/src/controllers/plugins/Plugin.hpp b/src/controllers/plugins/Plugin.hpp index c6fd10d94..8d056a8f2 100644 --- a/src/controllers/plugins/Plugin.hpp +++ b/src/controllers/plugins/Plugin.hpp @@ -2,8 +2,6 @@ #ifdef CHATTERINO_HAVE_PLUGINS # include "Application.hpp" -# include "common/Common.hpp" -# include "common/network/NetworkCommon.hpp" # include "controllers/plugins/api/EventType.hpp" # include "controllers/plugins/LuaUtilities.hpp" # include "controllers/plugins/PluginPermission.hpp" @@ -14,6 +12,7 @@ # include # include +# include # include # include # include @@ -105,35 +104,19 @@ public: return this->loadDirectory_.absoluteFilePath("data"); } - // Note: The CallbackFunction object's destructor will remove the function from the lua stack - using LuaCompletionCallback = - lua::CallbackFunction; - std::optional getCompletionCallback() + std::optional getCompletionCallback() { if (this->state_ == nullptr || !this->error_.isNull()) { return {}; } - // this uses magic enum to help automatic tooling find usages - auto typeName = - magic_enum::enum_name(lua::api::EventType::CompletionRequested); - std::string cbName; - cbName.reserve(5 + typeName.size()); - cbName += "c2cb-"; - cbName += typeName; - auto typ = - lua_getfield(this->state_, LUA_REGISTRYINDEX, cbName.c_str()); - if (typ != LUA_TFUNCTION) + auto it = + this->callbacks.find(lua::api::EventType::CompletionRequested); + if (it == this->callbacks.end()) { - lua_pop(this->state_, 1); return {}; } - - // move - return std::make_optional>( - this->state_, lua_gettop(this->state_)); + return it->second; } /** @@ -150,6 +133,8 @@ public: bool hasFSPermissionFor(bool write, const QString &path); bool hasHTTPPermissionFor(const QUrl &url); + std::map callbacks; + private: QDir loadDirectory_; lua_State *state_; diff --git a/src/controllers/plugins/PluginController.cpp b/src/controllers/plugins/PluginController.cpp index 024d89964..4058f0396 100644 --- a/src/controllers/plugins/PluginController.cpp +++ b/src/controllers/plugins/PluginController.cpp @@ -22,6 +22,7 @@ # include # include # include +# include # include # include @@ -150,7 +151,6 @@ void PluginController::openLibrariesFor(Plugin *plugin, const QDir &pluginDir) // NOLINTNEXTLINE(*-avoid-c-arrays) static const luaL_Reg c2Lib[] = { - {"register_callback", lua::api::c2_register_callback}, {"log", lua::api::c2_log}, {"later", lua::api::c2_later}, {nullptr, nullptr}, @@ -166,9 +166,6 @@ void PluginController::openLibrariesFor(Plugin *plugin, const QDir &pluginDir) lua::pushEnumTable(L); lua_setfield(L, c2libIdx, "LogLevel"); - lua::pushEnumTable(L); - lua_setfield(L, c2libIdx, "EventType"); - lua_setfield(L, gtable, "c2"); // ban functions @@ -290,11 +287,17 @@ void PluginController::initSol(sol::state_view &lua, Plugin *plugin) [plugin](const QString &name, sol::protected_function cb) { return plugin->registerCommand(name, std::move(cb)); }); + c2.set_function("register_callback", [plugin](lua::api::EventType ev, + sol::protected_function cb) { + lua::api::c2_register_callback(plugin, ev, std::move(cb)); + }); + lua::api::ChannelRef::createUserType(c2); lua::api::HTTPResponse::createUserType(c2); lua::api::HTTPRequest::createUserType(plugin->state_, c2); c2["ChannelType"] = lua::createEnumTable(lua); c2["HTTPMethod"] = lua::createEnumTable(lua); + c2["EventType"] = lua::createEnumTable(lua); } void PluginController::load(const QFileInfo &index, const QDir &pluginDir, @@ -438,32 +441,31 @@ std::pair PluginController::updateCustomCompletions( continue; } - lua::StackGuard guard(pl->state_); - auto opt = pl->getCompletionCallback(); if (opt) { qCDebug(chatterinoLua) << "Processing custom completions from plugin" << name; auto &cb = *opt; - auto errOrList = cb(lua::api::CompletionEvent{ - .query = query, - .full_text_content = fullTextContent, - .cursor_position = cursorPosition, - .is_first_word = isFirstWord, - }); - if (std::holds_alternative(errOrList)) + sol::state_view view(pl->state_); + auto errOrList = lua::tryCall( + cb, + toTable(pl->state_, lua::api::CompletionEvent{ + .query = query, + .full_text_content = fullTextContent, + .cursor_position = cursorPosition, + .is_first_word = isFirstWord, + })); + if (!errOrList.has_value()) { - guard.handled(); - int err = std::get(errOrList); qCDebug(chatterinoLua) << "Got error from plugin " << pl->meta.name << " while refreshing tab completion: " - << lua::humanErrorText(pl->state_, err); + << errOrList.get_unexpected().error(); continue; } - auto list = std::get(errOrList); + auto list = lua::api::CompletionList(*errOrList); if (list.hideOthers) { results = QStringList(list.values.begin(), list.values.end());