From f8fab6e22d1b6b18954cd550c9bcdb9fb410f804 Mon Sep 17 00:00:00 2001 From: Max Schillinger Date: Mon, 18 Mar 2024 10:08:40 +0100 Subject: [PATCH] Add command completion with tab key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the command prompt, press to get a list of all available commands and pick one (using vis-menu). This works also after typing the first letters of a command (p.e. `:la`). Co-authored-by: Matěj Cepl --- lua/plugins/complete-filename.lua | 18 ++++++++++---- vis-cmds.c | 13 +++++++++++ vis-lua.c | 39 +++++++++++++++++++++++++++++++ vis.h | 6 +++++ 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/lua/plugins/complete-filename.lua b/lua/plugins/complete-filename.lua index 43cf14b1d..8a16b71f2 100644 --- a/lua/plugins/complete-filename.lua +++ b/lua/plugins/complete-filename.lua @@ -22,9 +22,19 @@ local complete_filename = function(expand) range.finish = pos end - local cmdfmt = "vis-complete --file '%s'" - if expand then cmdfmt = "vis-open -- '%s'*" end - local status, out, err = vis:pipe(cmdfmt:format(prefix:gsub("'", "'\\''"))) + local status, out, err + if prefix:sub(1, 1) == ":" then + status, out, err = vis:complete_command(prefix:sub(2)) + if out then + out = out:gsub("\n$", ""):sub(#prefix) .. " " + end + pos = range.start + #prefix + expand = false + else + local cmdfmt = "vis-complete --file '%s'" + if expand then cmdfmt = "vis-open -- '%s'*" end + status, out, err = vis:pipe(cmdfmt:format(prefix:gsub("'", "'\\''"))) + end if status ~= 0 or not out then if err then vis:info(err) end return @@ -48,4 +58,4 @@ end, "Complete file name") -- complete file path at primary selection location using vis-open(1) vis:map(vis.modes.INSERT, "", function() complete_filename(true); -end, "Complete file name (expands path)") +end, "Complete file name (expands path) or command") diff --git a/vis-cmds.c b/vis-cmds.c index 4cfb3bd5d..bbbaffd05 100644 --- a/vis-cmds.c +++ b/vis-cmds.c @@ -696,6 +696,19 @@ static bool print_cmd(const char *key, void *value, void *data) { return text_appendf(data, " %-30s %s\n", usage, help ? help : ""); } +static bool print_cmd_name(const char *key, void *value, void *data) { + CommandDef *cmd = value; + size_t len_data = strlen(data); + size_t remaining_capacity = VIS_COMMAND_BUFFER_MAX - len_data; + if (remaining_capacity >= strlen(cmd->name) + 2) /* adding 2 for \n and \0 */ + snprintf((char *)data + len_data, remaining_capacity, "%s\n", cmd->name); + return true; +} + +void vis_print_cmds(Vis *vis, char *cmd_list) { + map_iterate(vis->cmds, print_cmd_name, cmd_list); +} + static bool print_option(const char *key, void *value, void *txt) { char desc[256]; const OptionDef *opt = value; diff --git a/vis-lua.c b/vis-lua.c index e3e85c345..d2ce9b7eb 100644 --- a/vis-lua.c +++ b/vis-lua.c @@ -1146,6 +1146,44 @@ static int command_register(lua_State *L) { return 1; } +/*** + * Let user pick a command matching the given prefix. + * + * The editor core will be blocked while the external process is running. + * + * @function complete_command + * @tparam string prefix the prefix of the command to be completed + * @treturn int code the exit status of the executed command + * @treturn string stdout the data written to stdout + * @treturn string stderr the data written to stderr + */ +static int complete_command(lua_State *L) { + Vis *vis = obj_ref_check(L, 1, "vis"); + const char *prefix = luaL_checkstring(L, 2); + char *out = NULL, *err = NULL; + File *file = vis->win ? vis->win->file : NULL; + Filerange range = text_range_new(0, 0); + + char cmd_list[VIS_COMMAND_BUFFER_MAX] = {0}; + vis_print_cmds(vis, cmd_list); + char cmd[VIS_COMMAND_BUFFER_MAX + 32] = {0}; /* 32 for echo/grep/vis-menu */ + sprintf(cmd, "echo '%s' | grep '^%s' | vis-menu -b", cmd_list, prefix); + + int status = vis_pipe_collect(vis, file, &range, (const char*[]){ cmd, NULL }, &out, &err, false); + lua_pushinteger(L, status); + if (out) + lua_pushstring(L, out); + else + lua_pushnil(L); + free(out); + if (err) + lua_pushstring(L, err); + else + lua_pushnil(L); + free(err); + return 3; +} + /*** * Push keys to input queue and interpret them. * @@ -1531,6 +1569,7 @@ static const struct luaL_Reg vis_lua[] = { { "option_register", option_register }, { "option_unregister", option_unregister }, { "command_register", command_register }, + { "complete_command", complete_command }, { "feedkeys", feedkeys }, { "insert", insert }, { "replace", replace }, diff --git a/vis.h b/vis.h index eb8ec92d1..82ca60f24 100644 --- a/vis.h +++ b/vis.h @@ -40,6 +40,9 @@ typedef struct Win Win; /* maximum bytes needed for string representation of a (pseudo) key */ #define VIS_KEY_LENGTH_MAX 64 +/* maximum bytes used for list of available commands */ +#define VIS_COMMAND_BUFFER_MAX 1000 + /** Union used to pass arguments to key action functions. */ typedef union { bool b; @@ -838,6 +841,9 @@ bool vis_option_unregister(Vis*, const char *name); /** Execute any kind (``:``, ``?``, ``/``) of prompt command */ bool vis_prompt_cmd(Vis*, const char *cmd); +/** Write newline separated list of available commands to ``cmd_list`` */ +void vis_print_cmds(Vis*, char *cmd_list); + /** * Pipe a given file range to an external process. *