degesch: Lua: add autocomplete hooks
This commit is contained in:
parent
91f3bd60df
commit
f39e2a4bc8
182
degesch.c
182
degesch.c
|
@ -1474,6 +1474,38 @@ struct irc_hook_vtable
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
struct completion_word
|
||||||
|
{
|
||||||
|
size_t start; ///< Offset to start of word
|
||||||
|
size_t end; ///< Offset to end of word
|
||||||
|
};
|
||||||
|
|
||||||
|
struct completion
|
||||||
|
{
|
||||||
|
char *line; ///< The line which is being completed
|
||||||
|
|
||||||
|
struct completion_word *words; ///< Word locations
|
||||||
|
size_t words_len; ///< Number of words
|
||||||
|
size_t words_alloc; ///< Number of words allocated
|
||||||
|
|
||||||
|
size_t location; ///< Which word is being completed
|
||||||
|
};
|
||||||
|
|
||||||
|
struct completion_hook
|
||||||
|
{
|
||||||
|
struct hook super; ///< Common hook fields
|
||||||
|
struct completion_hook_vtable *vtable;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct completion_hook_vtable
|
||||||
|
{
|
||||||
|
/// Tries to add possible completions of "word" to "output"
|
||||||
|
void (*complete) (struct completion_hook *self,
|
||||||
|
struct completion *data, const char *word, struct str_vector *output);
|
||||||
|
};
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
struct app_context
|
struct app_context
|
||||||
{
|
{
|
||||||
bool no_colors; ///< Disable attribute printing
|
bool no_colors; ///< Disable attribute printing
|
||||||
|
@ -1543,6 +1575,7 @@ struct app_context
|
||||||
struct plugin *plugins; ///< Loaded plugins
|
struct plugin *plugins; ///< Loaded plugins
|
||||||
struct hook *input_hooks; ///< Input hooks
|
struct hook *input_hooks; ///< Input hooks
|
||||||
struct hook *irc_hooks; ///< IRC hooks
|
struct hook *irc_hooks; ///< IRC hooks
|
||||||
|
struct hook *completion_hooks; ///< Autocomplete hooks
|
||||||
}
|
}
|
||||||
*g_ctx;
|
*g_ctx;
|
||||||
|
|
||||||
|
@ -7644,6 +7677,7 @@ enum lua_hook_type
|
||||||
XLUA_HOOK_DEFUNCT, ///< No longer functional
|
XLUA_HOOK_DEFUNCT, ///< No longer functional
|
||||||
XLUA_HOOK_INPUT, ///< Input hook
|
XLUA_HOOK_INPUT, ///< Input hook
|
||||||
XLUA_HOOK_IRC, ///< IRC hook
|
XLUA_HOOK_IRC, ///< IRC hook
|
||||||
|
XLUA_HOOK_COMPLETION, ///< Autocomplete
|
||||||
XLUA_HOOK_TIMER, ///< One-shot timer
|
XLUA_HOOK_TIMER, ///< One-shot timer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7657,6 +7691,7 @@ struct lua_hook
|
||||||
struct hook hook; ///< Hook base structure
|
struct hook hook; ///< Hook base structure
|
||||||
struct input_hook input_hook; ///< Input hook
|
struct input_hook input_hook; ///< Input hook
|
||||||
struct irc_hook irc_hook; ///< IRC hook
|
struct irc_hook irc_hook; ///< IRC hook
|
||||||
|
struct completion_hook c_hook; ///< Autocomplete hook
|
||||||
|
|
||||||
struct poller_timer timer; ///< Timer
|
struct poller_timer timer; ///< Timer
|
||||||
}
|
}
|
||||||
|
@ -7670,10 +7705,13 @@ lua_hook_unhook (lua_State *L)
|
||||||
switch (hook->type)
|
switch (hook->type)
|
||||||
{
|
{
|
||||||
case XLUA_HOOK_INPUT:
|
case XLUA_HOOK_INPUT:
|
||||||
LIST_UNLINK (hook->plugin->ctx->input_hooks, &hook->data.hook);
|
LIST_UNLINK (hook->plugin->ctx->input_hooks, &hook->data.hook);
|
||||||
break;
|
break;
|
||||||
case XLUA_HOOK_IRC:
|
case XLUA_HOOK_IRC:
|
||||||
LIST_UNLINK (hook->plugin->ctx->irc_hooks, &hook->data.hook);
|
LIST_UNLINK (hook->plugin->ctx->irc_hooks, &hook->data.hook);
|
||||||
|
break;
|
||||||
|
case XLUA_HOOK_COMPLETION:
|
||||||
|
LIST_UNLINK (hook->plugin->ctx->completion_hooks, &hook->data.hook);
|
||||||
break;
|
break;
|
||||||
case XLUA_HOOK_TIMER:
|
case XLUA_HOOK_TIMER:
|
||||||
poller_timer_reset (&hook->data.timer);
|
poller_timer_reset (&hook->data.timer);
|
||||||
|
@ -7766,6 +7804,95 @@ struct irc_hook_vtable lua_irc_hook_vtable =
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static void
|
||||||
|
lua_plugin_push_completion (lua_State *L, struct completion *data)
|
||||||
|
{
|
||||||
|
lua_createtable (L, 0, 3);
|
||||||
|
|
||||||
|
lua_pushstring (L, data->line);
|
||||||
|
lua_setfield (L, -2, "line");
|
||||||
|
|
||||||
|
lua_createtable (L, data->words_len, 0);
|
||||||
|
for (size_t i = 0; i < data->words_len; i++)
|
||||||
|
{
|
||||||
|
lua_pushlstring (L, data->line + data->words[i].start,
|
||||||
|
data->words[i].end - data->words[i].start);
|
||||||
|
lua_rawseti (L, -2, i + 1);
|
||||||
|
}
|
||||||
|
lua_setfield (L, -2, "words");
|
||||||
|
|
||||||
|
lua_pushinteger (L, data->location);
|
||||||
|
lua_setfield (L, -2, "location");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
lua_completion_hook_process_value (lua_State *L, struct str_vector *output,
|
||||||
|
struct error **e)
|
||||||
|
{
|
||||||
|
if (lua_type (L, -1) != LUA_TSTRING)
|
||||||
|
FAIL ("%s: %s", "invalid type", lua_typename (L, lua_type (L, -1)));
|
||||||
|
|
||||||
|
size_t len;
|
||||||
|
const char *value = lua_tolstring (L, -1, &len);
|
||||||
|
if (!utf8_validate (value, len))
|
||||||
|
FAIL ("must be valid UTF-8");
|
||||||
|
|
||||||
|
str_vector_add (output, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
lua_completion_hook_process (lua_State *L, struct str_vector *output,
|
||||||
|
struct error **e)
|
||||||
|
{
|
||||||
|
if (lua_isnil (L, -1))
|
||||||
|
return true;
|
||||||
|
if (!lua_istable (L, -1))
|
||||||
|
FAIL ("must return either a table or nil");
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
for (lua_Integer i = 1; success && lua_rawgeti (L, -1, i); i++)
|
||||||
|
if ((success = lua_completion_hook_process_value (L, output, e)))
|
||||||
|
lua_pop (L, 1);
|
||||||
|
lua_pop (L, 1);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
lua_completion_hook_complete (struct completion_hook *self,
|
||||||
|
struct completion *data, const char *word, struct str_vector *output)
|
||||||
|
{
|
||||||
|
struct lua_hook *hook =
|
||||||
|
CONTAINER_OF (self, struct lua_hook, data.c_hook);
|
||||||
|
struct lua_plugin *plugin = hook->plugin;
|
||||||
|
lua_State *L = plugin->L;
|
||||||
|
|
||||||
|
lua_rawgeti (L, LUA_REGISTRYINDEX, hook->ref_callback);
|
||||||
|
lua_rawgetp (L, LUA_REGISTRYINDEX, hook); // 1: hook
|
||||||
|
lua_plugin_push_completion (L, data); // 2: data
|
||||||
|
|
||||||
|
lua_plugin_push_buffer (plugin, plugin->ctx->current_buffer);
|
||||||
|
lua_setfield (L, -2, "buffer");
|
||||||
|
|
||||||
|
lua_pushstring (L, word); // 3: word
|
||||||
|
|
||||||
|
struct error *e = NULL;
|
||||||
|
if (lua_plugin_call (plugin, 3, 1, &e))
|
||||||
|
{
|
||||||
|
lua_completion_hook_process (L, output, &e);
|
||||||
|
lua_pop (L, 1);
|
||||||
|
}
|
||||||
|
if (e)
|
||||||
|
lua_plugin_log_error (plugin, "autocomplete hook", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct completion_hook_vtable lua_completion_hook_vtable =
|
||||||
|
{
|
||||||
|
.complete = lua_completion_hook_complete,
|
||||||
|
};
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
static void
|
static void
|
||||||
lua_timer_hook_dispatch (void *user_data)
|
lua_timer_hook_dispatch (void *user_data)
|
||||||
{
|
{
|
||||||
|
@ -7832,6 +7959,18 @@ lua_plugin_hook_irc (lua_State *L)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
lua_plugin_hook_completion (lua_State *L)
|
||||||
|
{
|
||||||
|
struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1));
|
||||||
|
struct lua_hook *hook = lua_plugin_push_hook
|
||||||
|
(plugin, 1, XLUA_HOOK_COMPLETION, luaL_optinteger (L, 2, 0));
|
||||||
|
hook->data.c_hook.vtable = &lua_completion_hook_vtable;
|
||||||
|
plugin->ctx->completion_hooks =
|
||||||
|
hook_insert (plugin->ctx->completion_hooks, &hook->data.hook);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
lua_plugin_hook_timer (lua_State *L)
|
lua_plugin_hook_timer (lua_State *L)
|
||||||
{
|
{
|
||||||
|
@ -8115,7 +8254,8 @@ lua_plugin_setup_config (lua_State *L)
|
||||||
if (lua_type (L, -2) != LUA_TSTRING
|
if (lua_type (L, -2) != LUA_TSTRING
|
||||||
|| lua_type (L, -1) != LUA_TTABLE)
|
|| lua_type (L, -1) != LUA_TTABLE)
|
||||||
return luaL_error (L, "%s: %s -> %s", "invalid types",
|
return luaL_error (L, "%s: %s -> %s", "invalid types",
|
||||||
lua_typename (L, -2), lua_typename (L, -1));
|
lua_typename (L, lua_type (L, -2)),
|
||||||
|
lua_typename (L, lua_type (L, -1)));
|
||||||
lua_plugin_add_config_schema (plugin, subtree, lua_tostring (L, -2));
|
lua_plugin_add_config_schema (plugin, subtree, lua_tostring (L, -2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8519,12 +8659,13 @@ lua_plugin_connect (lua_State *L)
|
||||||
|
|
||||||
static luaL_Reg lua_plugin_library[] =
|
static luaL_Reg lua_plugin_library[] =
|
||||||
{
|
{
|
||||||
{ "hook_input", lua_plugin_hook_input },
|
{ "hook_input", lua_plugin_hook_input },
|
||||||
{ "hook_irc", lua_plugin_hook_irc },
|
{ "hook_irc", lua_plugin_hook_irc },
|
||||||
{ "hook_timer", lua_plugin_hook_timer },
|
{ "hook_completion", lua_plugin_hook_completion },
|
||||||
{ "setup_config", lua_plugin_setup_config },
|
{ "hook_timer", lua_plugin_hook_timer },
|
||||||
{ "connect", lua_plugin_connect },
|
{ "setup_config", lua_plugin_setup_config },
|
||||||
{ NULL, NULL },
|
{ "connect", lua_plugin_connect },
|
||||||
|
{ NULL, NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
@ -10440,23 +10581,6 @@ process_input (struct app_context *ctx, char *user_input)
|
||||||
// The amount of crap that goes into this is truly insane.
|
// The amount of crap that goes into this is truly insane.
|
||||||
// It's mostly because of Editline's total ignorance of this task.
|
// It's mostly because of Editline's total ignorance of this task.
|
||||||
|
|
||||||
struct completion_word
|
|
||||||
{
|
|
||||||
size_t start; ///< Offset to start of word
|
|
||||||
size_t end; ///< Offset to end of word
|
|
||||||
};
|
|
||||||
|
|
||||||
struct completion
|
|
||||||
{
|
|
||||||
char *line; ///< The line which is being completed
|
|
||||||
|
|
||||||
struct completion_word *words; ///< Word locations
|
|
||||||
size_t words_len; ///< Number of words
|
|
||||||
size_t words_alloc; ///< Number of words allocated
|
|
||||||
|
|
||||||
size_t location; ///< Which word is being completed
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
completion_init (struct completion *self)
|
completion_init (struct completion *self)
|
||||||
{
|
{
|
||||||
|
@ -10698,6 +10822,12 @@ complete_word (struct app_context *ctx, struct completion *data,
|
||||||
if (try_topic) complete_topic (ctx, data, word, &words);
|
if (try_topic) complete_topic (ctx, data, word, &words);
|
||||||
if (try_nicknames) complete_nicknames (ctx, data, word, &words);
|
if (try_nicknames) complete_nicknames (ctx, data, word, &words);
|
||||||
|
|
||||||
|
LIST_FOR_EACH (struct hook, iter, ctx->completion_hooks)
|
||||||
|
{
|
||||||
|
struct completion_hook *hook = (struct completion_hook *) iter;
|
||||||
|
hook->vtable->complete (hook, data, word, &words);
|
||||||
|
}
|
||||||
|
|
||||||
if (words.len == 1)
|
if (words.len == 1)
|
||||||
{
|
{
|
||||||
// Nothing matched
|
// Nothing matched
|
||||||
|
|
Loading…
Reference in New Issue