mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-01-21 12:11:26 +01:00
572 lines
16 KiB
Text
572 lines
16 KiB
Text
#autoload
|
|
|
|
# Complete the arguments of the current command according to the
|
|
# descriptions given as arguments to this function.
|
|
|
|
local long cmd="$words[1]" descr odescr mesg subopts opt opt2 usecc autod
|
|
local oldcontext="$curcontext" hasopts rawret optarg singopt alwopt
|
|
local setnormarg start rest
|
|
local -a match mbegin mend
|
|
|
|
long=$argv[(I)--]
|
|
if (( long )); then
|
|
local name tmp tmpargv
|
|
|
|
if [[ long -eq 1 ]]; then
|
|
tmpargv=()
|
|
else
|
|
tmpargv=( "${(@)argv[1,long-1]}" )
|
|
fi
|
|
|
|
name=${~words[1]}
|
|
[[ "$name" = [^/]*/* ]] && name="$PWD/$name"
|
|
|
|
name="_args_cache_${name}"
|
|
name="${name//[^a-zA-Z0-9_]/_}"
|
|
|
|
if (( ! ${(P)+name} )); then
|
|
local iopts sopts pattern tmpo dir cur cache
|
|
typeset -U lopts
|
|
|
|
cache=()
|
|
|
|
# We have to build a new long-option cache, get the `-i' and
|
|
# `-s' options.
|
|
|
|
set -- "${(@)argv[long+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+=( "$tmp[@]" )
|
|
else
|
|
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 into
|
|
# 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 tab after the
|
|
# option up to the end.
|
|
|
|
tmp=()
|
|
_call_program options ${~words[1]} --help 2>&1 | while IFS= read -r opt; do
|
|
if (( ${#tmp} )); then
|
|
# Previous line had no comment. Is the current one suitable?
|
|
# It's hard to be sure, but if it there was nothing on the
|
|
# previous line and the current one is indented more than
|
|
# a couple of spaces (and isn't completely whitespace or punctuation)
|
|
# there's a pretty good chance.
|
|
if [[ $opt = [[:space:]][[:space:]][[:space:]]*[[:alpha:]]* ]]; then
|
|
# Assume so.
|
|
opt=${opt##[[:space:]]##}
|
|
# Same substitution as below.
|
|
lopts+=("${^tmp[@]}":${${${opt//:/-}//\[/(}//\]/)})
|
|
tmp=()
|
|
# Finished with this line.
|
|
continue
|
|
else
|
|
# Still no comment, add the previous options anyway.
|
|
lopts+=("${tmp[@]}")
|
|
tmp=()
|
|
fi
|
|
fi
|
|
while [[ $opt = [,[:space:]]#(#b)(-[^,[:space:]]#)(*) ]]; do
|
|
# We used to remove the brackets from "[=STUFF]",
|
|
# but later the code appears to handle it with the brackets
|
|
# present. Maybe the problem was that the intervening code
|
|
# didn't. If it's buggy without removing them, the problem
|
|
# probably is later, not here.
|
|
start=${match[1]}
|
|
rest=${match[2]}
|
|
if [[ -z ${tmp[(r)${start%%[^a-zA-Z0-9_-]#}]} ]]; then
|
|
# variant syntax seen in fetchmail:
|
|
# --[fetch]all means --fetchall or --all.
|
|
# maybe needs to be more general
|
|
if [[ $start = (#b)(*)\[(*)\](*) ]]; then
|
|
tmp+=("${match[1]}${match[2]}${match[3]}" "${match[1]}${match[3]}")
|
|
else
|
|
tmp+=($start)
|
|
fi
|
|
fi
|
|
opt=$rest
|
|
done
|
|
# If there's left over text, assume it's a description; it
|
|
# may be truncated but if it's too long it's no use anyway.
|
|
# There's one hiccup: we sometimes get descriptions like
|
|
# --foo fooarg Do some foo stuff with foo arg
|
|
# and we need to remove fooarg. Use whitespace for hints.
|
|
opt=${opt## [^[:space:]]## }
|
|
opt=${opt##[[:space:]]##}
|
|
if [[ -n $opt ]]; then
|
|
# Add description after a ":", converting any : in the description
|
|
# to a -. Use RCQUOTES to append this to all versions of the option.
|
|
lopts+=("${^tmp[@]}":${${${opt//:/-}//\[/(}//\]/)})
|
|
tmp=()
|
|
# If there's no comment, we'll see if there's one on the
|
|
# next line.
|
|
fi
|
|
done
|
|
# Tidy up any remaining uncommented options.
|
|
if (( ${#tmp} )); then
|
|
lopts+=("${tmp[@]}")
|
|
fi
|
|
|
|
# Remove options also described by user-defined specs.
|
|
|
|
tmp=()
|
|
# Ignore any argument and description information when searching
|
|
# the long options array here and below.
|
|
for opt in "${(@)${(@)lopts:#--}%%[\[:=]*}"; do
|
|
|
|
# Using (( ... )) gives a parse error.
|
|
|
|
let "$tmpargv[(I)(|\([^\)]#\))(|\*)${opt}(|[-+]|=(|-))(|\[*\])(|:*)]" ||
|
|
tmp+=( "$lopts[(r)$opt(|[\[:=]*)]" )
|
|
done
|
|
lopts=( "$tmp[@]" )
|
|
|
|
# Now remove all ignored options ...
|
|
|
|
while (( $#iopts )); do
|
|
lopts=( ${lopts:#$~iopts[1](|[\[:=]*)} )
|
|
shift iopts
|
|
done
|
|
|
|
# ... and add "same" options
|
|
|
|
while (( $#sopts )); do
|
|
# This implements adding things like --disable-* based
|
|
# on the existence of --enable-*.
|
|
# TODO: there's no anchoring here, is that correct?
|
|
# If it's not, careful with the [\[:=]* stuff.
|
|
lopts+=( ${lopts/$~sopts[1]/$sopts[2]} )
|
|
shift 2 sopts
|
|
done
|
|
|
|
# Then we walk through the descriptions plus a few builtin ones.
|
|
# The last one matches all options; the `special' description and action
|
|
# makes those options be completed without an argument description.
|
|
|
|
argv+=(
|
|
'*=FILE*:file:_files'
|
|
'*=(DIR|PATH)*:directory:_files -/'
|
|
'*=*:=: '
|
|
'*: : '
|
|
)
|
|
|
|
while (( $# )); do
|
|
|
|
# First, we get the pattern and the action to use and take them
|
|
# from the positional parameters.
|
|
|
|
# This is the first bit of the arguments in the special form
|
|
# for converting --help texts, taking account of any quoting
|
|
# of colons.
|
|
pattern="${${${(M)1#*[^\\]:}[1,-2]}//\\\\:/:}"
|
|
# Any action specifications that go with it.
|
|
descr="${1#${pattern}}"
|
|
if [[ "$pattern" = *\(-\) ]]; then
|
|
# This is the special form to disallow arguments
|
|
# in the next word.
|
|
pattern="$pattern[1,-4]"
|
|
dir=-
|
|
else
|
|
dir=
|
|
fi
|
|
shift
|
|
|
|
# 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.
|
|
|
|
# Ignore :descriptions at the ends of lopts for matching this;
|
|
# they aren't in the patterns.
|
|
tmp=("${(@M)lopts:##$~pattern(|:*)}")
|
|
lopts=("${(@)lopts:##$~pattern(|:*)}")
|
|
|
|
(( $#tmp )) || continue
|
|
|
|
opt=''
|
|
|
|
# If there are option strings with a `[=', we take these to get an
|
|
# optional argument.
|
|
|
|
tmpo=("${(@M)tmp:#*\[\=*}")
|
|
if (( $#tmpo )); then
|
|
tmp=("${(@)tmp:#*\[\=*}")
|
|
|
|
for opt in "$tmpo[@]"; do
|
|
# Look for --option:description and turn it into
|
|
# --option[description]. We didn't do that above
|
|
# since it could get confused with the [=ARG] stuff.
|
|
if [[ $opt = (#b)(*):([^:]#) ]]; then
|
|
opt=$match[1]
|
|
odescr="[${match[2]}]"
|
|
else
|
|
odescr=
|
|
fi
|
|
if [[ $opt = (#b)(*)\[\=* ]]; then
|
|
opt2=${${match[1]}//[^a-zA-Z0-9_-]}=-${dir}${odescr}
|
|
else
|
|
opt2=${${opt}//[^a-zA-Z0-9_-]}=${dir}${odescr}
|
|
fi
|
|
if [[ "$descr" = :\=* ]]; then
|
|
cache+=( "${opt2}::${(L)${opt%\]}#*\=}: " )
|
|
elif [[ "$descr" = ::* ]]; then
|
|
cache+=( "${opt2}${descr}" )
|
|
else
|
|
cache+=( "${opt2}:${descr}" )
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Descriptions with `=': mandatory argument.
|
|
# Basically the same as the foregoing.
|
|
# TODO: could they be combined?
|
|
|
|
tmpo=("${(@M)tmp:#*\=*}")
|
|
if (( $#tmpo )); then
|
|
tmp=("${(@)tmp:#*\=*}")
|
|
|
|
for opt in "$tmpo[@]"; do
|
|
if [[ $opt = (#b)(*):([^:]#) ]]; then
|
|
opt=$match[1]
|
|
odescr="[${match[2]}]"
|
|
else
|
|
odescr=
|
|
fi
|
|
opt2="${${opt%%\=*}//[^a-zA-Z0-9_-]}=${dir}${odescr}"
|
|
if [[ "$descr" = :\=* ]]; then
|
|
cache+=( "${opt2}:${(L)${opt%\]}#*\=}: " )
|
|
else
|
|
cache+=( "${opt2}${descr}" )
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Everything else is just added as an option without arguments or
|
|
# as described by $descr.
|
|
|
|
if (( $#tmp )); then
|
|
tmp=(
|
|
# commands with a description of the option (as opposed
|
|
# to the argument, which is what descr contains): needs to be
|
|
# "option[description]".
|
|
# Careful: \[ on RHS of substitution keeps the backslash,
|
|
# I discovered after about half an hour, so don't do that.
|
|
"${(@)^${(@)tmp:#^*:*}//:/[}]"
|
|
# commands with no description
|
|
"${(@)${(@)tmp:#*:*}//[^a-zA-Z0-9_-]}")
|
|
if [[ -n "$descr" && "$descr" != ': : ' ]]; then
|
|
cache+=( "${(@)^tmp}${descr}" )
|
|
else
|
|
cache+=( "$tmp[@]" )
|
|
fi
|
|
fi
|
|
done
|
|
set -A "$name" "${(@)cache:# #}"
|
|
fi
|
|
set -- "$tmpargv[@]" "${(@P)name}"
|
|
fi
|
|
|
|
subopts=()
|
|
singopt=()
|
|
while [[ "$1" = -(O*|[CRWnsw]) ]]; do
|
|
case "$1" in
|
|
-C) usecc=yes; shift ;;
|
|
-O) subopts=( "${(@P)2}" ); shift 2 ;;
|
|
-O*) subopts=( "${(@P)${1[3,-1]}}" ); shift ;;
|
|
-R) rawret=yes; shift;;
|
|
-n) setnormarg=yes; NORMARG=-1; shift;;
|
|
-w) optarg=yes; shift;;
|
|
-s) singopt=(-s); shift;;
|
|
-W) alwopt=arg; shift;;
|
|
esac
|
|
done
|
|
|
|
[[ "$PREFIX" = [-+] ]] && alwopt=arg
|
|
|
|
zstyle -s ":completion:${curcontext}:options" auto-description autod
|
|
|
|
if (( $# )) && comparguments -i "$autod" "$singopt[@]" "$@"; then
|
|
local action noargs aret expl local tried ret=1
|
|
local next direct odirect equal single matcher matched ws tmp1 tmp2 tmp3
|
|
local opts subc tc prefix suffix descrs actions subcs anum
|
|
local origpre="$PREFIX" origipre="$IPREFIX" nm="$compstate[nmatches]"
|
|
|
|
if comparguments -D descrs actions subcs; then
|
|
if comparguments -O next direct odirect equal; then
|
|
opts=yes
|
|
_tags "$subcs[@]" options
|
|
else
|
|
_tags "$subcs[@]"
|
|
fi
|
|
else
|
|
if comparguments -a; then
|
|
noargs='no more arguments'
|
|
else
|
|
noargs='no arguments'
|
|
fi
|
|
if comparguments -O next direct odirect equal; then
|
|
opts=yes
|
|
_tags options
|
|
elif [[ $? -eq 2 ]]; then
|
|
compadd -Q - "${PREFIX}${SUFFIX}"
|
|
return 0
|
|
else
|
|
_message "$noargs"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
comparguments -M matcher
|
|
|
|
context=()
|
|
state=()
|
|
|
|
while true; do
|
|
while _tags; do
|
|
anum=1
|
|
if [[ -z "$tried" ]]; then
|
|
while [[ anum -le $#descrs ]]; do
|
|
|
|
action="$actions[anum]"
|
|
descr="$descrs[anum]"
|
|
subc="$subcs[anum++]"
|
|
|
|
if [[ $subc = argument* && -n $setnormarg ]]; then
|
|
comparguments -n NORMARG
|
|
fi
|
|
|
|
if [[ -n "$matched" ]] || _requested "$subc"; then
|
|
|
|
curcontext="${oldcontext%:*}:$subc"
|
|
|
|
_description "$subc" expl "$descr"
|
|
|
|
if [[ "$action" = \=\ * ]]; then
|
|
action="$action[3,-1]"
|
|
words=( "$subc" "$words[@]" )
|
|
(( CURRENT++ ))
|
|
fi
|
|
|
|
if [[ "$action" = -\>* ]]; then
|
|
action="${${action[3,-1]##[ ]#}%%[ ]#}"
|
|
if (( ! $state[(I)$action] )); then
|
|
comparguments -W line opt_args
|
|
state+=( "$action" )
|
|
if [[ -n "$usecc" ]]; then
|
|
curcontext="${oldcontext%:*}:$subc"
|
|
else
|
|
context+=( "$subc" )
|
|
fi
|
|
compstate[restore]=''
|
|
aret=yes
|
|
fi
|
|
else
|
|
if [[ -z "$local" ]]; then
|
|
local line
|
|
typeset -A opt_args
|
|
local=yes
|
|
fi
|
|
|
|
comparguments -W line opt_args
|
|
|
|
if [[ "$action" = \ # ]]; then
|
|
|
|
# An empty action means that we should just display a message.
|
|
|
|
_message -e "$subc" "$descr"
|
|
mesg=yes
|
|
tried=yes
|
|
alwopt=${alwopt:-yes}
|
|
elif [[ "$action" = \(\(*\)\) ]]; then
|
|
|
|
# ((...)) contains literal strings with descriptions.
|
|
|
|
eval ws\=\( "${action[3,-3]}" \)
|
|
|
|
_describe -t "$subc" "$descr" ws -M "$matcher" "$subopts[@]" ||
|
|
alwopt=${alwopt:-yes}
|
|
tried=yes
|
|
|
|
elif [[ "$action" = \(*\) ]]; then
|
|
|
|
# Anything inside `(...)' is added directly.
|
|
|
|
eval ws\=\( "${action[2,-2]}" \)
|
|
|
|
_all_labels "$subc" expl "$descr" compadd "$subopts[@]" -a - ws ||
|
|
alwopt=${alwopt:-yes}
|
|
tried=yes
|
|
elif [[ "$action" = \{*\} ]]; then
|
|
|
|
# A string in braces is evaluated.
|
|
|
|
while _next_label "$subc" expl "$descr"; do
|
|
eval "$action[2,-2]" && ret=0
|
|
done
|
|
(( ret )) && alwopt=${alwopt:-yes}
|
|
tried=yes
|
|
elif [[ "$action" = \ * ]]; then
|
|
|
|
# If the action starts with a space, we just call it.
|
|
|
|
eval "action=( $action )"
|
|
while _next_label "$subc" expl "$descr"; do
|
|
"$action[@]" && ret=0
|
|
done
|
|
(( ret )) && alwopt=${alwopt:-yes}
|
|
tried=yes
|
|
else
|
|
|
|
# Otherwise we call it with the description-arguments.
|
|
|
|
eval "action=( $action )"
|
|
while _next_label "$subc" expl "$descr"; do
|
|
"$action[1]" "$subopts[@]" "$expl[@]" "${(@)action[2,-1]}" && ret=0
|
|
done
|
|
(( ret )) && alwopt=${alwopt:-yes}
|
|
tried=yes
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
if _requested options &&
|
|
[[ -z "$hasopts" &&
|
|
-z "$matched" &&
|
|
( -z "$aret" || "$PREFIX" = "$origpre" ) ]] &&
|
|
{ ! zstyle -T ":completion:${oldcontext%:*}:options" prefix-needed ||
|
|
[[ "$origpre" = [-+]* || -z "$aret$mesg$tried" ]] } ; then
|
|
local prevpre="$PREFIX" previpre="$IPREFIX" prevcontext="$curcontext"
|
|
|
|
curcontext="${oldcontext%:*}:options"
|
|
|
|
hasopts=yes
|
|
|
|
PREFIX="$origpre"
|
|
IPREFIX="$origipre"
|
|
|
|
if [[ -z "$alwopt" || -z "$tried" || "$alwopt" = arg ]] &&
|
|
comparguments -s single; then
|
|
|
|
if [[ "$single" = direct ]]; then
|
|
_all_labels options expl option \
|
|
compadd -QS '' - "${PREFIX}${SUFFIX}"
|
|
elif [[ -z "$optarg" && "$single" = next ]]; then
|
|
_all_labels options expl option \
|
|
compadd -Q - "${PREFIX}${SUFFIX}"
|
|
elif [[ "$single" = equal ]]; then
|
|
_all_labels options expl option \
|
|
compadd -QqS= - "${PREFIX}${SUFFIX}"
|
|
else
|
|
|
|
tmp1=( "$next[@]" "$direct[@]" "$odirect[@]" "$equal[@]" )
|
|
|
|
[[ "$PREFIX" = [-+]* ]] && tmp1=( "${(@M)tmp1:#${PREFIX[1]}*}" )
|
|
|
|
[[ "$single" = next ]] &&
|
|
tmp1=( "${(@)tmp1:#[-+]${PREFIX[-1]}((#e)|:*)}" )
|
|
|
|
[[ "$PREFIX" != --* ]] && tmp1=( "${(@)tmp1:#--*}" )
|
|
tmp3=( "${(M@)tmp1:#[-+]?[^:]*}" )
|
|
tmp1=( "${(M@)tmp1:#[-+]?(|:*)}" )
|
|
tmp2=( "${PREFIX}${(@M)^${(@)${(@)tmp1%%:*}#[-+]}:#?}" )
|
|
|
|
_describe -O option \
|
|
tmp1 tmp2 -Q -S '' -- \
|
|
tmp3 -Q
|
|
|
|
[[ -n "$optarg" && "$single" = next && nm -eq $compstate[nmatches] ]] &&
|
|
_all_labels options expl option \
|
|
compadd -Q - "${PREFIX}${SUFFIX}"
|
|
|
|
fi
|
|
single=yes
|
|
else
|
|
next+=( "$odirect[@]" )
|
|
_describe -O option \
|
|
next -Q -M "$matcher" -- \
|
|
direct -QS '' -M "$matcher" -- \
|
|
equal -QqS= -M "$matcher"
|
|
fi
|
|
PREFIX="$prevpre"
|
|
IPREFIX="$previpre"
|
|
curcontext="$prevcontext"
|
|
fi
|
|
[[ -n "$tried" && "${${alwopt:+$origpre}:-$PREFIX}" != [-+]* ]] && break
|
|
done
|
|
if [[ -n "$opts" && -z "$aret" &&
|
|
-z "$matched" &&
|
|
( -z "$tried" || -n "$alwopt" ) &&
|
|
nm -eq compstate[nmatches] ]]; then
|
|
|
|
PREFIX="$origpre"
|
|
IPREFIX="$origipre"
|
|
|
|
prefix="${PREFIX#*\=}"
|
|
suffix="$SUFFIX"
|
|
PREFIX="${PREFIX%%\=*}"
|
|
SUFFIX=''
|
|
|
|
compadd -M "$matcher" -D equal - "${(@)equal%%:*}"
|
|
|
|
if [[ $#equal -eq 1 ]]; then
|
|
PREFIX="$prefix"
|
|
SUFFIX="$suffix"
|
|
IPREFIX="${IPREFIX}${equal[1]%%:*}="
|
|
matched=yes
|
|
|
|
comparguments -L "${equal[1]%%:*}" descrs actions subcs
|
|
|
|
_tags "$subcs[@]"
|
|
|
|
continue
|
|
fi
|
|
fi
|
|
break
|
|
done
|
|
|
|
[[ -z "$aret" || -z "$usecc" ]] && curcontext="$oldcontext"
|
|
|
|
if [[ -n "$aret" ]]; then
|
|
[[ -n $rawret ]] && return 300
|
|
|
|
### Returning non-zero would allow the calling function to add its own
|
|
### completions if we generated only options and have to use a ->state
|
|
### action. But if that then doesn't generate matches, the calling
|
|
### function's return value would be wrong unless it compares
|
|
### $compstate[nmatches] to its previous value. Ugly.
|
|
###
|
|
### return 1
|
|
else
|
|
[[ -n "$noargs" && nm -eq "$compstate[nmatches]" ]] && _message "$noargs"
|
|
fi
|
|
# Set the return value.
|
|
|
|
[[ nm -ne "$compstate[nmatches]" ]]
|
|
else
|
|
return 1
|
|
fi
|