degesch: Lua: add autocomplete hooks

This commit is contained in:
Přemysl Eric Janouch 2016-01-15 01:44:35 +01:00
parent 91f3bd60df
commit f39e2a4bc8
1 changed files with 156 additions and 26 deletions

182
degesch.c
View File

@ -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
{
bool no_colors; ///< Disable attribute printing
@ -1543,6 +1575,7 @@ struct app_context
struct plugin *plugins; ///< Loaded plugins
struct hook *input_hooks; ///< Input hooks
struct hook *irc_hooks; ///< IRC hooks
struct hook *completion_hooks; ///< Autocomplete hooks
}
*g_ctx;
@ -7644,6 +7677,7 @@ enum lua_hook_type
XLUA_HOOK_DEFUNCT, ///< No longer functional
XLUA_HOOK_INPUT, ///< Input hook
XLUA_HOOK_IRC, ///< IRC hook
XLUA_HOOK_COMPLETION, ///< Autocomplete
XLUA_HOOK_TIMER, ///< One-shot timer
};
@ -7657,6 +7691,7 @@ struct lua_hook
struct hook hook; ///< Hook base structure
struct input_hook input_hook; ///< Input hook
struct irc_hook irc_hook; ///< IRC hook
struct completion_hook c_hook; ///< Autocomplete hook
struct poller_timer timer; ///< Timer
}
@ -7670,10 +7705,13 @@ lua_hook_unhook (lua_State *L)
switch (hook->type)
{
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;
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;
case XLUA_HOOK_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
lua_timer_hook_dispatch (void *user_data)
{
@ -7832,6 +7959,18 @@ lua_plugin_hook_irc (lua_State *L)
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
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
|| lua_type (L, -1) != LUA_TTABLE)
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));
}
@ -8519,12 +8659,13 @@ lua_plugin_connect (lua_State *L)
static luaL_Reg lua_plugin_library[] =
{
{ "hook_input", lua_plugin_hook_input },
{ "hook_irc", lua_plugin_hook_irc },
{ "hook_timer", lua_plugin_hook_timer },
{ "setup_config", lua_plugin_setup_config },
{ "connect", lua_plugin_connect },
{ NULL, NULL },
{ "hook_input", lua_plugin_hook_input },
{ "hook_irc", lua_plugin_hook_irc },
{ "hook_completion", lua_plugin_hook_completion },
{ "hook_timer", lua_plugin_hook_timer },
{ "setup_config", lua_plugin_setup_config },
{ "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.
// 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
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_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)
{
// Nothing matched