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