This fixes completions of labels (`+L:' for `lei import' and
`L:' for `lei q') so they can appear anywhere in the
command-line.

I mainly wanted this for `lei import $URL +L:label', but
this also fixes `lei forget-external' completions for URLs
(which involve colons).
---
 contrib/completion/lei-completion.bash | 15 ++++++++-----
 lib/PublicInbox/LeiExternal.pm         | 31 +++++++++++---------------
 lib/PublicInbox/LeiForgetExternal.pm   |  8 ++-----
 lib/PublicInbox/LeiImport.pm           | 22 +++++++++++-------
 lib/PublicInbox/LeiQuery.pm            |  2 ++
 5 files changed, 40 insertions(+), 38 deletions(-)

diff --git a/contrib/completion/lei-completion.bash 
b/contrib/completion/lei-completion.bash
index 5c137e68..b86afa2c 100644
--- a/contrib/completion/lei-completion.bash
+++ b/contrib/completion/lei-completion.bash
@@ -1,16 +1,19 @@
-# Copyright (C) 2020-2021 all contributors <meta@public-inbox.org>
+# Copyright (C) all contributors <meta@public-inbox.org>
 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
 
 # preliminary bash completion support for lei (Local Email Interface)
 # Needs a lot of work, see `lei__complete' in lib/PublicInbox::LEI.pm
 _lei() {
        local wordlist="$(lei _complete ${COMP_WORDS[@]})"
-       case $wordlist in
-       *':'* | *'='* | '//'*) compopt -o nospace ;;
-       *) compopt +o nospace ;; # the default
-       esac
        wordlist="${wordlist//;/\\\\;}" # escape ';' for ';UIDVALIDITY' and such
-       COMPREPLY=($(compgen -W "$wordlist" -- "${COMP_WORDS[COMP_CWORD]}"))
+
+       local word="${COMP_WORDS[COMP_CWORD]}"
+       if test "$word" = ':' && test $COMP_CWORD -ge 1
+       then
+               COMPREPLY=($(compgen -W "$wordlist" --))
+       else
+               COMPREPLY=($(compgen -W "$wordlist" -- "$word"))
+       fi
        return 0
 }
 complete -o default -o bashdefault -F _lei lei
