Less annoying directory completion (see http://bugs.debian.org/343458)
The current bash completion script is quite painful in conjuntion with
deep directory trees because it adds a space after each successful
directory completion. Eg. "hg clone /ho<tab>" is completed to "hg clone
/home " when what you really want is "hg clone /home/" (assuming the
complete path to the repository looks like /home/foo/hg...).
That's because the 'complete' command does not know about the type of
completion it receives from the _hg shell function. When only a single
completion is returned, it assumes completion is complete and tells
readline to add a trailing space. This behaviour is usually wanted, but
not in the case of directory completion.
I've attached a patch that circumvents this problem by only returning
successful completions for directories that contain a .hg subdirectory.
If no repositories are found, no completions are returned either, and
bash falls back to ordinary (filename) completion. I find this behaviour
a lot less annoying than the current one.
Alternative: Use option nospace for the 'complete' command and let _hg
itself take care of adding a trailing space where appropriate. That's a
far more intrusive change, though.
shopt -s extglob
_hg_commands()
{
local all commands result
all=($(hg --debug help | sed -e '1,/^list of commands:/d' \
-e '/^global options:/,$d' \
-e '/^ [^ ]/!d; s/^ //; s/[,:]//g;'))
commands="${all[*]##debug*}"
result=$(compgen -W "${commands[*]}" -- "$cur")
# hide debug commands from users, but complete them if
# there is no other possible command
if [ "$result" = "" ]; then
local debug
debug=(${all[*]##!(debug*)})
debug="${debug[*]/g/debug}"
result=$(compgen -W "$debug" -- "$cur")
fi
COMPREPLY=(${COMPREPLY[@]:-} $result)
}
_hg_paths()
{
local paths="$(hg paths | sed -e 's/ = .*$//')"
COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W "$paths" -- "$cur" ))
}
_hg_repos()
{
local i
for i in $( compgen -d -- "$cur" ); do
test ! -d "$i"/.hg || COMPREPLY=(${COMPREPLY[@]:-} "$i")
done
}
_hg_status()
{
local files="$( hg status -$1 | cut -b 3- )"
COMPREPLY=(${COMPREPLY[@]:-} $( compgen -W "$files" -- "$cur" ))
}
_hg_tags()
{
local tags="$(hg tags | sed -e 's/[0-9]*:[a-f0-9]\{40\}$//; s/ *$//')"
COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$tags" -- "$cur") )
}
# this is "kind of" ugly...
_hg_count_non_option()
{
local i count=0
local filters="$1"
for (( i=1; $i<=$COMP_CWORD; i++ )); do
if [[ "${COMP_WORDS[i]}" != -* ]]; then
if [[ ${COMP_WORDS[i-1]} == @($filters|$global_args) ]]; then
continue
fi
count=$(($count + 1))
fi
done
echo $(($count - 1))
}
_hg()
{
local cur prev cmd opts i
# global options that receive an argument
local global_args='--cwd|-R|--repository'
COMPREPLY=()
cur="$2"
prev="$3"
# searching for the command
# (first non-option argument that doesn't follow a global option that
# receives an argument)
for (( i=1; $i<=$COMP_CWORD; i++ )); do
if [[ ${COMP_WORDS[i]} != -* ]]; then
if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
cmd="${COMP_WORDS[i]}"
break
fi
fi
done
if [[ "$cur" == -* ]]; then
# this assumes that there are no commands with spaces in the name
opts=$(hg -v help $cmd | sed -e '/^ *-/!d; s/ [^- ].*//')
COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$opts" -- "$cur") )
return
fi
# global options
case "$prev" in
-R|--repository)
_hg_repos
return
;;
--cwd)
# Stick with default bash completion
return
;;
esac
if [ -z "$cmd" ] || [ $COMP_CWORD -eq $i ]; then
_hg_commands
return
fi
# canonicalize command name
cmd=$(hg -q help "$cmd" | sed -e 's/^hg //; s/ .*//; 1q')
if [ "$cmd" != status ] && [ "$prev" = -r ] || [ "$prev" = --rev ]; then
_hg_tags
return
fi
case "$cmd" in
help)
_hg_commands
;;
export|manifest|update)
_hg_tags
;;
pull|push|outgoing|incoming)
_hg_paths
_hg_repos
;;
paths)
_hg_paths
;;
add)
_hg_status "u"
;;
commit)
_hg_status "mra"
;;
remove)
_hg_status "r"
;;
forget)
_hg_status "a"
;;
diff)
_hg_status "mra"
;;
revert)
_hg_status "mra"
;;
clone)
local count=$(_hg_count_non_option)
if [ $count = 1 ]; then
_hg_paths
fi
_hg_repos
;;
debugindex|debugindexdot)
COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -X "!*.i" -- "$cur" ))
;;
debugdata)
COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -X "!*.d" -- "$cur" ))
;;
cat)
local count=$(_hg_count_non_option '-o|--output')
if [ $count = 2 ]; then
_hg_tags
else
COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" ))
fi
;;
*)
COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" ))
;;
esac
}
complete -o bashdefault -o default -F _hg hg 2> /dev/null \
|| complete -o default -F _hg hg