1
0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-11-20 05:53:52 +01:00
zsh/Completion/Core/_path_files
Bart Schaefer 4ed8c26c50 12133: Protect completion system from stray error output and assorted fatal
errors when expanding strings taken from the command line.
2000-07-02 17:21:54 +00:00

579 lines
16 KiB
Plaintext

#autoload
# Utility function for in-path completion. This allows `/u/l/b<TAB>'
# to complete to `/usr/local/bin'.
local linepath realpath donepath prepath testpath exppath skips skipped
local tmp1 tmp2 tmp3 tmp4 i orig eorig pre suf tpre tsuf opre osuf cpre
local pats haspats ignore pfxsfx sopt gopt opt sdirs ignpar cfopt
local nm=$compstate[nmatches] menu matcher mopts sort match mid accex fake
typeset -U prepaths exppaths
setopt localoptions nullglob rcexpandparam
unsetopt markdirs globsubst shwordsplit nounset
exppaths=()
# Get the options.
zparseopts -a mopts \
'P:=pfxsfx' 'S:=pfxsfx' 'q=pfxsfx' 'r:=pfxsfx' 'R:=pfxsfx' \
'W:=prepaths' 'F:=ignore' 'M+:=matcher' \
J+: V+: X+: 1: 2: n: 'f=tmp1' '/=tmp1' 'g+:-=tmp1'
sopt="-${(@j::M)${(@)tmp1#-}#?}"
(( $tmp1[(I)-[/g]*] )) && haspats=yes
(( $tmp1[(I)-g*] )) && gopt=yes
if (( $tmp1[(I)-/] )); then
pats=( '*(-/)' ${=${(M)tmp1:#-g*}#-g} )
else
pats=( "${(@)=${(@M)tmp1:#-g*}#-g}" )
fi
pats=( "${(@)pats:# #}" )
if (( $#prepaths )); then
tmp1="${prepaths[2]}"
if [[ "$tmp1[1]" = '(' ]]; then
prepaths=( ${^=tmp1[2,-2]%/}/ )
elif [[ "$tmp1[1]" = '/' ]]; then
prepaths=( "${tmp1%/}/" )
else
prepaths=( ${(P)^tmp1%/}/ )
(( ! $#prepaths )) && prepaths=( ${tmp1%/}/ )
fi
(( ! $#prepaths )) && prepaths=( '' )
else
prepaths=( '' )
fi
if (( $#ignore )); then
if [[ "${ignore[2]}" = \(* ]]; then
ignore=( ${=ignore[2][2,-2]} )
else
ignore=( ${(P)ignore[2]} )
fi
fi
# If we were given no file selection option, we behave as if we were given
# a `-f'.
if [[ "$sopt" = -(f|) ]]; then
if [[ -z "$gopt" ]]; then
sopt='-f'
pats=('*')
else
unset sopt
fi
fi
if (( ! $mopts[(I)-[JVX]] )); then
local expl
if [[ -z "$gopt" && "$sopt" = -/ ]]; then
_description directories expl directory
else
_description files expl file
fi
tmp1=$expl[(I)-M*]
if (( tmp1 )); then
if (( $#matcher )); then
matcher[2]="$matcher[2] $expl[1+tmp1]"
else
matcher=(-M "$expl[1+tmp1]")
fi
fi
mopts=( "$mopts[@]" "$expl[@]" )
fi
# If given no `-F' option, we may want to use $fignore, turned into patterns.
[[ -z "$_comp_no_ignore" && $#ignore -eq 0 &&
( -z $gopt || "$pats" = \ #\*\ # ) && -n $FIGNORE ]] &&
ignore=( "?*${^fignore[@]}" )
if (( $#ignore )); then
_comp_ignore=( "$_comp_ignore[@]" "$ignore[@]" )
(( $mopts[(I)-F] )) || mopts=( "$mopts[@]" -F _comp_ignore )
fi
(( $#matcher )) && mopts=( "$mopts[@]" "$matcher[@]" )
if zstyle -s ":completion:${curcontext}:files" file-sort tmp1; then
case "$tmp1" in
*size*) sort=oL;;
*links*) sort=ol;;
*(time|date|modi)*) sort=om;;
*access*) sort=oa;;
*(inode|change)*) sort=oc;;
*) sort=on;;
esac
[[ "$tmp1" = *rev* ]] && sort[1]=O
if [[ "$sort" = on ]]; then
sort=
else
mopts=( "${(@)mopts/#-J/-V}" )
tmp2=()
for tmp1 in "$pats[@]"; do
if [[ "$tmp1" = (#b)(?*)(\(\([^\|~]##\)\)) ]]; then
tmp2=( "$tmp2[@]" "${match[1]}((${sort}${match[2][3,-1]}" )
elif [[ "$tmp1" = (#b)(?*)(\([^\|~]##\)) ]]; then
tmp2=( "$tmp2[@]" "${match[1]}(${sort}${match[2][2,-1]}" )
else
tmp2=( "$tmp2[@]" "${tmp1}(${sort})" )
fi
done
pats=( "$tmp2[@]" )
fi
fi
# Check if we have to skip over sequences of slashes. The value of $skips
# is used below to match the pathname components we always have to accept
# immediatly.
if zstyle -t ":completion:${curcontext}:paths" squeeze-slashes; then
skips='((.|..|)/)##'
else
skips='((.|..)/)##'
fi
zstyle -s ":completion:${curcontext}:paths" special-dirs sdirs
[[ "$pats" = ((|*[[:blank:]])\*(|[[:blank:]]*)|*\([^[:blank:]]#/[^[:blank:]]#\)*) ]] &&
sopt=$sopt/
zstyle -a ":completion:${curcontext}:files" accept-exact accex
zstyle -a ":completion:${curcontext}:files" fake fake
zstyle -s ":completion:${curcontext}:files" ignore-parents ignpar
if [[ -n "$compstate[pattern_match]" &&
( ( -z "$SUFFIX" && "$PREFIX" = *\([^\|\~]##\) ) ||
"$SUFFIX" = *\([^\|\~]##\) ) ]]; then
# Copy all glob qualifiers from the line to
# the patterns used when generating matches
if [[ "$SUFFIX" = *\([^\|\~]##\) ]]; then
tmp3="${${(M)SUFFIX%\([^\|\~]##\)}[2,-2]}"
SUFFIX="${SUFFIX%\($tmp3\)}"
else
tmp3="${${(M)PREFIX%\([^\|\~]##\)}[2,-2]}"
PREFIX="${PREFIX%\($tmp3\)}"
fi
tmp2=()
for tmp1 in "$pats[@]"; do
if [[ "$tmp1" = (#b)(?*)(\(\([^\|~]##\)\)) ]]; then
tmp2=( "$tmp2[@]" "${match[1]}((${tmp3}${match[2][3,-1]}" )
elif [[ "$tmp1" = (#b)(?*)(\([^\|~]##\)) ]]; then
tmp2=( "$tmp2[@]" "${match[1]}(${tmp3}${match[2][2,-1]}" )
else
tmp2=( "$tmp2[@]" "${tmp1}(${tmp3})" )
fi
done
pats=( "$tmp2[@]" )
fi
# We get the prefix and the suffix from the line and save the whole
# original string. Then we see if we will do menucompletion.
pre="$PREFIX"
suf="$SUFFIX"
opre="$PREFIX"
osuf="$SUFFIX"
orig="${PREFIX}${SUFFIX}"
eorig="$orig"
[[ $compstate[insert] = (*menu|[0-9]*) || -n "$_comp_correct" ||
( -n "$compstate[pattern_match]" &&
"${orig#\~}" != (|*[^\\])[][*?#~^\|\<\>]* ) ]] && menu=yes
[[ -n "$_comp_correct" ]] && cfopt=-
# Now let's have a closer look at the string to complete.
if [[ "$pre[1]" = \~ && -z "$compstate[quote]" ]]; then
# It begins with `~', so remember anything before the first slash to be able
# to report it to the completion code. Also get an expanded version of it
# (in `realpath'), so that we can generate the matches. Then remove that
# prefix from the string to complete, set `donepath' to build the correct
# paths and make sure that the loop below is run only once with an empty
# prefix path by setting `prepaths'.
linepath="${pre[2,-1]%%/*}"
if [[ -z "$linepath" ]]; then
realpath="${HOME%/}/"
elif (( $+userdirs[$linepath] )); then
realpath="${userdirs[$linepath]%/}/"
elif (( $+nameddirs[$linepath] )); then
realpath="${nameddirs[$linepath]%/}/"
elif [[ "$linepath" = ([-+]|)[0-9]## ]]; then
if [[ "$linepath" != [-+]* ]]; then
if [[ -o pushdminus ]]; then
linepath="-$linepath"
else
linepath="+$linepath"
fi
fi
if [[ "$linepath" = -* ]]; then
tmp=$(( $#dirstack $linepath ))
else
tmp=$linepath[2,-1]
fi
[[ -o pushdminus ]] && tmp=$(( $#dirstack - $tmp ))
if (( ! tmp )); then
realpath=$PWD/
elif [[ tmp -le $#dirstack ]]; then
realpath=$dirstack[tmp]/
else
_message 'not enough directory stack entries'
return 1
fi
elif [[ "$linepath" = [-+] ]]; then
realpath=${~:-\~$linepath}/
else
_message "unknown user \`$linepath'"
return 1
fi
linepath="~${linepath}/"
[[ "$realpath" = "$linepath" ]] && return 1
pre="${pre#*/}"
orig="${orig#*/}"
donepath=
prepaths=( '' )
elif [[ "$pre" = *\$*/* && "$compstate[quote]" != \" ]]; then
# If there is a parameter expansion in the word from the line, we try
# to complete the beast by expanding the prefix and completing anything
# after the first slash after the parameter expansion.
# This fails for things like `f/$foo/b/<TAB>' where the first `f' is
# meant as a partial path.
linepath="${(M)pre##*\$[^/]##/}"
realpath=${(e)~linepath}
[[ "$realpath" = "$linepath" ]] && return 1
pre="${pre#${linepath}}"
i="${#linepath//[^\\/]}"
orig="${orig[1,(in:i:)/][1,-2]}"
donepath=
prepaths=( '' )
else
# If the string does not start with a `~' we don't remove a prefix from the
# string.
linepath=
realpath=
if [[ "$pre[1]" = / ]]; then
# If it is a absolute path name, we remove the first slash and put it in
# `donepath' meaning that we treat it as the path that was already handled.
# Also, we don't use the paths from `-W'.
pre="$pre[2,-1]"
orig="$orig[2,-1]"
donepath='/'
prepaths=( '' )
else
# The common case, we just use the string as it is, unless it begins with
# `./' or `../' in which case we don't use the paths from `-W'.
[[ "$pre" = (.|..)/* ]] && prepaths=( '' )
donepath=
fi
fi
# Now we generate the matches. First we loop over all prefix paths given
# with the `-W' option.
for prepath in "$prepaths[@]"; do
# Get local copies of the prefix, suffix, and the prefix path to use
# in the following loop, which walks through the pathname components
# in the string from the line.
skipped=
cpre=
tpre="$pre"
tsuf="$suf"
testpath="$donepath"
tmp2="${(M)tpre##${~skips}}"
tpre="${tpre#$tmp2}"
tmp1=( "$prepath$realpath$donepath$tmp2" )
while true; do
# Get the prefix and suffix for matching.
if [[ "$tpre" = */* ]]; then
PREFIX="${tpre%%/*}"
SUFFIX=
else
PREFIX="${tpre}"
SUFFIX="${tsuf%%/*}"
fi
# Get the matching files by globbing.
if [[ "$tpre$tsuf" = */* ]]; then
compfiles -P$cfopt tmp1 accex "$skipped" "$_matcher" "$sdirs" fake
elif [[ "$sopt" = *[/f]* ]]; then
compfiles -p$cfopt tmp1 accex "$skipped" "$_matcher" "$sdirs" fake "$pats[@]"
else
compfiles -p$cfopt tmp1 accex "$skipped" "$_matcher" '' fake "$pats[@]"
fi
eval 'tmp1=( $~tmp1 )' 2>/dev/null
if [[ -n "$PREFIX$SUFFIX" ]]; then
# See which of them match what's on the line.
if [[ "$tmp1[1]" = */* ]]; then
if [[ -n "$_comp_correct" ]]; then
tmp2=( "$tmp1[@]" )
builtin compadd -D tmp1 -F _comp_ignore "$matcher[@]" - "${(@)tmp1:t}"
if [[ $#tmp1 -eq 0 ]]; then
tmp1=( "$tmp2[@]" )
compadd -D tmp1 -F _comp_ignore "$matcher[@]" - "${(@)tmp2:t}"
fi
else
tmp2=( "$tmp1[@]" )
compadd -D tmp1 -F _comp_ignore "$matcher[@]" - "${(@)tmp1:t}"
fi
else
tmp2=( '' )
compadd -D tmp1 -F _comp_ignore "$matcher[@]" -a tmp1
fi
# If no file matches, save the expanded path and continue with
# the outer loop.
if (( ! $#tmp1 )); then
if [[ "$tmp2[1]" = */* ]]; then
tmp2=( "${(@)tmp2#${prepath}${realpath}}" )
if [[ "$tmp2[1]" = */* ]]; then
tmp2=( "${(@)tmp2:h}" )
compquote tmp2
if [[ "$tmp2" = */ ]]; then
exppaths=( "$exppaths[@]" ${^tmp2}${tpre}${tsuf} )
else
exppaths=( "$exppaths[@]" ${^tmp2}/${tpre}${tsuf} )
fi
else
exppaths=( "$exppaths[@]" ${tpre}${tsuf} )
fi
fi
continue 2
fi
elif (( ! $#tmp1 )); then
# A little extra hack: if we were completing `foo/<TAB>' and `foo'
# contains no files, this will normally produce no matches and other
# completers might think that's it's their time now. But if the next
# completer is _correct or something like that, this will result in
# an attempt to correct a valid directory name. So we just add the
# original string in such a case so that the command line doesn't
# change but other completers still think there are matches.
# We do this only if we weren't given a `-g' or `-/' option because
# otherwise this would keep `_files' from completing all filenames
# if none of the patterns match.
if [[ -z "$tpre$tsuf" && -n "$pre$suf" ]]; then
pfxsfx=(-S '' "$pfxsfx[@]")
### Don't remember what the break was good for. We explicitly
### execute this only when there are no matches in the directory,
### so why continue?
###
### tmp1=( "$tmp2[@]" )
### break
elif [[ -n "$haspats" && -z "$tpre$tsuf$suf" && "$pre" = */ ]]; then
PREFIX="${opre}"
SUFFIX="${osuf}"
compadd -nQS '' - "$linepath$donepath$orig"
tmp4=-
fi
continue 2
fi
if [[ -n "$ignpar" && -z "$_comp_no_ignore" &&
"$tpre$tsuf" != */* && $#tmp1 -ne 0 &&
( "$ignpar" != *dir* || "$pats" = '*(-/)' ) &&
( "$ignpar" != *..* || "$tmp1[1]" = *../* ) ]]; then
compfiles -i tmp1 _comp_ignore "$ignpar" "$prepath$realpath$donepath"
(( $#_comp_ignore && $mopts[(I)-F] )) ||
mopts=( "$mopts[@]" -F _comp_ignore )
fi
# Step over to the next component, if any.
if [[ "$tpre" = */* ]]; then
tpre="${tpre#*/}"
elif [[ "$tsuf" = */* ]]; then
tpre="${tsuf#*/}"
tsuf=
else
break
fi
# There are more components, so skip over the next components and make a
# slash be added.
tmp2="${(M)tpre##((.|..|)/)##}"
if [[ -n "$tmp2" ]]; then
skipped="/$tmp2"
tpre="${tpre#$tmp2}"
else
skipped=/
fi
done
# The next loop searches the first ambiguous component.
tmp3="$pre$suf"
tpre="$pre"
tsuf="$suf"
[[ -n "${prepath}${realpath}${testpath}" ]] &&
tmp1=( "${(@)tmp1#${prepath}${realpath}${testpath}}" )
while true; do
# First we check if some of the files match the original string
# for this component. If there are some we remove all other
# names. This avoids having `foo' complete to `foo' and `foobar'.
# The return value is non-zero if the component is ambiguous.
compfiles -r tmp1 "$tmp3"
tmp4=$?
if [[ "$tpre" = */* ]]; then
tmp2="${cpre}${tpre%%/*}"
PREFIX="${donepath}${linepath}${tmp2}"
SUFFIX="/${tpre#*/}${tsuf#*/}"
else
tmp2="${cpre}${tpre}"
PREFIX="${donepath}${linepath}${tmp2}"
SUFFIX="${tsuf}"
fi
if (( tmp4 )) ||
[[ -n "$compstate[pattern_match]" &&
"$tmp2" = (|*[^\\])[][*?#~^\|\<\>]* ]]; then
# It is. For menucompletion we now add the possible completions
# for this component with the unambigous prefix we have built
# and the rest of the string from the line as the suffix.
# For normal completion we add the rests of the filenames
# collected as the suffixes to make the completion code expand
# it as far as possible.
tmp2="$testpath"
compquote tmp1 tmp2
if [[ -n $menu || -z "$compstate[insert]" ]] ||
! zstyle -t ":completion:${curcontext}:paths" expand suffix; then
(( tmp4 )) && zstyle -t ":completion:${curcontext}:paths" ambiguous &&
compstate[to_end]=
if [[ "$tmp3" = */* ]]; then
compadd -Qf "$mopts[@]" -p "$linepath$tmp2" -s "/${tmp3#*/}" \
-W "$prepath$realpath$testpath" \
"$pfxsfx[@]" -M "r:|/=* r:|=*" \
- "${(@)tmp1%%/*}"
else
compadd -Qf "$mopts[@]" -p "$linepath$tmp2" \
-W "$prepath$realpath$testpath" \
"$pfxsfx[@]" -M "r:|/=* r:|=*" \
-a tmp1
fi
else
if [[ "$tmp3" = */* ]]; then
tmp3=( -Qf "$mopts[@]" -p "$linepath$tmp2"
-W "$prepath$realpath$testpath"
"$pfxsfx[@]" -M "r:|/=* r:|=*" )
for i in "$tmp1[@]"; do
compadd "$tmp3[@]" -s "/${i#*/}" - "${i%%/*}"
done
else
compadd -Qf "$mopts[@]" -p "$linepath$tmp2" \
-W "$prepath$realpath$testpath" \
"$pfxsfx[@]" -M "r:|/=* r:|=*" \
-a tmp1
fi
fi
tmp4=-
break
fi
# If we have checked all components, we stop now and add the
# strings collected after the loop.
if [[ "$tmp3" != */* ]]; then
tmp4=
break
fi
# Otherwise we add the unambiguous component to `testpath' and
# take it from the filenames.
testpath="${testpath}${tmp1[1]%%/*}/"
tmp1=( "${(@)tmp1#*/}" )
tmp3="${tmp3#*/}"
if [[ "$tpre" = */* ]]; then
cpre="${cpre}${tpre%%/*}/"
tpre="${tpre#*/}"
elif [[ "$tsuf" = */* ]]; then
[[ "$tsuf" != /* ]] && mid="$testpath"
cpre="${cpre}${tpre}/"
tpre="${tsuf#*/}"
tsuf=
else
tpre=
tsuf=
fi
done
if [[ -z "$tmp4" ]]; then
if [[ "$mid" = */ ]]; then
PREFIX="${opre}"
SUFFIX="${osuf}"
tmp4="${testpath#${mid}}"
tmp3="${mid%/*/}"
tmp2="${${mid%/}##*/}"
compquote tmp4 tmp3 tmp2 tmp1
for i in "$tmp1[@]"; do
compadd -Qf "$mopts[@]" -p "$linepath$tmp3/" -s "/$tmp4$i" \
-W "$prepath$realpath${mid%/*/}/" \
"$pfxsfx[@]" -M "r:|/=* r:|=*" - "$tmp2"
done
else
if [[ "$osuf" = */* ]]; then
PREFIX="${opre}${osuf}"
SUFFIX=
else
PREFIX="${opre}"
SUFFIX="${osuf}"
fi
tmp4="$testpath"
compquote tmp4 tmp1
compadd -Qf "$mopts[@]" -p "$linepath$tmp4" -W "$prepath$realpath$testpath" \
"$pfxsfx[@]" -M "r:|/=* r:|=*" -a tmp1
fi
fi
done
# If we are configured to expand paths as far as possible and we collected
# expanded paths that are different from the string on the line, we add
# them as possible matches.
if zstyle -t ":completion:${curcontext}:paths" expand prefix &&
[[ nm -eq compstate[nmatches] && $#exppaths -ne 0 &&
"$exppaths" != "$eorig" ]]; then
PREFIX="${opre}"
SUFFIX="${osuf}"
compadd -Q "$mopts[@]" -S '' -M "r:|/=* r:|=*" -p "$linepath" -a exppaths
fi
[[ nm -ne compstate[nmatches] ]]