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
1999-04-15 18:05:38 +00:00

312 lines
10 KiB
Plaintext

#autoload
# Utility function for in-path completion.
# Supported arguments are: `-f', `-/', `-g <patterns>', `-J <group>',
# `-V <group>', `-W paths', `-X explanation', and `-F <ignore>'. All but
# the last have the same syntax and meaning as for `complist'. The
# `-F <ignore>' option may be used to give a list of suffixes either by
# giving the name of an array or literally by giving them in a string
# surrounded by parentheses. Files with one of the suffixes thus given
# are treated like files with one of the suffixes in the `fignore' array
# in normal completion.
#
# This function uses the helper functions `_match_test' and `_match_pattern'.
# First see if we should generate matches for the global matcher in use.
_match_test _path_files || return
# Yes, so...
local nm prepaths str linepath realpath donepath patstr prepath testpath rest
local tmp1 collect tmp2 suffixes i ignore matchflags opt group sopt pats gopt
local addpfx addsfx expl
setopt localoptions nullglob rcexpandparam globdots extendedglob
unsetopt markdirs globsubst shwordsplit nounset
prepaths=('')
ignore=()
group=()
sopt='-'
gopt=''
pats=()
addpfx=()
addsfx=()
expl=()
# Get the options.
while getopts "P:S:W:F:J:V:X:f/g:" opt; do
case "$opt" in
P) addpfx=(-P "$OPTARG")
;;
S) addsfx=(-S "$OPTARG")
;;
W) tmp1="$OPTARG"
if [[ "$tmp1[1]" = '(' ]]; then
prepaths=( ${^=tmp1[2,-2]}/ )
else
prepaths=( ${(P)=${tmp1}} )
(( ! $#prepaths )) && prepaths=( ${tmp1}/ )
fi
(( ! $#prepaths )) && prepaths=( '' )
;;
F) tmp1="$OPTARG"
if [[ "$tmp1[1]" = '(' ]]; then
ignore=( ${^=tmp1[2,-2]}/ )
else
ignore=( ${(P)${tmp1}} )
fi
(( $#ignore )) && ignore=(-F "( $ignore )")
;;
[JV]) group=("-$opt" "$OPTARG")
;;
X) expl=(-X "$OPTARG")
;;
f) sopt="${sopt}f"
pats=("$pats[@]" '*')
;;
/) sopt="${sopt}/"
pats=("$pats[@]" '*(-/)')
;;
g) gopt='-g'
pats=("$pats[@]" ${=OPTARG})
;;
esac
done
# If we were given no file selection option, we behave as if we were given
# a `-f'.
if [[ "$sopt" = - ]]; then
if [[ -z "$gopt" ]]; then
sopt='-f'
pats=('*')
else
unset sopt
fi
fi
# str holds the whole string from the command line with a `*' between
# the prefix and the suffix.
str="${PREFIX:q}*${SUFFIX:q}"
# If the string began with a `~', the quoting turned this into `\~',
# remove the slash.
[[ "$str" = \\\~* ]] && str="$str[2,-1]"
# We will first try normal completion called with `complist', but only if we
# weren't given a `-F' option.
if (( ! $#ignore )); then
# First build an array containing the `-W' option, if there is any and we
# want to use it. We don't want to use it if the string from the command line
# is a absolute path or relative to the current directory.
if [[ -z "$tmp1[1]" || "$str[1]" = [~/] || "$str" = (.|..)/* ]]; then
tmp1=()
else
tmp1=(-W "( $prepaths )")
fi
# Now call complist.
nm=$NMATCHES
if [[ -z "$gopt" ]]; then
complist "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" "$tmp1[@]" $sopt
else
complist "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" "$tmp1[@]" $sopt -g "$pats"
fi
# If this generated any matches, we don't want to do in-path completion.
[[ -nmatches nm ]] || return
# No `-F' option, so we want to use `fignore'.
ignore=(-F fignore)
fi
# Now let's have a closer look at the string to complete.
if [[ "$str[1]" = \~ ]]; 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="${str%%/*}/"
eval realpath\=$linepath
str="${str#*/}"
donepath=''
prepaths=( '' )
else
# If the string does not start with a `~' we don't remove a prefix from the
# string.
liniepath=''
realpath=''
if [[ "$str[1]" = / ]]; then
# If it is a absolut 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'.
str="$str[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'.
[[ "$str" = (.|..)/* ]] && prepaths=( '' )
donepath=''
fi
fi
# First we skip over all pathname components in `str' which really exist in
# the file-system, so that `/usr/lib/l<TAB>' doesn't offer you `lib' and
# `lib5'. Pathname components skipped this way are taken from `str' and added
# to `donepath'.
while [[ "$str" = */* ]] do
[[ -e "$realpath$donepath${str%%/*}" ]] || break
donepath="$donepath${str%%/*}/"
str="${str#*/}"
done
# Now build the glob pattern by calling `_match_pattern'.
patstr="$str"
matchflags=""
_match_pattern _path_files patstr matchflags
# We almost expect the pattern to have changed `..' into `*.*.', `/.' into
# `/*.', and probably to contain two or more consecutive `*'s. Since these
# have special meaning for globbing, we remove them. But before that, we
# add the pattern for matching any characters before a slash.
patstr="$patstr:gs-/-*/-:gs/*.*.//:gs-/*.-/.-:gs/**/*/"
# Finally, generate the matches. First we loop over all the paths from `-W'.
# Note that in this loop `str' is used as a modifyable version of `patstr'
# and `testpath' is a modifyable version of `donepath'.
for prepath in "$prepaths[@]"; do
str="$patstr"
testpath="$donepath"
# The second loop tests the components of the path in `str' to get the
# possible matches.
while [[ "$str" = */* ]] do
# `rest' is the pathname after the first slash that is left. In `tmp1'
# we get the globbing matches for the pathname component currently
# handled.
rest="${str#*/}"
tmp1="${prepath}${realpath}${testpath}${~matchflags}${str%%/*}(-/)"
tmp1=( $~tmp1 )
if [[ $#tmp1 -eq 0 ]]; then
# If this didn't produce any matches, we don't need to test this path
# any further, so continue with the next `-W' path, if any.
continue 2
elif [[ $#tmp1 -gt 1 ]]; then
# If it produced more than one match, we want to remove those which
# don't have possible following pathname components matching the
# rest of the string we are completing. (The case with only one
# match is handled below.)
# In `collect' we will collect those of the produced pathnames that
# have a matching possible path-suffix. In `suffixes' we build an
# array containing strings build from the rest of the string to
# complete and the glob patterns we were given as arguments.
collect=()
suffixes=( $rest$^pats )
suffixes=( "${(@)suffixes:gs.**.*.}" )
# In the loop the prefixes from the `tmp1' array produced above and
# the suffixes we just built are used to produce possible matches
# via globbing.
for i in $tmp1; do
tmp2=( ${~i}/${~matchflags}${~suffixes} )
[[ $#tmp2 -ne 0 ]] && collect=( $collect $i )
done
# If this test showed that none of the matches from the glob in `tmp1'
# has a possible sub-path matching what's on the line, we give up and
# continue with the next `-W' path.
if [[ $#collect -eq 0 ]]; then
continue 2
elif [[ $#collect -ne 1 ]]; then
# If we have more than one possible match, this means that the
# pathname component currently handled is ambiguous, so we give
# it to the completion code.
# First we build the full path prefix in `tmp1'.
tmp1="$prepath$realpath$testpath"
# Now produce all matching pathnames in `collect'.
collect=( ${~collect}/${~matchflags}${~suffixes} )
# And then remove the common path prefix from all these matches.
collect=( ${collect#$tmp1} )
# Finally, we add all these matches with the common (unexpanded)
# pathprefix (the `-p' option), the path-prefix (the `-W' option)
# to allow the completion code to test file type, and the path-
# suffix (the `-s' option). We also tell the completion code that
# these are file names and that `fignore' should be used as usual
# (the `-f' and `-F' options).
for i in $collect; do
compadd "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" -p "$linepath$testpath" -W "$tmp1" -s "/${i#*/}" -f "$ignore[@]" - "${i%%/*}"
done
# We have just finished handling all the matches from above, so we
# can continue with the next `-W' path.
continue 2
fi
# We reach this point if only one of the path prefixes in `tmp1'
# has a existing path-suffix matching the string from the line.
# In this case we accept this match and continue with the next
# path-name component.
tmp1=( "$collect[1]" )
fi
# This is also reached if the first globbing produced only one match
# in this case we just continue with the next pathname component, too.
tmp1="$tmp1[1]"
testpath="$testpath${tmp1##*/}/"
str="$rest"
done
# We are here if all pathname components except the last one (which is still
# not tested) are unambiguous. So we add matches with the full path prefix,
# no path suffix, the `-W' we are currently handling, all the matches we
# can produce in this directory, if any.
tmp1="$prepath$realpath$testpath"
suffixes=( $str$^pats )
suffixes=( "${(@)suffixes:gs.**.*.}" )
tmp2=( ${~tmp1}${~matchflags}${~suffixes} )
if [[ $#tmp2 -eq 0 && "$sopt" = */* ]]; then
[[ "$testpath[-1]" = / ]] && testpath="$testpath[1,-2]"
compadd "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" -f - "$linepath$testpath"
else
compadd "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" -p "$linepath$testpath" -W "$prepath$realpath$testpath" -f "$ignore[@]" - ${(@)tmp2#$tmp1}
fi
done