1
0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-10-03 17:31:33 +02:00
zsh/Completion/Core/_path_files
2000-05-25 12:01:37 +00:00

587 lines
17 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 remt sopt gopt opt sdirs ignpar
local nm=$compstate[nmatches] menu matcher mopts sort match mid
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 &&
[[ "$sdirs" = (yes|true|on|1) ]] && sdirs=yes
[[ "$pats" = ((|*[[:blank:]])\*(|[[:blank:]]*)|*\([^[:blank:]]#/[^[:blank:]]#\)*) ]] &&
sopt=$sopt/
zstyle -s ":completion:${curcontext}:files" ignore-parents ignpar
# 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#\~}" != "${${orig#\~}:q}" ) ]] && menu=yes
# 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 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.
tmp2=( "$tmp1[@]" )
if [[ "$tpre$tsuf" = */* ]]; then
if [[ ! -o globdots && "$PREFIX" = .* ]]; then
tmp1=( ${^tmp1}${skipped}*(-/) ${^tmp1}${skipped}.*(-/) )
else
tmp1=( ${^tmp1}${skipped}*(-/) )
fi
if [[ -n "$sdirs" && ( -o globdots || "$PREFIX" = .* ) ]]; then
if [[ "$sdirs" = yes ]]; then
tmp1=( "$tmp1[@]" $^tmp2${skipped}{.,..} )
elif [[ "$sdirs" = .. ]]; then
tmp1=( "$tmp1[@]" $^tmp2${skipped}.. )
fi
fi
else
if [[ ! -o globdots && "$PREFIX" = .* ]]; then
tmp1=( ${^tmp1}${skipped}${^~pats} ${^tmp1}${skipped}.${^~pats:#.*} )
else
tmp1=( ${^tmp1}${skipped}${^~pats} )
fi
if [[ -n "$sdirs" && "$sopt" = *[/f]* &&
( -o globdots || "$PREFIX" = .* ) ]]; then
if [[ "$sdirs" = yes ]]; then
tmp1=( "$tmp1[@]" $^tmp2${skipped}{.,..} )
elif [[ "$sdirs" = .. ]]; then
tmp1=( "$tmp1[@]" $^tmp2${skipped}.. )
fi
fi
fi
if [[ -n "$PREFIX$SUFFIX" ]]; then
# See which of them match what's on the line.
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
[[ "$tmp1[1]" = */* ]] && tmp2=( "$tmp1[@]" )
builtin compadd -D tmp1 -F _comp_ignore "$matcher[@]" - "${(@)tmp1:t}"
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" = *../* ) ]]; then
if [[ "$ignpar" = *parent* ]]; then
for i in ${(M)^tmp1:#*/*}(-/); do
remt="${${i#$prepath$realpath$donepath}%/*}"
while [[ "$remt" = */* &&
! "$prepath$realpath$donepath$remt" -ef "$i" ]]; do
remt="${remt%/*}"
done
[[ "$remt" = */* || "$remt" -ef "$i" ]] &&
_comp_ignore=( "$_comp_ignore[@]" "${(q)i}" )
done
fi
if [[ "$ignpar" = *pwd* ]]; then
for i in ${^tmp1}(-/); do
[[ "$i" -ef "$PWD" ]] && _comp_ignore=( "$_comp_ignore[@]" "${(q)i}" )
done
fi
(( $#_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"
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'.
if [[ "$tmp3" = */* ]]; then
tmp4=( "${(@M)tmp1:#${tmp3%%/*}/*}" )
(( $#tmp4 )) && tmp1=( "$tmp4[@]" )
fi
# Next we see if this component is ambiguous.
if [[ "$tmp3" = */* ]]; then
tmp4=$tmp1[(I)^${${tmp1[1]%%/*}//(#b)([][\\<>(|)^#~*?])/\\$match[1]}/*]
else
tmp4=$tmp1[(I)^${tmp1[1]//(#b)([][\\<>(|)^#~*?])/\\$match[1]}]
fi
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" != "${(q)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:|=*" \
- "$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:|=*" \
- "$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:|=*" - "$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" - "$exppaths[@]"
fi
[[ nm -ne compstate[nmatches] ]]