diff --git a/lib/PublicInbox/LeiExternal.pm b/lib/PublicInbox/LeiExternal.pm
index 3e2a2288..31b9bd1e 100644
--- a/lib/PublicInbox/LeiExternal.pm
+++ b/lib/PublicInbox/LeiExternal.pm
@@ -86,39 +86,34 @@ sub canonicalize_excludes {
 # returns an anonymous sub which returns an array of potential results
 sub complete_url_prepare {
        my $argv = $_[-1]; # $_[0] may be $lei
-       # Workaround bash word-splitting URLs to ['https', ':', '//' ...]
-       # Maybe there's a better way to go about this in
-       # contrib/completion/lei-completion.bash
-       my $re = '';
-       my $cur = pop(@$argv) // '';
+       # Workaround bash default COMP_WORDBREAKS splitting URLs to
+       # ['https', ':', '//', ...].  COMP_WORDBREAKS is global for all
+       # completions loaded, not just ours, so we can't change it.
+       # cf. contrib/completion/lei-completion.bash
+       my ($pfx, $cur)  = ('', pop(@$argv) // '');
        if (@$argv) {
                my @x = @$argv;
-               if ($cur eq ':' && @x) {
+               if ($cur =~ /\A[:;=]\z/) { # COMP_WORDBREAKS + URL union
                        push @x, $cur;
                        $cur = '';
                }
-               while (@x > 2 && $x[0] !~ /\A(?:http|nntp|imap)s?\z/i &&
-                               $x[1] ne ':') {
-                       shift @x;
+               while (@x && $pfx !~ m!\A(?: (?:[\+\-]?(?:L|kw):) |
+                               (?:(?:imap|nntp|http)s?:) |
+                               (?:--\w?\z)|(?:-\w?\z) )!x) {
+                       $pfx = pop(@x).$pfx;
                }
-               if (@x >= 2) { # qw(https : hostname : 443) or qw(http :)
-                       $re = join('', @x);
-               } else { # just filter out the flags and hope for the best
-                       $re = join('', grep(!/^-/, @$argv));
-               }
-               $re = quotemeta($re);
        }
+       my $re = qr!\A\Q$pfx\E(\Q$cur\E.*)!;
        my $match_cb = sub {
                # the "//;" here (for AUTH=ANONYMOUS) interacts badly with
                # bash tab completion, strip it out for now since our commands
                # work w/o it.  Not sure if there's a better solution...
                $_[0] =~ s!//;AUTH=ANONYMOUS\@!//!i;
-               $_[0] =~ s!;!\\;!g;
                # only return the part specified on the CLI
                # don't duplicate if already 100% completed
-               $_[0] =~ /\A$re(\Q$cur\E.*)/ ? ($cur eq $1 ? () : $1) : ()
+               $_[0] =~ $re ? ($cur eq $1 ? () : $1) : ()
        };
-       wantarray ? ($re, $cur, $match_cb) : $match_cb;
+       wantarray ? ($pfx, $cur, $match_cb) : $match_cb;
 }
 
 1;
diff --git a/lib/PublicInbox/LeiForgetExternal.pm 
b/lib/PublicInbox/LeiForgetExternal.pm
index 07f0ac80..39bfc60b 100644
--- a/lib/PublicInbox/LeiForgetExternal.pm
+++ b/lib/PublicInbox/LeiForgetExternal.pm
@@ -32,14 +32,10 @@ sub lei_forget_external {
 sub _complete_forget_external {
        my ($lei, @argv) = @_;
        my $cfg = $lei->_lei_cfg or return ();
-       my ($cur, $re, $match_cb) = $lei->complete_url_prepare(\@argv);
-       # FIXME: bash completion off "http:" or "https:" when the last
-       # character is a colon doesn't work properly even if we're
-       # returning "//$HTTP_HOST/$PATH_INFO/", not sure why, could
-       # be a bash issue.
+       my ($pfx, $cur, $match_cb) = $lei->complete_url_prepare(\@argv);
        map {
                $match_cb->(substr($_, length('external.')));
-       } grep(/\Aexternal\.$re\Q$cur/, @{$cfg->{-section_order}});
+       } grep(/\Aexternal\.\Q$pfx$cur/, @{$cfg->{-section_order}});
 }
 
 1;
diff --git a/lib/PublicInbox/LeiImport.pm b/lib/PublicInbox/LeiImport.pm
index 2d91e4c4..9053048a 100644
--- a/lib/PublicInbox/LeiImport.pm
+++ b/lib/PublicInbox/LeiImport.pm
@@ -115,18 +115,24 @@ sub lei_import { # the main "lei import" method
 
 sub _complete_import {
        my ($lei, @argv) = @_;
-       my ($re, $cur, $match_cb) = $lei->complete_url_prepare(\@argv);
-       my @k = $lei->url_folder_cache->keys($argv[-1] // undef, 1);
+       my $has_arg = @argv;
+       my ($pfx, $cur, $match_cb) = $lei->complete_url_prepare(\@argv);
+       my @try = $has_arg ? ($pfx.$cur, $argv[-1]) : ($argv[-1]);
+       push(@try, undef) if defined $try[-1];
+       my (@f, @k);
+       for (@try) {
+               @k = $lei->url_folder_cache->keys($_, 1) and last;
+       }
        my @L = eval { $lei->_lei_store->search->all_terms('L') };
        push(@k, map { "+L:$_" } @L);
-       my @m = map { $match_cb->($_) } @k;
-       my %f = map { $_ => 1 } (@m ? @m : @k);
        if (my $lms = $lei->lms) {
-               @k = $lms->folders($argv[-1] // undef, 1);
-               @m = map { $match_cb->($_) } @k;
-               if (@m) { @f{@m} = @m } else { @f{@k} = @k }
+               for (@try) {
+                       @f = $lms->folders($_, 1) and last;
+               }
+               push @k, @f;
        }
-       keys %f;
+       my @m = map { $match_cb->($_) } @k;
+       @m ? @m : @k;
 }
 
 no warnings 'once';
diff --git a/lib/PublicInbox/LeiQuery.pm b/lib/PublicInbox/LeiQuery.pm
index 358574ea..3337e5d4 100644
--- a/lib/PublicInbox/LeiQuery.pm
+++ b/lib/PublicInbox/LeiQuery.pm
@@ -173,6 +173,8 @@ no query allowed on command-line with --stdin
 # shell completion helper called by lei__complete
 sub _complete_q {
        my ($self, @argv) = @_;
+       join('', @argv) =~ /\bL:\S*\z/ and
+               return eval { $self->_lei_store->search->all_terms('L') };
        my @cur;
        my $cb = $self->lazy_cb(qw(forget-external _complete_));
        while (@argv) {

Reply via email to