view contrib/bash_completion @ 11322:3d6915f5a2bb

improve --branch processing (and differentiate from # syntax) Previously #foo and --branch foo were handled identically. The behavior of #foo hasn't changed, but --branch now works like this: 1) If branchmap is not supported on the remote, the operation fails. 2) If branch is '.', substitute with branch of the working dir parent. 3) If branch exists remotely, its heads are expanded. 4) Otherwise, the operation fails. Tests have been added for the new cases.
author Sune Foldager <cryo@cyanite.org>
date Thu, 10 Jun 2010 12:46:09 +0200
parents 64a6a896e5fb
children 91af149b5cd7
line wrap: on
line source

# bash completion for the Mercurial distributed SCM

# Docs:
#
# If you source this file from your .bashrc, bash should be able to
# complete a command line that uses hg with all the available commands
# and options and sometimes even arguments.
#
# Mercurial allows you to define additional commands through extensions.
# Bash should be able to automatically figure out the name of these new
# commands and their options.  See below for how to define _hg_opt_foo
# and _hg_cmd_foo functions to fine-tune the completion for option and
# non-option arguments, respectively.
#
#
# Notes about completion for specific commands:
#
# - the completion function for the email command from the patchbomb
#   extension will try to call _hg_emails to get a list of e-mail
#   addresses.  It's up to the user to define this function.  For
#   example, put the addresses of the lists that you usually patchbomb
#   in ~/.patchbomb-to and the addresses that you usually use to send
#   the patchbombs in ~/.patchbomb-from and use something like this:
#
#      _hg_emails()
#      {
#          if [ -r ~/.patchbomb-$1 ]; then
#              cat ~/.patchbomb-$1
#          fi
#      }
#
#
# Writing completion functions for additional commands:
#
# If it exists, the function _hg_cmd_foo will be called without
# arguments to generate the completion candidates for the hg command
# "foo".  If the command receives some arguments that aren't options
# even though they start with a "-", you can define a function called
# _hg_opt_foo to generate the completion candidates.  If _hg_opt_foo
# doesn't return 0, regular completion for options is attempted.
#
# In addition to the regular completion variables provided by bash,
# the following variables are also set:
# - $hg - the hg program being used (e.g. /usr/bin/hg)
# - $cmd - the name of the hg command being completed
# - $cmd_index - the index of $cmd in $COMP_WORDS
# - $cur - the current argument being completed
# - $prev - the argument before $cur
# - $global_args - "|"-separated list of global options that accept
#                  an argument (e.g. '--cwd|-R|--repository')
# - $canonical - 1 if we canonicalized $cmd before calling the function
#                0 otherwise
#

shopt -s extglob

_hg_commands()
{
    local commands
    commands="$("$hg" debugcomplete "$cur" 2>/dev/null)" || commands=""
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$commands' -- "$cur"))
}

_hg_paths()
{
    local paths="$("$hg" paths 2>/dev/null | 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 -n$1 . 2>/dev/null)"
    local IFS=$'\n'
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur"))
}

_hg_tags()
{
    local tags="$("$hg" tags -q 2>/dev/null)"
    local IFS=$'\n'
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$tags' -- "$cur"))
}

_hg_branches()
{
    local branches="$("$hg" branches -q 2>/dev/null)"
    local IFS=$'\n'
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$branches' -- "$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 cmd_index opts i
    # global options that receive an argument
    local global_args='--cwd|-R|--repository'
    local hg="$1"
    local canonical=0

    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]}"
		cmd_index=$i
		break
	    fi
	fi
    done

    if [[ "$cur" == -* ]]; then
	if [ "$(type -t "_hg_opt_$cmd")" = function ] && "_hg_opt_$cmd"; then
	    return
	fi

	opts=$("$hg" debugcomplete --options "$cmd" 2>/dev/null)

	COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$opts' -- "$cur"))
	return
    fi

    # global options
    case "$prev" in
	-R|--repository)
	    _hg_paths
	    _hg_repos
	    return
	;;
	--cwd)
	    # Stick with default bash completion
	    return
	;;
    esac

    if [ -z "$cmd" ] || [ $COMP_CWORD -eq $i ]; then
	_hg_commands
	return
    fi

    # try to generate completion candidates for whatever command the user typed
    local help
    if _hg_command_specific; then
	return
    fi

    # canonicalize the command name and try again
    help=$("$hg" help "$cmd" 2>/dev/null)
    if [ $? -ne 0 ]; then
	# Probably either the command doesn't exist or it's ambiguous
	return
    fi
    cmd=${help#hg }
    cmd=${cmd%%[$' \n']*}
    canonical=1
    _hg_command_specific
}

