1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-05-11 18:16:15 +02:00
zsh/Completion/Base/Utility/_arguments
Eitan Adler 110ffae9fe 40035: Cosmetic fixes for comments and documentation.
Mostly fixes to doubled words.
2016-11-29 17:13:52 +00:00

582 lines
17 KiB
Plaintext

#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
subopts=()
singopt=()
while [[ "$1" = -([AMO]*|[CRSWnsw]) ]]; 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;;
-W) alwopt=arg; shift;;
-[Ss]) singopt+=( $1 ); shift;;
-[AM]) singopt+=( $1 $2 ); shift 2 ;;
-[AM]*) singopt+=( $1 ); shift ;;
esac
done
[[ $1 = ':' ]] && shift
singopt+=( ':' ) # always end with ':' to indicate the end of options
[[ "$PREFIX" = [-+] ]] && alwopt=arg
long=$argv[(I)--]
if (( long )); then
local name tmp tmpargv
tmpargv=( "${(@)argv[1,long-1]}" ) # optspec's before --, if any
name=${~words[1]} 2>/dev/null
[[ "$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 -Ua 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.
# Add a ':' after the option anyways, to make the matching of
# the options lateron work as intended.
# It will be removed again later.
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=''
# Clean suffix ':' added earlier
tmp=("${(@)tmp%:}")
# 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
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=()
state_descr=()
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" )
state_descr+=( "$descr" )
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