about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTanaka Akira <akr@users.sourceforge.net>1999-10-12 09:32:45 +0000
committerTanaka Akira <akr@users.sourceforge.net>1999-10-12 09:32:45 +0000
commit0f076fcde8a342c081b8eab0a2f135335d552d79 (patch)
tree13bf91d328057d7b41df879e7ad80128f70baeab
parent0749034911b1e28d4d9abba5472201972a5e37ce (diff)
downloadzsh-0f076fcde8a342c081b8eab0a2f135335d552d79.tar.gz
zsh-0f076fcde8a342c081b8eab0a2f135335d552d79.tar.xz
zsh-0f076fcde8a342c081b8eab0a2f135335d552d79.zip
manual/8219
-rw-r--r--Completion/Base/_arguments1022
-rw-r--r--Completion/Base/_describe104
-rw-r--r--Completion/Base/_values339
-rw-r--r--Completion/Core/compinit4
-rw-r--r--Doc/Zsh/compsys.yo9
-rw-r--r--Src/Zle/compctl.c91
-rw-r--r--Src/Zle/computil.c1891
-rw-r--r--Src/Zle/computil.mdd3
-rw-r--r--Src/Zle/zle_tricky.c16
9 files changed, 2232 insertions, 1247 deletions
diff --git a/Completion/Base/_arguments b/Completion/Base/_arguments
index 173a23a73..9875d979b 100644
--- a/Completion/Base/_arguments
+++ b/Completion/Base/_arguments
@@ -5,904 +5,294 @@
 
 setopt localoptions extendedglob
 
-local args rest ws cur nth def nm expl descr action opt arg tmp xor
-local single uns ret=1 aret soptseq soptseq1 sopts prefix _line odescr
-local beg optbeg argbeg nargbeg inopt inrest fromrest cmd="$words[1]"
-local matched curopt noargs i tmp1 tmp2 tmp3 suffix match
+local long cmd="$words[1]" descr
 
-# Associative arrays used to collect information about the options.
+long=$argv[(I)--]
+if (( long )); then
+  local name tmp tmpargv
 
-typeset -A opts dopts odopts xors _options
-
-# Fill the cache if we were called with different arguments.
-
-if [[ "$*" != "$_args_cache_descr" ]]; then
-  _args_cache_descr="$*"
-
-  unset _args_cache_{opts,dopts,odopts,odescr,xors}
-  typeset -gA _args_cache_{opts,dopts,odopts,xors}
-
-  unset _args_cache_{long,longcmd,single,match,rest,args,sopts,soptseq,soptseq1}
-
-  # Default match spec.
-
-  _args_cache_match='r:|[_-]=* r:|=*'
-
-  # See if we are using single-letter options or have a match spec.
-
-  while [[ "$1" = -(s|M*) ]]; do
-    if [[ "$1" = -s ]]; then
-      shift
-      _args_cache_single=yes
-    elif [[ "$1" = -M?* ]]; then
-      _args_cache_match="${1[3,-1]}"
-      shift
-    else
-      _args_cache_match="$2"
-      shift 2
-    fi
-  done
-
-  # See if we support long options, too.
-
-  nth=$argv[(I)--]
-  if (( nth )); then
-    local tmpargv
+  if [[ long -eq 1 ]]; then
+    tmpargv=()
+  else
+    tmpargv=( "${(@)argv[1,long-1]}" )
+  fi
 
