Add a new completion API for experimental plugins feature. (#5000)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -2,14 +2,19 @@
|
||||
|
||||
#ifdef CHATTERINO_HAVE_PLUGINS
|
||||
|
||||
# include "common/QLogging.hpp"
|
||||
|
||||
# include <lua.h>
|
||||
# include <lualib.h>
|
||||
# include <magic_enum/magic_enum.hpp>
|
||||
# include <QList>
|
||||
|
||||
# include <cassert>
|
||||
# include <optional>
|
||||
# include <string>
|
||||
# include <string_view>
|
||||
# include <type_traits>
|
||||
# include <variant>
|
||||
# include <vector>
|
||||
struct lua_State;
|
||||
class QJsonObject;
|
||||
@@ -19,6 +24,12 @@ struct CommandContext;
|
||||
|
||||
namespace chatterino::lua {
|
||||
|
||||
namespace api {
|
||||
struct CompletionList;
|
||||
} // namespace api
|
||||
|
||||
constexpr int ERROR_BAD_PEEK = LUA_OK - 1;
|
||||
|
||||
/**
|
||||
* @brief Dumps the Lua stack into qCDebug(chatterinoLua)
|
||||
*
|
||||
@@ -52,20 +63,136 @@ StackIdx push(lua_State *L, const CommandContext &ctx);
|
||||
StackIdx push(lua_State *L, const QString &str);
|
||||
StackIdx push(lua_State *L, const std::string &str);
|
||||
StackIdx push(lua_State *L, const bool &b);
|
||||
StackIdx push(lua_State *L, const int &b);
|
||||
|
||||
// returns OK?
|
||||
bool peek(lua_State *L, bool *out, StackIdx idx = -1);
|
||||
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.
|
||||
*/
|
||||
QString toString(lua_State *L, StackIdx idx = -1);
|
||||
|
||||
// This object ensures that the stack is of expected size when it is destroyed
|
||||
class StackGuard
|
||||
{
|
||||
int expected;
|
||||
lua_State *L;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Use this constructor if you expect the stack size to be the same on the
|
||||
* destruction of the object as its creation
|
||||
*/
|
||||
StackGuard(lua_State *L)
|
||||
: expected(lua_gettop(L))
|
||||
, L(L)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this if you expect the stack size changing, diff is the expected difference
|
||||
* Ex: diff=3 means three elements added to the stack
|
||||
*/
|
||||
StackGuard(lua_State *L, int diff)
|
||||
: expected(lua_gettop(L) + diff)
|
||||
, L(L)
|
||||
{
|
||||
}
|
||||
|
||||
~StackGuard()
|
||||
{
|
||||
if (expected < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int after = lua_gettop(this->L);
|
||||
if (this->expected != after)
|
||||
{
|
||||
stackDump(this->L, "StackGuard check tripped");
|
||||
// clang-format off
|
||||
// clang format likes to insert a new line which means that some builds won't show this message fully
|
||||
assert(false && "internal error: lua stack was not in an expected state");
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
// This object isn't meant to be passed around
|
||||
StackGuard operator=(StackGuard &) = delete;
|
||||
StackGuard &operator=(StackGuard &&) = delete;
|
||||
StackGuard(StackGuard &) = delete;
|
||||
StackGuard(StackGuard &&) = delete;
|
||||
|
||||
// This function tells the StackGuard that the stack isn't in an expected state but it was handled
|
||||
void handled()
|
||||
{
|
||||
this->expected = -1;
|
||||
}
|
||||
};
|
||||
|
||||
/// TEMPLATES
|
||||
|
||||
template <typename T>
|
||||
bool peek(lua_State *L, std::optional<T> *out, StackIdx idx = -1)
|
||||
{
|
||||
if (lua_isnil(L, idx))
|
||||
{
|
||||
*out = std::nullopt;
|
||||
return true;
|
||||
}
|
||||
|
||||
*out = T();
|
||||
return peek(L, out->operator->(), idx);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool peek(lua_State *L, std::vector<T> *vec, StackIdx idx = -1)
|
||||
{
|
||||
StackGuard guard(L);
|
||||
|
||||
if (!lua_istable(L, idx))
|
||||
{
|
||||
lua::stackDump(L, "!table");
|
||||
qCDebug(chatterinoLua)
|
||||
<< "value is not a table, type is" << lua_type(L, idx);
|
||||
return false;
|
||||
}
|
||||
auto len = lua_rawlen(L, idx);
|
||||
if (len == 0)
|
||||
{
|
||||
qCDebug(chatterinoLua) << "value has 0 length";
|
||||
return true;
|
||||
}
|
||||
if (len > 1'000'000)
|
||||
{
|
||||
qCDebug(chatterinoLua) << "value is too long";
|
||||
return false;
|
||||
}
|
||||
// count like lua
|
||||
for (int i = 1; i <= len; i++)
|
||||
{
|
||||
lua_geti(L, idx, i);
|
||||
std::optional<T> obj;
|
||||
if (!lua::peek(L, &obj))
|
||||
{
|
||||
//lua_seti(L, LUA_REGISTRYINDEX, 1); // lazy
|
||||
qCDebug(chatterinoLua)
|
||||
<< "Failed to convert lua object into c++: at array index " << i
|
||||
<< ":";
|
||||
stackDump(L, "bad conversion into string");
|
||||
return false;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
vec->push_back(obj.value());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Converts object at stack index idx to enum given by template parameter T
|
||||
*/
|
||||
@@ -150,6 +277,7 @@ StackIdx push(lua_State *L, T inp)
|
||||
template <typename T>
|
||||
bool pop(lua_State *L, T *out, StackIdx idx = -1)
|
||||
{
|
||||
StackGuard guard(L, -1);
|
||||
auto ok = peek(L, out, idx);
|
||||
if (ok)
|
||||
{
|
||||
@@ -186,6 +314,58 @@ StackIdx pushEnumTable(lua_State *L)
|
||||
return out;
|
||||
}
|
||||
|
||||
// Represents a Lua function on the stack
|
||||
template <typename ReturnType, typename... Args>
|
||||
class CallbackFunction
|
||||
{
|
||||
StackIdx stackIdx_;
|
||||
lua_State *L;
|
||||
|
||||
public:
|
||||
CallbackFunction(lua_State *L, StackIdx stackIdx)
|
||||
: stackIdx_(stackIdx)
|
||||
, L(L)
|
||||
{
|
||||
}
|
||||
|
||||
// this type owns the stackidx, it must not be trivially copiable
|
||||
CallbackFunction operator=(CallbackFunction &) = delete;
|
||||
CallbackFunction(CallbackFunction &) = delete;
|
||||
|
||||
// Permit only move
|
||||
CallbackFunction &operator=(CallbackFunction &&) = default;
|
||||
CallbackFunction(CallbackFunction &&) = default;
|
||||
|
||||
~CallbackFunction()
|
||||
{
|
||||
lua_remove(L, this->stackIdx_);
|
||||
}
|
||||
|
||||
std::variant<int, ReturnType> operator()(Args... arguments)
|
||||
{
|
||||
lua_pushvalue(this->L, this->stackIdx_);
|
||||
( // apparently this calls lua::push() for every Arg
|
||||
[this, &arguments] {
|
||||
lua::push(this->L, arguments);
|
||||
}(),
|
||||
...);
|
||||
|
||||
int res = lua_pcall(L, sizeof...(Args), 1, 0);
|
||||
if (res != LUA_OK)
|
||||
{
|
||||
qCDebug(chatterinoLua) << "error is: " << res;
|
||||
return {res};
|
||||
}
|
||||
|
||||
ReturnType val;
|
||||
if (!lua::pop(L, &val))
|
||||
{
|
||||
return {ERROR_BAD_PEEK};
|
||||
}
|
||||
return {val};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace chatterino::lua
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user