# 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

# 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_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
}