# This script is to be run by a user to set up the new function based
# completion system.  The functions themselves are assumed to be already
# available in some directory; they should have been installed with the
# the shell.  If they have been, the commands `autoload -U compinit; compinit'
# in the shell startup file should be enough, although you can run
# compinstall for more configuration choices.
#
# Simply run this script as a function and answer the questions.
# Normally it will alter ~/.zshrc (or wherever ZDOTDIR puts it), but you
# can make that unwritable and it will leave the lines in a temporary file
# instead.  It doesn't matter if .zshrc didn't exist before.  If your
# .zshrc usually exits before the end, then you should take the code added
# by compinstall and put it (including the comment lines at the start and
# end) at the point you want it to be executed.  If you run compinstall
# again it will find and replace those lines, so you can use this script to
# modify what compinstall previously added to ~/.zshrc.
#
# It is safe to abort with ^C any time you are being prompted for
# information; your .zshrc will not be altered.
#
# To do:
#  - Should probably offer to set different options for _approximate than
#    for _complete if both are used.
#  - Could add code for setting other completers and options.
#  - Could add keys for context-sensitive help.


emulate -L zsh

typeset _ci_options _ci_f _ci_fdir _ci_files _ci_dumpfile _ci_lines
typeset _ci_type _ci_completer _ci_accept _ci_cprompt _ci_startline
typeset _ci_endline _ci_ifile _ci_tmpf _ci_compconf _ci_warn
typeset _ci_dtype _ci_existing _ci_line _ci_end

# Look for the defaults.
_ci_startline='# The following lines were added by compinstall'
_ci_endline='# End of lines added by compinstall'

_ci_ifile=${ZDOTDIR:-~}/.zshrc
_ci_lines=''
_ci_existing=''

typeset -A _ci_defaults

