#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 ignore pfx pfxsfx sopt gopt opt sdirs ignpar cfopt listsfx local nm=$compstate[nmatches] menu matcher mopts sort match mid accex fake typeset -U prepaths exppaths exppaths=() # Get the options. zparseopts -a mopts \ 'P:=pfx' '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}" pats=( '*(-/)' ${${(z):-x $pats}[2,-1]} ) else pats="${(@)${(@M)tmp1:#-g*}#-g}" pats=( ${${(z):-x $pats}[2,-1]} ) fi pats=( "${(@)pats:# #}" ) if (( $#pfx )); then compset -P "$pfx[2]" || pfxsfx=( "$pfx[@]" "$pfxsfx[@]" ) fi 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}:" 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 # immediately. if zstyle -t ":completion:${curcontext}:paths" squeeze-slashes; then skips='((.|..|)/)##' else skips='((.|..)/)##' fi zstyle -s ":completion:${curcontext}:paths" special-dirs sdirs zstyle -t ":completion:${curcontext}:paths" list-suffixes && listsfx=yes [[ "$pats" = ((|*[[:blank:]])\*(|[[:blank:]]*|\([^[:blank:]]##\))|*\([^[:blank:]]#/[^[:blank:]]#\)*) ]] && sopt=$sopt/ zstyle -a ":completion:${curcontext}:paths" accept-exact accex zstyle -a ":completion:${curcontext}:" fake-files fake zstyle -s ":completion:${curcontext}:" ignore-parents ignpar if [[ -n "$compstate[pattern_match]" && ( ( -z "$SUFFIX" && "$PREFIX" = (|*[^\$])\([^\|\~]##\) ) || "$SUFFIX" = (|*[^\$])\([^\|\~]##\) ) ]]; then # Copy all glob qualifiers from the line to # the patterns used when generating matches if [[ "$SUFFIX" = *\([^\|\~]##\) ]]; then tmp3="${${(M)SUFFIX%\([^\|\~]##\)}[2,-2]}" SUFFIX="${SUFFIX%\($tmp3\)}" else tmp3="${${(M)PREFIX%\([^\|\~]##\)}[2,-2]}" PREFIX="${PREFIX%\($tmp3\)}" fi tmp2=() for tmp1 in "$pats[@]"; do if [[ "$tmp1" = (#b)(*[^\$])(\(\([^\|~]##\)\)) ]]; then tmp2=( "$tmp2[@]" "${match[1]}((${tmp3}${match[2][3,-1]}" ) elif [[ "$tmp1" = (#b)(*[^\$])(\([^\|~]##\)) ]]; then tmp2=( "$tmp2[@]" "${match[1]}(${tmp3}${match[2][2,-1]}" ) else tmp2=( "$tmp2[@]" "${tmp1}(${tmp3})" ) fi done pats=( "$tmp2[@]" ) fi # We get the prefix and the suffix from the line and save the whole # original string. Then we see if we will do menu completion. 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#\~}" != (|*[^\\])[][*?#~^\|\<\>]* ) ]] && menu=yes [[ -n "$_comp_correct" ]] && cfopt=- # Now let's have a closer look at the string to complete. if [[ "$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/' where the first `f' is # meant as a partial path. linepath="${(M)pre##*\$[^/]##/}" eval 'realpath=${(e)~linepath}' 2>/dev/null [[ -z "$realpath" || "$realpath" = "$linepath" ]] && return 1 pre="${pre#${linepath}}" i="${#linepath//[^\\/]}" orig="${orig[1,(in:i:)/][1,-2]}" donepath= prepaths=( '' ) elif [[ "$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 [[ "$linepath" = ([-+]|)[0-9]## ]]; then if [[ "$linepath" != [-+]* ]]; then tmp1="$linepath" else if [[ "$linepath" = -* ]]; then tmp1=$(( $#dirstack $linepath )) else tmp1=$linepath[2,-1] fi [[ -o pushdminus ]] && tmp1=$(( $#dirstack - $tmp1 )) fi if (( ! tmp1 )); then realpath=$PWD/ elif [[ tmp1 -le $#dirstack ]]; then realpath=$dirstack[tmp1]/ else _message 'not enough directory stack entries' return 1 fi elif [[ "$linepath" = [-+] ]]; then realpath=${~:-\~$linepath}/ else eval "realpath=~${linepath}/" 2>/dev/null if [[ -z "$realpath" ]]; then _message "unknown user \`$linepath'" return 1 fi fi linepath="~${linepath}/" [[ "$realpath" = "$linepath" ]] && return 1 pre="${pre#*/}" orig="${orig#*/}" donepath= prepaths=( '' ) else # If the string does not start with a `~' we don't remove a prefix from the # string. linepath= realpath= if zstyle -s ":completion:${curcontext}:" preserve-prefix tmp1 && [[ -n "$tmp1" && "$pre" = (#b)(${~tmp1})* ]]; then pre="$pre[${#match[1]}+1,-1]" orig="$orig[${#match[1]}+1,-1]" donepath="$match[1]" prepaths=( '' ) elif [[ "$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 # Force auto-mounting. There might be a better way... : ${^tmp1}/${PREFIX}${SUFFIX}/.(/) # Get the matching files by globbing. tmp2=( "$tmp1[@]" ) if [[ "$tpre$tsuf" = */* ]]; then compfiles -P$cfopt tmp1 accex "$skipped" "$_matcher $matcher[2]" "$sdirs" fake elif [[ "$sopt" = *[/f]* ]]; then compfiles -p$cfopt tmp1 accex "$skipped" "$_matcher $matcher[2]" "$sdirs" fake "$pats[@]" else compfiles -p$cfopt tmp1 accex "$skipped" "$_matcher $matcher[2]" '' fake "$pats[@]" fi tmp1=( $~tmp1 ) 2> /dev/null if [[ -n "$PREFIX$SUFFIX" ]]; then # See which of them match what's on the line. if (( ! $#tmp1 )); then tmp2=( ${^tmp2}/$PREFIX$SUFFIX ) elif [[ "$tmp1[1]" = */* ]]; then if [[ -n "$_comp_correct" ]]; then tmp2=( "$tmp1[@]" ) builtin compadd -D tmp1 "$matcher[@]" - "${(@)tmp1:t}" if [[ $#tmp1 -eq 0 ]]; then tmp1=( "$tmp2[@]" ) compadd -D tmp1 "$matcher[@]" - "${(@)tmp2:t}" fi else tmp2=( "$tmp1[@]" ) compadd -D tmp1 "$matcher[@]" - "${(@)tmp1:t}" fi else tmp2=( '' ) compadd -D tmp1 "$matcher[@]" -a tmp1 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 elif [[ ${tpre}${tsuf} = */* ]]; then exppaths=( "$exppaths[@]" ${tpre}${tsuf} ) ### this once was in an `else' (not `elif') 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 [[ -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[1]" = *../* ) ]]; then compfiles -i tmp1 _comp_ignore "$ignpar" "$prepath$realpath$donepath" (( $#_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. tmp1=( ${tmp1//(#b)([][()|*?^#~<>\\=])/\\${match[1]}} ) 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" [[ -n "${prepath}${realpath}${testpath}" ]] && 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'. # The return value is non-zero if the component is ambiguous. compfiles -r tmp1 "${(Q)tmp3}" tmp4=$? if [[ "$tpre" = */* ]]; then tmp2="${cpre}${tpre%%/*}" PREFIX="${donepath}${linepath}${tmp2}" SUFFIX="/${tpre#*/}${tsuf#*/}" else tmp2="${cpre}${tpre}" PREFIX="${donepath}${linepath}${tmp2}" SUFFIX="${tsuf}" fi # This once tested `|| [[ -n "$compstate[pattern_match]" && # "$tmp2" = (|*[^\\])[][*?#~^\|\<\>]* ]]' but it should now be smart # enough to handle multiple components with patterns. if (( tmp4 )); then # It is. For menu completion we now add the possible completions # for this component with the unambiguous 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" if [[ -n "$linepath" ]]; then compquote -p tmp2 tmp1 elif [[ -n "$tmp2" ]]; then compquote -p tmp1 compquote tmp2 else compquote tmp1 tmp2 fi if [[ -z "$_comp_correct" && "$compstate[pattern_match]" = \* && -n "$listsfx" && "$tmp2" = (|*[^\\])[][*?#~^\|\<\>]* ]]; then PREFIX="$opre" SUFFIX="$osuf" fi # This once tested `-n $menu ||' but our menu-completion expert says # that's not what we want. if [[ -z "$compstate[insert]" ]] || { ! zstyle -t ":completion:${curcontext}:paths" expand suffix && [[ -z "$listsfx" && ( -n "$_comp_correct" || -z "$compstate[pattern_match]" || "$SUFFIX" != */* || "${SUFFIX#*/}" = (|*[^\\])[][*?#~^\|\<\>]* ) ]] }; then (( tmp4 )) && zstyle -t ":completion:${curcontext}:paths" ambiguous && compstate[to_end]= if [[ "$tmp3" = */* ]]; then if [[ -z "$listsfx" || "$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%%/*}/${tmp3#*/}" fi else compadd -Qf "$mopts[@]" -p "$linepath$tmp2" \ -W "$prepath$realpath$testpath" \ "$pfxsfx[@]" -M "r:|/=* r:|=*" \ -a tmp1 fi else if [[ "$tmp3" = */* ]]; then tmp4=( -Qf "$mopts[@]" -p "$linepath$tmp2" -W "$prepath$realpath$testpath" "$pfxsfx[@]" -M "r:|/=* r:|=*" ) if [[ -z "$listsfx" ]]; then for i in "$tmp1[@]"; do compadd "$tmp4[@]" -s "/${i#*/}" - "${i%%/*}" done else [[ -n "$compstate[pattern_match]" ]] && SUFFIX="${SUFFIX:s./.*/}*" for i in "$tmp1[@]"; do compadd "$tmp4[@]" - "$i" done fi else compadd -Qf "$mopts[@]" -p "$linepath$tmp2" \ -W "$prepath$realpath$testpath" \ "$pfxsfx[@]" -M "r:|/=* r:|=*" \ -a 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]%%/*}/" tmp3="${tmp3#*/}" if [[ "$tpre" = */* ]]; then if [[ -z "$_comp_correct" && -n "$compstate[pattern_match]" && "$tmp2" = (|*[^\\])[][*?#~^\|\<\>]* ]]; then cpre="${cpre}${tmp1[1]%%/*}/" else cpre="${cpre}${tpre%%/*}/" fi tpre="${tpre#*/}" elif [[ "$tsuf" = */* ]]; then [[ "$tsuf" != /* ]] && mid="$testpath" if [[ -z "$_comp_correct" && -n "$compstate[pattern_match]" && "$tmp2" = (|*[^\\])[][*?#~^\|\<\>]* ]]; then cpre="${cpre}${tmp1[1]%%/*}/" else cpre="${cpre}${tpre}/" fi tpre="${tsuf#*/}" tsuf= else tpre= tsuf= fi tmp1=( "${(@)tmp1#*/}" ) done if [[ -z "$tmp4" ]]; then if [[ "$mid" = */ ]]; then PREFIX="${opre}" SUFFIX="${osuf}" tmp4="${testpath#${mid}}" tmp3="${mid%/*/}" tmp2="${${mid%/}##*/}" if [[ -n "$linepath" ]]; then compquote -p tmp3 else compquote tmp3 fi compquote tmp4 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" if [[ -n "$linepath" ]]; then compquote -p tmp4 tmp1 elif [[ -n "$tmp4" ]]; then compquote -p tmp1 compquote tmp4 else compquote tmp4 tmp1 fi if [[ -z "$_comp_correct" && -n "$compstate[pattern_match]" && "${PREFIX#\~}$SUFFIX" = (|*[^\\])[][*?#~^\|\<\>]* ]]; then compadd -Qf -W "$prepath$realpath" "$pfxsfx[@]" "$mopts[@]" \ -M "r:|/=* r:|=*" - "$linepath$tmp4${(@)^tmp1}" else compadd -Qf -p "$linepath$tmp4" -W "$prepath$realpath$testpath" \ "$pfxsfx[@]" "$mopts[@]" -M "r:|/=* r:|=*" -a tmp1 fi 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. Do that only if we are currently trying the # last entry in the matcher-list style, otherwise other match specs might # make the suffix that didn't match this time match in one of the following # attempts. if [[ _matcher_num -eq ${#_matchers} ]] && zstyle -t ":completion:${curcontext}:paths" expand prefix && [[ nm -eq compstate[nmatches] && $#exppaths -ne 0 && "$linepath$exppaths" != "$eorig" ]]; then PREFIX="${opre}" SUFFIX="${osuf}" compadd -Q "$mopts[@]" -S '' -M "r:|/=* r:|=*" -p "$linepath" -a exppaths fi [[ nm -ne compstate[nmatches] ]]