#autoload # Utility function for in-path completion. # Supported arguments are: `-f', `-/', `-g ', `-J ', # `-V ', `-W paths', `-X explanation', `-P prefix', `-S suffix', # `-q', `-r remove-chars', `-R remove-func', and `-F '. All but # the last have the same syntax and meaning as for `compgen' or # `compadd', respectively. The `-F ' 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 supports one configuration key: # # path_expand # If this is set to a non-empty string, the partially typed path # from the line will be expanded as far as possible even if trailing # pathname components can not be completed. local linepath realpath donepath prepath testpath exppath local tmp1 tmp2 tmp3 tmp4 i orig pre suf tpre tsuf local pats ignore group expl addpfx addsfx remsfx local nm=$compstate[nmatches] menu typeset -U prepaths exppaths setopt localoptions nullglob rcexpandparam extendedglob unsetopt markdirs globsubst shwordsplit nounset exppaths=() prepaths=('') ignore=() group=() sopt='-' gopt='' pats=() addpfx=() addsfx=() remsfx=() expl=() # Get the options. while getopts "P:S:qr:R:W:F:J:V:X:f/g:" 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[@]" '*(-/)') ;; g) gopt='-g' pats=("$pats[@]" ${=OPTARG}) ;; esac done [[ -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" orig="${PREFIX}${SUFFIX}" [[ $compstate[insert] = (*menu|[0-9]*) || -n "$_comp_correct" || ( $#compstate[pattern_match] -ne 0 && "${orig#\~}" != "${${orig#\~}:q}" ) ]] && menu=yes # We will first try normal completion called with `compgen', but only if we # weren't given a `-F', `-r', or `-R' option or we are in the string. if [[ -z "$suf" && $#ignore -eq 0 && $#remsfx -eq 0 && -z "$_comp_correct" ]]; 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 "$prepaths[1]" || "$pre[1]" = [~/] || "$pre" = (.|..)/* ]]; then tmp1=() else tmp1=(-W "( $prepaths )") fi # Now call compgen. if [[ -z "$gopt" ]]; then compgen "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" "$tmp1[@]" $sopt else compgen "$addpfx[@]" "$addsfx[@]" "$group[@]" "$expl[@]" "$tmp1[@]" $sopt -g "$pats" fi # If this generated any matches, we don't want to do in-path completion. [[ compstate[nmatches] -eq nm ]] || return 0 fi # 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=( '' ) 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 tmp1=( ${^tmp1}*(D/) ) else tmp1=( ${^tmp1}${^~pats} ) fi if [[ -n "$PREFIX$SUFFIX" ]]; then # See which of them match what's on the line. compadd -O tmp2 "$ignore[@]" - "${(@)tmp1##*/}" # If no file matches, save the expanded path and continue with # the outer loop. if [[ $#tmp2 -eq 0 ]]; then if [[ "$tmp1[1]" = */* ]]; then tmp2=( "${(@)tmp1#${prepath}${realpath}}" ) if [[ "$tmp2[1]" = */* ]]; then exppaths=( "$exppaths[@]" ${^tmp2%/*}/${tpre}${tsuf} ) else exppaths=( "$exppaths[@]" ${tpre}${tsuf} ) fi fi continue 2 fi # Remove all files that weren't matched. if [[ "$tmp1[1]" = */* ]]; then tmp1=( "${(@M)tmp1:#*/(${(j:|:)~${(@)tmp2:q}})}" ) else tmp1=( "${(@M)tmp1:#(${(j:|:)~${(@)tmp2:q}})}" ) fi elif (( ! $#tmp1 )); then 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" 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 avoid 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 [[ -n $menu ]]; then if [[ "$tmp3" = */* ]]; then compadd -Uf -p "$linepath$testpath" -s "/${tmp3#*/}" \ -W "$prepath$realpath$testpath" "$ignore[@]" \ "$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \ "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \ - "${(@)tmp1%%/*}" else compadd -Uf -p "$linepath$testpath" \ -W "$prepath$realpath$testpath" "$ignore[@]" \ "$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \ "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \ - "$tmp1[@]" fi else if [[ "$tmp3" = */* ]]; then for i in "$tmp1[@]"; do compadd -Uf -p "$linepath$testpath" -s "/${i#*/}" \ -W "$prepath$realpath$testpath" "$ignore[@]" \ "$addpfx[@]" "$addsfx[@]" "$remsfx[@]" -M 'r:|/=* r:|=*' \ "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \ - "${i%%/*}" done else compadd -Uf -p "$linepath$testpath" \ -W "$prepath$realpath$testpath" "$ignore[@]" \ "$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \ "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \ - "$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#*/}" done if [[ -z "$tmp4" ]]; then compadd -Uf -p "$linepath$testpath" \ -W "$prepath$realpath$testpath" "$ignore[@]" \ "$addpfx[@]" "$addsfx[@]" "$remsfx[@]" \ "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \ - "$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 [[ -n "$compconfig[path_expand]" && $#exppaths -ne 0 && nm -eq compstate[nmatches] ]]; then compadd -U -S '' "$group[@]" "$expl[@]" -i "$IPREFIX" -I "$ISUFFIX" \ -p "$linepath" - "${(@)exppaths}" fi [[ nm -eq compstate[nmatches] ]]