-    if [[ nth -eq 1 ]]; then
-      tmpargv=()
-    else
-      tmpargv=( "${(@)argv[1,nth-1]}" )
-    fi
+  name=${~words[1]}
+  if [[ "$name" != /* ]]; then
+    tmp="$PWD/$name"
+  fi
+  name="_args_cache_${name}"
+  name="${name//[^a-zA-Z0-9_]/_}"
 
-    tmp=${~words[1]}
-    if [[ "$tmp" != /* ]]; then
-      tmp="$PWD/$tmp"
-    fi
+  if (( ! ${(P)+name} )); then
+    local iopts sopts pattern tmpo cur opt cache
+    typeset -U lopts
 
-    if [[ "$tmp" != "$_args_cache_longcmd" ]]; then
-      local iopts pattern tmpo
-      typeset -U lopts
+    cache=()
 
-      _args_cache_longcmd="$tmp"
+    # We have to build a new long-option cache, get the `-i' and
+    # `-s' options.
 
-      # We have to build the long-option cache anew, get the `-i' and
-      # `-s' options.
+    set -- "${(@)argv[long+1,-1]}"
 
-      set -- "${(@)argv[nth+1,-1]}"
+    iopts=()
+    sopts=()
+    while [[ "$1" = -[is]* ]]; do
+      if [[ "$1" = -??* ]]; then
+        tmp="${1[3,-1]}"
+        cur=1
+      else
+        tmp="$2"
+	cur=2
+      fi
+      if [[ "$tmp[1]" = '(' ]]; then
+	tmp=( ${=tmp[2,-2]} )
+      else
+	tmp=( "${(@P)tmp}" )
+      fi
+      if [[ "$1" = -i* ]]; then
+        iopts=( "$iopts[@]" "$tmp[@]" )
+      else
+        sopts=( "$sopts[@]" "$tmp[@]" )
+      fi
+      shift cur
+    done
 
-      iopts=()
-      sopts=()
-      while [[ "$1" = -[is]* ]]; do
-        if [[ "$1" = -??* ]]; then
-	  tmp="${1[3,-1]}"
-	  cur=1
-        else
-          tmp="$2"
-	  cur=2
-        fi
-	if [[ "$tmp[1]" = '(' ]]; then
-	  tmp=( ${=tmp[2,-2]} )
-        else
-	  tmp=( "${(@P)tmp}" )
-        fi
-	if [[ "$1" = -i* ]]; then
-          iopts=( "$iopts[@]" "$tmp[@]" )
-        else
-          sopts=( "$sopts[@]" "$tmp[@]" )
-        fi
-	shift cur
-      done
-
-      # Now get the long option names by calling the command with `--help'.
-      # The parameter expansion trickery first gets the lines as separate
-      # array elements. Then we select all lines whose first non-blank
-      # character is a hyphen. Since some commands document more than one
-      # option per line, separated by commas, we convert commas int
-      # newlines and then split the result again at newlines after joining 
-      # the old array elements with newlines between them. Then we select
-      # those elements that start with two hyphens, remove anything up to
-      # those hyphens and anything from the space or comma after the
-      # option up to the end. 
-
-      lopts=("--${(@)^${(@)${(@)${(@M)${(@ps:\n:j:\n:)${(@)${(@M)${(@f)$(${~words[1]} --help 2>&1)//\[--/
+    # Now get the long option names by calling the command with `--help'.
+    # The parameter expansion trickery first gets the lines as separate
+    # array elements. Then we select all lines whose first non-blank
+    # character is a hyphen. Since some commands document more than one
+    # option per line, separated by commas, we convert commas int
+    # newlines and then split the result again at newlines after joining 
+    # the old array elements with newlines between them. Then we select
+    # those elements that start with two hyphens, remove anything up to
+    # those hyphens and anything from the space or comma after the
+    # option up to the end. 
+
+    lopts=("--${(@)^${(@)${(@)${(@M)${(@ps:\n:j:\n:)${(@)${(@M)${(@f)$(${~words[1]} --help 2>&1)//\[--/
 --}:#[ 	]#-*}//,/
 }}:#[ 	]#--*}#*--}%%[, ]*}:#}")
 
-      # Now remove all ignored options ...
-
-      while (( $#iopts )); do
-        lopts=( ${lopts:#$~iopts[1]} )
-        shift iopts
-      done
+    # Now remove all ignored options ...
 
-      # ... and add "same" options
-
-      while (( $#sopts )); do
-        lopts=( $lopts ${lopts/$sopts[1]/$sopts[2]} )
-        shift 2 sopts
-      done
-
-      # Then we walk through the descriptions plus a few builtin ones.
+    while (( $#iopts )); do
+      lopts=( ${lopts:#$~iopts[1]} )
+      shift iopts
+    done
 
-      set -- "$@" '*=FILE*:file:_files' \
-             '*=(DIR|PATH)*:directory:_files -/' '*: :'
+    # ... and add "same" options
 
-      while (( $# )); do
+    while (( $#sopts )); do
+      lopts=( $lopts ${lopts/$sopts[1]/$sopts[2]} )
+      shift 2 sopts
+    done
 
-        # First, we get the pattern and the action to use and take them
-        # from the positional parameters.
+    # Then we walk through the descriptions plus a few builtin ones.
 
-        pattern="${${${(M)1#*[^\\]:}[1,-2]}//\\\\:/:}"
-        descr="${1#${pattern}}"
-        shift
+    set -- "$@" '*=FILE*:file:_files' \
+           '*=(DIR|PATH)*:directory:_files -/' '*: :'
 
-        # We get all options matching the pattern and take them from the
-        # list we have built. If no option matches the pattern, we
-        # continue with the next.
+    while (( $# )); do
 
-        tmp=("${(@M)lopts:##$~pattern}")
-        lopts=("${(@)lopts:##$~pattern}")
+      # First, we get the pattern and the action to use and take them
+      # from the positional parameters.
 
-        (( $#tmp )) || continue
+      pattern="${${${(M)1#*[^\\]:}[1,-2]}//\\\\:/:}"
+      descr="${1#${pattern}}"
+      shift
 
-        opt=''
+      # We get all options matching the pattern and take them from the
+      # list we have built. If no option matches the pattern, we
+      # continue with the next.
 
-        # If there are option strings with a `[=', we take these get an
-        # optional argument.
+      tmp=("${(@M)lopts:##$~pattern}")
+      lopts=("${(@)lopts:##$~pattern}")
 
-        tmpo=("${(@M)tmp:#*\[\=*}")
-        if (( $#tmpo )); then
-          tmp=("${(@)tmp:#*\[\=*}")
-          tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")
+      (( $#tmp )) || continue
 
-	  if [[ "$descr" = ::* ]]; then
-	    _args_cache_long=( "$_args_cache_long[@]"
-	                       "${(@)^tmpo}=${descr}" )
-          else
-	    _args_cache_long=( "$_args_cache_long[@]"
-	                       "${(@)^tmpo}=:${descr}" )
-          fi
-        fi
+      opt=''
 
-	# Descriptions with `=': mandatory argument.
+      # If there are option strings with a `[=', we take these get an
+      # optional argument.
 
-        tmpo=("${(@M)tmp:#*\=*}")
-        if (( $#tmpo )); then
-          tmp=("${(@)tmp:#*\=*}")
-          tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")
+      tmpo=("${(@M)tmp:#*\[\=*}")
+      if (( $#tmpo )); then
+        tmp=("${(@)tmp:#*\[\=*}")
+        tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")
 
-	  if [[ "$descr" = ::* ]]; then
-	    _args_cache_long=( "$_args_cache_long[@]"
-	                       "${(@)^tmpo}=${descr[2,-1]}" )
-          else
-	    _args_cache_long=( "$_args_cache_long[@]"
-	                       "${(@)^tmpo}=${descr}" )
-          fi
+        if [[ "$descr" = ::* ]]; then
+	  cache=( "$cache[@]" "${(@)^tmpo}=${descr}" )
+        else
+	  cache=( "$cache[@]" "${(@)^tmpo}=:${descr}" )
         fi
-
-	# Everything else is just added as a option without arguments.
-
-        if (( $#tmp )); then
-          tmp=("${(@)tmp//[^a-zA-Z0-9-]}")
-	  _args_cache_long=( "$_args_cache_long[@]" "$tmp[@]" )
-	fi
-      done
-    fi
-    _args_cache_long=( "${(@)_args_cache_long:# #}" )
-
-    set -- "$tmpargv[@]" "$_args_cache_long[@]"
-  fi
-
-  # Now parse the arguments...
-
-  odescr=()
-  args=()
-  nth=1
-  while (( $# )); do
-
-    descr=''
-    xor=''
-
-    # Get the names of other values that are mutually exclusive with
-    # this one.
-
-    if [[ "$1" = \(*\)* ]]; then
-      xor="${${1[2,-1]}%%\)*}"
-      1="${1#*\)}"
-    fi
-
-    # Get a description, if any.
-
-    if [[ "$1" = *\[*\](|:*) ]]; then
-      descr="${${1#*\[}%%\]*}"
-      1="${1/\[$descr\]}"
-    elif [[ -n "$compconfig[autodescribe_options]" &&
-            "$1" = [-+][^:]##:[^:]#[^\\]:[^:]# ]]; then
-      descr="${${${${(M)${1#*:}#*[^\\]:}[1,-2]}## #}%% #}"
-      [[ -n "$descr" ]] && descr="${compconfig[autodescribe_options]//\\%d/$descr}"
-    fi
-
-    # Description for both the `-foo' and `+foo' form?
-
-    if [[ "$1" = (\*|)(-+|+-)[^:]* ]]; then
-
-      # With a `*' at the beginning, the option may appear more than
-      # once.
-
-      if [[ "$1" = \** ]]; then
-        tmp="${1[4,-1]%%:*}"
-        [[ "$tmp" = *[-+] ]] && tmp="$tmp[1,-2]"
-      else
-        tmp="${1[3,-1]%%:*}"
-        [[ "$tmp" = *[-+] ]] && tmp="$tmp[1,-2]"
-        xor="$xor -$tmp +$tmp"
       fi
 
-      # If the option name ends in a `-', the first argument comes
-      # directly after the option, if it ends in a `+', the first
-      # argument *may* come directly after the option, otherwise it
-      # is in the next word.
-
-      if [[ "$1" = [^:]##-:* ]]; then
-        _args_cache_dopts[-$tmp]="${1#*:}"
-        _args_cache_dopts[+$tmp]="${1#*:}"
-      elif [[ "$1" = [^:]##[+=]:* ]]; then
-        _args_cache_odopts[-$tmp]="${1#*:}"
-        _args_cache_odopts[+$tmp]="${1#*:}"
-      elif [[ "$1" = *:* ]]; then
-        _args_cache_opts[-$tmp]="${1#*:}"
-        _args_cache_opts[+$tmp]="${1#*:}"
-      else
-        _args_cache_opts[-$tmp]=''
-        _args_cache_opts[+$tmp]=''
-      fi
+      # Descriptions with `=': mandatory argument.
 
-      _args_cache_odescr=( "$_args_cache_odescr[@]" {-,+}"${tmp}:$descr" )
-      if [[ -n "$xor" ]]; then
-        _args_cache_xors[-$tmp]="${${xor##[ 	]#}%%[ 	]#}"
-        _args_cache_xors[+$tmp]="${${xor##[ 	]#}%%[ 	]#}"
-      fi
-    elif [[ "$1" = (\*|)[-+]* ]]; then
-
-      # With a `*' at the beginning, the option may appear more than
-      # once.
-
-      if [[ "$1" = \** ]]; then
-        tmp="${1[2,-1]%%:*}"
-        [[ "$tmp" = *[-+] ]] && tmp="$tmp[1,-2]"
-      else
-        tmp="${1%%:*}"
-        [[ "$tmp" = [-+]?*[-+] ]] && tmp="$tmp[1,-2]"
-	xor="$xor ${tmp%\=}"
-      fi
+      tmpo=("${(@M)tmp:#*\=*}")
+      if (( $#tmpo )); then
+        tmp=("${(@)tmp:#*\=*}")
+        tmpo=("${(@)${(@)tmpo%%\=*}//[^a-z0-9-]}")
 
-      # If the option name ends in a `-', the first argument comes
-      # directly after the option, if it ends in a `+', the first
-      # argument *may* come directly after the option, otherwise it
-      # is in the next word.
-
-      if [[ "$1" = [^:]##-:* ]]; then
-        _args_cache_dopts[$tmp]="${1#*:}"
-      elif [[ "$1" = [^:]##[+=]:* ]]; then
-        _args_cache_odopts[$tmp]="${1#*:}"
-      elif [[ "$1" = *:* ]]; then
-        _args_cache_opts[$tmp]="${1#*:}"
-      else
-        _args_cache_opts[$tmp]=''
+        if [[ "$descr" = ::* ]]; then
+	  cache=( "$cache[@]" "${(@)^tmpo}=${descr[2,-1]}" )
+        else
+	  cache=( "$cache[@]" "${(@)^tmpo}=${descr}" )
+        fi
       fi
-      _args_cache_odescr=( "$_args_cache_odescr[@]" "${tmp%\=}:$descr" )
-      [[ -n "$xor" ]] && 
-          _args_cache_xors[${tmp%\=}]="${${xor##[ 	]#}%%[ 	]#}"
-    elif [[ "$1" = \*::* ]]; then
 
-      # This is `*:...', describing `all other arguments', with argument 
-      # range restriction.
+      # Everything else is just added as a option without arguments.
 
-      if [[ "$1" = \*:::* ]]; then
-        _args_cache_rest="*${1[3,-1]}"
-      else
-        _args_cache_rest="$1"
+      if (( $#tmp )); then
+        tmp=("${(@)tmp//[^a-zA-Z0-9-]}")
+	cache=( "$cache[@]" "$tmp[@]" )
       fi
-    elif [[ "$1" = \*:* ]]; then
-
-      # This is `*:...', describing `all other arguments'.
-
-      _args_cache_rest="${1[3,-1]}"
-    elif [[ "$1" = :* ]]; then
-
-      # This is `:...', describing `the next argument'.
-
-      _args_cache_args[nth++]="${1#*:}"
-    else
-
-      # And this is `n:...', describing the `n'th argument.
-
-      _args_cache_args[${1%%:*}]="${1#*:}"
-      nth=$(( ${1%%:*} + 1 ))
-    fi
-    shift
-  done
-
-  if [[ -n "$_args_cache_single" ]]; then
-    _args_cache_soptseq="${(@j::)${(@M)${(@k)_args_cache_opts[(R)]}:#[-+]?}#[-+]}"
-    if [[ -n "$_args_cache_soptseq" ]]; then
-      _args_cache_soptseq="[$_args_cache_soptseq]#"
-      _args_cache_soptseq1="$_args_cache_soptseq#"
-    else
-      _args_cache_soptseq=''
-      _args_cache_soptseq1=''
-    fi
-    _args_cache_sopts="${(@j::)${(@)${(@M)${=:-${(k)_args_cache_opts} ${(k)_args_cache_dopts} ${(k)_args_cache_odopts}}:#[-+]?(|=)}#?}%\=}"
-  else
-    _args_cache_soptseq=''
-    _args_cache_soptseq1=''
-    _args_cache_sopts=''
+    done
+    eval "${name}=( \"\${(@)cache:# #}\" )"
   fi
+  set -- "$tmpargv[@]" "${(@P)name}"
 fi
 
-soptseq="$_args_cache_soptseq"
-soptseq1="$_args_cache_soptseq1"
-sopts="$_args_cache_sopts"
-args=( "$_args_cache_args[@]" )
-rest="$_args_cache_rest"
-opts=( "${(@kv)_args_cache_opts}" )
-dopts=( "${(@kv)_args_cache_dopts}" )
-odopts=( "${(@kv)_args_cache_odopts}" )
-odescr=( "$_args_cache_odescr[@]" )
-xors=( "${(@kv)_args_cache_xors}" )
-single="$_args_cache_single"
-match="$_args_cache_match"
-
-# Parse the command line...
-
-ws=( "${(@)words[2,-1]}" )
-cur=$(( CURRENT-2 ))
-nth=1
-_line=( "$words[1]" )
-beg=2
-argbeg=1
-optbeg=1
-nargbeg=1
-
-# ...until the current word is reached.
-
-while [[ cur -gt 0 ]]; do
-
-  if [[ -n "$def" && -n "$curopt" ]]; then
-    if [[ -n "$_options[$curopt]" ]]; then
-      _options[$curopt]="$_options[$curopt]:${ws[1]//:/\\:}"
-    else
-      _options[$curopt]="${ws[1]//:/\\:}"
-    fi
-  fi
+if comparguments -i "$compconfig[autodescribe_options]" "$@"; then
+  local nm="$compstate[nmatches]" action noargs aret expl local
+  local next direct odirect equal single match matched ws tmp1 tmp2
 
-  # `def' holds the description for the option we are currently after.
-  # Check if the next argument for the option is optional.
-
-  if [[ "$def" = :* ]]; then
-    opt=yes
-  else
-    opt=''
-  fi
-  arg=''
-
-  # See if we are after an option getting n arguments ended by something
-  # that matches the current word.
-
-  if [[ "$def" = \**[^\\]:* && "$ws[1]" = ${~${(M)def#*[^\\]:}[2,-2]} ]]; then
-    def=''
-    curopt=''
-    shift 1 ws
-    (( cur-- ))
-    (( beg++ ))
-    continue
-  fi
-
-  # Remove one description/action pair from `def' if that isn't empty.
-
-  if [[ -n "$def" && "$def" != \** ]]; then
-    if [[ "$def" = ?*[^\\]:*[^\\]:* ]]; then
-      def="${def#?*[^\\]:*[^\\]:}"
-      argbeg="$beg"
+  if ! comparguments -D descr action; then
+    if comparguments -a; then
+      noargs='no more arguments'
     else
-      def=''
-      curopt=''
+      noargs='no arguments'
     fi
-  elif [[ -z "$def" ]]; then
-
-    # Make sure we test for options below and handle normal arguments.
-
-    opt=yes
-    arg=yes
-    curopt=''
   fi
 
-  if [[ -n "$opt" ]]; then
+  while true; do
 
-    # `opt' was set above if we have to test if the word is an option.
-    # We first test for the simple options -- those without arguments or
-    # those whose arguments have to be given as separate words.
+    if [[ -z "$noargs" || -n "$matched" ]]; then
+      _description expl "$descr"
 
-    if (( $+opts[$ws[1]] )); then
-
-      # Options that may only be given once are removed from the
-      # associative array so that we don't offer them again.
-
-      def="$opts[$ws[1]]"
-      curopt="$ws[1]"
-      _options[$curopt]=''
-      optbeg="$beg"
-      argbeg="$beg"
-      inopt=yes
-      if [[ -n "$xors[$ws[1]]" ]]; then
-	if [[ "$xors[$ws[1]]" = (*\ |):(\ *|) ]]; then
-          args=()
-	  rest=''
-        fi
-        odescr=( "${(@)odescr:#(${(j:|:)~${=xors[$ws[1]]}}):*}" )
-        unset {{,d,od}opts,xors}\[${^=xors[$ws[1]]}\]
-      fi
-    else
-      uns=''
-      if [[ -n "$sopts" && "$ws[1]" = [-+]${~soptseq}[$sopts] ]]; then
-	tmp="${ws[1][1]}${ws[1][-1]}"
-	if (( $+opts[$tmp] )); then
-	  def="$opts[$tmp]"
-          curopt="$tmp"
-	  for i in ${(s::)ws[1][2,-1]}; do
-            _options[${ws[1][1]}$i]=''
-	  done
-	  optbeg="$beg"
-	  argbeg="$beg"
-          inopt=yes
-	  uns="${ws[1][2,-1]}"
-	  opt=''
-	fi
-      fi
-
-      # If the word is none of the simple options, test for those
-      # whose first argument has to or may come directly after the
-      # option. This is done in two loops looking very much alike.
-
-      if [[ -n "$opt" && $#dopts -ne 0 ]]; then
-
-	# First we get the option names.
-
-	tmp=( "${(@k)dopts}" )
-
-	# Then we loop over them and see if the current word begins
-	# with one of the option names.
-
-	while (( $#tmp )); do
-          if [[ -n "$sopts" && $tmp[1] = [-+]? ]]; then
-	    if [[ "$ws[1]" = ${tmp[1][1]}${~soptseq}${tmp[1][2]}* ]]; then
-	      uns="${ws[1][2,-1]%%${tmp[1][2]}*}${tmp[1][2]}"
-	      break;
-	    fi
-	  elif [[ "$ws[1]" = ${tmp[1]}* ]]; then
-            break
-	  fi
-	  shift 1 tmp
-	done
-
-	if (( $#tmp )); then
-
-	  # It does. So use the description for it, but only from
-	  # the second argument on, because we are searching the
-	  # description for the next command line argument.
-
-	  opt=''
-	  def="$dopts[$tmp[1]]"
-          curopt="$tmp[1]"
-	  _options[$curopt]="${ws[1]#${tmp[1]}}"
-	  optbeg="$beg"
-	  argbeg="$beg"
-	  inopt=yes
-	  if [[ -n "$xors[$tmp[1]]" ]]; then
-	    if [[ "$xors[$ws[1]]" = (*\ |):(\ *|) ]]; then
-              args=()
-	      rest=''
-            fi
-            odescr=( "${(@)odescr:#(${(j:|:)~${=xors[$tmp[1]]}}):*}" )
-            unset {{,d,od}opts,xors}\[${^=xors[$tmp[1]]}\]
-          fi
-	  if [[ "$def" = [^*]*[^\\]:*[^\\]:* ]]; then
-            def="${def#?*[^\\]:*[^\\]:}"
-          elif [[ "$def" != \** ]]; then
-            def=''
-	  fi
-        fi
-      fi
-      if [[ -n "$opt" && $#odopts -ne 0 ]]; then
-	tmp=( "${(@k)odopts%\=}" )
-	while (( $#tmp )); do
-          if [[ -n "$sopts" && $tmp[1] = [-+]? ]]; then
-	    if [[ "$ws[1]" = ${tmp[1][1]}${~soptseq}${tmp[1][2]}* ]]; then
-	      uns="${ws[1][2,-1]%%${tmp[1][2]}*}${tmp[1][2]}"
-	      break;
-	    fi
-	  elif [[ "$ws[1]" = ${tmp[1]}* ]]; then
-	    break
-	  fi
-	  shift 1 tmp
-	done
-
-	if (( $#tmp )); then
-	  opt=''
-	  def="$odopts[$tmp[1]]"
-	  curopt="$tmp[1]"
-          if [[ -z "$def" ]]; then
-	    def="$odopts[$tmp[1]=]"
-	    if [[ "$ws[1]" = ${tmp[1]}?* ]]; then
-	      _options[$curopt]="${ws[1]#${tmp[1]}=}"
-            else
-	      _options[$curopt]=''
-	    fi
-          else
-	    if [[ "$ws[1]" = ${tmp[1]}?* ]]; then
-	      _options[$curopt]="${ws[1]#${tmp[1]}}"
-            else
-	      _options[$curopt]=''
-	    fi
-	  fi
-	  optbeg="$beg"
-	  argbeg="$beg"
-	  inopt=yes
-	  if [[ -n "$xors[$tmp[1]]" ]]; then
-	    if [[ "$xors[$ws[1]]" = (*\ |):(\ *|) ]]; then
-              args=()
-	      rest=''
-            fi
-            odescr=( "${(@)odescr:#(${(j:|:)~${=xors[$tmp[1]]}}):*}" )
-            unset {{,d,od}opts,xors}\[${^=xors[$tmp[1]]}\]
-          fi
-
-	  # For options whose first argument *may* come after the
-	  # option, we skip over the first description only if there
-	  # is something after the option name on the line.
-
-	  if [[ ( -z "$sopts" && ( "$def" = :* || "$ws[1]" != "$tmp[1]" ) ) ||
-                ( -n "$sopts" && ( ( $tmp[1] = [-+]? && ( "$def" = :* || "$ws[1]" != "${tmp[1][1]}"${~soptseq}"${tmp[1][2]}" ) ) ||
-		  		   ( $tmp[1] != [-+]? && ( "$def" = :* || "$ws[1]" != "$tmp[1]" ) ) ) ) ]]; then
-	    if [[ "$def" = [^*]*[^\\]:*[^\\]:* ]]; then
-              def="${def#?*[^\\]:*[^\\]:}"
-	      optbeg="$beg"
-	      argbeg="$beg"
-            elif [[ "$def" != \** ]]; then
-              def=''
-            fi
-	  fi
-        fi
-      fi
-
-      [[ -n "$sopts" && -n "$opt" && "$ws[1]" = [-+]${~soptseq} ]] && \
-          uns="${ws[1][2,-1]}"
-
-      if [[ -n "$uns" ]]; then
-        uns="${(@j::)${(M@)${(v)=xors[(I)${ws[1][1]}[$uns]]}:#??}#[-+]}"
-	if  [[ -n "$uns" ]]; then
-	  tmp=(
-	    "opts[${(@)^opts[(I)${ws[1][1]}[$uns]]}]"
-	    "dopts[${(@)^dopts[(I)${ws[1][1]}[$uns]]}]"
-	    "odopts[${(@)^odopts[(I)${ws[1][1]}[$uns]]}]"
-	    "xors[${(@)^xors[(I)${ws[1][1]}[$uns]]}]"
-	  )
-	  odescr=( "${(@)odescr:#${ws[1][1]}[$uns]:*}" )
-	  (( $#tmp )) && unset "$tmp[@]"
-        fi
-      fi
-
-      # If we didn't find a matching option description and we were
-      # told to use normal argument descriptions, just increase
-      # our counter `nth'.
-
-      if [[ -n "$opt" && -n "$arg" ]]; then
-        def=''
-	_line=( "$_line[@]" "$ws[1]" )
-	[[ -n "$inopt" ]] && nargbeg=$(( beg - 1 ))
-	inopt=''
-        if [[ -z "$args[nth]" && "$rest" = \*::* ]]; then
-	  inrest=yes
-	  break
+      if [[ "$action" = -\>* ]]; then
+        comparguments -W line options
+        state="${${action[3,-1]##[ 	]#}%%[ 	]#}"
+        compstate[restore]=''
+        aret=yes
+      else
+        if [[ -z "$local" ]]; then
+          local line
+          typeset -A options
+          local=yes
         fi
-	(( nth++ ))
-      fi
-    fi
-  fi
 
-  shift 1 ws
-  (( cur-- ))
-  (( beg++ ))
-done
+        comparguments -W line options
 
-[[ -n "$inopt" ]] && nargbeg=$(( beg - 1 ))
+        if [[ "$action" = \ # ]]; then
 
-# Now generate the matches.
+          # An empty action means that we should just display a message.
 
-nm="$compstate[nmatches]"
+          [[ -n "$matched" ]] && compadd -n -Q -S '' -s "$SUFFIX" - "$PREFIX"
+          _message "$descr"
+          break
 
-if [[ -z "$def" || "$def" = :* ]]; then
-  local pre="$PREFIX"
+        elif [[ "$action" = \(\(*\)\) ]]; then
 
-  uns=''
+          # ((...)) contains literal strings with descriptions.
 
-  # We either don't have a description for an argument of an option
-  # or we have a description for a optional argument.
+          eval ws\=\( "${action[3,-3]}" \)
 
-  opt=yes
+          _describe -c "$cmd" "$descr" ws -M "$match"
 
-  if [[ -z "$def" ]]; then
-
-    # If we have none at all, use the one for this argument position.
-
-    def="$args[nth]"
-    if [[ -z "$def" ]]; then
-      def="$rest"
-      optbeg="$nargbeg"
-      argbeg="$nargbeg"
-      fromrest=yes
-      [[ -n "$inrest" ]] && opt=''
-    fi
-    if [[ -z "$def" ]]; then
-      if [[ -z "$args$rest" ]]; then
-        noargs='no arguments'
-      else
-        noargs='no more arguments'
-      fi
-    fi
-  fi
+        elif [[ "$action" = \(*\) ]]; then
 
-  # In any case, we have to complete option names here, but we may
-  # be in a string that starts with an option name and continues with
-  # the first argument, test that (again, two loops).
-
-  if [[ -n "$opt" && $#dopts -ne 0 ]]; then
-
-    # Get the option names.
-
-    tmp=( "${(@k)dopts}" )
-    prefix="$PREFIX"
-    while (( $#tmp )); do
-      if [[ -n "$sopts" && $tmp[1] = [-+]? ]] && compset -P "${tmp[1][1]}${~soptseq}${tmp[1][2]}"; then
-        def="$dopts[$tmp[1]]"
-	opt=''
-	uns="${prefix[2,-1]%%${tmp[1][2]}*}${tmp[1][2]}"
-	for i in ${(s::)prefix[2,-1]%%${tmp[1][2]}*} ${tmp[1][2]}; do
-	  _options[${prefix[1]}$i]=''
-	done
-	noargs=''
-	break
-      elif compset -P "$tmp[1]"; then
-
-	# The current string starts with the option name, so ignore
-	# that and complete the rest of the string.
-
-	def="$dopts[$tmp[1]]"
-	opt=''
-	noargs=''
-	break
-      fi
-      shift 1 tmp
-    done
-  fi
-  if [[ -n "$opt" && $#odopts -ne 0 ]]; then
-    tmp=( "${(@k)odopts}" )
-    prefix="$PREFIX"
-    while (( $#tmp )); do
-      if [[ -n "$sopts" && $tmp[1] = [-+]?(|=) ]] && compset -P "${tmp[1][1]}${~soptseq}${tmp[1][2]}${tmp[1][3]}"; then
-        def="$odopts[$tmp[1]]"
-	opt=''
-	uns="${prefix[2,-1]%%${tmp[1][2]}*}${tmp[1][2]}"
-	for i in ${(s::)prefix[2,-1]%%${tmp[1][2]}*} ${tmp[1][2]}; do
-	  _options[${prefix[1]}$i]=''
-	done
-	noargs=''
-	break
-      elif compset -P "$tmp[1]"; then
-	def="$odopts[$tmp[1]]"
-	opt=''
-	noargs=''
-	break
-      fi
-      shift 1 tmp
-    done
-  fi
-
-  [[ -n "$sopts" && -n "$opt" && "$PREFIX" = [-+]${~soptseq}[$sopts] ]] &&
-      uns="${PREFIX[2,-1]}"
-
-  if [[ -n "$uns" ]]; then
-    uns="${(@j::)${(M@)${(v)=xors[(I)${ws[1][1]}[$uns]]}:#??}#[-+]}"
-    if [[ -n "$uns" ]]; then
-      tmp=(
-        "opts[${(@)^opts[(I)${pre[1]}[$uns]]}]"
-        "dopts[${(@)^dopts[(I)${pre[1]}[$uns]]}]"
-        "odopts[${(@)^odopts[(I)${pre[1]}[$uns](|=)]}]"
-        "xors[${(@)^xors[(I)${pre[1]}[$uns]]}]"
-      )
-      odescr=( "${(@)odescr:#${pre[1]}[$uns]:*}" )
-      (( $#tmp )) && unset "$tmp[@]"
-    fi
-  fi
-
-  # If we aren't in an argument directly after a option name, all option
-  # names are possible matches.
-
-  [[ -z "$opt" || ( "$def" = \** &&
-                    ( -z "$fromrest" || CURRENT -ne argbeg+1 ) ) ]] && opt=''
-else
-  opt=''
-fi
+          # Anything inside `(...)' is added directly.
 
-# Now add the matches from the description, if any.
+          compadd "$expl[@]" - ${=action[2,-2]}
+        elif [[ "$action" = \{*\} ]]; then
 
-while true; do
+          # A string in braces is evaluated.
 
-  if [[ -n "$def" ]]; then
+          eval "$action[2,-2]"
 
-    # Ignore the leading colon or `*...' describing optional arguments.
+        elif [[ "$action" = \ * ]]; then
 
-    if [[ "$def" = :* ]]; then
-      def="$def[2,-1]"
-    elif [[ "$def" = \** ]]; then
-      tmp="${${(M)def#*[^\\]:}[2,-2]}"
-      def="${def#*[^\\]:}"
+          # If the action starts with a space, we just call it.
 
-      if [[ "$def" = :* ]]; then
-        if [[ "$def" = ::* ]]; then
-          def="$def[3,-1]"
-	  beg=$argbeg
+          ${(e)=~action}
         else
-	  def="$def[2,-1]"
-	  beg=$optbeg
-        fi
-
-        [[ beg -ge $#words ]] && beg=$(( $#words - 1 ))
 
-        shift beg words
-        (( CURRENT -= beg ))
+          # Otherwise we call it with the description-arguments built above.
 
-        if [[ -n "$tmp" ]]; then
-          tmp="$words[(ib:CURRENT:)${~tmp}]"
-	  [[ tmp -le $#words ]] && words=( "${(@)words[1,tmp-1]}" )
+          action=( $=action )
+          ${(e)action[1]} "$expl[@]" ${(e)~action[2,-1]}
         fi
       fi
     fi
 
-    # Get the description and the action.
-
-    descr="${${${(M)def#*[^\\]:}[1,-2]}//\\\\:/:}"
-    if [[ "$def" = *[^\\]:*[^\\]:* ]]; then
-      action="${${${(M)${def#*[^\\]:}#*[^\\]:}[1,-2]}//\\\\:/:}"
-    else
-      action="${${def#*[^\\]:}//\\\\:/:}"
-    fi
-
-    _description expl "$descr"
-
-    if [[ "$action" = -\>* ]]; then
-      line=( "$_line[@]" )
-      options=( "${(@kv)_options}" )
-      state="${${action[3,-1]##[ 	]#}%%[ 	]#}"
-      compstate[restore]=''
-      aret=yes
-    else
-      if [[ "${(t)line}" != *local* ]]; then
-        local line
-	typeset -A options
-      fi
-
-      line=( "$_line[@]" )
-      options=( "${(@kv)_options}" )
-
-      if [[ "$action" = \ # ]]; then
-
-        # An empty action means that we should just display a message.
-
-        [[ -n "$matched" ]] && compadd -n -Q -S '' -s "$SUFFIX" - "$PREFIX"
-        _message "$descr"
-        break
-
-      elif [[ "$action" = \(\(*\)\) ]]; then
-
-        # ((...)) contains literal strings with descriptions.
-
-        eval ws\=\( "${action[3,-3]}" \)
-
-        _describe -c "$cmd" "$descr" ws -M "$match"
-
-      elif [[ "$action" = \(*\) ]]; then
-
-        # Anything inside `(...)' is added directly.
-
-        compadd "$expl[@]" - ${=action[2,-2]}
-      elif [[ "$action" = \{*\} ]]; then
+    if [[ -z "$matched" ]] &&
+       comparguments -O next direct odirect equal &&
+       [[ ( ( nm -eq compstate[nmatches] || -n "$noargs" ) && -z "$aret" ) ||
+          -z "$compconfig[option_prefix]" || 
+          "$compconfig[option_prefix]" = *\!${cmd}* ||
+          "$PREFIX" = [-+]* ]]; then
 
-        # A string in braces is evaluated.
+      comparguments -M match
 
-        eval "$action[2,-2]"
+      if comparguments -s single; then
 
-      elif [[ "$action" = \ * ]]; then
+        _description expl option
 
-        # If the action starts with a space, we just call it.
+        if [[ "$single" = direct ]]; then
+	  compadd "$expl[@]" -QS '' - "${PREFIX}${SUFFIX}"
+        elif [[ "$single" = next ]]; then
+	  compadd "$expl[@]" -Q - "${PREFIX}${SUFFIX}"
+        elif [[ "$single" = equal ]]; then
+	  compadd "$expl[@]" -QqS= - "${PREFIX}${SUFFIX}"
+        else
+	  tmp1=( "$next[@]" "$direct[@]" "$odirect[@]" "$equal[@]" )
+	  tmp2=( "${PREFIX}${(@)^tmp1%%:*}" )
 
-        ${(e)=~action}
+          _describe -o -c "$cmd" option tmp1 tmp2 -Q -S ''
+        fi
+        single=yes
       else
-
-        # Otherwise we call it with the description-arguments built above.
-
-        action=( $=action )
-        ${(e)action[1]} "$expl[@]" ${(e)~action[2,-1]}
-      fi
-    fi
-  fi
-
-  # Probably add the option names.
-
-  if [[ -n "$opt" &&
-        ( ( ( nm -eq compstate[nmatches] || -n "$noargs" ) && -z "$aret" ) ||
-          -z "$compconfig[option_prefix]" || 
-          "$compconfig[option_prefix]" = *\!${cmd}* ||
-          "$PREFIX" = [-+]* ) ]]; then
-    _description expl option
-    if [[ -n "$sopts" && -n "$PREFIX" &&
-      "$PREFIX" = [-+]${~soptseq}[$sopts] ]]; then
-      if [[ "$PREFIX" = [-+]${~soptseq1} ]]; then
-        tmp1=( "${(@Mo)odescr:#[-+]?:*}" )
-        tmp2=(
-               "${PREFIX}${(@k)^opts[(I)${PREFIX[1]}?]#?}" \
-	       "${PREFIX}${(@k)^dopts[(I)${PREFIX[1]}?]#?}" \
-	       "${PREFIX}${(@)^${(@k)odopts[(I)${PREFIX[1]}?(|=)]#?}%=}"
-        )
-        tmp2=( "${(@o)tmp2}" )
-
+        next=( "$next[@]" "$odirect[@]" )
         _describe -o -c "$cmd" option \
-          tmp1 tmp2  -Q -M 'r:|[_-]=* r:|=*'
-      else
-        # The last option takes an argument in the next word.
-
-        compadd "$expl[@]" -Q -M "$match" - "${PREFIX}" && ret=0
+          next -Q -M "$match" -- \
+          direct -QS '' -M "$match" -- \
+          equal -QqS= -M "$match"
       fi
-    else
-      tmp1=( "${(@k)opts}" "${(@k)odopts[(I)*[^=]]}" )
-      tmp1=( "${(@M)odescr:#(${(j:|:)~tmp1}):*}" )
-      tmp2=( "${(@M)odescr:#(${(kj:|:)~dopts}):*}" )
-      tmp3=( "${(@M)odescr:#(${(kj:|:)~odopts[(I)*=]%=}):*}" )
-      _describe -o -c "$cmd" option \
-        tmp1 -Q -M "$match" -- \
-        tmp2 -QS '' -M "$match" -- \
-        tmp3 -QqS= -M "$match"
-    fi
-  fi
 
-  if [[ nm -eq compstate[nmatches] && 
-        ( ( -z "$single" && "$PREFIX" = [-+]*\=* ) ||
-          ( $#_args_cache_long -ne 0 && "$PREFIX" = --*\=* ) ) ]]; then
-    tmp=( "${(@Mk)odopts:#[^:]#\=}" )
-    prefix="${PREFIX#*\=}"
-    suffix="$SUFFIX"
-    PREFIX="${PREFIX%%\=*}"
-    SUFFIX=''
-    compadd -M "$match" -D tmp - "${(@)tmp%\=}"
-
-    if [[ $#tmp -eq 1 ]]; then
-      def="$odopts[$tmp[1]]"
-      PREFIX="$prefix"
-      SUFFIX="$suffix"
-      IPREFIX="$tmp[1]"
-      matched=yes
-      continue
+      if [[ nm -eq compstate[nmatches] && -z "$aret" &&
+            ( ( -z "$single" && "$PREFIX" = [-+]*\=* ) ||
+              "$PREFIX" = --* ) ]]; then
+
+        local prefix suffix
+
+	prefix="${PREFIX#*\=}"
+	suffix="$SUFFIX"
+	PREFIX="${PREFIX%%\=*}"
+	SUFFIX=''
+	compadd -M "$match" -D equal - "${(@)equal%%:*}"
+
+        if [[ $#equal -eq 1 ]]; then
+	  PREFIX="$prefix"
+	  SUFFIX="$suffix"
+	  IPREFIX="${IPREFIX}${equal[1]%%:*}="
+	  matched=yes
+	  comparguments -L "$equal[1]" descr action
+	  continue
+	fi
+      fi
     fi
-  fi
+    break
+  done
 
-  break
-done
+  [[ -n "$aret" ]] && return 300
 
-[[ -n "$aret" ]] && return 300
+  [[ -n "$noargs" ]] && _message "$noargs"
 
-[[ -n "$noargs" ]] && _message "$noargs"
+  # Set the return value.
 
-# Set the return value.
+  [[ nm -ne "$compstate[nmatches]" ]]
 
-[[ nm -ne "$compstate[nmatches]" ]]
+else
+  return 1
+fi
diff --git a/Completion/Base/_describe b/Completion/Base/_describe
index c95674121..c0b3d7174 100644
--- a/Completion/Base/_describe
+++ b/Completion/Base/_describe
@@ -4,8 +4,7 @@
 
 setopt localoptions extendedglob
 
-local gdescr isopt cmd opt nsets tmp descr match descrs matches adescr i
-local disps disp expl tmps tmpd tmpmd tmpms name ret=1 showd _nm
+local isopt cmd opt expl tmps tmpd tmpmd tmpms ret=1 showd _nm hide
 
 cmd="$words[1]"
 
@@ -45,101 +44,22 @@ else
      "$compconfig[describe_values]" != *\!${cmd}* ]] && showd=yes
 fi
 
-gdescr="$1"
+_description expl "$1"
 shift
 
-# Now interpret the arguments.
-
-nsets=0
-adescr=()
-descrs=()
-matches=()
-while (( $# )); do
-  (( nsets++ ))
-  descr="$1"
-  [[ -n "$showd" ]] && adescr=( "$adescr[@]" "${(@PM)^descr:#*:?*},$nsets" )
-  if [[ $# = 1 || "$2" = -* ]]; then
-    match=''
-    shift
-  else
-    match="$2"
-    shift 2
-  fi
-  tmp=$argv[(i)--]
-  if [[ tmp -eq 1 ]]; then
-    opt=()
-  else
-    opt=( "${(@)argv[1,tmp-1]}" )
-  fi
-  if [[ tmp -gt $# ]]; then
-    argv=()
-  else
-    shift tmp
-  fi
-
-  # `descr' and `matches' collect the names of the arrays containing the
-  # possible matches with descriptions and the matches to add.
-  # The options to give to `compadd' are stored in local arrays.
+if [[ -n "$showd" ]]; then
+  compdescribe -I ' -- ' "$@"
+else
+  compdescribe -i ' -- ' "$@"
+fi
 
-  descrs[nsets]="$descr"
-  matches[nsets]="$match"
-  typeset -a _descr_opts_$nsets
-  eval "_descr_opts_${nsets}=( \"\$opt[@]\" )"
-done
+[[ -n "$isopt" && "$compconfig[option_prefix]" = hide* ]] && hide=yes
 
-(( nsets )) || return 1
-
-# Build the display strings if needed.
-
-[[ -n "$showd" ]] && _display disps "$adescr[@]"
-_description expl "$gdescr"
-
-# Loop through the array/option sets we have.
-
-i=0
-while [[ ++i -le nsets ]]; do
-  name=_descr_opts_$i
-  [[ -n "$showd" ]] && disp=( "${(@)${(@M)disps:#*,${i}}%,*}" )
-  descr=( "${(@P)descrs[i]}" )
-
-  # We collect the strings to display in `tmpd' (one string per line)
-  # and `tmps' (in columns) and the matches to add in `tmpmd' and `tmpms'.
-
-  tmpd=()
-  tmps=()
-  tmpmd=()
-  tmpms=()
-  if [[ -n "$matches[i]" ]]; then
-    match=( "${(@P)matches[i]}" )
-    while (( $#match )); do
-      if [[ -n "$showd" && "$descr[1]" = *:?* ]]; then
-	tmpd=( "$tmpd[@]" "$disp[1]" )
-        tmpmd=( "$tmpmd[@]" "$match[1]" )
-        shift 1 disp
-      else
-        tmps=( "$tmps[@]" "${descr[1]%%:*}" )
-        tmpms=( "$tmpms[@]" "$match[1]" )
-      fi
-      shift 1 match
-      shift 1 descr
-    done
-  else
-    while (( $#descr )); do
-      if [[ -n "$showd" && "$descr[1]" = *:?* ]]; then
-	tmpd=( "$tmpd[@]" "$disp[1]" )
-        tmpmd=( "$tmpmd[@]" "${descr[1]%%:*}" )
-        shift 1 disp
-      else
-        tmps=( "$tmps[@]" "${descr[1]%%:*}" )
-        tmpms=( "$tmpms[@]" "${descr[1]%%:*}" )
-      fi
-      shift 1 descr
-    done
-  fi
+while compdescribe -g args tmpd tmpmd tmps tmpms; do
 
   # See if we should remove the option prefix characters.
 
-  if [[ -n "$isopt" && "$compconfig[option_prefix]" = hide* ]]; then
+  if [[ -n "$hide" ]]; then
     if [[ "$PREFIX" = --* ]]; then
       tmpd=( "${(@)tmpd#--}" )
       tmps=( "${(@)tmps#--}" )
@@ -148,8 +68,8 @@ while [[ ++i -le nsets ]]; do
       tmps=( "${(@)tmps#[-+]}" )
     fi
   fi
-  compadd "${(@P)name}" "$expl[@]" -ld tmpd - "$tmpmd[@]" && ret=0
-  compadd "${(@P)name}" "$expl[@]" -d tmps - "$tmpms[@]" && ret=0
+  compadd "$args[@]" "$expl[@]" -ld tmpd - "$tmpmd[@]" && ret=0
+  compadd "$args[@]" "$expl[@]" -d tmps - "$tmpms[@]" && ret=0
 done
 
 return ret
diff --git a/Completion/Base/_values b/Completion/Base/_values
index 5b413313b..aff2f92d8 100644
--- a/Completion/Base/_values
+++ b/Completion/Base/_values
@@ -2,328 +2,71 @@
 
 setopt localoptions extendedglob
 
-local name arg def descr xor str tmp ret=1 expl nm="$compstate[nmatches]"
-local snames odescr gdescr sep esep spat tmp1 tmp2 tmp3 opts
-typeset -A names onames xors _values
+if compvalues -i "$@"; then
 
-# Probably fill our cache.
+  local noargs args opts descr action expl sep
 
-if [[ "$*" != "$_vals_cache_args" ]]; then
-  _vals_cache_args="$*"
+  if ! compvalues -D descr action; then
+    compvalues -V noargs args opts
 
-  unset _vals_cache_{sep,esep,descr,names,onames,snames,xors,odescr}
-
-  typeset -gA _vals_cache_{names,onames,xors}
-  _vals_cache_snames=()
-  _vals_cache_odescr=()
-
-  # Get the separator, if any.
-
-  if [[ "$1" = -s ]]; then
-    _vals_cache_sep="$2"
-    [[ -z "$2" ]] && _vals_cache_esep=yes
-    shift 2
-  fi
-
-  # This is the description string for the values.
-
-  _vals_cache_descr="$1"
-  shift
-
-  # Now parse the descriptions.
-
-  while (( $# )); do
-
-    # Get the `name', anything before an unquoted colon.
-
-    if [[ "$1" = *[^\\]:* ]]; then
-      name="${${${(M)1#*[^\\]:}[1,-2]}//\\\\:/:}"
-    else
-      name="$1"
-    fi
-
-    descr=''
-    xor=''
-
-    # Get a description, if any.
-
-    if [[ "$name" = *\[*\] ]]; then
-      descr="${${name#*\[}[1,-2]}"
-      name="${name%%\[*}"
-    fi
-
-    # Get the names of other values that are mutually exclusive with
-    # this one.
-
-    if [[ "$name" = \(*\)* ]]; then
-      xor="${${name[2,-1]}%%\)*}"
-      name="${name#*\)}"
-    fi
-
-    # Finally see if this value may appear more than once.
-
-    if [[ "$name" = \** ]]; then
-      name="$name[2,-1]"
-    else
-      xor="$xor $name"
-    fi
-
-    # Store the information in the cache.
-
-    _vals_cache_odescr=( "$_vals_cache_odescr[@]" "${name}:$descr" )
-    [[ -n "$xor" ]] && _vals_cache_xors[$name]="${${xor##[ 	]#}%%[ 	]#}"
-
-    # Get the description and store that.
-
-    if [[ "$1" = *[^\\]:* ]]; then
-      descr=":${1#*[^\\]:}"
-    else
-      descr=''
-    fi
-
-    if [[ "$descr" = ::* ]]; then
-
-       # Optional argument.
-
-      _vals_cache_onames[$name]="$descr[3,-1]"
-    elif [[ "$descr" = :* ]]; then
-
-      # Mandatory argument.
-
-      _vals_cache_names[$name]="$descr[2,-1]"
-    else
-
-      # No argument.
-
-      _vals_cache_snames=( "$_vals_cache_snames[@]" "$name" )
-    fi
-    shift
-  done
-fi
-
-snames=( "$_vals_cache_snames[@]" )
-names=( "${(@kv)_vals_cache_names}" )
-onames=( "${(@kv)_vals_cache_onames}" )
-xors=( "${(@kv)_vals_cache_xors}" )
-odescr=( "$_vals_cache_odescr[@]" )
-gdescr="$_vals_cache_descr"
-sep="$_vals_cache_sep"
-esep="$_vals_cache_esep"
-
-if [[ -n "$sep$esep" ]]; then
-
-  # We have a separator character. We parse the PREFIX and SUFFIX to
-  # see if any of the values that must not appear more than once are
-  # already on the line.
-
-  if [[ -n "$esep" ]]; then
-    spat='?*'
-  else
-    spat="*${sep}*"
-  fi
-
-  while [[ "$PREFIX" = $~spat ]]; do
-
-    # Get one part, remove it from PREFIX and put it into IPREFIX.
-
-    if [[ -n "$esep" ]]; then
-      tmp="$PREFIX[1]"
-      IPREFIX="${IPREFIX}${tmp}"
-      PREFIX="${PREFIX[2,-1]}"
-    else
-      tmp="${PREFIX%%${sep}*}"
-      PREFIX="${PREFIX#*${sep}}"
-      IPREFIX="${IPREFIX}${tmp}${sep}"
-    fi
-
-    # Get the value `name'.
-
-    name="${tmp%%\=*}"
-
-    if [[ "$tmp" = *\=* ]]; then
-      _values[$name]="${tmp#*\=}"
-    else
-      _values[$name]=''
-    fi
-
-    # And remove the descriptions for the values this one makes
-    # superfluous.
-
-    if [[ -n "$xors[$name]" ]]; then
-      snames=( "${(@)snames:#(${(j:|:)~${=xors[$name]}})}" )
-      odescr=( "${(@)odescr:#(${(j:|:)~${=xors[$name]}}):*}" )
-      unset {names,onames,xors}\[$name\]
-    fi
-  done
-  if [[ "$SUFFIX" = $~spat ]]; then
-
-    # The same for the suffix.
-
-    if [[ -n "$esep" ]]; then
-      str=''
-    else
-      str="${SUFFIX%%${sep}*}"
-      SUFFIX="${SUFFIX#*${sep}}"
-    fi
+    if [[ "$PREFIX" = *\=* ]]; then
+      local name
 
-    while [[ -n "$SUFFIX" ]]; do
-      if [[ -n "$esep" ]]; then
-        tmp="$SUFFIX[-1]"
-        ISUFFIX="${SUFFIX[-1]}$ISUFFIX"
-	SUFFIX="$SUFFIX[1,-2]"
+      name="${PREFIX%%\=*}"
+      if compvalues -L "$name" descr action; then
+        IPREFIX="${IPREFIX}${name}="
+        PREFIX="${PREFIX#*\=}"
       else
-        tmp="${SUFFIX##*${sep}}"
-        if [[ "$SUFFIX" = *${sep}* ]]; then
-          SUFFIX="${SUFFIX%${sep}*}"
-        else
-          SUFFIX=''
-        fi
-	ISUFFIX="${sep}${tmp}${ISUFFIX}"
-      fi
+        local prefix suffix
 
-      name="${tmp%%\=*}"
+	prefix="${PREFIX#*\=}"
+	suffix="$SUFFIX"
+	PREFIX="$name"
+	SUFFIX=''
+	args=( "$args[@]" "$opts[@]" )
+	compadd -M 'r:|[_-]=* r:|=*' -D args - "${(@)args[@]%%:*}"
 
-      if [[ "$tmp" = *\=* ]]; then
-        _values[$name]="${tmp#*\=}"
-      else
-        _values[$name]=''
-      fi
+	[[ $#args -ne 1 ]] && return 1
 
-      if [[ -n "$xors[$name]" ]]; then
-        snames=( "${(@)snames:#(${(j:|:)~${=xors[$name]}})}" )
-        odescr=( "${(@)odescr:#(${(j:|:)~${=xors[$name]}}):*}" )
-        unset {names,onames,xors}\[$name\]
+        PREFIX="$prefix"
+	SUFFIX="$suffix"
+        IPREFIX="${IPREFIX}${args[1]%%:*}="
+	compvalues -L "${args[1]%%:*}" descr action
       fi
-    done
-    SUFFIX="$str"
-  fi
-fi
-
-descr=''
-str="$PREFIX$SUFFIX"
-
-if [[ "$str" = *\=* ]]; then
-
-  # The string from the line contains a `=', so we get the stuff before
-  # it and after it and see what we can do here...
-
-  name="${str%%\=*}"
-  arg="${str#*\=}"
-
-  if (( $snames[(I)${name}] )); then
-
-    # According to our information, the value doesn't get an argument,
-    # so give up.
-
-    _message "\`${name}' gets no value"
-    return 1
-  elif (( $+names[$name] )); then
-
-    # It has to get an argument, we skip over the name and complete
-    # the argument (below).
-
-    def="$names[$name]"
-    if ! compset -P '*\='; then
-      IPREFIX="${IPREFIX}${name}="
-      PREFIX="$arg"
-      SUFFIX=''
-    fi
-  elif (( $+onames[$name] )); then
-
-    # Gets an optional argument, same as previous case.
-
-    def="$onames[$name]"
-    if ! compset -P '*\='; then
-      IPREFIX="${IPREFIX}${name}="
-      PREFIX="$arg"
-      SUFFIX=''
-    fi
-  else
-    local pre="$PREFIX" suf="$SUFFIX"
-
-    # The part before the `=' isn't a known value name, so we see if
-    # it matches only one of the known names.
-
-    if [[ "$PREFIX" = *\=* ]]; then
-      PREFIX="${PREFIX%%\=*}"
-      pre="${pre#*\=}"
-      SUFFIX=''
     else
-      SUFFIX="${SUFFIX%%\=*}"
-      pre="${suf#*\=}"
-      suf=''
-    fi
-
-    tmp=( "${(@k)names}" "${(@k)onames}" )
-    compadd -M 'r:|[-_]=* r:|=*' -D tmp - "$tmp[@]"
-
-    if [[ $#tmp -eq 1 ]]; then
-
-      # It does, so we use that name and immediatly start completing
-      # the argument for it.
+      compvalues -d descr
+      if [[ ${#noargs}+${#args}+${#opts} -ne 1 ]] && compvalues -s sep; then
+        sep=( "-qQS$sep" )
+      else
+        sep=()
+      fi
 
-      IPREFIX="${IPREFIX}${tmp[1]}="
-      PREFIX="$pre"
-      SUFFIX="$suf"
+      _describe "$descr" \
+        noargs "$sep[@]" -M 'r:|[_-]=* r:|=*' -- \
+        args -S= -M 'r:|[_-]=* r:|=*' -- \
+        opts -qS= -M 'r:|[_-]=* r:|=*'
 
-      def="$names[$tmp[1]]"
-      [[ -z "$def" ]] && def="$onames[$tmp[1]]"
-    elif (( $#tmp )); then
-      _message "ambiguous option \`${PREFIX}${SUFFIX}'"
-      return 1
-    else
-      _message "unknown option \`${PREFIX}${SUFFIX}'"
-      return 1
+      return
     fi
   fi
-else
-
-  # No `=', just complete value names.
-
-  if [[ -n "$sep" && ${#snames}+${#names}+${#onames} -ne 1 ]]; then
-    opts=( "-qS$sep" )
-  else
-    opts=()
-  fi
-
-  tmp1=( "${(@M)odescr:#(${(j:|:)~snames}):*}" )
-  tmp2=( "${(@M)odescr:#(${(kj:|:)~names}):*}" )
-  tmp3=( "${(@M)odescr:#(${(kj:|:)~onames}):*}" )
-
-  _describe "$gdescr" \
-  tmp1 "$opts[@]" -M 'r:|[_-]=* r:|=*' -- \
-  tmp2 -S= "$opts[@]" -M 'r:|[_-]=* r:|=*' -- \
-  tmp3 -qS= "$opts[@]" -M 'r:|[_-]=* r:|=*'
-
-  return ret
-fi
-
-if [[ -z "$def" ]]; then
-  _message 'no value'
-  return 1
-else
-  local action
-
-  descr="${${${(M)def#*[^\\]:}[1,-2]}//\\\\:/:}"
-  action="${${def#*[^\\]:}//\\\\:/:}"
 
   _description expl "$descr"
 
   # We add the separator character as a autoremovable suffix unless
   # we have only one possible value left.
 
-  [[ -n "$sep" && ${#snames}+${#names}+${#onames} -ne 1 ]] &&
+  [[ ${#snames}+${#names}+${#onames} -ne 1 ]] && compvalues -s sep &&
       expl=( "-qS$sep" "$expl[@]" )
 
   if [[ "$action" = -\>* ]]; then
-    values=( "${(@kv)_values}" )
+    compvalues -v values
     state="${${action[3,-1]##[ 	]#}%%[ 	]#}"
     compstate[restore]=''
     return 1
   else
     typeset -A values
 
-    values=( "${(@kv)_values}" )
+    compvalues -v values
 
     if [[ "$action" = \ # ]]; then
 
@@ -341,7 +84,7 @@ else
 
       if [[ -n "$compconfig[describe_values]" &&
             "$compconfig[describe_values]" != *\!${cmd}* ]] &&
-         _display tmp "$ws[@]"; then
+         compdisplay tmp ' -- ' "$ws[@]"; then
 	compadd "$expl[@]" -M 'r:|[_-]=* r:|=*' -ld tmp - "${(@)ws%%:*}"
       else
 	compadd "$expl[@]" - "${(@)ws%%:*}"
@@ -370,6 +113,10 @@ else
       ${(e)action[1]} "$expl[@]" ${(e)~action[2,-1]}
     fi
   fi
-fi
 
-[[ nm -ne "$compstate[nmatches]" ]]
+  [[ nm -ne "$compstate[nmatches]" ]]
+  return
+
+else
+  return 1;
+fi
diff --git a/Completion/Core/compinit b/Completion/Core/compinit
index 020727624..83f24b9a9 100644
--- a/Completion/Core/compinit
+++ b/Completion/Core/compinit
@@ -78,6 +78,10 @@ while [[ $# -gt 0 && $1 = -[dDf] ]]; do
   fi
 done
 
+# Load the helper module.
+
+zmodload computil
+
 # The associative array containing the definitions for the commands.
 # Definitions for patterns will be stored in the normal arrays `_patcomps'
 # and `_postpatcomps'.
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
index 7c38dac27..8c393620e 100644
--- a/Doc/Zsh/compsys.yo
+++ b/Doc/Zsh/compsys.yo
@@ -753,7 +753,8 @@ startitem()
 item(var(n)tt(:)var(message)tt(:)var(action))(
 This describes the var(n)'th normal argument. The var(message) will be 
 printed above the matches generated and the var(action) says what can
-be completed in this position (see below).
+be completed in this position (see below). If there are two colons
+before the var(message), this describes an optional argument.
 )
 item(tt(:)var(message)tt(:)var(action))(
 Like the previous one, but describing the em(next) argument. I.e. if
@@ -766,10 +767,12 @@ This describes how arguments are to be completed for which no
 description with one of the first two forms was given. This also means 
 that any number of arguments can be completed.
 
-If there are two colons before the message (as in
+If there are two colons before the var(message) (as in
 `tt(*::)var(message)tt(:)var(action)') the tt(words) special array and 
 the tt(CURRENT) special parameter will be restricted to only the
-normal arguments when the var(action) is executed or evaluated.
+normal arguments when the var(action) is executed or evaluated. With
+three colons before the var(message) they will be restructed to only
+the normal arguments covered by this description.
 )
 item(var(opt-spec)[var(description) ...])(
 This describes an option and (if at least one var(description) is
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index 0f42372f0..ce80bbee5 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -1919,50 +1919,75 @@ bin_compadd(char *name, char **argv, char *ops, int func)
 #define CVT_SUFNUM   4
 #define CVT_SUFPAT   5
 
-static void
+/**/
+void
 ignore_prefix(int l)
 {
-    char *tmp, sav = compprefix[l];
-
-    compprefix[l] = '\0';
-    tmp = tricat(compiprefix, compprefix, "");
-    zsfree(compiprefix);
-    compiprefix = tmp;
-    compprefix[l] = sav;
-    tmp = ztrdup(compprefix + l);
-    zsfree(compprefix);
-    compprefix = tmp;
+    if (l) {
+	char *tmp, sav;
+	int pl = strlen(compprefix);
+
+	if (l > pl)
+	    l = pl;
+
+	sav = compprefix[l];
+
+	compprefix[l] = '\0';
+	tmp = tricat(compiprefix, compprefix, "");
+	zsfree(compiprefix);
+	compiprefix = tmp;
+	compprefix[l] = sav;
+	tmp = ztrdup(compprefix + l);
+	zsfree(compprefix);
+	compprefix = tmp;
+    }
 }
 
-static void
+/**/
+void
 ignore_suffix(int l)
 {
-    char *tmp, sav;
-
-    l = strlen(compsuffix) - l;
-    tmp = tricat(compsuffix + l, compisuffix, "");
-    zsfree(compisuffix);
-    compisuffix = tmp;
-    sav = compsuffix[l];
-    compsuffix[l] = '\0';
-    tmp = ztrdup(compsuffix);
-    compsuffix[l] = sav;
-    zsfree(compsuffix);
-    compsuffix = tmp;
+    if (l) {
+	char *tmp, sav;
+	int sl = strlen(compsuffix);
+
+	if ((l = sl - l) < 0)
+	    l = 0;
+
+	tmp = tricat(compsuffix + l, compisuffix, "");
+	zsfree(compisuffix);
+	compisuffix = tmp;
+	sav = compsuffix[l];
+	compsuffix[l] = '\0';
+	tmp = ztrdup(compsuffix);
+	compsuffix[l] = sav;
+	zsfree(compsuffix);
+	compsuffix = tmp;
+    }
 }
 
 /**/
-static void
+void
 restrict_range(int b, int e)
 {
-    int i = e - b + 1;
-    char **p = (char **) zcalloc((i + 1) * sizeof(char *)), **q, **pp;
-
-    for (q = p, pp = compwords + b; i; i--, q++, pp++)
-	*q = ztrdup(*pp);
-    freearray(compwords);
-    compwords = p;
-    compcurrent -= b;
+    int wl = arrlen(compwords) - 1;
+
+    if (wl && b >= 0 && e >= 0 && (b > 0 || e < wl)) {
+	int i;
+	char **p, **q, **pp;
+
+	if (e > wl)
+	    e = wl;
+
+	i = e - b + 1;
+	p = (char **) zcalloc((i + 1) * sizeof(char *));
+
+	for (q = p, pp = compwords + b; i; i--, q++, pp++)
+	    *q = ztrdup(*pp);
+	freearray(compwords);
+	compwords = p;
+	compcurrent -= b;
+    }
 }
 
 static int
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
new file mode 100644
index 000000000..aed3d9808
--- /dev/null
+++ b/Src/Zle/computil.c
@@ -0,0 +1,1891 @@
+/*
+ * computil.c - completion utilities
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1999 Sven Wischnowsky
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Sven Wischnowsky or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Sven Wischnowsky and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Sven Wischnowsky and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software
+ * provided hereunder is on an "as is" basis, and Sven Wischnowsky and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "computil.mdh"
+#include "computil.pro"
+
+
+/* Help for `_display'. */
+
+typedef struct cdisp *Cdisp;
+
+struct cdisp {
+    int pre, suf, colon;
+};
+
+static void
+cdisp_calc(Cdisp disp, char **args)
+{
+    char *cp;
+    int i;
+
+    for (; *args; args++) {
+	if ((cp = strchr(*args, ':')) && cp[1]) {
+	    disp->colon++;
+	    if ((i = cp - *args) > disp->pre)
+		disp->pre = i;
+	    if ((i = strlen(cp + 1)) > disp->suf)
+		disp->suf = i;
+	}
+    }
+}
+
+static char **
+cdisp_build(Cdisp disp, char *sep, char **args)
+{
+    int sl = strlen(sep), pre = disp->pre, suf;
+    VARARR(char, buf, disp->pre + disp->suf + sl + 1);
+    char **ret, **rp, *cp;
+
+    ret = (char **) zalloc((arrlen(args) + 1) * sizeof(char *));
+
+    memcpy(buf + pre, sep, sl);
+    suf = pre + sl;
+
+    for (rp = ret; *args; args++) {
+	if ((cp = strchr(*args, ':')) && cp[1]) {
+	    memset(buf, ' ', pre);
+	    memcpy(buf, *args, (cp - *args));
+	    strcpy(buf + suf, cp + 1);
+	    *rp++ = ztrdup(buf);
+	} else {
+	    if (cp)
+		*cp = '\0';
+	    *rp++ = ztrdup(*args);
+	    if (cp)
+		*cp = ':';
+	}
+    }
+    *rp = NULL;
+
+    return ret;
+}
+
+/**/
+static int
+bin_compdisplay(char *nam, char **args, char *ops, int func)
+{
+    struct cdisp disp;
+
+    if (incompfunc != 1) {
+	zerrnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
+    disp.pre = disp.suf = disp.colon = 0;
+
+    cdisp_calc(&disp, args + 2);
+    setaparam(args[0], cdisp_build(&disp, args[1], args + 2));
+
+    return !disp.colon;
+}
+
+/* Help fuer `_describe'. */
+
+typedef struct cdset *Cdset;
+
+struct cdstate {
+    int showd;
+    char *sep;
+    Cdset sets;
+    struct cdisp disp;
+};
+
+struct cdset {
+    Cdset next;
+    char **opts;
+    char **strs;
+    char **matches;
+};
+
+static struct cdstate cd_state;
+static int cd_parsed = 0;
+
+static void
+free_cdsets(Cdset p)
+{
+    Cdset n;
+
+    for (; p; p = n) {
+	n = p->next;
+	if (p->opts)
+	    freearray(p->opts);
+	if (p->strs)
+	    freearray(p->strs);
+	if (p->matches)
+	    freearray(p->matches);
+	zfree(p, sizeof(*p));
+    }
+}
+
+static int
+cd_init(char *nam, char *sep, char **args, int disp)
+{
+    Cdset *setp, set;
+    char **ap, *tmp;
+
+    if (cd_parsed) {
+	zsfree(cd_state.sep);
+	free_cdsets(cd_state.sets);
+    }
+    setp = &(cd_state.sets);
+    cd_state.sep = ztrdup(sep);
+    cd_state.sets = NULL;
+    cd_state.disp.pre = cd_state.disp.suf = cd_state.disp.colon = 0;
+    cd_state.showd = disp;
+
+    while (*args) {
+	*setp = set = (Cdset) zcalloc(sizeof(*set));
+	setp = &(set->next);
+
+	if (!(ap = get_user_var(*args))) {
+	    zerrnam(nam, "invalid argument: %s", *args, 0);
+	    return 1;
+	}
+	PERMALLOC {
+	    set->strs = arrdup(ap);
+	} LASTALLOC;
+
+	if (disp)
+	    cdisp_calc(&(cd_state.disp), set->strs);
+
+	if (*++args && **args != '-') {
+	    if (!(ap = get_user_var(*args))) {
+		zerrnam(nam, "invalid argument: %s", *args, 0);
+		return 1;
+	    }
+	    PERMALLOC {
+		set->matches = arrdup(ap);
+	    } LASTALLOC;
+	    args++;
+	}
+	for (ap = args; *args &&
+		 (args[0][0] != '-' || args[0][1] != '-' || args[0][2]);
+	     args++);
+
+	tmp = *args;
+	*args = NULL;
+	PERMALLOC {
+	    set->opts = arrdup(ap);
+	} LASTALLOC;
+	if ((*args = tmp))
+	    args++;
+    }
+    return 0;
+}
+
+static int
+cd_get(char **params)
+{
+    Cdset set;
+
+    if ((set = cd_state.sets)) {
+	char **sd, **sdp, **md, **mdp, **ss, **ssp, **ms, **msp;
+	char **p, **mp, *cp;
+	int dl = 1, sl = 1, sepl = strlen(cd_state.sep);
+	int pre = cd_state.disp.pre, suf = cd_state.disp.suf;
+	VARARR(char, buf, pre + suf + sepl + 1);
+
+	for (p = set->strs; *p; p++)
+	    if (cd_state.showd && (cp = strchr(*p, ':')) && cp[1])
+		dl++;
+	    else
+		sl++;
+
+	sd = (char **) zalloc(dl * sizeof(char *));
+	ss = (char **) zalloc(sl * sizeof(char *));
+	md = (char **) zalloc(dl * sizeof(char *));
+	ms = (char **) zalloc(sl * sizeof(char *));
+
+	if (cd_state.showd) {
+	    memcpy(buf + pre, cd_state.sep, sepl);
+	    suf = pre + sepl;
+	}
+	for (sdp = sd, ssp = ss, mdp = md, msp = ms,
+		 p = set->strs, mp = set->matches; *p; p++) {
+	    if ((cp = strchr(*p, ':')) && cp[1] && cd_state.showd) {
+		memset(buf, ' ', pre);
+		memcpy(buf, *p, (cp - *p));
+		strcpy(buf + suf, cp + 1);
+		*sdp++ = ztrdup(buf);
+		if (mp) {
+		    *mdp++ = ztrdup(*mp);
+		    if (*mp)
+			mp++;
+		} else {
+		    *cp = '\0';
+		    *mdp++ = ztrdup(*p);
+		    *cp = ':';
+		}
+	    } else {
+		if (cp)
+		    *cp = '\0';
+		*ssp++ = ztrdup(*p);
+		if (mp) {
+		    *msp++ = ztrdup(*mp);
+		    if (*mp)
+			mp++;
+		} else
+		    *msp++ = ztrdup(*p);
+		if (cp)
+		    *cp = ':';
+	    }
+	}
+	*sdp = *ssp = *mdp = *msp = NULL;
+
+	PERMALLOC {
+	    p = arrdup(set->opts);
+	} LASTALLOC;
+
+	setaparam(params[0], p);
+	setaparam(params[1], sd);
+	setaparam(params[2], md);
+	setaparam(params[3], ss);
+	setaparam(params[4], ms);
+
+	cd_state.sets = set->next;
+	set->next = NULL;
+	free_cdsets(set);
+
+	return 0;
+    }
+    return 1;
+}
+
+/**/
+static int
+bin_compdescribe(char *nam, char **args, char *ops, int func)
+{
+    if (incompfunc != 1) {
+	zerrnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
+    if (!args[0][0] || !args[0][1] || args[0][2]) {
+	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i':
+    case 'I':
+	cd_parsed = 1;
+	return cd_init(nam, args[1], args + 2, (args[0][1] == 'I'));
+    case 'g':
+	if (cd_parsed) {
+	    int n = arrlen(args);
+
+	    if (n != 6) {
+		zerrnam(nam, (n < 6 ? "not enough arguments" :
+			      "too many arguments"), NULL, 0);
+		return 1;
+	    }
+	    return cd_get(args + 1);
+	} else {
+	    zerrnam(nam, "no parsed state", NULL, 0);
+	    return 1;
+	}
+    }
+    zerrnam(nam, "invalid option: %s", args[0], 0);
+    return 1;
+}
+
+/* Help for `_arguments'. */
+
+typedef struct cadef *Cadef;
+typedef struct caopt *Caopt;
+typedef struct caarg *Caarg;
+
+struct cadef {
+    Cadef next;
+    Caopt opts;
+    int nopts, ndopts, nodopts;
+    Caarg args;
+    Caarg rest;
+    char **defs;
+    int ndefs;
+    int lastt;
+    Caopt *single;
+    char *match;
+    int argsactive;
+};
+
+struct caopt {
+    Caopt next;
+    char *name;
+    char *descr;
+    char **xor;
+    int type;
+    Caarg args;
+    int active;
+    int num;
+};
+
+#define CAO_NEXT    1
+#define CAO_DIRECT  2
+#define CAO_ODIRECT 3
+#define CAO_EQUAL   4
+
+struct caarg {
+    Caarg next;
+    char *descr;
+    char *action;
+    int type;
+    char *end;
+    int num;
+};
+
+#define CAA_NORMAL 1
+#define CAA_OPT    2
+#define CAA_REST   3
+#define CAA_RARGS  4
+#define CAA_RREST  5
+
+#define MAX_CACACHE 8
+static Cadef cadef_cache[MAX_CACACHE];
+
+static int
+arrcmp(char **a, char **b)
+{
+    if (!a && !b)
+	return 1;
+    else if (!a || !b)
+	return 0;
+    else {
+	while (*a && *b)
+	    if (strcmp(*a++, *b++))
+		return 0;
+
+	return (!*a && !*b);
+    }
+}
+
+static void
+free_caargs(Caarg a)
+{
+    Caarg n;
+
+    for (; a; a = n) {
+	n = a->next;
+	zsfree(a->descr);
+	zsfree(a->action);
+	zsfree(a->end);
+	zfree(a, sizeof(*a));
+    }
+}
+
+static void
+free_cadef(Cadef d)
+{
+    if (d) {
+	Caopt p, n;
+
+	zsfree(d->match);
+	freearray(d->defs);
+
+	for (p = d->opts; p; p = n) {
+	    n = p->next;
+	    zsfree(p->name);
+	    zsfree(p->descr);
+	    freearray(p->xor);
+	    free_caargs(p->args);
+	    zfree(p, sizeof(*p));
+	}
+	free_caargs(d->args);
+	free_caargs(d->rest);
+	if (d->single)
+	    zfree(d->single, 256 * sizeof(Caopt));
+	zfree(d, sizeof(*d));
+    }
+}
+
+static char *
+rembslashcolon(char *s)
+{
+    char *p, *r;
+
+    r = p = s = dupstring(s);
+
+    while (*s) {
+	if (s[0] != '\\' || s[1] != ':')
+	    *p++ = *s;
+	s++;
+    }
+    *p = '\0';
+
+    return r;
+}
+
+static Caarg
+parse_caarg(int mult, int type, int num, char **def)
+{
+    Caarg ret = (Caarg) zalloc(sizeof(*ret));
+    char *p = *def, *d, sav;
+
+    ret->next = NULL;
+    ret->descr = ret->action = ret->end = NULL;
+    ret->num = num;
+    ret->type = type;
+
+    for (d = p; *p && *p != ':'; p++)
+	if (*p == '\\' && p[1])
+	    p++;
+    sav = *p;
+    *p = '\0';
+    ret->descr = ztrdup(rembslashcolon(d));
+    if (sav) {
+	if (mult) {
+	    for (d = ++p; *p && *p != ':'; p++)
+		if (*p == '\\' && p[1])
+		    p++;
+	    sav = *p;
+	    *p = '\0';
+	    ret->action = ztrdup(rembslashcolon(d));
+	    if (sav)
+		*p = ':';
+	} else
+	    ret->action = ztrdup(rembslashcolon(p + 1));
+    }
+    *def = p;
+
+    return ret;
+}
+
+static Cadef
+parse_cadef(char *nam, char **args)
+{
+    Cadef ret;
+    Caopt *optp;
+    char **oargs = args, *p, *q, *match = "r:|[_-]=* r:|=*", **xor;
+    char *adpre, *adsuf;
+    int single = 0, anum = 1, xnum, nopts, ndopts, nodopts;
+
+    nopts = ndopts = nodopts = 0;
+
+    for (p = args[0]; *p && (p[0] != '%' || p[1] != 'd'); p++);
+
+    if (*p) {
+	*p = '\0';
+	adpre = dupstring(args[0]);
+	*p = '%';
+	adsuf = dupstring(p + 2);
+    } else
+	adpre = adsuf = NULL;
+
+    args++;
+    while ((p = *args)) {
+	if (!strcmp(p, "-s"))
+	    single = 1;
+	else if (p[0] == '-' && p[1] == 'M') {
+	    if (p[2])
+		match = p + 2;
+	    else if (args[1])
+		match = *++args;
+	    else {
+		args++;
+		break;
+	    }
+	} else
+	    break;
+	args++;
+    }
+    if (!*args)
+	return NULL;
+
+    PERMALLOC {
+	ret = (Cadef) zalloc(sizeof(*ret));
+	ret->next = NULL;
+	ret->opts = NULL;
+	ret->args = ret->rest = NULL;
+	ret->defs = arrdup(oargs);
+	ret->ndefs = arrlen(oargs);
+	ret->lastt = time(0);
+	if (single) {
+	    ret->single = (Caopt *) zalloc(256 * sizeof(Caopt));
+	    memset(ret->single, 0, 256 * sizeof(Caopt));
+	} else
+	    ret->single = NULL;
+	ret->match = ztrdup(match);
+    } LASTALLOC;
+
+    for (optp = &(ret->opts); *args; args++) {
+	p = dupstring(*args);
+	xnum = 0;
+	if (*p == '(') {
+	    LinkList list = newlinklist();
+	    LinkNode node;
+	    char **xp, sav;
+
+	    while (*p && *p != ')') {
+		for (p++; inblank(*p); p++);
+
+		if (*p == ')')
+		    break;
+		for (q = p++; *p && *p != ')' && !inblank(*p); p++);
+
+		if (!*p)
+		    break;
+
+		sav = *p;
+		*p = '\0';
+		addlinknode(list, dupstring(q));
+		xnum++;
+		*p = sav;
+	    }
+	    if (*p != ')') {
+		free_cadef(ret);
+		zerrnam(nam, "invalid argument: %s", *args, 0);
+		return NULL;
+	    }
+	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
+	    for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
+		*xp = ztrdup((char *) getdata(node));
+	    xp[0] = xp[1] = NULL;
+
+	    p++;
+	} else
+	    xor = NULL;
+	
+	if (*p == '-' || *p == '+' ||
+	    (*p == '*' && (p[1] == '-' || p[1] == '+'))) {
+	    Caopt opt;
+	    Caarg oargs = NULL;
+	    int multi, otype = CAO_NEXT, again = 0;
+	    char *name, *descr, c;
+
+	    rec:
+
+	    if ((multi = (*p == '*')))
+		p++;
+
+	    if ((p[0] == '-' && p[1] == '+') ||
+		(p[0] == '+' && p[1] == '-')) {
+		name = ++p;
+		*p = (again ? '-' : '+');
+		again = 1 - again;
+	    } else {
+		name = p;
+		if (p[0] == '-' && p[1] == '-')
+		    p++;
+	    }
+	    for (p++; *p && *p != ':' && *p != '[' &&
+		     ((*p != '-' && *p != '+' && *p != '=') ||
+		      (p[1] != ':' && p[1] != '[')); p++)
+		if (*p == '\\' && p[1])
+		    p++;
+
+	    c = *p;
+	    *p = '\0';
+	    if (c == '-') {
+		otype = CAO_DIRECT;
+		c = *++p;
+	    } else if (c == '+') {
+		otype = CAO_ODIRECT;
+		c = *++p;
+	    } else if (c == '=') {
+		otype = CAO_EQUAL;
+		c = *++p;
+	    }
+	    if (c == '[') {
+		for (descr = ++p; *p && *p != ']'; p++)
+		    if (*p == '\\' && p[1])
+			p++;
+
+		if (!*p) {
+		    free_cadef(ret);
+		    zerrnam(nam, "invalid option definition: %s", *args, 0);
+		    return NULL;
+		}
+		*p++ = '\0';
+		c = *p;
+	    } else
+		descr = NULL;
+
+	    if (c && c != ':') {
+		free_cadef(ret);
+		zerrnam(nam, "invalid option definition: %s", *args, 0);
+		return NULL;
+	    }
+	    if (!multi) {
+		if (!xor) {
+		    xor = (char **) zalloc(2 * sizeof(char *));
+		    xor[1] = NULL;
+		}
+		xor[xnum] = ztrdup(name);
+	    }
+	    if (c == ':') {
+		Caarg *oargp = &oargs;
+		int atype, rest;
+		char *end;
+
+		while (c == ':') {
+		    rest = 0;
+		    end = NULL;
+
+		    if (*++p == ':') {
+			atype = CAA_OPT;
+			p++;
+		    } else if (*p == '*') {
+			if (*++p != ':') {
+			    for (end = ++p; *p && *p != ':'; p++)
+				if (*p == '\\' && p[1])
+				    p++;
+			}
+			if (*p != ':') {
+			    free_cadef(ret);
+			    free_caargs(oargs);
+			    zerrnam(nam, "invalid option definition: %s",
+				    *args, 0);
+			    return NULL;
+			}
+			if (*++p == ':') {
+			    if (*++p == ':') {
+				atype = CAA_RREST;
+				p++;
+			    } else
+				atype = CAA_RARGS;
+			} else
+			    atype = CAA_REST;
+			rest = 1;
+		    } else
+			atype = CAA_NORMAL;
+		    *oargp = parse_caarg(!rest, atype, 0, &p);
+		    oargp = &((*oargp)->next);
+		    if (rest)
+			break;
+		    c = *p;
+		}
+	    }
+	    PERMALLOC {
+		*optp = opt = (Caopt) zalloc(sizeof(*opt));
+		optp = &((*optp)->next);
+
+		opt->next = NULL;
+		opt->name = ztrdup(name);
+		if (descr)
+		    opt->descr = ztrdup(descr);
+		else if (adpre && oargs && !oargs->next)
+		    opt->descr = tricat(adpre, oargs->descr, adsuf);
+		else
+		    opt->descr = NULL;
+		opt->xor = xor;
+		opt->type = otype;
+		opt->args = oargs;
+		opt->num = nopts++;
+	    } LASTALLOC;
+
+	    if (otype == CAO_DIRECT)
+		ndopts++;
+	    else if (otype == CAO_ODIRECT || otype == CAO_EQUAL)
+		nodopts++;
+
+	    if (single && name[1] && !name[2])
+		ret->single[STOUC(name[1])] = opt;
+
+	    if (again) {
+		p = dupstring(*args);
+		goto rec;
+	    }
+	} else if (*p == '*') {
+	    int type = CAA_REST;
+
+	    if (*++p != ':') {
+		free_cadef(ret);
+		zerrnam(nam, "invalid rest argument definition: %s", *args, 0);
+		return NULL;
+	    }
+	    if (ret->rest) {
+		free_cadef(ret);
+		zerrnam(nam, "doubled rest argument definition: %s", *args, 0);
+		return NULL;
+	    }
+	    if (*++p == ':') {
+		if (*++p == ':') {
+		    type = CAA_RREST;
+		    p++;
+		} else
+		    type = CAA_RARGS;
+	    }
+	    ret->rest = parse_caarg(0, type, -1, &p);
+	} else {
+	    int type = CAA_NORMAL;
+	    Caarg arg, tmp, pre;
+
+	    if (idigit(*p)) {
+		int num = 0;
+
+		while (*p && idigit(*p))
+		    num = (num * 10) + ((int) *p++);
+
+		anum = num + 1;
+	    } else
+		anum++;
+
+	    if (*p != ':') {
+		free_cadef(ret);
+		zerrnam(nam, "invalid argument: %s", *args, 0);
+		return NULL;
+	    }
+	    if (*++p == ':') {
+		type = CAA_OPT;
+		p++;
+	    }
+	    arg = parse_caarg(0, type, anum - 1, &p);
+
+	    for (tmp = ret->args, pre = NULL;
+		 tmp && tmp->num < anum - 1;
+		 pre = tmp, tmp = tmp->next);
+
+	    if (tmp && tmp->num == anum - 1) {
+		free_cadef(ret);
+		free_caargs(arg);
+		zerrnam(nam, "doubled argument definition: %s", *args, 0);
+		return NULL;
+	    }
+	    arg->next = tmp;
+	    if (pre)
+		pre->next = arg;
+	    else
+		ret->args = arg;
+	}
+    }
+    ret->nopts = nopts;
+    ret->ndopts = ndopts;
+    ret->nodopts = nodopts;
+    
+    return ret;
+}
+
+static Cadef
+get_cadef(char *nam, char **args)
+{
+    Cadef *p, *min, new;
+    int i, na = arrlen(args);
+
+    for (i = MAX_CACACHE, p = cadef_cache, min = NULL; *p && i--; p++)
+	if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
+	    (*p)->lastt = time(0);
+
+	    return *p;
+	} else if (!min || !*p || (*p)->lastt < (*min)->lastt)
+	    min = p;
+    if (i)
+	min = p;
+    if ((new = parse_cadef(nam, args))) {
+	free_cadef(*min);
+	*min = new;
+    }
+    return new;
+}
+
+static Caopt
+ca_get_opt(Cadef d, char *line, int full, char **end)
+{
+    Caopt p;
+
+    if (full) {
+	for (p = d->opts; p; p = p->next)
+	    if (p->active && !strcmp(p->name, line))
+		return p;
+    } else {
+	for (p = d->opts; p; p = p->next)
+	    if (p->active && p->args && p->type != CAO_NEXT &&
+		strpfx(p->name, line)) {
+		if (end) {
+		    int l = strlen(p->name);
+
+		    if (p->type == CAO_EQUAL && line[l] == '=')
+			l++;
+
+		    *end = line + l;
+		}
+		return p;
+	    }
+    }
+    return NULL;
+}
+
+static Caopt
+ca_get_sopt(Cadef d, char *line, int full, char **end)
+{
+    Caopt p;
+
+    line++;
+
+    if (full) {
+	for (p = NULL; *line; line++)
+	    if (!(p = d->single[STOUC(*line)]) || !p->active ||
+		(line[1] && p->args))
+		return NULL;
+	return p;
+    } else {
+	for (p = NULL; *line; line++)
+	    if ((p = d->single[STOUC(*line)]) && p->active &&
+		p->args && p->type != CAO_NEXT) {
+		if (end) {
+		    line++;
+		    if (p->type == CAO_EQUAL && *line == '=')
+			line++;
+		    *end = line;
+		}
+		break;
+	    } else if (!p || !p->active || (line[1] && p->args))
+		return NULL;
+	return p;
+    }
+    return NULL;
+}
+
+static Caarg
+ca_get_arg(Cadef d, int n)
+{
+    if (d->argsactive) {
+	Caarg a = d->args;
+
+	while (a && a->num < n)
+	    a = a->next;
+
+	if (a && a->num == n)
+	    return a;
+
+	return d->rest;
+    }
+    return NULL;
+}
+
+static void
+ca_inactive(Cadef d, char **xor)
+{
+    if (xor) {
+	Caopt opt;
+
+	for (; *xor; xor++) {
+	    if (xor[0][0] == ':' && !xor[0][1])
+		d->argsactive = 0;
+	    else if ((opt = ca_get_opt(d, *xor, 1, NULL)))
+		opt->active = 0;
+	}
+    }
+}
+
+struct castate {
+    Cadef d;
+    Caarg def, ddef;
+    Caopt curopt;
+    int opt, arg, argbeg, optbeg, nargbeg, restbeg;
+    int inopt, inrest, inarg, nth, doff, singles;
+    LinkList args;
+    LinkList *oargs;
+};
+
+static struct castate ca_laststate;
+static int ca_parsed = 0, ca_alloced = 0;
+
+static void
+ca_parse_line(Cadef d)
+{
+    Caarg adef, ddef;
+    Caopt ptr;
+    struct castate state;
+    char *line, *pe;
+    int cur, doff;
+    Patprog endpat = NULL;
+
+    if (ca_alloced) {
+	int i = ca_laststate.d->nopts;
+	LinkList *p = ca_laststate.oargs;
+
+	freelinklist(ca_laststate.args, freestr);
+	while (i--)
+	    if (*p++)
+		freelinklist(p[-1], freestr);
+    }
+    for (ptr = d->opts; ptr; ptr = ptr->next)
+	ptr->active = 1;
+    d->argsactive = 1;
+
+    state.d = d;
+    state.def = state.ddef = NULL;
+    state.curopt = NULL;
+    state.argbeg = state.optbeg = state.nargbeg = state.restbeg =
+	state.nth = state.inopt = state.inarg = state.opt = state.arg = 1;
+    state.inrest = state.doff = state.singles = state.doff = 0;
+    PERMALLOC {
+	state.args = newlinklist();
+	state.oargs = (LinkList *) zalloc(d->nopts * sizeof(LinkList));
+	memset(state.oargs, 0, d->nopts * sizeof(LinkList));
+    } LASTALLOC;
+    ca_alloced = 1;
+
+    memcpy(&ca_laststate, &state, sizeof(state));
+
+    if (!compwords[1]) {
+	ca_laststate.opt = ca_laststate.arg = 0;
+
+	return;
+    }
+    for (line = compwords[1], cur = 2, state.curopt = NULL, state.def = NULL;
+	 line; line = compwords[cur++]) {
+	ddef = adef = NULL;
+	doff = state.singles = 0;
+	if (state.def) {
+	    state.arg = 0;
+	    if (state.curopt) {
+		PERMALLOC {
+		    addlinknode(state.oargs[state.curopt->num], ztrdup(line));
+		} LASTALLOC;
+	    }
+	    state.opt = (state.def->type == CAA_OPT && line[0] && line[1]);
+
+	    if (state.def->type == CAA_REST || state.def->type == CAA_RARGS ||
+		state.def->type == CAA_RREST) {
+		if (state.def->end && pattry(endpat, line)) {
+		    state.def = NULL;
+		    state.curopt = NULL;
+		    continue;
+		}
+	    } else if ((state.def = state.def->next))
+		state.argbeg = cur;
+	    else
+		state.curopt = NULL;
+	} else {
+	    state.opt = (line[0] && line[1]);
+	    state.arg = 1;
+	    state.curopt = NULL;
+	}
+	pe = NULL;
+
+	if (state.opt && (state.curopt = ca_get_opt(d, line, 0, &pe))) {
+	    ddef = state.def = state.curopt->args;
+	    doff = pe - line;
+	    state.optbeg = state.argbeg = state.inopt = cur;
+	    PERMALLOC {
+		state.oargs[state.curopt->num] = newlinklist();
+	    } LASTALLOC;
+	    ca_inactive(d, state.curopt->xor);
+
+	    if (state.def &&
+		(state.curopt->type == CAO_DIRECT ||
+		 (state.curopt->type == CAO_ODIRECT && pe[0]) ||
+		 (state.curopt->type == CAO_EQUAL &&
+		  (pe[0] || pe[-1] == '=')))) {
+		if (state.def->type != CAA_REST &&
+		    state.def->type != CAA_RARGS &&
+		    state.def->type != CAA_RREST)
+		    state.def = state.def->next;
+		PERMALLOC {
+		    addlinknode(state.oargs[state.curopt->num], ztrdup(pe));
+		} LASTALLOC;
+	    }
+	    if (!state.def)
+		state.curopt = NULL;
+	} else if (state.opt && d->single &&
+		   (state.curopt = ca_get_sopt(d, line, 0, &pe))) {
+	    char *p;
+	    Caopt tmpopt;
+
+	    ddef = state.def = state.curopt->args;
+	    doff = pe - line;
+	    state.optbeg = state.argbeg = state.inopt = cur;
+	    state.singles = !*pe;
+
+	    for (p = line + 1; p <= pe; p++) {
+		if ((tmpopt = d->single[STOUC(*p)])) {
+		    PERMALLOC {
+			state.oargs[tmpopt->num] = newlinklist();
+		    } LASTALLOC;
+		    ca_inactive(d, tmpopt->xor);
+		}
+	    }
+	    if (state.def &&
+		(state.curopt->type == CAO_DIRECT ||
+		 (state.curopt->type == CAO_ODIRECT && pe[0]) ||
+		 (state.curopt->type == CAO_EQUAL &&
+		  (pe[0] || pe[-1] == '=')))) {
+		if (state.def->type != CAA_REST &&
+		    state.def->type != CAA_RARGS &&
+		    state.def->type != CAA_RREST)
+		    state.def = state.def->next;
+		PERMALLOC {
+		    addlinknode(state.oargs[state.curopt->num], ztrdup(pe));
+		} LASTALLOC;
+	    }
+	    if (!state.def)
+		state.curopt = NULL;
+	} else if (state.arg) {
+	    PERMALLOC {
+		addlinknode(state.args, ztrdup(line));
+	    } LASTALLOC;
+	    if ((adef = state.def = ca_get_arg(d, state.nth)) &&
+		(state.def->type == CAA_RREST ||
+		 state.def->type == CAA_RARGS)) {
+		state.inrest = 0;
+		for (; line; line = compwords[cur++]) {
+		    PERMALLOC {
+			addlinknode(state.args, ztrdup(line));
+		    } LASTALLOC;
+		}
+		break;
+	    }
+	    if (state.inopt) {
+		state.inopt = 0;
+		state.nargbeg = cur - 1;
+	    }
+	    if (state.def && state.def->type != CAA_NORMAL &&
+		state.def->type != CAA_OPT && state.inarg) {
+		state.restbeg = cur;
+		state.inarg = 0;
+	    } else if (!state.def || state.def->type == CAA_NORMAL ||
+		       state.def->type == CAA_OPT)
+		state.inarg = 1;
+	    state.nth++;
+	    state.def = NULL;
+	}
+	if (state.def && state.curopt &&
+	    (state.def->type == CAA_RREST || state.def->type == CAA_RARGS)) {
+	    if (state.def->end)
+		endpat = patcompile(state.def->end, 0, NULL);
+	    else {
+		LinkList l = state.oargs[state.curopt->num];
+
+		for (; line; line = compwords[cur++]) {
+		    PERMALLOC {
+			addlinknode(l, line);
+		    } LASTALLOC;
+		}
+		break;
+	    }
+	}
+	if (cur + 1 == compcurrent) {
+	    memcpy(&ca_laststate, &state, sizeof(state));
+	    ca_laststate.ddef = NULL;
+	    ca_laststate.doff = 0;
+	} else if (cur == compcurrent && !ca_laststate.def) {
+	    if ((ca_laststate.def = ddef))
+		ca_laststate.doff = doff;
+	    else {
+		ca_laststate.def = adef;
+		ca_laststate.ddef = NULL;
+		ca_laststate.argbeg = state.nargbeg;
+		ca_laststate.optbeg = state.restbeg;
+		ca_laststate.singles = state.singles;
+	    }
+	}
+    }
+}
+
+static char *
+ca_colonlist(LinkList l)
+{
+    if (l) {
+	LinkNode n;
+	int len = 1;
+	char *p, *ret, *q;
+
+	for (n = firstnode(l); n; incnode(n))
+	    for (p = (char *) getdata(n); *p; p++)
+		len += (*p == ':' ? 2 : 1);
+
+	ret = q = (char *) zalloc(len);
+
+	for (n = firstnode(l); n; incnode(n))
+	    for (p = (char *) getdata(n); *p; p++) {
+		if (*p == ':')
+		    *q++ = '\\';
+		*q++ = *p;
+	    }
+	*q = '\0';
+
+	return ret;
+    } else
+	return ztrdup("");
+}
+
+static int
+bin_comparguments(char *nam, char **args, char *ops, int func)
+{
+    int min, max, n;
+
+    if (incompfunc != 1) {
+	zerrnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
+    if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
+	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	return 1;
+    }
+    if (args[0][1] != 'i' && !ca_parsed) {
+	zerrnam(nam, "no parsed state", NULL, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i': min = 2; max = -1; break;
+    case 'D': min = 2; max =  2; break;
+    case 'O': min = 4; max =  4; break;
+    case 'L': min = 3; max =  3; break;
+    case 's': min = 1; max =  1; break;
+    case 'M': min = 1; max =  1; break;
+    case 'a': min = 0; max =  0; break;
+    case 'W': min = 2; max =  2; break;
+    default:
+	zerrnam(nam, "invalid option: %s", args[0], 0);
+	return 1;
+    }
+    n = arrlen(args) - 1;
+    if (n < min) {
+	zerrnam(nam, "not enough arguments", NULL, 0);
+	return 1;
+    } else if (max >= 0 && n > max) {
+	zerrnam(nam, "too many arguments", NULL, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i':
+	if (compcurrent > 1 && compwords[0]) {
+	    Cadef def = get_cadef(nam, args + 1);
+	    int cap = ca_parsed;
+
+	    ca_parsed = 0;
+
+	    if (!def)
+		return 1;
+
+	    ca_parsed = cap;
+	    ca_parse_line(def);
+	    ca_parsed = 1;
+
+	    return 0;
+	}
+	return 1;
+
+    case 'D':
+	{
+	    Caarg arg = ca_laststate.def;
+
+	    if (arg) {
+		setsparam(args[1], ztrdup(arg->descr));
+		setsparam(args[2], ztrdup(arg->action));
+
+		ignore_prefix(ca_laststate.doff);
+		if (arg->type == CAA_RARGS)
+		    restrict_range(ca_laststate.argbeg - 1,
+				   arrlen(compwords) - 1);
+		else if (arg->type == CAA_RREST)
+		    restrict_range(ca_laststate.optbeg - 1,
+				   arrlen(compwords) - 1);
+		return 0;
+	    }
+	    return 1;
+	}
+    case 'O':
+	if (ca_laststate.opt) {
+	    LinkList next = newlinklist();
+	    LinkList direct = newlinklist();
+	    LinkList odirect = newlinklist();
+	    LinkList equal = newlinklist(), l;
+	    Caopt p;
+	    char *str;
+
+	    for (p = ca_laststate.d->opts; p; p = p->next) {
+		if (p->active) {
+		    switch (p->type) {
+		    case CAO_NEXT:    l = next;    break;
+		    case CAO_DIRECT:  l = direct;  break;
+		    case CAO_ODIRECT: l = odirect; break;
+		    default:          l = equal;   break;
+		    }
+		    if (p->descr) {
+			int len = strlen(p->name) + strlen(p->descr) + 2;
+
+			str = (char *) zhalloc(len);
+			strcpy(str, p->name);
+			strcat(str, ":");
+			strcat(str, p->descr);
+		    } else
+			str = p->name;
+		    addlinknode(l, str);
+		}
+	    }
+	    set_list_array(args[1], next);
+	    set_list_array(args[2], direct);
+	    set_list_array(args[3], odirect);
+	    set_list_array(args[4], equal);
+
+	    return 0;
+	} else
+	    return 1;
+    case 'L':
+	{
+	    Caopt opt = ca_get_opt(ca_laststate.d, args[1], 1, NULL);
+
+	    if (opt && opt->args) {
+		setsparam(args[2], ztrdup(opt->args->descr));
+		setsparam(args[3], ztrdup(opt->args->action));
+
+		return 0;
+	    }
+	    return 1;
+	}
+    case 's':
+	if (ca_laststate.d->single && ca_laststate.singles) {
+	    setsparam(args[1],
+		      ztrdup(ca_laststate.ddef ?
+			     (ca_laststate.ddef->type == CAO_DIRECT ?
+			      "direct" :
+			      (ca_laststate.ddef->type == CAO_EQUAL ?
+			       "equal" : "next")) : ""));
+	    return 0;
+	} else
+	    return 1;
+    case 'M':
+	setsparam(args[1], ztrdup(ca_laststate.d->match));
+	return 0;
+    case 'a':
+	return !(ca_laststate.d->args || ca_laststate.d->rest);
+    case 'W':
+	{
+	    char **ret, **p;
+	    LinkNode n;
+	    LinkList *a;
+	    Caopt o;
+	    int num;
+
+	    ret = p = zalloc((countlinknodes(ca_laststate.args) + 1) *
+			     sizeof(char *));
+
+	    for (n = firstnode(ca_laststate.args); n; incnode(n))
+		*p++ = ztrdup((char *) getdata(n));
+	    *p = NULL;
+
+	    setaparam(args[1], ret);
+
+	    for (num = 0, o = ca_laststate.d->opts, a = ca_laststate.oargs; o;
+		 o = o->next, a++)
+		if (*a)
+		    num += 2;
+
+	    ret = p = zalloc((num + 1) * sizeof(char *));
+
+	    for (o = ca_laststate.d->opts, a = ca_laststate.oargs; o;
+		 o = o->next, a++) {
+		if (*a) {
+		    *p++ = ztrdup(o->name);
+		    *p++ = ca_colonlist(*a);
+		}
+	    }
+	    *p = NULL;
+
+	    sethparam(args[2], ret);
+	}
+	return 0;
+    }
+    return 1;
+}
+
+/* Help for `_values'. */
+
+typedef struct cvdef *Cvdef;
+typedef struct cvval *Cvval;
+
+struct cvdef {
+    char *descr;
+    int hassep;
+    char sep;
+    Cvdef next;
+    Cvval vals;
+    char **defs;
+    int ndefs;
+    int lastt;
+};
+
+struct cvval {
+    Cvval next;
+    char *name;
+    char *descr;
+    char **xor;
+    int type;
+    Caarg arg;
+    int active;
+};
+
+#define CVV_NOARG 0
+#define CVV_ARG   1
+#define CVV_OPT   2
+
+#define MAX_CVCACHE 8
+static Cvdef cvdef_cache[MAX_CVCACHE];
+
+static void
+free_cvdef(Cvdef d)
+{
+    if (d) {
+	Cvval p, n;
+
+	zsfree(d->descr);
+	freearray(d->defs);
+
+	for (p = d->vals; p; p = n) {
+	    n = p->next;
+	    zsfree(p->name);
+	    zsfree(p->descr);
+	    freearray(p->xor);
+	    free_caargs(p->arg);
+	    zfree(p, sizeof(*p));
+	}
+	zfree(d, sizeof(*d));
+    }
+}
+
+static Cvdef
+parse_cvdef(char *nam, char **args)
+{
+    Cvdef ret;
+    Cvval val, *valp;
+    Caarg arg;
+    char **oargs = args, sep, *name, *descr, *p, *q, **xor, c;
+    int xnum, multi, vtype, hassep;
+
+    if (args[0][0] == '-' && args[0][1] == 's' && !args[0][2]) {
+	if (args[1][0] && args[1][1]) {
+	    zerrnam(nam, "invalid separator: %s", args[1], 0);
+	    return NULL;
+	}
+	hassep = 1;
+	sep = args[1][0];
+	args += 2;
+    }
+    if (!args[0] || !args[1]) {
+	zerrnam(nam, "not enough arguments", NULL, 0);
+	return NULL;
+    }
+    descr = *args++;
+
+    PERMALLOC {
+	ret = (Cvdef) zalloc(sizeof(*ret));
+	ret->descr = ztrdup(descr);
+	ret->hassep = hassep;
+	ret->sep = sep;
+	ret->next = NULL;
+	ret->vals = NULL;
+	ret->defs = arrdup(oargs);
+	ret->ndefs = arrlen(oargs);
+	ret->lastt = time(0);
+    } LASTALLOC;
+
+    for (valp = &(ret->vals); *args; args++) {
+	p = dupstring(*args);
+	xnum = 0;
+	if (*p == '(') {
+	    LinkList list = newlinklist();
+	    LinkNode node;
+	    char **xp, sav;
+
+	    while (*p && *p != ')') {
+		for (p++; inblank(*p); p++);
+
+		if (*p == ')')
+		    break;
+		for (q = p++; *p && *p != ')' && !inblank(*p); p++);
+
+		if (!*p)
+		    break;
+
+		sav = *p;
+		*p = '\0';
+		addlinknode(list, dupstring(q));
+		xnum++;
+		*p = sav;
+	    }
+	    if (*p != ')') {
+		free_cvdef(ret);
+		zerrnam(nam, "invalid argument: %s", *args, 0);
+		return NULL;
+	    }
+	    xor = (char **) zalloc((xnum + 2) * sizeof(char *));
+	    for (node = firstnode(list), xp = xor; node; incnode(node), xp++)
+		*xp = ztrdup((char *) getdata(node));
+	    xp[0] = xp[1] = NULL;
+
+	    p++;
+	} else
+	    xor = NULL;
+
+	if ((multi = (*p == '*')))
+	    p++;
+
+	for (name = p; *p && *p != ':' && *p != '['; p++)
+	    if (*p == '\\' && p[1])
+		p++;
+
+	if (hassep && !sep && name + 1 != p) {
+	    free_cvdef(ret);
+	    zerrnam(nam, "no multi-letter values with empty separator allowed", NULL, 0);
+	    return NULL;
+	}
+	if ((c = *p) == '[') {
+	    *p = '\0';
+	    for (descr = ++p; *p && *p != ']'; p++)
+		if (*p == '\\' && p[1])
+		    p++;
+
+	    if (!*p) {
+		free_cvdef(ret);
+		zerrnam(nam, "invalid value definition: %s", *args, 0);
+		return NULL;
+	    }
+	    *p++ = '\0';
+	    c = *p;
+	} else {
+	    *p = '\0';
+	    descr = NULL;
+	}
+	if (c && c != ':') {
+	    free_cvdef(ret);
+	    zerrnam(nam, "invalid value definition: %s", *args, 0);
+	    return NULL;
+	}
+	if (!multi) {
+	    if (!xor) {
+		xor = (char **) zalloc(2 * sizeof(char *));
+		xor[1] = NULL;
+	    }
+	    xor[xnum] = ztrdup(name);
+	}
+	if (c == ':') {
+	    if (hassep && !sep) {
+		free_cvdef(ret);
+		zerrnam(nam, "no value with argument with empty separator allowed", NULL, 0);
+		return NULL;
+	    }
+	    if (*++p == ':') {
+		p++;
+		vtype = CVV_OPT;
+	    } else
+		vtype = CVV_ARG;
+	    arg = parse_caarg(0, 0, 0, &p);
+	} else {
+	    vtype = CVV_NOARG;
+	    arg = NULL;
+	}
+	PERMALLOC {
+	    *valp = val = (Cvval) zalloc(sizeof(*val));
+	    valp = &((*valp)->next);
+
+	    val->next = NULL;
+	    val->name = ztrdup(name);
+	    val->descr = ztrdup(descr);
+	    val->xor = xor;
+	    val->type = vtype;
+	    val->arg = arg;
+	} LASTALLOC;
+    }
+    return ret;
+}
+
+static Cvdef
+get_cvdef(char *nam, char **args)
+{
+    Cvdef *p, *min, new;
+    int i, na = arrlen(args);
+
+    for (i = MAX_CVCACHE, p = cvdef_cache, min = NULL; *p && i--; p++)
+	if (*p && na == (*p)->ndefs && arrcmp(args, (*p)->defs)) {
+	    (*p)->lastt = time(0);
+
+	    return *p;
+	} else if (!min || !*p || (*p)->lastt < (*min)->lastt)
+	    min = p;
+    if (i)
+	min = p;
+    if ((new = parse_cvdef(nam, args))) {
+	free_cvdef(*min);
+	*min = new;
+    }
+    return new;
+}
+
+static Cvval
+cv_get_val(Cvdef d, char *name)
+{
+    Cvval p;
+
+    for (p = d->vals; p; p = p->next)
+	if (!strcmp(name, p->name))
+	    return p;
+
+    return NULL;
+}
+
+static void
+cv_inactive(Cvdef d, char **xor)
+{
+    if (xor) {
+	Cvval val;
+
+	for (; *xor; xor++)
+	    if ((val = cv_get_val(d, *xor)))
+		val->active = 0;
+    }
+}
+
+struct cvstate {
+    Cvdef d;
+    Caarg def;
+    Cvval val;
+    LinkList vals;
+};
+
+static struct cvstate cv_laststate;
+static int cv_parsed = 0, cv_alloced = 0;
+
+static void
+cv_parse_word(Cvdef d)
+{
+    Cvval ptr;
+    struct cvstate state;
+    char *str, *eq;
+
+    if (cv_alloced)
+	freelinklist(cv_laststate.vals, freestr);
+
+    for (ptr = d->vals; ptr; ptr = ptr->next)
+	ptr->active = 1;
+
+    state.d = d;
+    state.def = NULL;
+    state.val = NULL;
+    PERMALLOC {
+	state.vals = (LinkList) newlinklist();
+    } LASTALLOC;
+    cv_alloced = 1;
+
+    if (d->hassep) {
+	if (d->sep) {
+	    char *end;
+	    int heq;
+
+	    for (str = compprefix, end = strchr(str, d->sep); end;) {
+		*end = '\0';
+
+		if ((heq = !!(eq = strchr(str, '='))))
+		    *eq++ = '\0';
+		else
+		    eq = "";
+
+		if ((ptr = cv_get_val(d, str))) {
+		    PERMALLOC {
+			addlinknode(state.vals, ztrdup(str));
+			addlinknode(state.vals, ztrdup(eq));
+		    } LASTALLOC;
+
+		    cv_inactive(d, ptr->xor);
+		}
+		if (heq)
+		    eq[-1] = '=';
+
+		*end = d->sep;
+		str = end + 1;
+		end = strchr(str, d->sep);
+	    }
+	    ignore_prefix(str - compprefix);
+
+	    if ((str = strchr(compsuffix, d->sep))) {
+		char *beg = str;
+
+		for (str++; str; str = end) {
+		    if ((end = strchr(str, d->sep)))
+			*end = '\0';
+
+		    if ((heq = !!(eq = strchr(str, '='))))
+			*eq++ = '\0';
+		    else
+			eq = "";
+
+		    if ((ptr = cv_get_val(d, str))) {
+			PERMALLOC {
+			    addlinknode(state.vals, ztrdup(str));
+			    addlinknode(state.vals, ztrdup(eq));
+			} LASTALLOC;
+
+			cv_inactive(d, ptr->xor);
+		    }
+		    if (heq)
+			eq[-1] = '=';
+		    if (end)
+			*end++ = d->sep;
+		}
+		ignore_suffix(strlen(beg));
+	    }
+	} else {
+	    char tmp[2];
+
+	    tmp[1] = '\0';
+
+	    for (str = compprefix; *str; str++) {
+		tmp[0] = *str;
+		if ((ptr = cv_get_val(d, tmp))) {
+		    PERMALLOC {
+			addlinknode(state.vals, ztrdup(tmp));
+			addlinknode(state.vals, ztrdup(""));
+		    } LASTALLOC;
+
+		    cv_inactive(d, ptr->xor);
+		}
+	    }
+	    for (str = compsuffix; *str; str++) {
+		tmp[0] = *str;
+		if ((ptr = cv_get_val(d, tmp))) {
+		    PERMALLOC {
+			addlinknode(state.vals, ztrdup(tmp));
+			addlinknode(state.vals, ztrdup(""));
+		    } LASTALLOC;
+
+		    cv_inactive(d, ptr->xor);
+		}
+	    }
+	    ignore_prefix(strlen(compprefix));
+	    ignore_suffix(strlen(compsuffix));
+	}
+    }
+    str = tricat(compprefix, compsuffix, "");
+    zsfree(compprefix);
+    zsfree(compsuffix);
+    compprefix = str;
+    compsuffix = ztrdup("");
+
+    if ((eq = strchr(str, '='))) {
+	*eq++ = '\0';
+
+	if ((ptr = cv_get_val(d, str)) && ptr->type != CVV_NOARG) {
+	    eq[-1] = '=';
+	    ignore_prefix(eq - str);
+	    state.def = ptr->arg;
+	    state.val = ptr;
+	} else
+	    eq[-1] = '=';
+    }
+    memcpy(&cv_laststate, &state, sizeof(state));
+}
+
+static int
+bin_compvalues(char *nam, char **args, char *ops, int func)
+{
+    int min, max, n;
+
+    if (incompfunc != 1) {
+	zerrnam(nam, "can only be called from completion function", NULL, 0);
+	return 1;
+    }
+    if (args[0][0] != '-' || !args[0][1] || args[0][2]) {
+	zerrnam(nam, "invalid argument: %s", args[0], 0);
+	return 1;
+    }
+    if (args[0][1] != 'i' && !cv_parsed) {
+	zerrnam(nam, "no parsed state", NULL, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i': min = 2; max = -1; break;
+    case 'D': min = 2; max =  2; break;
+    case 'V': min = 3; max =  3; break;
+    case 's': min = 1; max =  1; break;
+    case 'd': min = 1; max =  1; break;
+    case 'L': min = 3; max =  3; break;
+    case 'v': min = 1; max =  1; break;
+    default:
+	zerrnam(nam, "invalid option: %s", args[0], 0);
+	return 1;
+    }
+    n = arrlen(args) - 1;
+    if (n < min) {
+	zerrnam(nam, "not enough arguments", NULL, 0);
+	return 1;
+    } else if (max >= 0 && n > max) {
+	zerrnam(nam, "too many arguments", NULL, 0);
+	return 1;
+    }
+    switch (args[0][1]) {
+    case 'i':
+	{
+	    Cvdef def = get_cvdef(nam, args + 1);
+	    int cvp = cv_parsed;
+
+	    cv_parsed = 0;
+
+	    if (!def)
+		return 1;
+
+	    cv_parsed = cvp;
+	    cv_parse_word(def);
+	    cv_parsed = 1;
+
+	    return 0;
+	}
+	return 1;
+
+    case 'D':
+	{
+	    Caarg arg = cv_laststate.def;
+
+	    if (arg) {
+		setsparam(args[1], ztrdup(arg->descr));
+		setsparam(args[2], ztrdup(arg->action));
+
+		return 0;
+	    }
+	    return 1;
+	}
+    case 'V':
+	{
+	    LinkList noarg = newlinklist();
+	    LinkList arg = newlinklist();
+	    LinkList opt = newlinklist(), l;
+	    Cvval p;
+	    char *str;
+
+	    for (p = cv_laststate.d->vals; p; p = p->next) {
+		if (p->active) {
+		    switch (p->type) {
+		    case CVV_NOARG: l = noarg; break;
+		    case CVV_ARG:   l = arg;   break;
+		    default:        l = opt;   break;
+		    }
+		    if (p->descr) {
+			int len = strlen(p->name) + strlen(p->descr) + 2;
+
+			str = (char *) zhalloc(len);
+			strcpy(str, p->name);
+			strcat(str, ":");
+			strcat(str, p->descr);
+		    } else
+			str = p->name;
+		    addlinknode(l, str);
+		}
+	    }
+	    set_list_array(args[1], noarg);
+	    set_list_array(args[2], arg);
+	    set_list_array(args[3], opt);
+
+	    return 0;
+	}
+    case 's':
+	if (cv_laststate.d->hassep) {
+	    char tmp[2];
+
+	    tmp[0] = cv_laststate.d->sep;
+	    tmp[1] = '\0';
+	    setsparam(args[1], ztrdup(tmp));
+
+	    return 0;
+	}
+	return 1;
+    case 'd':
+	setsparam(args[1], ztrdup(cv_laststate.d->descr));
+	return 0;
+    case 'L':
+	{
+	    Cvval val = cv_get_val(cv_laststate.d, args[1]);
+
+	    if (val && val->arg) {
+		setsparam(args[2], val->arg->descr);
+		setsparam(args[3], val->arg->action);
+
+		return 0;
+	    }
+	    return 1;
+	}
+    case 'v':
+	if (cv_laststate.vals) {
+	    char **ret, **p;
+	    LinkNode n;
+
+	    ret = (char **) zalloc((countlinknodes(cv_laststate.vals) + 1) *
+				   sizeof(char *));
+
+	    for (n = firstnode(cv_laststate.vals), p = ret; n; incnode(n), p++)
+		*p = ztrdup((char *) getdata(n));
+	    *p = NULL;
+
+	    sethparam(args[1], ret);
+
+	    return 0;
+	}
+	return 1;
+    }
+    return 1;
+}
+
+
+static struct builtin bintab[] = {
+    BUILTIN("compdisplay", 0, bin_compdisplay, 2, -1, 0, NULL, NULL),
+    BUILTIN("compdescribe", 0, bin_compdescribe, 3, -1, 0, NULL, NULL),
+    BUILTIN("comparguments", 0, bin_comparguments, 1, -1, 0, NULL, NULL),
+    BUILTIN("compvalues", 0, bin_compvalues, 1, -1, 0, NULL, NULL),
+};
+
+
+/**/
+int
+setup_computil(Module m)
+{
+    memset(cadef_cache, 0, sizeof(cadef_cache));
+    memset(cvdef_cache, 0, sizeof(cvdef_cache));
+
+    return 0;
+}
+
+/**/
+int
+boot_computil(Module m)
+{
+    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+}
+
+#ifdef MODULE
+
+/**/
+int
+cleanup_computil(Module m)
+{
+    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
+}
+
+/**/
+int
+finish_computil(Module m)
+{
+    int i;
+
+    for (i = 0; i < MAX_CACACHE; i++)
+	free_cadef(cadef_cache[i]);
+    for (i = 0; i < MAX_CVCACHE; i++)
+	free_cvdef(cvdef_cache[i]);
+
+    return 0;
+}
+
+#endif
diff --git a/Src/Zle/computil.mdd b/Src/Zle/computil.mdd
new file mode 100644
index 000000000..a1453ae8a
--- /dev/null
+++ b/Src/Zle/computil.mdd
@@ -0,0 +1,3 @@
+moddeps="compctl zle"
+
+objects="computil.o"
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index c82205cfb..7d14e34ad 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -3966,8 +3966,9 @@ add_match_data(int alt, char *str, Cline line,
 
 /* This stores the strings from the list in an array. */
 
-static void
-set_param(char *name, LinkList l)
+/**/
+void
+set_list_array(char *name, LinkList l)
 {
     char **a, **p;
     LinkNode n;
@@ -4267,11 +4268,11 @@ addmatches(Cadata dat, char **argv)
 	    compnmatches = mnum;
 	    compnnmatches = nmnum;
 	    if (dat->apar)
-		set_param(dat->apar, aparl);
+		set_list_array(dat->apar, aparl);
 	    if (dat->opar)
-		set_param(dat->opar, oparl);
+		set_list_array(dat->opar, oparl);
 	    if (dat->dpar)
-		set_param(dat->dpar, dparl);
+		set_list_array(dat->dpar, dparl);
 	    if (dat->ylist) {
 		if (dat->group) {
 		    endcmgroup(get_user_var(dat->ylist));
@@ -7143,7 +7144,7 @@ invalidatelist(void)
 /* Get the words from a variable or a compctl -k list. */
 
 /**/
-static char **
+char **
 get_user_var(char *nam)
 {
     if (!nam)
@@ -8804,12 +8805,13 @@ calclist(void)
 		g->width = 0;
 
 		for (p = g->matches; (m = *p); p++)
-		    if (!(m->flags & CMF_HIDE))
+		    if (!(m->flags & CMF_HIDE)) {
 			if (m->disp) {
 			    if (!(m->flags & CMF_DISPLINE))
 				glines += 1 + (mlens[m->gnum] / columns);
 			} else if (!(m->flags & CMF_NOLIST))
 			    glines += 1 + ((1 + mlens[m->gnum]) / columns);
+		    }
 	    }
 	}
 	g->lins = glines;