diff --git a/degesch.c b/degesch.c index fe503aa..92669fd 100644 --- a/degesch.c +++ b/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 { 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