if [[ -f $_ci_ifile ]]; then
  # This assumes the lines haven't been altered by the user too much
  # after they were added.
  _ci_compconf=0
  sed -n "/^$_ci_startline/,/^$_ci_endline/p" $_ci_ifile |
  while read -rA _ci_line; do
    if (( $_ci_compconf )); then
      # parse a compconf component as first argument
      if [[ $_ci_line[-1] != \\ ]]; then
	_ci_end=-1
	_ci_compconf=0
      else
	_ci_end=-2
      fi
      if [[ $_ci_line[1] = *=* ]]; then
	_ci_f="${${_ci_line[1,$_ci_end]}#*=}"
	if [[ $_ci_f = \'*\' ]]; then
	  # strip quotes
	  _ci_f=${_ci_f[2,-2]//\'\\\'\'/\'}
	fi
	_ci_defaults[${_ci_line[1]%%\=*}]=$_ci_f
      fi
      _ci_existing="${_ci_existing}  $_ci_line
"
    elif [[ $_ci_line[1] = compinit ]]; then
      # parse the line running compinit
      [[ $_ci_line[2] = -f ]]  && _ci_fdir=$_ci_line[3]
      [[ $_ci_line[-2] = -d ]] && _ci_dumpfile=$_ci_line[-1]
    elif [[ $_ci_line[1] = _compdir=* ]]; then
      _ci_fdir=${_ci_line[1]##_compdir=}
    elif [[ $_ci_line[1] = compconf ]]; then
      # parse a compconf component as second argument (should be completer)
      [[ $_ci_line[2] = completer=* ]] &&
        _ci_completer=${_ci_line[2]#completer=}
      [[ $_ci_line[-1] == \\ ]] && _ci_compconf=1
      _ci_existing="${_ci_existing}$_ci_line
"
    elif [[ $_ci_line[1] != \#* && $_ci_line[1] != (autoload|\[\[) ]]; then
      if [[ -z $_ci_warn ]]; then
	_ci_warn=1
	print "Warning:  existing lines in compinstall setup not understood:"
      fi
      print - $_ci_line
      _ci_existing="${_ci_existing}$_ci_line
"
    fi
  done
fi


# Find out where the completion functions are kept.

if [[ -z $_ci_fdir || ! -f ${~_ci_fdir}/compinit ||
  ! -f ${~_ci_fdir}/compdump ]]; then
  for _ci_f in $fpath; do
    if [[ $_ci_f != . && -f $_ci_f/compinit && -f $_ci_f/compdump ]]; then
      _ci_fdir=$_ci_f
      break
    elif [[ $_ci_f != . && -f $_ci_f/Core/compinit &&
      -f $_ci_f/Core/compdump ]]
    then
      _ci_fdir=$_ci_f/Core
      break
    fi
  done
fi

if [[ -z $_ci_fdir || ! -d ${~_ci_fdir} ]]; then
  print \
"Please edit the name of the directory where the completion functions are
installed.  If they are not installed, you will need to find them in the
Completion/* directories of the zsh distribution and install them yourself,
or insult your system manager for incompetence."
  vared -c _ci_fdir
  while [[ ! -d ${~_ci_fdir} || 
    ((! -f ${~_ci_fdir}/compinit || ! -f ${~_ci_fdir}/compdump) &&
    (! -f ${~_ci_fdir}/Core/compinit || ! -f ${~_ci_fdir}/Core/compdump)) ]]
  do
    print "I can't find them in that directory.  Try again or abort."
    vared _ci_fdir
  done
  if [[ -f ${~_ci_fdir}/Core/compinit && ! -f ${~_ci_fdir}/compinit ]]; then
    _ci_fdir=$_ci_fdir/Core
  fi
else
  print "Keeping existing completion directiory $_ci_fdir"
fi

if [[ ${~_ci_fdir} != /* ]]; then
  _ci_fdir=$(cd $_ci_fdir;builtin pwd)
fi

# Check if this is in fpath already, else put it there (with ~'s expanded).
_ci_f=${~_ci_fdir}
[[ -z ${fpath[(r)$_ci_f]} ]] && fpath=($fpath $_ci_f)

# Contract $HOME to ~ in the parameter to be used for writing.
_ci_fdir=${_ci_fdir/#$HOME/\~}

# Now check the fpath, ignoring the directory .
_ci_files=( ${^~fpath:/.}/_(|*[^~])(N:t) )
if [[ $#_ci_files -lt 20 ]]; then
  print "
Hmmm, completion functions seem a bit thin on the ground.  There should
be lots of files with names beginning with an underscore (_).  You should
look and see what's happened to these.
[Hit return to continue]"
  read
fi


# Set up the dumpfile
_ci_dtype=existing
if [[ -z $_ci_dumpfile ]]; then
  _ci_dumpfile="${ZDOTDIR:-$HOME}/.zcompdump"
  _ci_dtype=standard
fi

if [[ -w ${~_ci_dumpfile:h} && ( ! -f ${~_ci_dumpfile} ||
  -w ${~_ci_dumpfile} ) ]]
then
  print "
Using $_ci_dtype dumpfile
  ${_ci_dumpfile}
to speed up initialisation.
[Hit return to continue]"
  read
else
  print "
I will force completion to dump its status, which will speed up the shell's
start-up considerably.  However, I can't write the file I'd like to, namely
${_ci_dumpfile}.  Please edit a replacement."
  vared _ci_dumpfile
  while ! touch ${~_ci_dumpfile} >& /dev/null; do
    print "Sorry, I can't write that either.  Try again."
    vared _ci_dumpfile
  done
  [[ -s $_ci_dumpfile ]] || rm -f $_ci_dumpfile
fi

_ci_lines="${_ci_lines}_compdir=$_ci_fdir
[[ -z \$fpath[(r)\$_compdir] ]] && fpath=(\$fpath \$_compdir)
autoload -U compinit
compinit"
[[ $_ci_dtype != standard ]] && _ci_lines="${_ci_lines} $_ci_dumpfile"
_ci_lines="${_ci_lines}
"


print "
Would you like to set some more advanced options?  Otherwise, you
can re-run compinstall later to set these. [n]"

# The whole of the next part should be indented, but I can't be bothered.
if read -q; then

 print "
In addition to completion, zsh can also perform correction of the
current word, or approximate completion, i.e. completion where the part of
the word typed so far can be corrected; or it can try correction, then
approximate completion if that fails.  Would you like:
  0:  Just ordinary completion
  C:  Correction
  A:  Approximate completion
  B:  Both"
  if [[ -n $_ci_completer ]]; then
    print "  Default: use the current completer:\n$_ci_completer"
  else
    print "Please type one of the keys above."
  fi
  while read -k _ci_type; do
    print
    case $_ci_type in
      0*) _ci_completer=_complete
	  break
	  ;;
      [cC]*) _ci_completer=_complete:_correct
	     break
	     ;;
      [aA]*) _ci_completer=_complete:_approximate
	     break;
	     ;;
      [bB]*) _ci_completer=_complete:_correct:_approximate
	     break
	     ;;
      *) [[ -n $_ci_completer ]] && break
	 print Try again
	 ;;
    esac
  done

  _ci_lines="${_ci_lines}compconf completer=$_ci_completer"


  if [[ $_ci_completer = *(correct|approx)* ]]; then
    _ci_accept=${_ci_defaults[correct_accept]}
    _ci_cprompt=${_ci_defaults[correct_prompt]}
    print "
Correction and approximation will allow up to ${${_ci_accept:-2}%%[^0-9]*} \
errors. "
    case $_ci_accept in
      *n*!*|*!*n) print "A numeric prefix, if not 1, will cause correction \
not to be done."
		  ;;
      *n*) print "A numeric prefix gives the maximum number of errors which \
will be accepted."
           ;;
      *) print "The numeric prefix will not be used."
    esac
print "The correction prompt is \`${_ci_cprompt:-correct to:}'.
Do you want to change any of this? [n]"
    if read -q; then
      print "Number of errors to accept normally (0 is OK):"
      _ci_accept=${_ci_accept%%[^0-9]*}
      vared _ci_accept
      while [[ $_ci_accept != <-> ]]; do
	print "Please enter a number:"
	vared _ci_accept
      done
      print \
"How would you like the numeric prefix to be treated:
  0:  Not used by correction
  U:  The number gives the largest number of errors which will be
      accepted when correcting
  I:  If present, and not 1, do not perform correction?
Please type one of the keys above:"
      while read -k _ci_type; do
	print
	case $_ci_type in
	  0*) break
	      ;;
	  [uU]*) _ci_accept="${_ci_accept}n"
		 break
		 ;;
	  [Ii]*) _ci_accept="${_ci_accept}!n"
		 break
		 ;;
	  *) print Try again
	     ;;
	esac
      done
      print "
Instead of the prompt \`correct to:', you can have no prompt, or a
prompt of your choosing which can display the number of errors found by
containing the string \`%e'.  Do you wish to change the correction
prompt? [n]"
      if read -q; then
	print "Edit a new prompt (may be empty):"
	vared _ci_cprompt
	[[ -z $_ci_cprompt ]] && _ci_cprompt=':empty:'
      fi
    fi
    if [[ -n $_ci_accept ]]; then
      _ci_lines="$_ci_lines \\
  correct_accept='$_ci_accept'"
      unset '_ci_defaults[correct_accept]'
    fi
    if [[ -n $_ci_cprompt ]]; then
      _ci_cprompt=${_ci_cprompt##:empty:}
      _ci_lines="$_ci_lines \\
  correct_prompt='${_ci_cprompt//\'/\'\\\'\'}'"
      unset '_ci_defaults[correct_prompt]'
    fi
  fi

  _ci_warn=''
  for _ci_f in ${(k)_ci_defaults}; do
    if [[ -z $_ci_warn ]]; then
      print "
(Keeping other existing configuration settings...)"
      _ci_warn=1
    fi
    _ci_lines="$_ci_lines \\
  ${_ci_f}='${_ci_defaults[$_ci_f]//\'/\'\\\'\'}'"
  done

  _ci_lines="$_ci_lines
"

else

  if [[ -n $_ci_existing ]]; then
    print -nr "
I will retain the following lines from the existing completion setup:
$_ci_existing"
    _ci_lines="$_ci_lines${_ci_existing}"
  fi

fi				# End of advanced options


[[ -f $_ci_ifile ]] || touch $_ci_ifile
_ci_tmpf=${TMPPPREFIX:-/tmp/zsh}compinstall$$

if [[ ! -w $_ci_ifile ]]; then
  print "\nI can't write to $_ci_ifile.  I will leave the lines to add in
\`$_ci_tmpf' and you must add them by hand."
  print -r - "$_ci_startline
$_ci_lines$_ci_endline" >$_ci_tmpf
elif grep $_ci_endline $_ci_ifile >& /dev/null; then
  print -r - "$_ci_startline
$_ci_lines$_ci_endline" >$_ci_tmpf
  sed -e "/^$_ci_endline/r $_ci_tmpf
/^$_ci_startline/,/^$_ci_endline/d" $_ci_ifile >${_ci_tmpf}2 && 
  mv ${_ci_tmpf}2 $_ci_ifile &&
  print "\nSuccesfully modified old compinstall lines in $_ci_ifile."
  rm -f $_ci_tmpf ${_ci_tmpf}2
else
  print -r - "$_ci_startline
$_ci_lines$_ci_endline" >>$_ci_ifile &&
  print "\nSuccessfully appended lines to $_ci_ifile."
fi

unfunction compinstall
autoload -U compinstall

return 0