1#autoload 2 3# This completion function completes all paths given to it, and also tries to 4# offer completions which point to the same file as one of the paths given 5# (relative path when an absolute path is given, and vice versa; when ..'s are 6# present in the word to be completed, and some paths got from symlinks. 7 8# Usage: _canonical_paths [-A var] [-N] [-MJV12nfX] tag desc paths... 9 10# -A, if specified, takes the paths from the array variable specified. Paths can 11# also be specified on the command line as shown above. -N, if specified, 12# prevents canonicalizing the paths given before using them for completion, in 13# case they are already so. `tag' and `desc' arguments are well, obvious :) In 14# addition, the options -M, -J, -V, -1, -2, -n, -F, -X are passed to compadd. 15 16_canonical_paths_pwd() { 17 # Get the canonical directory name by changing to it. 18 integer chaselinks 19 [[ -o chaselinks ]] && (( chaselinks = 1 )) 20 setopt localoptions nopushdignoredups chaselinks 21 if builtin pushd -q -- $1 2>/dev/null; then 22 REPLY=$PWD 23 (( chaselinks )) || unsetopt chaselinks 24 builtin popd -q 25 else 26 REPLY=$1 27 fi 28} 29 30_canonical_paths_get_canonical_path() { 31 typeset newfile nondir 32 typeset -A seen 33 34 REPLY=$1 35 # Canonicalise the directory path. We may not be able to 36 # do this if we can't read all components. 37 if [[ -d $REPLY ]]; then 38 _canonical_paths_pwd $REPLY 39 else 40 # Resolve any trailing symbolic links, guarding against loops. 41 while [[ -z ${seen[$REPLY]} ]]; do 42 seen[$REPLY]=1 43 newfile=() 44 zstat -A newfile +link $REPLY 2>/dev/null 45 if [[ -n $newfile[1] ]]; then 46 REPLY=$newfile[1] 47 else 48 break 49 fi 50 done 51 if [[ $REPLY = */*[^/] && $REPLY != /[^/]# ]]; then 52 # Don't try this if there's a trailing slash or we're in 53 # the root directory. 54 nondir=${REPLY##*/#} 55 _canonical_paths_pwd ${REPLY%/#*} 56 REPLY+="/$nondir" 57 fi 58 fi 59} 60 61_canonical_paths_add_paths () { 62 local origpref=$1 expref rltrim curpref canpref subdir 63 [[ $2 != add ]] && matches=() 64 expref=${~origpref} 65 [[ $origpref == (|*/). ]] && rltrim=. 66 curpref=${${expref%$rltrim}:-./} 67 if zstat $curpref >&/dev/null; then 68 _canonical_paths_get_canonical_path $curpref 69 canpref=$REPLY 70 else 71 canpref=$curpref 72 fi 73 [[ $curpref == */ && $canpref == *[^/] ]] && canpref+=/ 74 canpref+=$rltrim 75 [[ $expref == *[^/] && $canpref == */ ]] && origpref+=/ 76 matches+=(${${(M)files:#$canpref*}/$canpref/$origpref}) 77 for subdir in $expref?*(@); do 78 _canonical_paths_add_paths ${subdir/$expref/$origpref} add 79 done 80} 81 82_canonical_paths() { 83 local __index 84 typeset -a __gopts __opts 85 86 zparseopts -D -a __gopts M: J: V: 1 2 n F: X: A:=__opts N=__opts 87 88 : ${1:=canonical-paths} ${2:=path} 89 90 __index=$__opts[(I)-A] 91 (( $__index )) && set -- $@ ${(P)__opts[__index+1]} 92 93 local expl ret=1 tag=$1 desc=$2 94 95 shift 2 96 97 if ! zmodload -F zsh/stat b:zstat 2>/dev/null; then 98 _wanted "$tag" expl "$desc" compadd $__gopts $@ && ret=0 99 return ret 100 fi 101 102 typeset REPLY 103 typeset -a matches files 104 105 if (( $__opts[(I)-N] )); then 106 files=($@) 107 else 108 for __index in $@; do 109 _canonical_paths_get_canonical_path $__index 110 files+=($REPLY) 111 done 112 fi 113 114 local base=$PREFIX 115 typeset -i blimit 116 117 _canonical_paths_add_paths $base 118 119 if [[ -z $base ]]; then 120 _canonical_paths_add_paths / add 121 elif [[ $base == ..(/.(|.))#(|/) ]]; then 122 123 # This style controls how many parent directory links (..) to chase searching 124 # for possible completions. The default is 8. Note that this chasing is 125 # triggered only when the user enters atleast a .. and the path completed 126 # contains only . or .. components. A value of 0 turns off .. link chasing 127 # altogether. 128 129 zstyle -s ":completion:${curcontext}:$tag" \ 130 canonical-paths-back-limit blimit || blimit=8 131 132 if [[ $base != */ ]]; then 133 [[ $base != *.. ]] && base+=. 134 base+=/ 135 fi 136 until [[ $base.. -ef $base || blimit -le 0 ]]; do 137 base+=../ 138 _canonical_paths_add_paths $base add 139 blimit+=-1 140 done 141 fi 142 143 _wanted "$tag" expl "$desc" compadd $__gopts -Q -U -a matches && ret=0 144 145 return ret 146} 147 148_canonical_paths "$@" 149