mirror of
git://git.code.sf.net/p/zsh/code
synced 2024-11-20 14:04:03 +01:00
312 lines
10 KiB
Plaintext
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
|