#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 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 the `ignored-suffixes'-style. if (( ! $#ignore )); then if _style -a files ignored-suffixes ignore; then ignore=(-F "( $ignore )") else # For now we still use the fignore parameter. # This may be removed some day. ignore=(-F fignore) fi fi # 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/' 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/' 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 ]] || ! _style paths expand '*suffix*'; then _style paths 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 _style paths 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] ]]