]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Clean up autocompletion a little
authorDavid Woodhouse <dwmw2@infradead.org>
Thu, 16 Apr 2020 14:06:19 +0000 (15:06 +0100)
committerDavid Woodhouse <dwmw2@infradead.org>
Thu, 16 Apr 2020 14:06:21 +0000 (15:06 +0100)
Make it handle short options with arguments. Long options with arguments
don't work yet because the = makes strange things happen.

Signed-off-by: David Woodhouse <dwmw2@infradead.org>
bash/openconnect.bash
main.c
tests/autocompletion

index 2424fe41307ec2d39bdf401133d5a1aa56aef795..5ca0593405a13acf10bd793499aa8d427ce5cf15 100644 (file)
 
 
 _complete_openconnect () {
-    export COMP_LINE COMP_POINT COMP_CWORD COMP_KEY COMP_TYPE
+    local cur
+    _get_comp_words_by_ref cur
+    # But if we do this, then our COMPREPLY isn't interpreted according to it.
+    #_get_comp_words_by_ref-n =: -w COMP_WORDS -i COMP_CWORD cur
     COMP_WORDS[0]="--autocomplete"
     local IFS=$'\n'
-    COMPREPLY=( $(openconnect "${COMP_WORDS[@]}") )
+    COMPREPLY=( $(COMP_CWORD=$COMP_CWORD /home/dwmw/git/openconnect/gtls-ibm/openconnect "${COMP_WORDS[@]}") )
+    local FILTERPAT="${COMPREPLY[1]}"
+    local PREFIX="${COMPREPLY[2]}"
+    local COMP_WORD=${cur#${PREFIX}}
     case "${COMPREPLY[0]}" in
        FILENAME)
-           if [ "${COMPREPLY[1]}" != "" ]; then
-               COMPREPLY=( $( compgen -f -o filenames -o plusdirs -X ${COMPREPLY[1]} ${COMP_WORDS[$COMP_CWORD]}) )
-           else
-               COMPREPLY=( $( compgen -f -o filenames -o plusdirs ${COMP_WORDS[$COMP_CWORD]}) )
-           fi
-           ;;
-
-       FILENAMEAT)
-           COMPREPLY=( $( compgen -P @ -f -o filenames -o plusdirs ${COMP_WORDS[$COMP_CWORD]#@}) )
+           compopt -o filenames
+           COMPREPLY=( $( compgen -A file -ofilenames -o plusdirs -X "${FILTERPAT}" -- "${COMP_WORD}") )
+           COMPREPLY=( "${COMPREPLY[@]/#/${PREFIX}}" )
            ;;
 
        EXECUTABLE)
-           COMPREPLY=( $( compgen -c -o plusdirs ${COMP_WORDS[$COMP_CWORD]}) )
+           compopt -o filenames
+           COMPREPLY=( $( compgen -A command -ofilenames -o plusdirs -- "${COMP_WORD}") )
+           COMPREPLY=( "${COMPREPLY[@]/#/${PREFIX}}" )
            ;;
 
        HOSTNAME)
-           COMPREPLY=( $( compgen -A hostname ${COMP_WORDS[$COMP_CWORD]}) )
+           compopt +o filenames
+           COMPREPLY=( $( compgen -A hostname -P "${PREFIX}" -- "${COMP_WORD}") )
            ;;
 
        USERNAME)
-           COMPREPLY=( $( compgen -A user ${COMP_WORDS[$COMP_CWORD]}) )
+           compopt +o filenames
+           COMPREPLY=( $( compgen -A user -P "${PREFIX}" -- "${COMP_WORD}") )
            ;;
+
+       *)
+           compopt +o filenames
+           ;;
+
     esac
 }
 
-complete -F _complete_openconnect -o filenames openconnect
+complete -F _complete_openconnect openconnect
diff --git a/main.c b/main.c
index a0522b18eb9c29287df9cd41b95a9c44d6e2741a..21a36461fc5aa39a0bffd7d9f5cfe89f0230706e 100644 (file)
--- a/main.c
+++ b/main.c
@@ -1100,27 +1100,38 @@ static void get_uids(const char *config_arg, uid_t *uid, gid_t *gid)
 }
 #endif
 
-static int complete_words(char *partial, ...)
+static int complete_words(const char *comp_opt, int prefixlen, ...)
 {
-       int partlen = strlen(partial);
+       int partlen = strlen(comp_opt + prefixlen);
        va_list vl;
        char *check;
 
-       va_start(vl, partial);
+       va_start(vl, prefixlen);
        while ( (check = va_arg(vl, char *)) ) {
-               if (!strncmp(partial, check, partlen))
-                       printf("%s\n", check);
+               if (!strncmp(comp_opt + prefixlen, check, partlen))
+                       printf("%.*s%s\n", prefixlen, comp_opt, check);
        }
        va_end(vl);
        return 0;
 }
 
