auto completion with tab: separate commands and tree structure

J Rt jean.rblt at gmail.com
Sat Mar 28 16:13:09 CET 2020


FYI, here is the new autocompletion script I am now using on my
machine. It shows in order 1) the commands 2) the options 3) the paths
/ files. Each of 1) 2) and 3) is internally sorted. Let me know if you
want to merge into master / how I should perform a push request on
your self-hosted repo :)


# completion file for bash

# Copyright (C) 2012 - 2014 Jason A. Donenfeld <Jason at zx2c4.com> and
# Brian Mattern <rephorm at rephorm.com>. All Rights Reserved.
# This file is licensed under the GPLv2+. Please see COPYING for more
information.
# Edited by Jean Rabault 2020

_sort_entries_string () {
    # local sorted_list=$(echo $1 | xargs -n1 | sort | xargs)
    local sorted_list=$(echo $1 | tr ' ' '\n' | sort | tr '\n' ' ')
    echo $sorted_list
}

_setup_tmp_compreply () {
    TMP_COMPREPLY=()
}

_append_tmp_compreply () {
    # sort the TMP_COMPREPLY array
    IFS=$'\n' TMP_COMPREPLY=($(sort <<<"${TMP_COMPREPLY[*]}")); unset IFS
    # append the tmp array to the COMPREPLY
    COMPREPLY+=( "${TMP_COMPREPLY[@]}" )
}

