mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Move events to sol
including tab completion
This commit is contained in:
parent
1008904fb1
commit
48a3adc8cf
7 changed files with 62 additions and 94 deletions
|
@ -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 <lauxlib.h>
|
||||
# include <lua.h>
|
||||
# include <lualib.h>
|
||||
# include <QFileInfo>
|
||||
# include <QList>
|
||||
# include <QLoggingCategory>
|
||||
# include <QTextCodec>
|
||||
# include <QUrl>
|
||||
# include <sol/forward.hpp>
|
||||
# include <sol/state_view.hpp>
|
||||
|
||||
# include <utility>
|
||||
|
||||
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<QStringList>("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)
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
# include "controllers/plugins/api/ChannelRef.hpp"
|
||||
# include "controllers/plugins/Plugin.hpp"
|
||||
|
||||
# include <lua.h>
|
||||
# include <QList>
|
||||
# include <QString>
|
||||
# include <sol/table.hpp>
|
||||
|
||||
# include <cassert>
|
||||
# include <memory>
|
||||
# include <vector>
|
||||
|
||||
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<QString> 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.
|
||||
|
|
|
@ -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{};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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_);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <semver/semver.hpp>
|
||||
# include <sol/forward.hpp>
|
||||
|
||||
# include <optional>
|
||||
# include <unordered_map>
|
||||
# include <unordered_set>
|
||||
# include <vector>
|
||||
|
@ -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<lua::api::CompletionList,
|
||||
lua::api::CompletionEvent>;
|
||||
std::optional<LuaCompletionCallback> getCompletionCallback()
|
||||
std::optional<sol::protected_function> 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<lua::CallbackFunction<
|
||||
lua::api::CompletionList, lua::api::CompletionEvent>>(
|
||||
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<lua::api::EventType, sol::protected_function> callbacks;
|
||||
|
||||
private:
|
||||
QDir loadDirectory_;
|
||||
lua_State *state_;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
# include <lua.h>
|
||||
# include <lualib.h>
|
||||
# include <QJsonDocument>
|
||||
# include <sol/forward.hpp>
|
||||
# include <sol/sol.hpp>
|
||||
|
||||
# include <memory>
|
||||
|
@ -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<lua::api::LogLevel>(L);
|
||||
lua_setfield(L, c2libIdx, "LogLevel");
|
||||
|
||||
lua::pushEnumTable<lua::api::EventType>(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<Channel::Type>(lua);
|
||||
c2["HTTPMethod"] = lua::createEnumTable<NetworkRequestType>(lua);
|
||||
c2["EventType"] = lua::createEnumTable<lua::api::EventType>(lua);
|
||||
}
|
||||
|
||||
void PluginController::load(const QFileInfo &index, const QDir &pluginDir,
|
||||
|
@ -438,32 +441,31 @@ std::pair<bool, QStringList> 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<int>(errOrList))
|
||||
sol::state_view view(pl->state_);
|
||||
auto errOrList = lua::tryCall<sol::table>(
|
||||
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<int>(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<lua::api::CompletionList>(errOrList);
|
||||
auto list = lua::api::CompletionList(*errOrList);
|
||||
if (list.hideOthers)
|
||||
{
|
||||
results = QStringList(list.values.begin(), list.values.end());
|
||||
|
|
Loading…
Reference in a new issue