This patch is rather big and introduces several new functions, but I kept all new functions in the one patch because they are all connected together. I had to extend vshReadlineOptionsGenerator() a lot, so it is possible to fully complete options by calling appropriate opt->completer(). vshReadlineOptionsCompletionGenerator() is simple new function which is used only when we need to fully complete specific option, e.g.: virsh # vol-key --vol <TAB> --- v2 * vshMalloc is now used in vshDetermineCommandName * vshStrdup instead of vshMalloc + snprintf * joined if's in vshReadlineOptionsCompletionGenerator v3 * fixed typo * removed useless if's v4 * rewritten so we can use only option completers instead of cmd+opt completers * added --help auto-completion tools/virsh.c | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 251 insertions(+), 32 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index bf2fbf8..321ed5d 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -2587,6 +2587,118 @@ vshCloseLogFile(vshControl *ctl) * ----------------- */ +static const vshCmdDef * +vshDetermineCommandName(void) +{ + const vshCmdDef *cmd = NULL; + char *p; + char *cmdname; + + if (!(p = strchr(rl_line_buffer, ' '))) + return NULL; + + cmdname = vshMalloc(NULL, (p - rl_line_buffer) + 1); + memcpy(cmdname, rl_line_buffer, p - rl_line_buffer); + + cmd = vshCmddefSearch(cmdname); + VIR_FREE(cmdname); + + return cmd; +} + +/* + * Check if option with opt_name is already fully completed. + */ +static bool +vshOptFullyCompleted(const char *opt_name) +{ + const vshCmdDef *cmd = NULL; + char *completed_name = NULL; + char **completed_list = NULL; + size_t completed_list_index; + size_t opts_index = 0; + bool opt_fully_completed = false; + + if (!opt_name) + return opt_fully_completed; + + cmd = vshDetermineCommandName(); + + if (!cmd) + return opt_fully_completed; + + while (cmd->opts[opts_index].name) { + const vshCmdOptDef *opt = &cmd->opts[opts_index]; + opts_index++; + + if (!STREQ(opt->name, opt_name) || !opt->completer) + continue; + + completed_list_index = 0; + completed_list = opt->completer(opt->completer_flags); + + if (!completed_list) + continue; + + while ((completed_name = completed_list[completed_list_index])) { + completed_list_index++; + if (strstr(rl_line_buffer, completed_name)) + opt_fully_completed = true; + } + virStringFreeList(completed_list); + } + return opt_fully_completed; +} + +/* + * Return option which is present in the rl_line_buffer, but is not fully + * auto-completed (opt->completer() hasn't been used). + */ +static const vshCmdOptDef * +vshGetOptMissingCmp(void) +{ + const vshCmdDef *cmd = NULL; + char *opt_name_prefixed = NULL; + char *completed_name = NULL; + char **completed_list = NULL; + size_t completed_list_index; + size_t opts_index = 0; + bool opt_completed; + + cmd = vshDetermineCommandName(); + + if (!cmd) + return NULL; + + while (cmd->opts[opts_index].name) { + const vshCmdOptDef *opt = &cmd->opts[opts_index]; + opts_index++; + opt_name_prefixed = vshMalloc(NULL, strlen(opt->name) + 3); + snprintf(opt_name_prefixed, strlen(opt->name) + 3, "--%s", opt->name); + + if (strstr(rl_line_buffer, opt_name_prefixed) && opt->completer) { + opt_completed = false; + completed_list_index = 0; + completed_list = opt->completer(opt->completer_flags); + + if (!completed_list) + continue; + + while ((completed_name = completed_list[completed_list_index])) { + completed_list_index++; + if (strstr(rl_line_buffer, completed_name)) + opt_completed = true; + } + virStringFreeList(completed_list); + + if (!opt_completed) + return opt; + } + } + + return NULL; +} + /* * Generator function for command completion. STATE lets us * know whether to start from scratch; without any state @@ -2631,28 +2743,27 @@ vshReadlineCommandGenerator(const char *text, int state) return NULL; } +/* + * Generator function for option completion. Provides --option name + * auto-completion and also advanced option completion by using opt->completer() + * functions. + */ static char * vshReadlineOptionsGenerator(const char *text, int state) { - static int list_index, len; - static const vshCmdDef *cmd = NULL; - const char *name; + static int opt_list_index, completed_list_index, len; + static char **completed_list; + static bool help_completed; + static const vshCmdDef *cmd; + char *opt_name_prefixed = NULL; + char *completed_name = NULL; 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); - list_index = 0; + cmd = vshDetermineCommandName(); + opt_list_index = 0; + completed_list_index = 0; len = strlen(text); - VIR_FREE(cmdname); + help_completed = false; } if (!cmd) @@ -2661,45 +2772,153 @@ vshReadlineOptionsGenerator(const char *text, int state) if (!cmd->opts) return NULL; - while ((name = cmd->opts[list_index].name)) { - const vshCmdOptDef *opt = &cmd->opts[list_index]; - char *res; + while (cmd->opts[opt_list_index].name) { + const vshCmdOptDef *opt = &cmd->opts[opt_list_index]; + opt_name_prefixed = vshMalloc(NULL, strlen(opt->name) + 3); + snprintf(opt_name_prefixed, strlen(opt->name) + 3, "--%s", opt->name); + + if (strstr(rl_line_buffer, opt_name_prefixed) || + vshOptFullyCompleted(opt->name) || + opt->type == VSH_OT_ARGV) { + /* We want to skip option which has been already auto-completed + * (is present in rl_line_buffer) or fully auto-completed + * (opt->completer() has been successfully applied on this option) + * and also ignore non --option. + */ + opt_list_index++; + continue; + } + + if (opt->flags == VSH_OFLAG_REQ && opt->type == VSH_OT_DATA && + opt->completer && !vshOptFullyCompleted(opt->name)) { + /* Call opt->completer() for option marked as required + * (option itself does not necessarily needs to be already + * auto-completed). + */ + + if (!completed_list_index) + completed_list = opt->completer(opt->completer_flags); + + if (completed_list) { + while ((completed_name = completed_list[completed_list_index])) { + completed_list_index++; + + if (len > 0 && !STRPREFIX(completed_name, text)) { + /* Skip irrelevant names. */ + continue; + } + return vshStrdup(NULL, completed_name); + } + } + virStringFreeList(completed_list); + completed_list_index = 0; + } - list_index++; + if (len > 2 && !STRPREFIX(opt_name_prefixed, text)) { + /* We want to pass options that are only relevant for provided + * @text. + * + * This has to be after the opt->completer() call, because + * completed_name can sometimes partially match --option, + * .e.g. --pool pool1 + */ + opt_list_index++; + continue; + } - if (opt->type == VSH_OT_DATA || opt->type == VSH_OT_ARGV) - /* ignore non --option */ + if (len > 0 && !STRPREFIX(text, "-")) { + /* Skip options when user wants to auto-complete something that + * does not starts with prefix "-". + */ + opt_list_index++; continue; + } - if (len > 2) { - if (STRNEQLEN(name, text + 2, len - 2)) - continue; + opt_list_index++; + return opt_name_prefixed; + } + + if (!help_completed) { + /* When appropriate, auto-complete --help option. */ + if ((len > 2 && !STRPREFIX("--help", text)) || + (len > 0 && !STRPREFIX(text, "-")) || + strstr(rl_line_buffer, "--help")) { + return NULL; } - res = vshMalloc(NULL, strlen(name) + 3); - snprintf(res, strlen(name) + 3, "--%s", name); - return res; + help_completed = true; + return vshStrdup(NULL, "--help"); } /* If no names matched, then return NULL. */ return NULL; } +/* + * Generator function for option completion. Provides advanced completion + * for command options. + */ +static char * +vshReadlineOptionsCompletionGenerator(const char *text, int state) +{ + static const vshCmdOptDef *opt = NULL; + static int completed_list_index, len; + static char **completed_list; + char *completed_name; + + if (!state) { + opt = vshGetOptMissingCmp(); + completed_list_index = 0; + len = strlen(text); + } + + if (!opt) + return NULL; + + if (!opt->completer) + return NULL; + + if (!state) + completed_list = opt->completer(opt->completer_flags); + + if (!completed_list) + return NULL; + + while ((completed_name = completed_list[completed_list_index])) { + completed_list_index++; + + if (STRNEQLEN(completed_name, text, len)) + /* Skip irrelevant names. */ + continue; + + return vshStrdup(NULL, completed_name); + } + virStringFreeList(completed_list); + + return NULL; +} + static char ** vshReadlineCompletion(const char *text, int start, int end ATTRIBUTE_UNUSED) { char **matches = (char **) NULL; + const vshCmdOptDef *opt_missing_cmp = vshGetOptMissingCmp(); - if (start == 0) + if (start == 0) { /* command name generator */ matches = rl_completion_matches(text, vshReadlineCommandGenerator); - else + } else { /* commands options */ - matches = rl_completion_matches(text, vshReadlineOptionsGenerator); + if (opt_missing_cmp) { + matches = rl_completion_matches(text, vshReadlineOptionsCompletionGenerator); + } else { + matches = rl_completion_matches(text, vshReadlineOptionsGenerator); + } + } + return matches; } - static int vshReadlineInit(vshControl *ctl) { -- 1.8.3.1 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list