+static int autocomplete_special(const char *verb, const char *prefix,
+                               int prefixlen, const char *filterpat)
+{
+       printf("%s\n", verb);
+       printf("%s\n", filterpat ? : "''");
+
+       if (prefixlen)
+               printf("%.*s\n", prefixlen, prefix);
+       return 0;
+}
+
 static int autocomplete(int argc, char **argv)
 {
        int opt;
        const char *comp_cword = getenv("COMP_CWORD");
        char *comp_opt;
-       int cword, longidx;
+       int cword, longidx, prefixlen = 0;
 
        /* Skip over the --autocomplete */
        argc--;
@@ -1140,19 +1151,68 @@ static int autocomplete(int argc, char **argv)
        opterr = 0;
 
        while (argv[optind]) {
-               int thisind = optind;
+               /* If optind is the one that is being autocompleted, don't
+                * let getopt_long() see it; we process it directly. */
+               if (argv[optind] == comp_opt) {
+                       if (!strncmp(comp_opt, "--", 2)) {
+                               const char *arg = strchr(comp_opt, '=');
+                               int matchlen;
+
+                               if (arg) {
+                                       /* We have --option=... so complete the arg */
+                                       matchlen = arg - comp_opt - 2;
+                                       for (longidx = 0; long_options[longidx].name; longidx++) {
+                                               if (!strncmp(comp_opt + 2, long_options[longidx].name, matchlen)) {
+                                                       prefixlen = matchlen + 3;
+                                                       opt = long_options[longidx].val;
+                                                       goto got_opt;
+                                               }
+                                       }
+                               } else {
+                                       /* Not --option= just --opt so complete the option name(s) */
+                                       comp_opt += 2;
+                               autocomplete_optname:
+                                       matchlen = strlen(comp_opt);
+                                       for (longidx = 0; long_options[longidx].name; longidx++) {
+                                               if (!strncmp(comp_opt, long_options[longidx].name, matchlen)) {
+                                                       printf("--%s\n", long_options[longidx].name);
+                                               }
+                                       }
+                               }
+                       } else if (comp_opt[0] == '-') {
+                               if (!comp_opt[1]) {
+                                       /* Just a single dash. Autocomplete like '--' with all the (long) options */
+                                       comp_opt++;
+                                       goto autocomplete_optname;
+                               }
+                               /* Single-char -X option, with or without an argument. */
+                               for (longidx = 0; long_options[longidx].name; longidx++) {
+                                       if (comp_opt[1] == long_options[longidx].val) {
+                                               if (comp_opt[2]) {
+                                                       if (long_options[longidx].has_arg) {
+                                                               prefixlen = 2;
+                                                               opt = long_options[longidx].val;
+                                                               goto got_opt;
+                                                       }
+                                               } else {
+                                                       /* Just the option; complete to the long name of same. */
+                                                       printf("--%s\n", long_options[longidx].name);
+                                               }
+                                               break;
+                                       }
+                               }
+                       } else
+                               printf("HOSTNAME\n");
 
-               /* Don't let getopt_long() assume it's a separator; instead
-                * assume they want to tab-complete to a real long option. */
-               if (argv[thisind] == comp_opt)
-                       goto empty_opt;
+                       return 0;
+               }
 
                /* Skip over non-option elements, in an attempt to prevent
                 * getopt_long() from reordering the array as we go. The problem
                 * is that we've seen it *delay* the reordering. So it processes
                 * the argv element *after* the non-option, but argv[optind] is
                 * still pointing to the non-option. */
-               if (argv[thisind][0] != '-') {
+               if (argv[optind][0] != '-') {
                        optind++;
                        continue;
                }
@@ -1168,100 +1228,66 @@ static int autocomplete(int argc, char **argv)
                if (opt == -1)
                        break;
 
-               if (argv[thisind] == comp_opt) {
-                       char *matcher;
-               empty_opt:
-                       matcher = NULL;
-                       if (!strncmp(comp_opt, "--", 2))
-                               matcher = comp_opt + 2;
-                       else if (!strcmp(comp_opt, "-"))
-                               matcher = comp_opt + 1;
-
-                       if (matcher) {
-                               int matchlen = strlen(matcher);
-                               const struct option *p = long_options;
-
-                               while (p->name) {
-                                       if (!strncmp(matcher, p->name, matchlen))
-                                               printf("--%s\n", p->name);
-                                       p++;
-                               }
-                       } else if (comp_opt[0] == '-') {
-                               if (comp_opt[1] && !comp_opt[2]) {
-                                       /* Single-char -X option. */
-                                       const struct option *longopt = long_options;
-                                       while (longopt->name) {
-                                               if (comp_opt[1] == longopt->val) {
-                                                       printf("--%s\n", longopt->name);
-                                                       break;
-                                               }
-                                               longopt++;
-                                       }
-                               }
-                       } else
-                               printf("HOSTNAME\n");
-
-                       return 0;
-               }
-
                if (optarg == comp_opt) {
+                       prefixlen = 0;
+               got_opt:
                        switch (opt) {
                        case 'k': /* --sslkey */
                        case 'c': /* --certificate */
-                               if (!strncmp(comp_opt, "pkcs11:", 7)) {
+                               if (!strncmp(comp_opt + prefixlen, "pkcs11:", 7)) {
                                        /* We could do clever things here... */
                                        return 0; /* .. but we don't. */
                                }
-                               printf("FILENAME\n!*.@(pem|der|p12|crt)\n");
+                               autocomplete_special("FILENAME", comp_opt, prefixlen, "!*.@(pem|der|p12|crt)");
                                break;
 
                        case OPT_CAFILE: /* --cafile */
-                               printf("FILENAME\n!*.@(pem|der|crt)\n");
+                               autocomplete_special("FILENAME", comp_opt, prefixlen, "!*.@(pem|der|crt)");
                                break;
 
                        case 'x': /* --xmlconfig */
-                               printf("FILENAME\n!*.xml\n");
+                               autocomplete_special("FILENAME", comp_opt, prefixlen, "!*.xml");
                                break;
 
                        case OPT_CONFIGFILE: /* --config */
                        case OPT_PIDFILE: /* --pid-file */
-                               printf("FILENAME\n");
+                               autocomplete_special("FILENAME", comp_opt, prefixlen, NULL);
                                break;
 
                        case 's': /* --script */
                        case OPT_CSD_WRAPPER: /* --csd-wrapper */
-                               printf("EXECUTABLE\n");
+                               autocomplete_special("EXECUTABLE", comp_opt, prefixlen, NULL);
                                break;
 
                        case OPT_LOCAL_HOSTNAME: /* --local-hostname */
-                               printf("HOSTNAME\n");
+                               autocomplete_special("HOSTNAME", comp_opt, prefixlen, NULL);
                                break;
 
                        case OPT_CSD_USER: /* --csd-user */
                        case 'U': /* --setuid */
-                               printf("USERNAME\n");
+                               autocomplete_special("USERNAME", comp_opt, prefixlen, NULL);
                                break;
 
                        case OPT_OS: /* --os */
-                               complete_words(comp_opt, "mac-intel", "android",
+                               complete_words(comp_opt, prefixlen, "mac-intel", "android",
                                               "linux-64", "linux", "apple-ios",
                                               "win", NULL);
                                break;
 
                        case OPT_COMPRESSION: /* --compression */
-                               complete_words(comp_opt, "none", "off", "all",
+                               complete_words(comp_opt, prefixlen, "none", "off", "all",
                                               "stateless", NULL);
                                break;
 
                        case OPT_PROTOCOL: /* --protocol */
                        {
                                struct oc_vpn_proto *protos, *p;
-                               int partlen = strlen(comp_opt);
+                               int partlen = strlen(comp_opt + prefixlen);
 
                                if (openconnect_get_supported_protocols(&protos) >= 0) {
                                        for (p = protos; p->name; p++) {
-                                               if(!strncmp(comp_opt, p->name, partlen))
-                                                       printf("%s\n", p->name);
+                                               if(!strncmp(comp_opt + prefixlen, p->name, partlen))
+                                                       printf("%.*s%s\n", prefixlen, comp_opt, p->name);
                                        }
                                        free(protos);
                                }
@@ -1274,18 +1300,23 @@ static int autocomplete(int argc, char **argv)
                                break;
 
                        case OPT_TOKEN_MODE: /* --token-mode */
-                               complete_words(comp_opt, "totp", "hotp", "oidc", NULL);
+                               complete_words(comp_opt, prefixlen, "totp", "hotp", "oidc", NULL);
                                if (openconnect_has_stoken_support())
-                                       complete_words(comp_opt, "rsa", NULL);
+                                       complete_words(comp_opt, prefixlen, "rsa", NULL);
                                if (openconnect_has_yubioath_support())
-                                       complete_words(comp_opt, "yubioath", NULL);
+                                       complete_words(comp_opt, prefixlen, "yubioath", NULL);
                                break;
 
                        case OPT_TOKEN_SECRET: /* --token-secret */
-                               if (!comp_opt[0] || comp_opt[0] == '/')
-                                       printf("FILENAME\n");
-                               else if (comp_opt[0] == '@')
-                                       printf("FILENAMEAT\n");
+                               switch (comp_opt[prefixlen]) {
+                               case '@':
+                                       prefixlen++;
+                                       /* Fall through */
+                               case 0:
+                               case '/':
+                                       autocomplete_special("FILENAME", comp_opt, prefixlen, NULL);
+                                       break;
+                               }
                                break;
 
                        case 'i': /* --interface */
index fb7ed139299cee805e3b52765557d7c350c39c5d..8b4a24b83c78a4f1bf4f059808c037d9ddee6676 100755 (executable)
@@ -71,3 +71,8 @@ all
 stateless"
 do_test -k foo --compr ''
 
+WORD=1
+RESULT="EXECUTABLE
+''
+-s"
+do_test -s/bin