Enable composer bash completion on Ubuntu

The composer bash completion from iArren is fantastic, but is doesn’t work out of the box on Ubuntu (14.04). I found out this is due to an old version of bash-completion on Ubuntu and with a simple fix I got it working.

The cool thing about iArren’s bash completion is not only the ablity to autocomplete the composer methods (install, update and so on) but it can query packages too. It uses composer show -a to get a list of all known packages. Thereafter, it lists all available versions of that package.

Showcase working of bash completion

The bash-completion project ships with some functions the completion files can use. A recent version comes with a function called _init_completion(), but is not included in /etc/bash_completion for some kind of reason.

The problem here is the composer autocomplete uses the _init_completion() and it’s not available on my machine! Other Ubuntu users have a similar issue and apparently this is due to an old version of bash or bash-completion shipped with 14.04. I am not sure if this is still the case for 14.10.

The simple fix is to load the function yourself and source it from your .bashrc. Create a file ~/.bash_completion with this content:

## Added because old bash-completion versions doesn't have the
## _init_completion() function

# Initialize completion and deal with various general things: do file
# and variable completion where appropriate, and adjust prev, words,
# and cword as if no redirections exist so that completions do not
# need to deal with them.  Before calling this function, make sure
# cur, prev, words, and cword are local, ditto split if you use -s.
#
# Options:
#     -n EXCLUDE  Passed to _get_comp_words_by_ref -n with redirection chars
#     -e XSPEC    Passed to _filedir as first arg for stderr redirections
#     -o XSPEC    Passed to _filedir as first arg for other output redirections
#     -i XSPEC    Passed to _filedir as first arg for stdin redirections
#     -s          Split long options with _split_longopt, implies -n =
# @return  True (0) if completion needs further processing,
#          False (> 0) no further processing is necessary.
#
_init_completion()
{
    local exclude flag outx errx inx OPTIND=1

    while getopts "n:e:o:i:s" flag "$@"; do
        case $flag in
            n) exclude+=$OPTARG ;;
            e) errx=$OPTARG ;;
            o) outx=$OPTARG ;;
            i) inx=$OPTARG ;;
            s) split=false ; exclude+== ;;
        esac
    done

    # For some reason completion functions are not invoked at all by
    # bash (at least as of 4.1.7) after the command line contains an
    # ampersand so we don't get a chance to deal with redirections
    # containing them, but if we did, hopefully the below would also
    # do the right thing with them...

    COMPREPLY=()
    local redir="@(?([0-9])<|?([0-9&])>?(>)|>&)"
    _get_comp_words_by_ref -n "$exclude<>&" cur prev words cword

    # Complete variable names.
    if [[ $cur =~ ^(\$\{?)([A-Za-z0-9_]*)$ ]]; then
        [[ $cur == *{* ]] && local suffix=} || local suffix=
        COMPREPLY=( $( compgen -P ${BASH_REMATCH[1]} -S "$suffix" -v -- \
            "${BASH_REMATCH[2]}" ) )
        return 1
    fi

    # Complete on files if current is a redirect possibly followed by a
    # filename, e.g. ">foo", or previous is a "bare" redirect, e.g. ">".
    if [[ $cur == $redir* || $prev == $redir ]]; then
        local xspec
        case $cur in
            2'>'*) xspec=$errx ;;
            *'>'*) xspec=$outx ;;
            *'<'*) xspec=$inx ;;
            *)
                case $prev in
                    2'>'*) xspec=$errx ;;
                    *'>'*) xspec=$outx ;;
                    *'<'*) xspec=$inx ;;
                esac
                ;;
        esac
        cur="${cur##$redir}"
        _filedir $xspec
        return 1
    fi

    # Remove all redirections so completions don't have to deal with them.
    local i skip
    for (( i=1; i < ${#words[@]}; )); do
        if [[ ${words[i]} == $redir* ]]; then
            # If "bare" redirect, remove also the next word (skip=2).
            [[ ${words[i]} == $redir ]] && skip=2 || skip=1
            words=( "${words[@]:0:i}" "${words[@]:i+skip}" )
            [[ $i -le $cword ]] && cword=$(( cword - skip ))
        else
            i=$(( ++i ))
        fi
    done

    [[ $cword -eq 0 ]] && return 1
    prev=${words[cword-1]}

    [[ $split ]] && _split_longopt && split=true

    return 0
}

This is extracted from the source file hosted on Github. Now, load this file from the ~/.bashrc and include ~/.bash_completion only when the _init_completion() is unknown:

# make sure we have the _init_completion() function
if type -t _init_completion | grep -q '^function$'; then
    . ~/.bash_completion
fi

The last step is to download the composer autocompletion file and save it as /etc/bash_completion.d/composer. Now reload bash (with source ~/.bashrc) and you’re good to go!