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>
This commit is contained in:
Yoann Desmouceaux
2017-05-18 11:00:48 +02:00
committed by Chris Luke
parent 8c4611b391
commit 3060e07b71
3 changed files with 259 additions and 0 deletions

View File

@ -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 @@ unformat_vlib_cli_sub_command (unformat_input_t * i, va_list * args)
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)
{

View File

@ -181,6 +181,10 @@ clib_error_t *vlib_cli_register_parse_rule (struct vlib_main_t *vm,
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 */
/*

View File

@ -1230,6 +1230,8 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
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 @@ unix_cli_line_process_one (unix_cli_main_t * cm,
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;