From 3987eba9ab13c414889904a487e92fb1a31f712d Mon Sep 17 00:00:00 2001 From: Nishith Shah Date: Fri, 8 Jul 2016 18:26:05 +0530 Subject: [PATCH] virsh: Introduce vshReadlineParse for improved auto-completion The new function works as expected, and matches the current level of autocomplete offered, along with several other improvements like quotes handling, multiple command completion and space handling. Now, it is easy to introduce options completer here. Signed-off-by: Nishith Shah --- tools/vsh.c | 221 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 200 insertions(+), 21 deletions(-) diff --git a/tools/vsh.c b/tools/vsh.c index fd93ccadf3..68f77859fb 100644 --- a/tools/vsh.c +++ b/tools/vsh.c @@ -2582,7 +2582,7 @@ vshReadlineCommandGenerator(const char *text, int state) } static char * -vshReadlineOptionsGenerator(const char *text, int state) +vshReadlineOptionsGenerator(const char *text, int state, const vshCmdDef *cmd_parsed) { static unsigned int list_index; static size_t len; @@ -2590,20 +2590,9 @@ vshReadlineOptionsGenerator(const char *text, int state) const char *name; if (!state) { - /* determine command name */ - char *p; - char *cmdname; - - if (!(p = strchr(rl_line_buffer, ' '))) - return NULL; - - cmdname = vshCalloc(NULL, (p - rl_line_buffer) + 1, 1); - memcpy(cmdname, rl_line_buffer, p - rl_line_buffer); - - cmd = vshCmddefSearch(cmdname); + cmd = cmd_parsed; list_index = 0; len = strlen(text); - VIR_FREE(cmdname); } if (!cmd) @@ -2623,8 +2612,13 @@ vshReadlineOptionsGenerator(const char *text, int state) continue; if (len > 2) { + /* provide auto-complete only when the text starts with -- */ + if (STRNEQLEN(text, "--", 2)) + return NULL; if (STRNEQLEN(name, text + 2, len - 2)) continue; + } else if (STRNEQLEN(text, "--", len)) { + return NULL; } res = vshMalloc(NULL, strlen(name) + 3); snprintf(res, strlen(name) + 3, "--%s", name); @@ -2635,18 +2629,201 @@ vshReadlineOptionsGenerator(const char *text, int state) return NULL; } +static char * +vshReadlineParse(const char *text, int state) +{ + static vshCommandParser parser, sanitizer; + vshCommandToken tk; + static const vshCmdDef *cmd; + const vshCmdOptDef *opt; + char *tkdata, *optstr, *const_tkdata, *res; + static char *ctext, *sanitized_text; + static uint64_t const_opts_need_arg, const_opts_required, const_opts_seen; + uint64_t opts_need_arg, opts_required, opts_seen; + unsigned int opt_index; + static bool cmd_exists, opts_filled, opt_exists; + static bool non_bool_opt_exists, data_acomplete; + + if (!state) { + parser.pos = rl_line_buffer; + parser.getNextArg = vshCommandStringGetArg; + + ctext = vshStrdup(NULL, text); + sanitizer.pos = ctext; + sanitizer.getNextArg = vshCommandStringGetArg; + + const_tkdata = NULL; + tkdata = NULL; + sanitized_text = NULL; + optstr = NULL; + res = NULL; + + /* Sanitize/de-quote the autocomplete text */ + tk = sanitizer.getNextArg(NULL, &sanitizer, &sanitized_text, false); + + /* No autocomplete if sanitized text is a token error or token end */ + if (tk == VSH_TK_ERROR) + goto error; + + tk = parser.getNextArg(NULL, &parser, &const_tkdata, false); + + if (tk == VSH_TK_ERROR) + goto error; + + /* Free-ing purposes */ + tkdata = const_tkdata; + /* Skip leading space */ + virSkipSpaces((const char**)&tkdata); + + /* Handle ';'s */ + while (tk == VSH_TK_SUBCMD_END) { + tk = parser.getNextArg(NULL, &parser, &const_tkdata, false); + tkdata = const_tkdata; + } + + /* Skip trailing space after ;*/ + virSkipSpaces((const char**)&tkdata); + + cmd_exists = false; + opts_filled = false; + non_bool_opt_exists = false; + data_acomplete = false; + + const_opts_need_arg = 0; + const_opts_required = 0; + const_opts_seen = 0; + + opt_index = 0; + + cmd = NULL; + opt = NULL; + + /* Parse till text to be auto-completed is reached */ + while (STRNEQ(tkdata, sanitized_text)) { + if (!cmd) { + if (!(cmd = vshCmddefSearch(tkdata))) + goto error; + cmd_exists = true; + + if (vshCmddefOptFill(cmd, &const_opts_need_arg, + &const_opts_required) < 0) + goto error; + opts_filled = true; + } else if (tkdata[0] == '-' && tkdata[1] == '-' && + c_isalnum(tkdata[2])) { + /* Command retrieved successfully, move to options */ + opts_need_arg = const_opts_need_arg; + opts_required = const_opts_required; + opts_seen = const_opts_seen; + optstr = strchr(tkdata + 2, '='); + opt_index = 0; + + if (optstr) { + *optstr = '\0'; + optstr = vshStrdup(NULL, optstr + 1); + } + + if (!(opt = vshCmddefGetOption(NULL, cmd, tkdata + 2, + &opts_seen, &opt_index, + &optstr, false))) { + /* Parsing failed wrt autocomplete */ + VIR_FREE(optstr); + goto error; + } + opt_exists = true; + VIR_FREE(const_tkdata); + if (opt->type != VSH_OT_BOOL) { + non_bool_opt_exists = true; + /* Opt exists and check for option data */ + if (optstr) { + const_tkdata = optstr; + tkdata = const_tkdata; + } else { + VIR_FREE(const_tkdata); + tk = parser.getNextArg(NULL, &parser, &const_tkdata, + false); + + if (tk == VSH_TK_ERROR) + goto error; + + tkdata = const_tkdata; + if (STREQ(tkdata, sanitized_text)) { + /* auto-complete non-bool option */ + data_acomplete = true; + break; + } + } + if (opt->type != VSH_OT_ARGV) + opts_need_arg &= ~(1ULL << opt_index); + } else { + tkdata = NULL; + /* opt type is BOOL */ + if (optstr) { + VIR_FREE(optstr); + goto error; + } + } + } else { + /* No -- option provided and some other token given */ + if (!opt_exists) { + goto error; + } else if (non_bool_opt_exists) { + /* TODO + * -- non bool option present, so parse the next arg + * or call completer on it if it is to be completed + */ + return NULL; + } + } + + VIR_FREE(const_tkdata); + tk = parser.getNextArg(NULL, &parser, &const_tkdata, false); + + if (tk == VSH_TK_ERROR) + goto error; + + while (tk == VSH_TK_SUBCMD_END) { + cmd = NULL; + cmd_exists = false; + opts_filled = false; + opt = NULL; + non_bool_opt_exists = false; + tk = parser.getNextArg(NULL, &parser, &const_tkdata, false); + } + + tkdata = const_tkdata; + + virSkipSpaces((const char**)&tkdata); + } + VIR_FREE(const_tkdata); + } + + if (!cmd_exists) + res = vshReadlineCommandGenerator(sanitized_text, state); + else if (opts_filled && !non_bool_opt_exists) + res = vshReadlineOptionsGenerator(sanitized_text, state, cmd); + + if (!res) { + VIR_FREE(sanitized_text); + VIR_FREE(ctext); + } + + return res; + + error: + VIR_FREE(const_tkdata); + VIR_FREE(sanitized_text); + VIR_FREE(ctext); + return NULL; + +} + static char ** -vshReadlineCompletion(const char *text, int start, - int end ATTRIBUTE_UNUSED) +vshReadlineCompletion(const char *text, int start, int end ATTRIBUTE_UNUSED) { char **matches = (char **) NULL; - if (start == 0) - /* command name generator */ - matches = rl_completion_matches(text, vshReadlineCommandGenerator); - else - /* commands options */ - matches = rl_completion_matches(text, vshReadlineOptionsGenerator); + matches = rl_completion_matches(text, vshReadlineParse); return matches; } @@ -2676,6 +2853,8 @@ vshReadlineInit(vshControl *ctl) /* Tell the completer that we want a crack first. */ rl_attempted_completion_function = vshReadlineCompletion; + rl_basic_word_break_characters = " \t\n\\`@$><=;|&{("; + if (virStringToUpper(&name_capitalized, ctl->name) < 0 || !(histsize_env = virStringJoin(strings, "_"))) goto cleanup;