_pass_complete_entries () {
    _setup_tmp_compreply

    prefix="${PASSWORD_STORE_DIR:-$HOME/.password-store/}"
    prefix="${prefix%/}/"
    suffix=".gpg"
    autoexpand=${1:-0}

    local IFS=$'\n'
    local items=($(compgen -f $prefix$cur))

    # Remember the value of the first item, to see if it is a directory. If
    # it is a directory, then don't add a space to the completion
    local firstitem=""
    # Use counter, can't use ${#items[@]} as we skip hidden directories
    local i=0

    for item in ${items[@]}; do
        [[ $item =~ /\.[^/]*$ ]] && continue

        # if there is a unique match, and it is a directory with one entry
        # autocomplete the subentry as well (recursively)
        if [[ ${#items[@]} -eq 1 && $autoexpand -eq 1 ]]; then
            while [[ -d $item ]]; do
                local subitems=($(compgen -f "$item/"))
                local filtereditems=( )
                for item2 in "${subitems[@]}"; do
                    [[ $item2 =~ /\.[^/]*$ ]] && continue
                    filtereditems+=( "$item2" )
                done
                if [[ ${#filtereditems[@]} -eq 1 ]]; then
                    item="${filtereditems[0]}"
                else
                    break
                fi
            done
        fi

        # append / to directories
        [[ -d $item ]] && item="$item/"

        item="${item%$suffix}"
        TMP_COMPREPLY+=("${item#$prefix}")
        if [[ $i -eq 0 ]]; then
            firstitem=$item
        fi
        let i+=1
    done

    # The only time we want to add a space to the end is if there is only
    # one match, and it is not a directory
    if [[ $i -gt 1 || ( $i -eq 1 && -d $firstitem ) ]]; then
        compopt -o nospace
    fi

    _append_tmp_compreply
}

_pass_complete_folders () {
    _setup_tmp_compreply

    prefix="${PASSWORD_STORE_DIR:-$HOME/.password-store/}"
    prefix="${prefix%/}/"

    local IFS=$'\n'
    local items=($(compgen -d $prefix$cur))
    for item in ${items[@]}; do
        [[ $item == $prefix.* ]] && continue
        TMP_COMPREPLY+=("${item#$prefix}/")
    done

    _append_tmp_compreply
}

_pass_complete_keys () {
    _setup_tmp_compreply

    local IFS=$'\n'
    local GPG=gpg2
    which $GPG >/dev/null || GPG=gpg
    # Extract names and email addresses from gpg --list-keys
    local keys="$($GPG --list-secret-keys --with-colons | cut -d : -f
10 | sort -u | sed '/^$/d')"
    TMP_COMPREPLY+=($(compgen -W "${keys}" -- ${cur}))

    _append_tmp_compreply
}

_pass()
{
    COMPREPLY=()
    local cur="${COMP_WORDS[COMP_CWORD]}"
    local commands=$(_sort_entries_string "init ls find grep show
insert generate edit rm mv cp git help version")
    if [[ $COMP_CWORD -gt 1 ]]; then
        local lastarg="${COMP_WORDS[$COMP_CWORD-1]}"
        case "${COMP_WORDS[1]}" in
            init)
                if [[ $lastarg == "-p" || $lastarg == "--path" ]]; then
                    _pass_complete_folders
                    compopt -o nospace
                else
                    COMPREPLY+=($(compgen -W "-p --path" -- ${cur}))
                    _pass_complete_keys
                fi
                ;;
            ls|list|edit)
                _pass_complete_entries
                ;;
            show|-*)
                COMPREPLY+=($(compgen -W "-c --clip" -- ${cur}))
                _pass_complete_entries 1
                ;;
            insert)
                COMPREPLY+=($(compgen -W "-e --echo -m --multiline -f
--force" -- ${cur}))
                _pass_complete_entries
                ;;
            generate)
                COMPREPLY+=($(compgen -W "-n --no-symbols -c --clip -f
--force -i --in-place" -- ${cur}))
                _pass_complete_entries
                ;;
            cp|copy|mv|rename)
                COMPREPLY+=($(compgen -W "-f --force" -- ${cur}))
                _pass_complete_entries
                ;;
            rm|remove|delete)
                COMPREPLY+=($(compgen -W "-r --recursive -f --force" -- ${cur}))
                _pass_complete_entries
                ;;
            git)
                COMPREPLY+=($(compgen -W "init push pull config log
reflog rebase" -- ${cur}))
                ;;
        esac
    else
        COMPREPLY+=($(compgen -W "${commands}" -- ${cur}))
        _pass_complete_entries 1
    fi
}

complete -o nosort -o filenames -F _pass pass

On Sat, Mar 28, 2020 at 2:45 PM J Rt <jean.rblt at gmail.com> wrote:
>
> Thanks for your help, got it to work now. All was needed is change the
> last line of the autocomplete script to:
>
> complete -o nosort -o filenames -F _pass pass
>
> the nosort option cancels the sorting done in the background by bash.
> Found it there, should be ok with bash 4.4 and higher:
>
> https://unix.stackexchange.com/questions/215937/disable-sorting-of-compreply-in-bash-complete-function
>
> now when hitting tab tab I get first the (1){pass commands or
> options}, then the (2){folders / files}.
>
> The sets (1) and (2) are not sorted though, but I think I will get it
> done easily.
>
> On Sat, Mar 28, 2020 at 8:50 AM Reed Wade <reedwade at misterbanal.net> wrote:
> >
> > > Ok, sounds good. Do you agree that this would be a bit nicer tab
> > > autocompletion? Or do you mind a lot having to type 'show' to get the
> > > second autocompletion? :) .
> >
> > I just always type 'show' so it only giving me the tree.
> >
> > > Do you think you would be able to implement the change you described
> > > in your last message?
> >
> > I don't think anyone want to remove the command shortcut show sorry :D
> >
> > > If not I can give it a look / try, but I do not
> > > have too much experience with this sort of scripting.
> >
> > I think the better thing to do in this case, is downloading the
> > pass script and the autocompletion script, putting them in your ~/bin
> > and .local/share/bash-completion/completions/ pathes and customize them.
> >
> > Removing the shortcut probably is easy, adapting the autocompletion
> > script will need a little more understanding.
> >
> > A little tuto I use myself is :
> > https://opensource.com/article/18/3/creating-bash-completion-script


More information about the Password-Store mailing list