Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new completion API for experimental plugins feature. #5000

Merged
merged 33 commits into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3cddaea
Add callback machinery
Mm2PL Sep 23, 2023
ca4058c
Add Plugin::getCompletionCallback()
Mm2PL Dec 3, 2023
dd27791
Add lua::peek for std::optional<T> and std::vector<T>
Mm2PL Dec 3, 2023
e957b5e
Add lua::peek for bool
Mm2PL Dec 3, 2023
d9e08d7
Add missing lua::api::CompletionList and peek() for it
Mm2PL Dec 3, 2023
e3c7129
Allow for custom completions from plugins
Mm2PL Dec 3, 2023
49de89c
Add missing typescript definitions
Mm2PL Dec 3, 2023
80f8c54
Make CallbackFunction RAII for freeing lua resource
Mm2PL Dec 7, 2023
3e01aa7
Add BalanceKepper utility object
Mm2PL Dec 7, 2023
8692bf1
Use BalanceKepper (a lot)
Mm2PL Dec 7, 2023
ac061a4
spot a bug with BalanceKepper
Mm2PL Dec 7, 2023
65b3c77
changelog
Mm2PL Dec 7, 2023
77e45b2
add missing lua_pushvalue call
Mm2PL Dec 7, 2023
b867a42
i've overdone this
Mm2PL Dec 7, 2023
1a188f7
Unrelated: Add error tracking on load
Mm2PL Dec 8, 2023
4a8f80d
Merge branch 'master' of github.com:Chatterino/chatterino2 into featu…
Mm2PL Dec 8, 2023
4c70134
Reformat includes
Mm2PL Dec 9, 2023
824d95d
Move log after the check if the plugin has a callback
Mm2PL Dec 9, 2023
1979ac8
Add a type-alias for the completion callback
Mm2PL Dec 9, 2023
1fa7dde
Did you know that conversion from int to bool is silent cause I sure as
Mm2PL Dec 9, 2023
5d4ceeb
Rename stackidx to stackIdx_
Mm2PL Dec 9, 2023
78c8001
Make 'default' more obvious
Mm2PL Dec 9, 2023
a6d3cb5
Rename BalanceKepper(!) to StackGuard and document it
Mm2PL Dec 9, 2023
34fc958
Rename usages of StackGuard to 'guard' from '_'
Mm2PL Dec 9, 2023
0789921
Add a note comment about error and enable state of plugins
Mm2PL Dec 9, 2023
b4e13b9
Merge branch 'master' of github.com:Chatterino/chatterino2 into featu…
Mm2PL Dec 9, 2023
6ea19a7
Add StackGuard::handled(), sometimes lua errors mess with the stack in
Mm2PL Dec 9, 2023
76be58f
Use StackGuard::handled() in PluginController::updateCustomCompletions
Mm2PL Dec 9, 2023
f72af94
fix formatting# with '#' will be ignored, and an empty message aborts…
pajlada Dec 10, 2023
2c179ee
Merge remote-tracking branch 'origin/master' into feature/lua-complet…
pajlada Dec 10, 2023
5661782
use this-> in updateCustomCompletions
pajlada Dec 10, 2023
bf6068c
unrelated nit: Use entryInfoList's filter to find dirs
pajlada Dec 10, 2023
4d469dc
Merge branch 'master' into feature/lua-completion-api-v2
pajlada Dec 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- Minor: Add an option to use new experimental smarter emote completion. (#4987)
- Minor: Add `--safe-mode` command line option that can be used for troubleshooting when Chatterino is misbehaving or is misconfigured. It disables hiding the settings button & prevents plugins from loading. (#4985)
- Minor: Updated the flatpakref link included with nightly builds to point to up-to-date flathub-beta builds. (#5008)
- Minor: Add a new completion API for experimental plugins feature. (#5000)
- Bugfix: Fixed an issue where certain emojis did not send to Twitch chat correctly. (#4840)
- Bugfix: Fixed capitalized channel names in log inclusion list not being logged. (#4848)
- Bugfix: Trimmed custom streamlink paths on all platforms making sure you don't accidentally add spaces at the beginning or end of its path. (#4834)
Expand Down
17 changes: 17 additions & 0 deletions docs/chatterino.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,21 @@ declare module c2 {
): boolean;
function send_msg(channel: String, text: String): boolean;
function system_msg(channel: String, text: String): boolean;

class CompletionList {
values: String[];
hide_others: boolean;
}

enum EventType {
RegisterCompletions = "RegisterCompletions",
}

type CbFuncCompletionsRequested = (query: string, full_text_content: string,
cursor_position: number, is_first_word: boolean) => CompletionList;
type CbFunc<T> = T extends EventType.RegisterCompletions ? CbFuncCompletionsRequested
: never;


function register_callback<T>(type: T, func: CbFunc<T>): void;
}
19 changes: 18 additions & 1 deletion src/controllers/completion/TabCompletionModel.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "controllers/completion/TabCompletionModel.hpp"

#include "Application.hpp"
#include "common/Channel.hpp"
#include "controllers/completion/sources/CommandSource.hpp"
#include "controllers/completion/sources/EmoteSource.hpp"
Expand All @@ -9,6 +10,9 @@
#include "controllers/completion/strategies/ClassicUserStrategy.hpp"
#include "controllers/completion/strategies/CommandStrategy.hpp"
#include "controllers/completion/strategies/SmartEmoteStrategy.hpp"
#include "controllers/plugins/LuaUtilities.hpp"
#include "controllers/plugins/Plugin.hpp"
#include "controllers/plugins/PluginController.hpp"
#include "singletons/Settings.hpp"

namespace chatterino {
Expand All @@ -19,7 +23,9 @@ TabCompletionModel::TabCompletionModel(Channel &channel, QObject *parent)
{
}

void TabCompletionModel::updateResults(const QString &query, bool isFirstWord)
void TabCompletionModel::updateResults(const QString &query,
const QString &fullTextContent,
int cursorPosition, bool isFirstWord)
{
this->updateSourceFromQuery(query);

Expand All @@ -29,6 +35,17 @@ void TabCompletionModel::updateResults(const QString &query, bool isFirstWord)

// Copy results to this model
QStringList results;
#ifdef CHATTERINO_HAVE_PLUGINS
// Try plugins first
bool done{};
std::tie(done, results) = getApp()->plugins->updateCustomCompletions(
query, fullTextContent, cursorPosition, isFirstWord);
if (done)
{
this->setStringList(results);
return;
}
#endif
this->source_->addToStringList(results, 0, isFirstWord);
this->setStringList(results);
}
Expand Down
6 changes: 5 additions & 1 deletion src/controllers/completion/TabCompletionModel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ class TabCompletionModel : public QStringListModel

/// @brief Updates the model based on the completion query
/// @param query Completion query
/// @param fullTextContent Full text of the input, used by plugins for contextual completion
/// @param cursorPosition Number of characters behind the cursor from the
/// beginning of fullTextContent, also used by plugins
/// @param isFirstWord Whether the completion is the first word in the input
void updateResults(const QString &query, bool isFirstWord = false);
void updateResults(const QString &query, const QString &fullTextContent,
int cursorPosition, bool isFirstWord = false);

private:
enum class SourceKind {
Expand Down
32 changes: 32 additions & 0 deletions src/controllers/plugins/LuaAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,37 @@ int c2_register_command(lua_State *L)
return 1;
}

int c2_register_callback(lua_State *L)
{
auto *pl = getApp()->plugins->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 callbackSavedName = QString("c2cb-%1").arg(
magic_enum::enum_name<EventType>(evtType).data());
lua_setfield(L, LUA_REGISTRYINDEX, callbackSavedName.toStdString().c_str());

lua_pop(L, 2);

return 0;
}

int c2_send_msg(lua_State *L)
{
QString text;
Expand Down Expand Up @@ -167,6 +198,7 @@ int c2_system_msg(lua_State *L)
lua::push(L, false);
return 1;
}

const auto chn = getApp()->twitch->getChannelOrEmpty(channel);
if (chn->isEmpty())
{
Expand Down
24 changes: 23 additions & 1 deletion src/controllers/plugins/LuaAPI.hpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#pragma once

#ifdef CHATTERINO_HAVE_PLUGINS
# include <QString>

# include <vector>

struct lua_State;
namespace chatterino::lua::api {
// names in this namespace reflect what's visible inside Lua and follow the lua naming scheme
// function names in this namespace reflect what's visible inside Lua and follow the lua naming scheme

// NOLINTBEGIN(readability-identifier-naming)
// Following functions are exposed in c2 table.
int c2_register_command(lua_State *L);
int c2_register_callback(lua_State *L);
int c2_send_msg(lua_State *L);
int c2_system_msg(lua_State *L);
int c2_log(lua_State *L);
Expand All @@ -23,6 +27,24 @@ int g_import(lua_State *L);
// Represents "calls" to qCDebug, qCInfo ...
enum class LogLevel { Debug, Info, Warning, Critical };

// Exposed as c2.EventType
// Represents callbacks c2 can do into lua world
enum class EventType {
CompletionRequested,
};

/**
* This is for custom completion, a registered function returns this type
* however in Lua array part (value) and object part (hideOthers) are in the same
* table.
*/
struct CompletionList {
std::vector<QString> values{};

// exposed as hide_others
bool hideOthers{};
};

} // namespace chatterino::lua::api

#endif
44 changes: 44 additions & 0 deletions src/controllers/plugins/LuaUtilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# include "common/Channel.hpp"
# include "common/QLogging.hpp"
# include "controllers/commands/CommandContext.hpp"
# include "controllers/plugins/LuaAPI.hpp"

# include <lauxlib.h>
# include <lua.h>
Expand Down Expand Up @@ -75,6 +76,9 @@ QString humanErrorText(lua_State *L, int errCode)
case LUA_ERRFILE:
errName = "(file error)";
break;
case ERROR_BAD_PEEK:
errName = "(unable to convert value to c++)";
break;
default:
errName = "(unknown error type)";
}
Expand Down Expand Up @@ -111,6 +115,7 @@ StackIdx push(lua_State *L, const std::string &str)

StackIdx push(lua_State *L, const CommandContext &ctx)
{
StackGuard guard(L, 1);
auto outIdx = pushEmptyTable(L, 2);

push(L, ctx.words);
Expand All @@ -127,8 +132,27 @@ StackIdx push(lua_State *L, const bool &b)
return lua_gettop(L);
}

StackIdx push(lua_State *L, const int &b)
{
lua_pushinteger(L, b);
return lua_gettop(L);
}

bool peek(lua_State *L, bool *out, StackIdx idx)
{
StackGuard guard(L);
if (!lua_isboolean(L, idx))
{
return false;
}

*out = bool(lua_toboolean(L, idx));
return true;
}

bool peek(lua_State *L, double *out, StackIdx idx)
{
StackGuard guard(L);
int ok{0};
auto v = lua_tonumberx(L, idx, &ok);
if (ok != 0)
Expand All @@ -140,6 +164,7 @@ bool peek(lua_State *L, double *out, StackIdx idx)

bool peek(lua_State *L, QString *out, StackIdx idx)
{
StackGuard guard(L);
size_t len{0};
const char *str = lua_tolstring(L, idx, &len);
if (str == nullptr)
Expand All @@ -156,6 +181,7 @@ bool peek(lua_State *L, QString *out, StackIdx idx)

bool peek(lua_State *L, QByteArray *out, StackIdx idx)
{
StackGuard guard(L);
size_t len{0};
const char *str = lua_tolstring(L, idx, &len);
if (str == nullptr)
Expand All @@ -172,6 +198,7 @@ bool peek(lua_State *L, QByteArray *out, StackIdx idx)

bool peek(lua_State *L, std::string *out, StackIdx idx)
{
StackGuard guard(L);
size_t len{0};
const char *str = lua_tolstring(L, idx, &len);
if (str == nullptr)
Expand All @@ -186,6 +213,23 @@ 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{};
Expand Down
Loading
Loading