Add TAB-based auto-completion to the CLI
Hitting tab:
- in the middle of a uniquely defined subcommand will expand the subcommand
- in the middle of a non-uniquely defined (or empty) subcommand will display
all possible subcommands, and possibly expand to the lowest common prefix
Change-Id: Ib858eefdb0353cd2c3aad472799d15cd537455a0
Signed-off-by: Yoann Desmouceaux <ydesmouc@cisco.com>
diff --git a/src/vlib/cli.c b/src/vlib/cli.c
index 5a0f7ec..9e14bee 100644
--- a/src/vlib/cli.c
+++ b/src/vlib/cli.c
@@ -40,6 +40,7 @@
#include <vlib/vlib.h>
#include <vppinfra/cpu.h>
#include <unistd.h>
+#include <ctype.h>
/* Root of all show commands. */
/* *INDENT-OFF* */
@@ -235,6 +236,107 @@
return is_unique;
}
+static int
+vlib_cli_cmp_strings (void *a1, void *a2)
+{
+ u8 *c1 = *(u8 **) a1;
+ u8 *c2 = *(u8 **) a2;
+
+ return vec_cmp (c1, c2);
+}
+
+u8 **
+vlib_cli_get_possible_completions (u8 * str)
+{
+ vlib_cli_command_t *c;
+ vlib_cli_sub_command_t *sc;
+ vlib_main_t *vm = vlib_get_main ();
+ vlib_cli_main_t *vcm = &vm->cli_main;
+ uword *match_bitmap = 0;
+ uword index, is_unique, help_next_level;
+ u8 **result = 0;
+ unformat_input_t input;
+ unformat_init_vector (&input, vec_dup (str));
+ c = vec_elt_at_index (vcm->commands, 0);
+
+ /* remove trailing whitespace, except for one of them */
+ while (vec_len (input.buffer) >= 2 &&
+ isspace (input.buffer[vec_len (input.buffer) - 1]) &&
+ isspace (input.buffer[vec_len (input.buffer) - 2]))
+ {
+ vec_del1 (input.buffer, vec_len (input.buffer) - 1);
+ }
+
+ /* if input is empty, directly return list of root commands */
+ if (vec_len (input.buffer) == 0 ||
+ (vec_len (input.buffer) == 1 && isspace (input.buffer[0])))
+ {
+ vec_foreach (sc, c->sub_commands)
+ {
+ vec_add1 (result, (u8 *) sc->name);
+ }
+ goto done;
+ }
+
+ /* add a trailing '?' so that vlib_cli_sub_command_match can find
+ * all commands starting with the input string */
+ vec_add1 (input.buffer, '?');
+
+ while (1)
+ {
+ match_bitmap = vlib_cli_sub_command_match (c, &input);
+ /* no match: return no result */
+ if (match_bitmap == 0)
+ {
+ goto done;
+ }
+ is_unique = clib_bitmap_count_set_bits (match_bitmap) == 1;
+ /* unique match: try to step one subcommand level further */
+ if (is_unique)
+ {
+ /* stop if no more input */
+ if (input.index >= vec_len (input.buffer) - 1)
+ {
+ break;
+ }
+
+ index = clib_bitmap_first_set (match_bitmap);
+ c = get_sub_command (vcm, c, index);
+ clib_bitmap_free (match_bitmap);
+ continue;
+ }
+ /* multiple matches: stop here, return all matches */
+ break;
+ }
+
+ /* remove trailing '?' */
+ vec_del1 (input.buffer, vec_len (input.buffer) - 1);
+
+ /* if we have a space at the end of input, and a unique match,
+ * autocomplete the next level of subcommands */
+ help_next_level = (vec_len (str) == 0) || isspace (str[vec_len (str) - 1]);
+ /* *INDENT-OFF* */
+ clib_bitmap_foreach(index, match_bitmap, {
+ if (help_next_level && is_unique) {
+ c = get_sub_command (vcm, c, index);
+ vec_foreach (sc, c->sub_commands) {
+ vec_add1 (result, (u8*) sc->name);
+ }
+ goto done; /* break doesn't work in this macro-loop */
+ }
+ sc = &c->sub_commands[index];
+ vec_add1(result, (u8*) sc->name);
+ });
+ /* *INDENT-ON* */
+
+done:
+ clib_bitmap_free (match_bitmap);
+ unformat_free (&input);
+
+ vec_sort_with_function (result, vlib_cli_cmp_strings);
+ return result;
+}
+
static u8 *
format_vlib_cli_command_help (u8 * s, va_list * args)
{
diff --git a/src/vlib/cli.h b/src/vlib/cli.h
index 009c7e8..e713808 100644
--- a/src/vlib/cli.h
+++ b/src/vlib/cli.h
@@ -181,6 +181,10 @@
uword unformat_vlib_cli_sub_input (unformat_input_t * i, va_list * args);
+/* Return an vector of strings consisting of possible auto-completions
+ * for a given input string */
+u8 **vlib_cli_get_possible_completions (u8 * input_str);
+
#endif /* included_vlib_cli_h */
/*
diff --git a/src/vlib/unix/cli.c b/src/vlib/unix/cli.c
index 88e2453..cf52421 100644
--- a/src/vlib/unix/cli.c
+++ b/src/vlib/unix/cli.c
@@ -1230,6 +1230,8 @@
u8 input, unix_cli_parse_action_t action)
{
u8 *prev;
+ u8 *save = 0;
+ u8 **possible_commands;
int j, delta;
switch (action)
@@ -1553,6 +1555,157 @@
break;
case UNIX_CLI_PARSE_ACTION_TAB:
+ if (cf->cursor < vec_len (cf->current_command))
+ {
+ /* if we are in the middle of a line, complete only if
+ * the cursor points to whitespace */
+ if (isspace (cf->current_command[cf->cursor]))
+ {
+ /* save and clear any input that is after the cursor */
+ vec_resize (save, vec_len (cf->current_command) - cf->cursor);
+ clib_memcpy (save, cf->current_command + cf->cursor,
+ vec_len (cf->current_command) - cf->cursor);
+ _vec_len (cf->current_command) = cf->cursor;
+ }
+ else
+ {
+ unix_vlib_cli_output_raw (cf, uf, (u8 *) "\a", 1);
+ break;
+ }
+ }
+ possible_commands =
+ vlib_cli_get_possible_completions (cf->current_command);
+ if (vec_len (possible_commands) == 1)
+ {
+ u32 j = cf->cursor;
+ u8 *completed = possible_commands[0];
+
+ /* find the last word of current_command */
+ while (j >= 1 && !isspace (cf->current_command[j - 1]))
+ {
+ j--;
+ unix_vlib_cli_output_raw (cf, uf, (u8 *) "\b", 1);
+ }
+ _vec_len (cf->current_command) = j;
+
+ /* replace it with the newly expanded command */
+ vec_append (cf->current_command, completed);
+
+ /* echo to the terminal */
+ unix_vlib_cli_output_raw (cf, uf, completed, vec_len (completed));
+
+ /* add one trailing space if needed */
+ if (vec_len (save) == 0)
+ {
+ vec_add1 (cf->current_command, ' ');
+ unix_vlib_cli_output_raw (cf, uf, (u8 *) " ", 1);
+ }
+
+ cf->cursor = vec_len (cf->current_command);
+
+ }
+ else if (vec_len (possible_commands) >= 2)
+ {
+ u8 **possible_command;
+ uword max_command_len = 0, min_command_len = ~0;
+ u32 i, j;
+
+ vec_foreach (possible_command, possible_commands)
+ {
+ if (vec_len (*possible_command) > max_command_len)
+ {
+ max_command_len = vec_len (*possible_command);
+ }
+ if (vec_len (*possible_command) < min_command_len)
+ {
+ min_command_len = vec_len (*possible_command);
+ }
+ }
+
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
+
+ i = 0;
+ vec_foreach (possible_command, possible_commands)
+ {
+ if (i + max_command_len >= cf->width)
+ {
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
+ i = 0;
+ }
+ unix_vlib_cli_output_raw (cf, uf, *possible_command,
+ vec_len (*possible_command));
+ for (j = vec_len (*possible_command); j < max_command_len + 2;
+ j++)
+ {
+ unix_vlib_cli_output_raw (cf, uf, (u8 *) " ", 1);
+ }
+ i += max_command_len + 2;
+ }
+
+ unix_vlib_cli_output_cooked (cf, uf, (u8 *) "\n", 1);
+
+ /* rewrite prompt */
+ unix_cli_cli_prompt (cf, uf);
+ unix_vlib_cli_output_raw (cf, uf, cf->current_command,
+ vec_len (cf->current_command));
+
+ /* count length of last word */
+ j = cf->cursor;
+ i = 0;
+ while (j >= 1 && !isspace (cf->current_command[j - 1]))
+ {
+ j--;
+ i++;
+ }
+
+ /* determine smallest common command */
+ for (; i < min_command_len; i++)
+ {
+ u8 common = '\0';
+ int stop = 0;
+ vec_foreach (possible_command, possible_commands)
+ {
+ if (common == '\0')
+ {
+ common = (*possible_command)[i];
+ }
+ else if (common != (*possible_command)[i])
+ {
+ stop = 1;
+ break;
+ }
+ }
+ if (!stop)
+ {
+ vec_add1 (cf->current_command, common);
+ cf->cursor++;
+ unix_vlib_cli_output_raw (cf, uf, (u8 *) & common, 1);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ unix_vlib_cli_output_raw (cf, uf, (u8 *) "\a", 1);
+ }
+
+ if (vec_len (save) > 0)
+ {
+ /* restore remaining input if tab was hit in the middle of a line */
+ unix_vlib_cli_output_raw (cf, uf, save, vec_len (save));
+ for (j = 0; j < vec_len (save); j++)
+ {
+ unix_vlib_cli_output_raw (cf, uf, (u8 *) "\b", 1);
+ }
+ vec_append (cf->current_command, save);
+ vec_free (save);
+ }
+ vec_free (possible_commands);
+
+ break;
case UNIX_CLI_PARSE_ACTION_YANK:
/* TODO */
break;