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;