_hg_command_specific()
{
    if [ "$(type -t "_hg_cmd_$cmd")" = function ]; then
	"_hg_cmd_$cmd"
	return 0
    fi

    if [ "$cmd" != status ] && [ "$prev" = -r ] || [ "$prev" == --rev ]; then
	if [ $canonical = 1 ]; then
	    _hg_tags
	    _hg_branches
	    return 0
	elif [[ status != "$cmd"* ]]; then
	    _hg_tags
	    _hg_branches
	    return 0
	else
	    return 1
	fi
    fi

    case "$cmd" in
	help)
	    _hg_commands
	;;
	export)
	    if _hg_ext_mq_patchlist qapplied && [ "${COMPREPLY[*]}" ]; then
		return 0
	    fi
	    _hg_tags
	    _hg_branches
	;;
	manifest|update)
	    _hg_tags
	    _hg_branches
	;;
	pull|push|outgoing|incoming)
	    _hg_paths
	    _hg_repos
	;;
	paths)
	    _hg_paths
	;;
	add)
	    _hg_status "u"
	;;
	merge)
	    _hg_tags
	    _hg_branches
	;;
	commit)
	    _hg_status "mar"
	;;
	remove)
	    _hg_status "d"
	;;
	forget)
	    _hg_status "a"
	;;
	diff)
	    _hg_status "mar"
	;;
	revert)
	    _hg_status "mard"
	;;
	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"))
	;;
	*)
	    return 1
	;;
    esac

    return 0
}

complete -o bashdefault -o default -F _hg hg 2>/dev/null \
    || complete -o default -F _hg hg


# Completion for commands provided by extensions

# bookmarks
_hg_bookmarks()
{
    local bookmarks="$("$hg" bookmarks --quiet 2>/dev/null )"
    local IFS=$'\n'
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$bookmarks' -- "$cur"))
}

_hg_cmd_bookmarks()
{
    if [[ "$prev" = @(-d|--delete|-m|--rename) ]]; then
        _hg_bookmarks
        return
    fi
}

# mq
_hg_ext_mq_patchlist()
{
    local patches
    patches=$("$hg" $1 2>/dev/null)
    if [ $? -eq 0 ] && [ "$patches" ]; then
	COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$patches' -- "$cur"))
	return 0
    fi
    return 1
}

_hg_ext_mq_queues()
{
    local root=$("$hg" root 2>/dev/null)
    local n
    for n in $(cd "$root"/.hg && compgen -d -- "$cur"); do
	# I think we're usually not interested in the regular "patches" queue
	# so just filter it.
	if [ "$n" != patches ] && [ -e "$root/.hg/$n/series" ]; then
	    COMPREPLY=(${COMPREPLY[@]:-} "$n")
	fi
    done
}

_hg_cmd_qpop()
{
    if [[ "$prev" = @(-n|--name) ]]; then
	_hg_ext_mq_queues
	return
    fi
    _hg_ext_mq_patchlist qapplied
}

_hg_cmd_qpush()
{
    if [[ "$prev" = @(-n|--name) ]]; then
	_hg_ext_mq_queues
	return
    fi
    _hg_ext_mq_patchlist qunapplied
}

_hg_cmd_qgoto()
{
    if [[ "$prev" = @(-n|--name) ]]; then
	_hg_ext_mq_queues
	return
    fi
    _hg_ext_mq_patchlist qseries
}

_hg_cmd_qdelete()
{
    local qcmd=qunapplied
    if [[ "$prev" = @(-r|--rev) ]]; then
	qcmd=qapplied
    fi
    _hg_ext_mq_patchlist $qcmd
}

_hg_cmd_qfinish()
{
    if [[ "$prev" = @(-a|--applied) ]]; then
	return
    fi
    _hg_ext_mq_patchlist qapplied
}

_hg_cmd_qsave()
{
    if [[ "$prev" = @(-n|--name) ]]; then
	_hg_ext_mq_queues
	return
    fi
}

_hg_cmd_strip()
{
    _hg_tags
    _hg_branches
}

