[libvirt] [PATCH 4/4] virsh: Introduce vshReadlineParse for improved auto-completion

Nishith Shah nishithshah.2211 at gmail.com
Fri Jul 8 12:56:05 UTC 2016


The new function works as expected, and matches the current level of
autocomplete offered, along with several other improvements like quotes
handling, multiple command completion and space handling. Now, it is easy
to introduce options completer here.

Signed-off-by: Nishith Shah <nishithshah.2211 at gmail.com>
---
 tools/vsh.c | 221 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 200 insertions(+), 21 deletions(-)

diff --git a/tools/vsh.c b/tools/vsh.c
index fd93cca..68f7785 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;
-- 
2.7.4




More information about the libvir-list mailing list