Tuesday, June 16, 2015

Bash completion of aliased commands, revisited

When you alias a command in Bash, tab completion no longer works. If you regularly take advantage of tab completion, this undoes most of the convenience of aliasing.

A quick web search throws up an old script by Ole Jorgen that solves this problem by "wrapping" the original completion command with some code that modifies variables to make the complete believe you'd typed out the aliased command in full and are tab completing as per usual.

Unfortunately, running that circa-'08 script on Ubuntu 14.04 LTS (using bash 4.3.11) causes it to choke on most attempted tab completions, with error messages like:

$COMP_POINT: substring expression < 0

The problem: the script modifies two of the variables made available to Bash completion (COMP_CWORD and COMP_WORDS), but misses others. With a little hackery we can modify that script to alter the other variables, COMP_LINE and COMP_POINT.

# Author.: Ole J, Chris C
# Date...: 14.06.2015
# License: Whatever

# Wraps a completion function
# make-completion-wrapper <actual completion function> <name of new func.>
#                         <command name> <list supplied arguments>
# eg.
#     alias agi='apt-get install'
#     make-completion-wrapper _apt_get _apt_get_install apt-get install
# defines a function called _apt_get_install (that's $2) that will complete
# the 'agi' alias. (complete -F _apt_get_install agi)
#
function make-completion-wrapper () {
    local function_name="$2"
    local arg_count=$(($#-3))
    local comp_function_name="$1"
    shift 2 # For convenience, drop the extracted arguments
    local arg=${@:1}
    local function="
function $function_name {
    ((COMP_CWORD+=$arg_count))

    local cmdlength
    cmdlength=\${#COMP_WORDS[0]}

    COMP_POINT=\$((\$COMP_POINT-\$cmdlength+${#arg}))
    COMP_LINE=\"$arg\${COMP_LINE[@]:\$cmdlength}\"

    COMP_WORDS=( "$@" \${COMP_WORDS[@]:1} )

    _init_completion
    "$comp_function_name"
    return 0
}"
    eval "$function"
}

Then usage proceeds as before:

alias sdr='screen -d -r'
make-completion-wrapper _screen _sdr screen -d -r
complete -F _sdr sdr