_hg_cmd_qcommit()
{
    local root=$("$hg" root 2>/dev/null)
    # this is run in a sub-shell, so we can't use _hg_status
    local files=$(cd "$root/.hg/patches" 2>/dev/null &&
                  "$hg" status -nmar 2>/dev/null)
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$files' -- "$cur"))
}

_hg_cmd_qfold()
{
    _hg_ext_mq_patchlist qunapplied
}

_hg_cmd_qrename()
{
    _hg_ext_mq_patchlist qseries
}

_hg_cmd_qheader()
{
    _hg_ext_mq_patchlist qseries
}

_hg_cmd_qclone()
{
    local count=$(_hg_count_non_option)
    if [ $count = 1 ]; then
	_hg_paths
    fi
    _hg_repos
}

_hg_ext_mq_guards()
{
    "$hg" qselect --series 2>/dev/null | sed -e 's/^.//'
}

_hg_cmd_qselect()
{
    local guards=$(_hg_ext_mq_guards)
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$guards' -- "$cur"))
}

_hg_cmd_qguard()
{
    local prefix=''

    if [[ "$cur" == +* ]]; then
	prefix=+
    elif [[ "$cur" == -* ]]; then
	prefix=-
    fi
    local ncur=${cur#[-+]}

    if ! [ "$prefix" ]; then
	_hg_ext_mq_patchlist qseries
	return
    fi

    local guards=$(_hg_ext_mq_guards)
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -P $prefix -W '$guards' -- "$ncur"))
}

_hg_opt_qguard()
{
    local i
    for ((i=cmd_index+1; i<=COMP_CWORD; i++)); do
	if [[ ${COMP_WORDS[i]} != -* ]]; then
	    if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
		_hg_cmd_qguard
		return 0
	    fi
	elif [ "${COMP_WORDS[i]}" = -- ]; then
	    _hg_cmd_qguard
	    return 0
	fi
    done
    return 1
}


# hbisect
_hg_cmd_bisect()
{
    local i subcmd

    # find the sub-command
    for ((i=cmd_index+1; i<=COMP_CWORD; i++)); do
	if [[ ${COMP_WORDS[i]} != -* ]]; then
	    if [[ ${COMP_WORDS[i-1]} != @($global_args) ]]; then
		subcmd="${COMP_WORDS[i]}"
		break
	    fi
	fi
    done

    if [ -z "$subcmd" ] || [ $COMP_CWORD -eq $i ] || [ "$subcmd" = help ]; then
	COMPREPLY=(${COMPREPLY[@]:-}
		   $(compgen -W 'bad good help init next reset' -- "$cur"))
	return
    fi

    case "$subcmd" in
	good|bad)
	    _hg_tags
	    _hg_branches
	    ;;
    esac

    return
}


# patchbomb
_hg_cmd_email()
{
    case "$prev" in
	-c|--cc|-t|--to|-f|--from|--bcc)
	    # we need an e-mail address. let the user provide a function
	    # to get them
	    if [ "$(type -t _hg_emails)" = function ]; then
		local arg=to
		if [[ "$prev" == @(-f|--from) ]]; then
		    arg=from
		fi
		local addresses=$(_hg_emails $arg)
		COMPREPLY=(${COMPREPLY[@]:-}
			   $(compgen -W '$addresses' -- "$cur"))
	    fi
	    return
	    ;;
	-m|--mbox)
	    # fallback to standard filename completion
	    return
	    ;;
	-s|--subject)
	    # free form string
	    return
	    ;;
    esac

    _hg_tags
    _hg_branches
    return
}


# gpg
_hg_cmd_sign()
{
    _hg_tags
    _hg_branches
}


# transplant
_hg_cmd_transplant()
{
    case "$prev" in
	-s|--source)
	    _hg_paths
	    _hg_repos
	    return
	    ;;
	--filter)
	    # standard filename completion
	    return
	    ;;
    esac

    # all other transplant options values and command parameters are revisions
    _hg_tags
    _hg_branches
    return
}

# shelve
_hg_shelves()
{
    local shelves="$("$hg" unshelve -l . 2>/dev/null)"
    local IFS=$'\n'
    COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$shelves' -- "$cur"))
}

_hg_cmd_shelve()
{
    _hg_status "mard"
}

_hg_cmd_unshelve()
{
    _hg_shelves
}