#autoload # Utility function for in-path completion. This allows `/u/l/b' # 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=no ignore pfxsfx rem remt sopt gopt opt local nm=$compstate[nmatches] menu matcher mopts atmp sort match 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 zstyle -s ":completion:${curcontext}:files" 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 # 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 # If given no `-F' option, we may want to use $fignore, turned into patterns. [[ $#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[@]" ) # 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[2,-1]%%/*}" if [[ -z "$linepath" ]]; then realpath="${HOME%/}/" elif (( $+userdirs[$linepath] )); then realpath="${userdirs[$linepath]%/}/" elif (( $+nameddirs[$linepath] )); then realpath="${nameddirs[$linepath]%/}/" else _message "unknown user \`$linepath'" return 1 fi linepath="~${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/' 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" 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 [[ -o globdots || "$PREFIX" = .* ]] && zstyle -s ":completion:${curcontext}:paths" special-dirs atmp; then if [[ "$atmp" = (yes|true|1|on) ]]; then tmp1=( "$tmp1[@]" . .. ) elif [[ "$atmp" = .. ]]; then tmp1=( "$tmp1[@]" .. ) fi fi else if [[ ! -o globdots && "$PREFIX" = .* ]]; then tmp1=( ${^tmp1}${skipped}${^~pats} ${^tmp1}${skipped}.${^~pats:#.*} ) else tmp1=( ${^tmp1}${skipped}${^~pats} ) fi if [[ "$sopt" = *[/f]* && ( -o globdots || "$PREFIX" = .* ) ]] && zstyle -s ":completion:${curcontext}:paths" special-dirs atmp; then if [[ "$atmp" = (yes|true|1|on) ]]; then tmp1=( "$tmp1[@]" . .. ) elif [[ "$atmp" = .. ]]; then tmp1=( "$tmp1[@]" .. ) 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/' 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 [[ "$haspats" = no && -z "$tpre$tsuf" && "$pre" = */ && -z "$suf" ]]; then PREFIX="${opre}" SUFFIX="${osuf}" compadd -nQS '' - "$linepath$donepath$orig" tmp4=- fi continue 2 fi if [[ "$tpre$tsuf" != */* && $#tmp1 -ne 0 ]] && zstyle -s ":completion:${curcontext}:files" ignore-parents rem && [[ ( "$rem" != *dir* || "$pats" = '*(-/)' ) && ( "$rem" != *..* || "$tmp1" = *../* ) ]]; then if [[ "$rem" = *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 [[ "$rem" = *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)^${(q)tmp1[1]%%/*}/*] else tmp4=$tmp1[(I)^${(q)tmp1[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" cursor && 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 atmp=( -Qf "$mopts[@]" -p "$linepath$tmp2" -W "$prepath$realpath$testpath" "$pfxsfx[@]" -M "r:|/=* r:|=*" ) for i in "$tmp1[@]"; do compadd "$atmp[@]" -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 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 "$mopts[@]" -p "$linepath$tmp4" -W "$prepath$realpath$testpath" \ "$pfxsfx[@]" -M "r:|/=* r:|=*" - "$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. if zstyle -t ":completion:${curcontext}:paths" expand prefix && [[ nm -eq compstate[nmatches] ]]; then exppaths=( "${(@)exppaths:#$eorig}" ) if (( $#exppaths )); then PREFIX="${opre}" SUFFIX="${osuf}" compadd -Q "$mopts[@]" -S '' -M "r:|/=* r:|=*" -p "$linepath" - "$exppaths[@]" fi fi [[ nm -ne compstate[nmatches] ]]