# Initialisation for new style completion. This mainly contains some helper
# function and aliases. Everything else is split into different files in this
# directory that will automatically be made autoloaded (see the end of this
# file).
# The names of the files that will be considered for autoloading have to
# start with two underscores (like `__setopt).
# The first line of these files will be read and has to say what should be
# done with its contents:
#
#   `#function <names ...>'
#     if the first line looks like this, the file is
#     autoloaded as a function and that function will
#     be called to generate the matches when completing
#     for one of the commands whose <name> is given
#
#   `#array <names ...>'
#     with a first line like this, the filename is taken as
#     the name of an array; when trying to generate matches
#     for the command <name>, the file will be sourced and
#     should define this array, the builtin `complist' will
#     then be called with the elements of this array as its
#     arguments; this is intended for simple definitions
#     for which you don't need a shell function
#
#   `#pattern-function <pattern>'
#     this defines a function that should be called to generate
#     matches for commands whose name matches <pattern>; note
#     that only one pattern may be given
#
#   `#pattern-array <pattern>'
#     like `#pattern-function' but defining an array
#
#   `#key-function <style> [ <key-sequence> ... ]
#     this is used to bind special completions to all the given
#     <key-sequence>(s). The <style> is the name of one of the built-in
#     completion widgets (complete-word, delete-char-or-list,
#     expand-or-complete, expand-or-complete-prefix, list-choices,
#     menu-complete, menu-expand-or-complete, or reverse-menu-complete).
#     This creates a widget behaving like <style> so that the
#     completions are chosen as given in the the rest of the file,
#     rather than by the context.  The widget has the same name as
#     the autoload file and can be bound using bindkey in the normal way.
#
#   `#key-array <style> [ <key-sequence> ... ]
#     like `#key-function', but defining an array instead
#
#   `#helper'
#     this is for helper functions that are not used to
#     generate matches, but should automatically be loaded
#     when they are called
#
# Note that no white space is allowed between the `#' and the rest of
# the string.


# An associative array for completions definitions. The keys of the entries
# are the names of the command, the values are names of functions or variables
# that are to be used to generate the matches.
# Pattern completions will be stored in an normal array named `patcomps'.
# Completion definitions bound directly to keys are stored in an assoc array
# named `keycomps'.

typeset -A comps
typeset -A keycomps


# This may be used to define completion handlers. The first argument is the
# name of the function or variable containing the definition, the other
# arguments are the command names for which this definition should be used.
# With only one argument the function/variable-name __$1 is used.
# If given the `-a' option, the function is defined as being autoloaded.

defcomp() {
  local name autol=''

  if [[ "$1" = -a ]]; then
    shift
    autol=yes
  fi
  if [[ $# -eq 1 ]]; then
    comps[$1]="__$1"
    [[ -z "$autol" ]] || autoload "__$1"
  else
    name="$1"
    shift
    for i; do
      comps[$i]="$name"
    done
    [[ -z "$autol" ]] || autoload "$name"
  fi
}


# Almost like `defcomp', but this always gets two arguments: the name of a
# variable or function describing what should be completed and the pattern
# that will be compared to the command names for which completion is attempted.

defpatcomp() {
  if [[ "$1" = -a ]]; then
    shift
    autoload "$1"
  fi
  if (( $+patcomps )) then
    patcomps=("$patcomps[@]" "$2 $1" )
  else
    patcomps=( "$2 $1" )
  fi
}


# This is used to define completion handlers directly bound to keys. The
# first argument is as for `defcomp', giving the handler. The second
# argument is the name of one of the built-in completion widgets. Any
# remaining arguments are used as key sequences to bind the widget.
# Typing that key sequence will complete the word the cursor is on
# according to the completion definition given and will behave as if the
# built-in completion widget was used.

defkeycomp() {
  local name

  if [[ "$1" = -a ]]; then
    shift
    autoload "$1"
    name="$1"
  elif [[ "${1[1]}" = ' ' ]]; then
    name="${1:t}"
  else
    name="$1"
  fi
  keycomps[$name]="$1"
  shift
  zle -C "$name" "$1" __main_key_complete
  shift
  while (( $# )); do
    bindkey "$1" "$name"
    shift
  done
}

# These can be used to easily save and restore the state of the special
# variables used by the completion code.

alias compsave='local _oprefix _oiprefix _oargv _ocurrent; \
                _oprefix="$PREFIX"; \
                _oiprefix="$IPREFIX"; \
                _oargv=( "$@" ); \
                _ocurrent="$CURRENT"'
alias compreset='PREFIX="$_oprefix"; \
                 IPREFIX="$_oiprefix"; \
                 argv=( "$_oargv[@]" ); \
		 CURRENT="$_ocur"'


# This is an easy way to get completion for sub-commands.

alias compsub='__normal "$@" || return 1'


# This searches $1 in the array for normal completions and calls the result.

compalso() {
  local tmp

  tmp="$comps[$1]"
  [[ -z "$tmp" ]] || callcomplete comps "$1" "$@"
}


# This generates matches. The first argument is the name of one of the
# arrays containing completion definitions. The second argument is the index
# into this array. The other arguments are the positional parameters to give
# to the completion function (containing the arguments from the command line).

callcomplete() {
  local file def

  # Get the definition from the array.

  eval "def=\$${1}[${2}]"

  # If the definition starts with a space then this means that we should
  # source a file to get the definition for an array.

  if [[ "$def[1]" = ' ' ]]; then
    # The definition starts with a space, so source the file and change
    # the definition.

    file="$def[2,-1]"
    builtin . "$file"
    def="${file:t}"
    eval "${1}[${2}]=$def"
  fi

  # Get rid of the array-name and -index.

  shift 2
  if [[ ${(P)+def} -eq 1 ]]; then
    # It is a parameter name, call complist directly.

    complist "${(@P)def}"
  else
    # Otherwise it's a function name, call this function.

    "$def" "$@"
  fi
}


# Now we make the files automatically autoloaded.

local dir file line func

for dir in $fpath; do
  [[ $dir = . ]] && continue
  for file in $dir/__*~*~(N); do
    read -rA line < $file
    func=$line[1]
    shift line
    if [[ $func = '#function' ]]; then
      defcomp -a ${file:t} "${line[@]}"
    elif [[ $func = '#array' ]]; then
      defcomp " $file" "${line[@]}"
    elif [[ $func = '#pattern-function' ]]; then
      defpatcomp -a ${file:t} "${line[@]}"
    elif [[ $func = '#pattern-array' ]]; then
      defcomp " $file" "${line[@]}"
    elif [[ $func = '#key-function' ]]; then
      defkeycomp -a "${file:t}" "${line[@]}"
    elif [[ $func = '#key-array' ]]; then
      defkeycomp " $file" "${line[@]}"
    elif [[ $func = '#helper' ]]; then
      autoload ${file:t}
    fi
  done
done


# Finally we make all this be called by changing the key bindings.

bindkey | while read -A line; do
            if [[ "$line[2]" = complete-word ||
	    	  "$line[2]" = delete-char-or-list ||
	    	  "$line[2]" = expand-or-complete ||
	    	  "$line[2]" = expand-or-complete-prefix ||
	    	  "$line[2]" = list-choices ||
	    	  "$line[2]" = menu-complete ||
	    	  "$line[2]" = menu-expand-or-complete ||
	    	  "$line[2]" = reverse-menu-complete ]]; then
              zle -C __complete_$line[2] $line[2] __main_complete
              bindkey "${line[1][2,-2]}" __complete_$line[2]
            fi
          done