mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-09-19 03:31:14 +02:00
434 lines
12 KiB
Text
434 lines
12 KiB
Text
#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
|
|
local tmp1 tmp2 tmp3 tmp4 i orig pre suf tpre tsuf opre osuf cpre
|
|
local pats haspats=no ignore group expl addpfx addsfx remsfx
|
|
local nm=$compstate[nmatches] menu match matcher
|
|
|
|
typeset -U prepaths exppaths
|
|
|
|
setopt localoptions nullglob rcexpandparam
|
|
unsetopt markdirs globsubst shwordsplit nounset
|
|
|
|
local sopt='-' gopt='' opt
|
|
exppaths=()
|
|
prepaths=('')
|
|
ignore=()
|
|
group=()
|
|
pats=()
|
|
addpfx=()
|
|
addsfx=()
|
|
remsfx=()
|
|
expl=()
|
|
matcher=()
|
|
|
|
# Get the options.
|
|
|
|
while getopts "P:S:qr:R:W:F:J:V:X:f/g:M:" opt; do
|
|
case "$opt" in
|
|
P) addpfx=(-P "$OPTARG")
|
|
;;
|
|
S) addsfx=(-S "$OPTARG")
|
|
;;
|
|
q) tmp1=yes
|
|
;;
|
|
[rR]) remsfx=("-$opt" "$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[@]" '*(-/)')
|
|
haspats=yes
|
|
;;
|
|
g) gopt='-g'
|
|
pats=("$pats[@]" ${=OPTARG})
|
|
haspats=yes
|
|
;;
|
|
M) match="$OPTARG"
|
|
matcher=(-M "$OPTARG")
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if (( ! ( $#group + $#expl ) )); then
|
|
if [[ "$sopt" = -/ ]]; then
|
|
_description expl directory
|
|
else
|
|
_description expl file
|
|
fi
|
|
fi
|
|
|
|
[[ -n "$tmp1" && $#addsfx -ne 0 ]] && addsfx[1]=-qS
|
|
|
|
# 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
|
|
|
|
# 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}"
|
|
|
|
[[ $compstate[insert] = (*menu|[0-9]*) || -n "$_comp_correct" ||
|
|
( $#compstate[pattern_match] -ne 0 &&
|
|
"${orig#\~}" != "${${orig#\~}:q}" ) ]] && menu=yes
|
|
|
|
# If given no `-F' option, we want to use `fignore'.
|
|
|
|
(( $#ignore )) || ignore=(-F fignore)
|
|
|
|
# Now let's have a closer look at the string to complete.
|
|
|
|
if [[ "$pre[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="${pre%%/*}/"
|
|
realpath=$~linepath
|
|
[[ "$realpath" = "$linepath" ]] && return 1
|
|
pre="${pre#*/}"
|
|
orig="${orig#*/}"
|
|
donepath=''
|
|
prepaths=( '' )
|
|
elif [[ "$pre" = *\$*/* ]]; 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 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'.
|
|
|
|
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.
|
|
|
|
tpre="$pre"
|
|
tsuf="$suf"
|
|
testpath="$donepath"
|
|
|
|
tmp1=( "$prepath$realpath$donepath" )
|
|
|
|
while true; do
|
|
|
|
# Skip over `./' and `../'.
|
|
|
|
if [[ "$tpre" = (.|..)/* ]]; then
|
|
tmp1=( ${^tmp1}${tpre%%/*}/ )
|
|
tpre="${tpre#*/}"
|
|
continue
|
|
fi
|
|
|
|
# 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
|
|
tmp2=( ${^tmp1}*(-/) )
|
|
[[ ! -o globdots && "$PREFIX" = .* ]] &&
|
|
tmp2=( "$tmp1[@]" ${^tmp1}.*(-/) )
|
|
else
|
|
tmp2=( ${^tmp1}${^~pats} )
|
|
[[ ! -o globdots && "$PREFIX" = .* ]] &&
|
|
tmp2=( "$tmp1[@]" ${^tmp1}.${^~pats} )
|
|
fi
|
|
tmp1=( "$tmp2[@]" )
|
|
|
|
if [[ -n "$PREFIX$SUFFIX" ]]; then
|
|
# See which of them match what's on the line.
|
|
|
|
tmp2=("$tmp1[@]")
|
|
compadd -D tmp1 "$ignore[@]" "$matcher[@]" - "${(@)tmp1:t}"
|
|
|
|
# If no file matches, save the expanded path and continue with
|
|
# the outer loop.
|
|
|
|
if [[ $#tmp1 -eq 0 ]]; then
|
|
if [[ "$tmp2[1]" = */* ]]; then
|
|
tmp2=( "${(@)tmp2#${prepath}${realpath}}" )
|
|
if [[ "$tmp2[1]" = */* ]]; then
|
|
tmp2=( "${(@)tmp2:h}" )
|
|
compquote tmp2
|
|
exppaths=( "$exppaths[@]" ${^tmp2}/${tpre}${tsuf} )
|
|
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
|
|
tmp1=( "$tmp2[@]" )
|
|
addsfx=(-S '')
|
|
remsfx=()
|
|
break;
|
|
elif [[ "$haspats" = no && -z "$tpre$tsuf" &&
|
|
"$pre" = */ && -z "$suf" ]]; then
|
|
PREFIX="${opre}"
|
|
SUFFIX="${osuf}"
|
|
compadd -nQS '' - "$linepath$donepath$orig"
|
|
tmp4=-
|
|
fi
|
|
continue 2
|
|
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 add a slash to the files we are
|
|
# collecting.
|
|
|
|
tmp1=( ${^tmp1}/ )
|
|
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%%/*}/*}" )
|
|
if (( $#tmp4 )); then
|
|
tmp1=( "$tmp4[@]" )
|
|
fi
|
|
fi
|
|
|
|
# Next we see if this component is ambiguous.
|
|
|
|
if [[ "$tmp3" = */* ]]; then
|
|
tmp4=( "${(@)tmp1:#${tmp1[1]%%/*}/*}" )
|
|
else
|
|
tmp4=( "${(@)tmp1:#${tmp1[1]}}" )
|
|
fi
|
|
|
|
if (( $#tmp4 )); 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.
|
|
|
|
if [[ "$tpre" = */* ]]; then
|
|
PREFIX="${donepath}${linepath}${cpre}${tpre%%/*}"
|
|
SUFFIX="/${tsuf#*/}"
|
|
else
|
|
PREFIX="${donepath}${linepath}${cpre}${tpre}"
|
|
SUFFIX="${tsuf}"
|
|
fi
|
|
|
|
tmp4="$testpath"
|
|
compquote tmp1 tmp4
|
|
|
|
if [[ -n $menu || "$compconfig[path_expand]" != *suffix* ]]; then
|
|
[[ -n "$compconfig[path_cursor]" ]] && compstate[to_end]=''
|
|
if [[ "$tmp3" = */* ]]; then
|
|
compadd -Qf -p "$linepath$tmp4" -s "/${tmp3#*/}" \
|
|
-W "$prepath$realpath$testpath" "$ignore[@]" \
|
|
"$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \
|
|
-M "r:|/=* r:|=* $match" "$group[@]" "$expl[@]" \
|
|
- "${(@)tmp1%%/*}"
|
|
else
|
|
compadd -Qf -p "$linepath$tmp4" \
|
|
-W "$prepath$realpath$testpath" "$ignore[@]" \
|
|
"$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \
|
|
-M "r:|/=* r:|=* $match" "$group[@]" "$expl[@]" \
|
|
- "$tmp1[@]"
|
|
fi
|
|
else
|
|
if [[ "$tmp3" = */* ]]; then
|
|
for i in "$tmp1[@]"; do
|
|
compadd -Qf -p "$linepath$tmp4" -s "/${i#*/}" \
|
|
-W "$prepath$realpath$testpath" "$ignore[@]" \
|
|
"$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \
|
|
-M "r:|/=* r:|=* $match" "$group[@]" "$expl[@]" \
|
|
- "${i%%/*}"
|
|
done
|
|
else
|
|
compadd -Qf -p "$linepath$tmp4" \
|
|
-W "$prepath$realpath$testpath" "$ignore[@]" \
|
|
"$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \
|
|
-M "r:|/=* r:|=* $match" "$group[@]" "$expl[@]" \
|
|
- "$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
|
|
cpre="${cpre}${tpre}/"
|
|
tpre="${tsuf#*/}"
|
|
tsuf=""
|
|
else
|
|
tpre=""
|
|
tsuf=""
|
|
fi
|
|
done
|
|
|
|
if [[ -z "$tmp4" ]]; then
|
|
if [[ "$osuf" = */* ]]; then
|
|
PREFIX="${opre}${osuf}"
|
|
SUFFIX=""
|
|
else
|
|
PREFIX="${opre}"
|
|
SUFFIX="${osuf}"
|
|
fi
|
|
tmp4="$testpath"
|
|
compquote tmp4 tmp1
|
|
compadd -Qf -p "$linepath$tmp4" \
|
|
-W "$prepath$realpath$testpath" "$ignore[@]" \
|
|
"$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \
|
|
-M "r:|/=* r:|=* $match" "$group[@]" "$expl[@]" \
|
|
- "$tmp1[@]"
|
|
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.
|
|
|
|
exppaths=( "${(@)exppaths:#$orig}" )
|
|
|
|
if [[ "$compconfig[path_expand]" = *prefix* &&
|
|
$#exppaths -gt 0 && nm -eq compstate[nmatches] ]]; then
|
|
PREFIX="${opre}"
|
|
SUFFIX="${osuf}"
|
|
compadd -Q -S '' "$group[@]" "$expl[@]" \
|
|
-M "r:|/=* r:|=* $match" -p "$linepath" - "$exppaths[@]"
|
|
fi
|
|
|
|
[[ nm -ne compstate[nmatches] ]]
|