[libvirt] [v4 2/5] virsh: Improve readline generators and readline completion

Tomas Meszaros exo at tty.sk
Tue Sep 10 15:54:27 UTC 2013


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




More information about the libvir-list mailing list