New completion generators responsible for advances command
and command options completions.
vshReadlineCommandCompletionGenerator - generator for advanced
command completions. This function will call some vshCmdCompleter
function (e.g. vshDomainCompleter), which will return relevant
data used for autocompletion (e.g. domain names).
vshReadlineOptionsCompletionGenerator - almost the same as the
vshReadlineCommandCompletionGenerator, but this one completes
cmd options.
vshReadlineCompletion() has become much more complex because we
now have more generator functions and therefore more states to
choose from.
---
v2
* vshMalloc is now used in vshDetermineCommandName
* vshStrdup instead of vshMalloc + snprintf
* joined if's in vshReadlineOptionsCompletionGenerator
v3
* fixed typo
* removed useless if's
.gnulib | 2 +-
tools/virsh.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 365 insertions(+), 23 deletions(-)
diff --git a/.gnulib b/.gnulib
index 0ba0877..644c404 160000
--- a/.gnulib
+++ b/.gnulib
@@ -1 +1 @@
-Subproject commit 0ba087759d2797c8f7d3c34bef6268ba3fd212cb
+Subproject commit 644c40496cf7d5a705a73c9dd32b035fcecc2ab1
diff --git a/tools/virsh.c b/tools/virsh.c
index 2ea44a6..5be8e7e 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -2515,6 +2515,25 @@ 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;
+}
+
/*
* Generator function for command completion. STATE lets us
* know whether to start from scratch; without any state
@@ -2562,25 +2581,14 @@ vshReadlineCommandGenerator(const char *text, int state)
static char *
vshReadlineOptionsGenerator(const char *text, int state)
{
- static int list_index, len;
static const vshCmdDef *cmd = NULL;
+ static int list_index, len;
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 = vshDetermineCommandName();
list_index = 0;
len = strlen(text);
- VIR_FREE(cmdname);
}
if (!cmd)
@@ -2612,22 +2620,356 @@ vshReadlineOptionsGenerator(const char *text, int state)
return NULL;
}
+/*
+ * Generator function for command completion, but unlike
+ * the vshReadlineCommandGenerator which completes command name, this function
+ * provides more advanced completion for commands by calling specific command
+ * completers (e.g. vshDomainCompleter).
+ */
+static char *
+vshReadlineCommandCompletionGenerator(const char *text, int state)
+{
+ static const vshCmdDef *cmd = NULL;
+ static int list_index, len;
+ char **completed_names = NULL;
+ char *name;
+
+ if (!state) {
+ cmd = vshDetermineCommandName();
+ list_index = 0;
+ len = strlen(text);
+ }
+
+ if (!cmd)
+ return NULL;
+
+ if (!cmd->completer)
+ return NULL;
+
+ completed_names = cmd->completer(cmd->completer_flags);
+
+ if (!completed_names)
+ return NULL;
+
+ while ((name = completed_names[list_index])) {
+ char *res;
+ list_index++;
+
+ if (STRNEQLEN(name, text, len))
+ /* Skip irrelevant names */
+ continue;
+
+ res = vshStrdup(NULL, name);
+ VIR_FREE(name);
+ return res;
+ }
+ VIR_FREE(completed_names);
+
+ return NULL;
+}
+
+/*
+ * Generator function for command option completion. Provides advances
+ * completion for command options.
+ */
+static char *
+vshReadlineOptionsCompletionGenerator(const char *text ATTRIBUTE_UNUSED,
+ int state ATTRIBUTE_UNUSED)
+{
+ static const vshCmdDef *cmd = NULL;
+ static const vshCmdOptDef *opt = NULL;
+ static int list_index, len;
+ unsigned long int opt_index = 0;
+ size_t i;
+ char **completed_names = NULL;
+ char *name;
+ char *ptr = NULL;
+
+ if (!state) {
+ cmd = vshDetermineCommandName();
+ list_index = 0;
+ len = strlen(text);
+ }
+
+ if (!cmd)
+ return NULL;
+
+ if (!cmd->opts)
+ return NULL;
+
+ for (i = 0; cmd->opts[i].name; i++) {
+ if ((ptr = strstr(rl_line_buffer, cmd->opts[i].name)) &&
+ (opt_index < (ptr - rl_line_buffer))) {
+ opt_index = ptr - rl_line_buffer;
+ opt = &cmd->opts[i];
+ }
+ }
+
+ if (!opt)
+ return NULL;
+
+ if (!opt->completer)
+ return NULL;
+
+ completed_names = opt->completer(opt->completer_flags);
+
+ if (!completed_names)
+ return NULL;
+
+ while ((name = completed_names[list_index])) {
+ char *res;
+ list_index++;
+
+ if (STRNEQLEN(name, text, len))
+ /* Skip irrelevant names */
+ continue;
+
+ res = vshStrdup(NULL, name);
+ VIR_FREE(name);
+ return res;
+ }
+ VIR_FREE(completed_names);
+
+ return NULL;
+}
+
+/*
+ * Returns current valid command name present in the rl_line_buffer.
+ */
+static char *
+vshCurrentCmd(void)
+{
+ const char *name;
+ const vshCmdGrp *grp;
+ const vshCmdDef *cmds;
+ size_t grp_list_index, cmd_list_index;
+ char *found_cmd = NULL;
+ char *rl_copy = NULL;
+ char *pch;
+
+ grp_list_index = 0;
+ cmd_list_index = 0;
+ grp = cmdGroups;
+
+ while (grp[grp_list_index].name) {
+ cmds = grp[grp_list_index].commands;
+
+ if (cmds[cmd_list_index].name) {
+ while ((name = cmds[cmd_list_index].name)) {
+ cmd_list_index++;
+
+ if (VIR_STRDUP(rl_copy, rl_line_buffer) < 0)
+ return NULL;
+
+ char *saveptr;
+ pch = strtok_r(rl_copy, " ", &saveptr);
+
+ while (pch != NULL) {
+ if (STREQ(pch, name))
+ if (VIR_STRDUP(found_cmd, name) < 0)
+ goto cleanup;
+ pch = strtok_r(NULL, " ", &saveptr);
+ }
+ }
+ } else {
+ cmd_list_index = 0;
+ grp_list_index++;
+ }
+ }
+
+ if (!found_cmd)
+ goto cleanup;
+
+ return found_cmd;
+
+cleanup:
+ VIR_FREE(rl_copy);
+ return NULL;
+}
+
+/*
+ * Returns current valid command option name present in the rl_line_buffer.
+ */
+static char *
+vshCurrentOpt(const char *cmd_name)
+{
+ const vshCmdDef *cmd = NULL;
+ const char *name;
+ unsigned long int opt_index = 0;
+ size_t cmd_opt_list_index;
+ char *found_opt = NULL;
+ char *ptr = NULL;
+
+ if (!cmd_name)
+ return NULL;
+
+ cmd = vshCmddefSearch(cmd_name);
+
+ if (!cmd)
+ return NULL;
+
+ if (!cmd->opts)
+ return NULL;
+
+ cmd_opt_list_index = 0;
+
+ while ((name = cmd->opts[cmd_opt_list_index].name)) {
+ cmd_opt_list_index++;
+
+ if ((ptr = strstr(rl_line_buffer, name))) {
+ if (opt_index < (ptr - rl_line_buffer)) {
+ opt_index = ptr - rl_line_buffer;
+ if (VIR_STRDUP(found_opt, name) < 0)
+ return NULL;
+ }
+ }
+ }
+
+ return found_opt;
+}
+
+/*
+ * Returns name of the command completion currently present in the rl_line_buffer.
+ */
+static char*
+vshCurrentCmdCom(const char *cmd_name)
+{
+ const vshCmdDef *cmd = NULL;
+ size_t i;
+ char **completed_names = NULL;
+ char *found_cmd_com = NULL;
+
+ if (!cmd_name)
+ return NULL;
+
+ cmd = vshCmddefSearch(cmd_name);
+
+ if (!cmd)
+ return NULL;
+
+ if (!cmd->completer)
+ return NULL;
+
+ completed_names = cmd->completer(cmd->completer_flags);
+
+ if (!completed_names)
+ return NULL;
+
+ for (i = 0; completed_names[i]; i++) {
+ if (strstr(rl_line_buffer, completed_names[i])) {
+ if (VIR_STRDUP(found_cmd_com, completed_names[i]) < 0)
+ return NULL;
+ }
+ }
+
+ return found_cmd_com;
+}
+
+/*
+ * Returns name of the option completion currently present in the rl_line_buffer.
+ */
+static char *
+vshCurrentOptCom(const char *cmd_name)
+{
+ const vshCmdDef *cmd = NULL;
+ const vshCmdOptDef *opt = NULL;
+ unsigned long int opt_index = 0;
+ size_t i;
+ char **completed_names = NULL;
+ char *found_opt_com = NULL;
+ char *ptr = NULL;
+
+ if (!cmd_name)
+ return NULL;
+
+ cmd = vshCmddefSearch(cmd_name);
+
+ if (!cmd)
+ return NULL;
+
+ if (!cmd->opts)
+ return NULL;
+
+ for (i = 0; cmd->opts[i].name; i++) {
+ if ((ptr = strstr(rl_line_buffer, cmd->opts[i].name))) {
+ if (opt_index < (ptr - rl_line_buffer)) {
+ opt_index = ptr - rl_line_buffer;
+ opt = &cmd->opts[i];
+ }
+ }
+ }
+
+ if (!opt)
+ return NULL;
+
+ if (!opt->completer)
+ return NULL;
+
+ completed_names = opt->completer(opt->completer_flags);
+
+ if (!completed_names)
+ return NULL;
+
+ for (i = 0; completed_names[i]; i++) {
+ if (strstr(rl_line_buffer, completed_names[i])) {
+ if (VIR_STRDUP(found_opt_com, completed_names[i]) < 0)
+ return NULL;
+ }
+ }
+
+ return found_opt_com;
+}
+
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;
+ const char *cmd = vshCurrentCmd();
+ const char *cmd_com = vshCurrentCmdCom(cmd);
+ const char *opt = vshCurrentOpt(cmd);
+ const char *opt_com = vshCurrentOptCom(cmd);
+ char **matches = (char **)NULL;
- if (start == 0)
- /* command name generator */
+ if (start == 0) {
+ /* Command name generator. */
matches = rl_completion_matches(text, vshReadlineCommandGenerator);
- else
- /* commands options */
- matches = rl_completion_matches(text, vshReadlineOptionsGenerator);
+ } else {
+ /* Command completer, commands options and commands options completer
+ * generators.
+ */
+ if (strstr(text, "-")) {
+ /* When user wants to see options. */
+ matches = rl_completion_matches(text, vshReadlineOptionsGenerator);
+ } else if (!cmd_com && !opt && !opt_com) {
+ matches = rl_completion_matches(text,
vshReadlineCommandCompletionGenerator);
+ if (!matches)
+ matches = rl_completion_matches(text, vshReadlineOptionsGenerator);
+ } else if (cmd_com && !opt && !opt_com) {
+ matches = rl_completion_matches(text, vshReadlineOptionsGenerator);
+ } else if (cmd_com && opt && !opt_com) {
+ matches = rl_completion_matches(text,
vshReadlineOptionsCompletionGenerator);
+ if (!matches)
+ matches = rl_completion_matches(text, vshReadlineOptionsGenerator);
+ } else if (!cmd_com && opt && !opt_com) {
+ matches = rl_completion_matches(text,
vshReadlineCommandCompletionGenerator);
+ if (!matches)
+ matches = rl_completion_matches(text,
vshReadlineOptionsCompletionGenerator);
+ if (!matches)
+ matches = rl_completion_matches(text, vshReadlineOptionsGenerator);
+ } else if (!cmd_com && opt && opt_com) {
+ matches = rl_completion_matches(text, vshReadlineOptionsGenerator);
+ } else if (cmd_com && opt && opt_com) {
+ matches = rl_completion_matches(text, vshReadlineOptionsGenerator);
+ }
+ }
+
+ VIR_FREE(cmd);
+ VIR_FREE(cmd_com);
+ VIR_FREE(opt);
+ VIR_FREE(opt_com);
+
return matches;
}
-
static int
vshReadlineInit(vshControl *ctl)
{
--
1.8.3.1