|
| 1 | +# maintains a jump-list of the directories you actually use |
| 2 | +# |
| 3 | +# INSTALL: |
| 4 | +# * put something like this in your config.fish: |
| 5 | +# source /path/to/z.fish |
| 6 | +# * cd around for a while to build up the db |
| 7 | +# * PROFIT!! |
| 8 | +# |
| 9 | +# USE: |
| 10 | +# * z foo # cd to most frecent dir matching foo |
| 11 | +# * z foo bar # cd to most frecent dir matching foo and bar |
| 12 | +# * z -r foo # cd to highest ranked dir matching foo |
| 13 | +# * z -t foo # cd to most recently accessed dir matching foo |
| 14 | +# * z -l foo # list matches instead of cd |
| 15 | +# * z -c foo # restrict matches to subdirs of $PWD |
| 16 | + |
| 17 | +complete -x -c z -a '(z --complete (commandline -t))' |
| 18 | + |
| 19 | +function addzhist --on-variable PWD |
| 20 | + z --add "$PWD" |
| 21 | +end |
| 22 | + |
| 23 | +function z -d "Jump to a recent directory." |
| 24 | + if not set -q z_datafile |
| 25 | + set -g z_datafile "$HOME/.z" |
| 26 | + end |
| 27 | + |
| 28 | + # add entries |
| 29 | + if [ "$argv[1]" = "--add" ] |
| 30 | + set -e argv[1] |
| 31 | + |
| 32 | + # $HOME isn't worth matching |
| 33 | + [ "$argv" = "$HOME" ]; and return |
| 34 | + |
| 35 | + set -l tempfile (command mktemp $z_datafile.XXXXXX) |
| 36 | + test -f $tempfile; or return |
| 37 | + |
| 38 | + # maintain the file |
| 39 | + command awk -v path="$argv" -v now=(date +%s) -F"|" ' |
| 40 | + BEGIN { |
| 41 | + rank[path] = 1 |
| 42 | + time[path] = now |
| 43 | + } |
| 44 | + $2 >= 1 { |
| 45 | + if( $1 == path ) { |
| 46 | + rank[$1] = $2 + 1 |
| 47 | + time[$1] = now |
| 48 | + } else { |
| 49 | + rank[$1] = $2 |
| 50 | + time[$1] = $3 |
| 51 | + } |
| 52 | + count += $2 |
| 53 | + } |
| 54 | + END { |
| 55 | + if( count > 6000 ) { |
| 56 | + for( i in rank ) print i "|" 0.9*rank[i] "|" time[i] # aging |
| 57 | + } else for( i in rank ) print i "|" rank[i] "|" time[i] |
| 58 | + } |
| 59 | + ' $z_datafile ^/dev/null > $tempfile |
| 60 | + if [ $status -ne 0 -a -f $z_datafile ] |
| 61 | + command rm -f "$tempfile" |
| 62 | + else |
| 63 | + command mv -f "$tempfile" "$z_datafile" |
| 64 | + end |
| 65 | + |
| 66 | + # tab completion |
| 67 | + else |
| 68 | + if [ "$argv[1]" = "--complete" ] |
| 69 | + command awk -v q="$argv[2]" -F"|" ' |
| 70 | + BEGIN { |
| 71 | + if( q == tolower(q) ) nocase = 1 |
| 72 | + split(q,fnd," ") |
| 73 | + } |
| 74 | + { |
| 75 | + if( system("test -d \"" $1 "\"") ) next |
| 76 | + if( nocase ) { |
| 77 | + for( i in fnd ) tolower($1) !~ tolower(fnd[i]) && $1 = "" |
| 78 | + if( $1 ) print $1 |
| 79 | + } else { |
| 80 | + for( i in fnd ) $1 !~ fnd[i] && $1 = "" |
| 81 | + if( $1 ) print $1 |
| 82 | + } |
| 83 | + } |
| 84 | + ' "$z_datafile" 2>/dev/null |
| 85 | + |
| 86 | + else |
| 87 | + # list/go |
| 88 | + set -l last '' |
| 89 | + set -l list 0 |
| 90 | + set -l typ '' |
| 91 | + set -l fnd '' |
| 92 | + |
| 93 | + while [ (count $argv) -gt 0 ] |
| 94 | + switch "$argv[1]" |
| 95 | + case -- '--' |
| 96 | + while [ "$argv[1]" ] |
| 97 | + set -e argv[1] |
| 98 | + set fnd "$fnd $argv[1]" |
| 99 | + end |
| 100 | + case -- '-*' |
| 101 | + set -l opt (echo $argv[1] | cut -b2-) |
| 102 | + while [ "$opt" ] |
| 103 | + switch (echo $opt | cut -b1) |
| 104 | + case -- 'c' |
| 105 | + set fnd "^$PWD $fnd" |
| 106 | + case -- 'h' |
| 107 | + echo "z [-chlrt] args" >&2 |
| 108 | + return |
| 109 | + case -- 'l' |
| 110 | + set list 1 |
| 111 | + case -- 'r' |
| 112 | + set typ "rank" |
| 113 | + case -- 't' |
| 114 | + set typ "recent" |
| 115 | + end |
| 116 | + set -l opt (echo $opt | cut -b2-) |
| 117 | + end |
| 118 | + case '*' |
| 119 | + set fnd "$fnd $argv[1]" |
| 120 | + end |
| 121 | + set last $argv[1] |
| 122 | + set -e argv[1] |
| 123 | + end |
| 124 | + |
| 125 | + [ "$fnd" -a "$fnd" != "^$PWD " ]; or set list 1 |
| 126 | + |
| 127 | + # if we hit enter on a completion just go there |
| 128 | + switch "$last" |
| 129 | + # completions will always start with / |
| 130 | + case -- '/*' |
| 131 | + [ -z "$list" -a -d "$last" ]; and cd "$last"; and return |
| 132 | + end |
| 133 | + |
| 134 | + # no file yet |
| 135 | + [ -f "$z_datafile" ]; or return |
| 136 | + |
| 137 | + set -l cd (command awk -v t=(date +%s) -v list="$list" -v typ="$typ" -v q="$fnd" -v z_datafile="$z_datafile" -F"|" ' |
| 138 | + function notdir(path, tmp) { |
| 139 | + n = gsub("/+", "/", path) |
| 140 | + for( i = 0; i < n; i++ ) path = path "/.." |
| 141 | + path = path z_datafile |
| 142 | + if( ( getline tmp < path ) >= 0 ) { |
| 143 | + close(path) |
| 144 | + return 0 |
| 145 | + } |
| 146 | + return 1 |
| 147 | + } |
| 148 | + function frecent(rank, time) { |
| 149 | + dx = t-time |
| 150 | + if( dx < 3600 ) return rank*4 |
| 151 | + if( dx < 86400 ) return rank*2 |
| 152 | + if( dx < 604800 ) return rank/2 |
| 153 | + return rank/4 |
| 154 | + } |
| 155 | + function output(files, toopen, override) { |
| 156 | + if( list ) { |
| 157 | + cmd = "sort -n >&2" |
| 158 | + for( i in files ) if( files[i] ) printf "%-10s %s\n", files[i], i | cmd |
| 159 | + if( override ) printf "%-10s %s\n", "common:", override > "/dev/stderr" |
| 160 | + } else { |
| 161 | + if( override ) toopen = override |
| 162 | + print toopen |
| 163 | + } |
| 164 | + } |
| 165 | + function common(matches) { |
| 166 | + # shortest match |
| 167 | + for( i in matches ) { |
| 168 | + if( matches[i] && (!short || length(i) < length(short)) ) short = i |
| 169 | + } |
| 170 | + if( short == "/" ) return |
| 171 | + # shortest match must be common to each match. escape special characters in |
| 172 | + # a copy when testing, so we can return the original. |
| 173 | + clean_short = short |
| 174 | + gsub(/[\(\)\[\]\|]/, "\\\\&", clean_short) |
| 175 | + for( i in matches ) if( matches[i] && i !~ clean_short ) return |
| 176 | + return short |
| 177 | + } |
| 178 | + BEGIN { split(q, a, " "); oldf = noldf = -9999999999 } |
| 179 | + { |
| 180 | + if( notdir($1) ) {next} |
| 181 | + if( typ == "rank" ) { |
| 182 | + f = $2 |
| 183 | + } else if( typ == "recent" ) { |
| 184 | + f = $3-t |
| 185 | + } else f = frecent($2, $3) |
| 186 | + wcase[$1] = nocase[$1] = f |
| 187 | + for( i in a ) { |
| 188 | + if( $1 !~ a[i] ) delete wcase[$1] |
| 189 | + if( tolower($1) !~ tolower(a[i]) ) delete nocase[$1] |
| 190 | + } |
| 191 | + if( wcase[$1] && wcase[$1] > oldf ) { |
| 192 | + cx = $1 |
| 193 | + oldf = wcase[$1] |
| 194 | + } else if( nocase[$1] && nocase[$1] > noldf ) { |
| 195 | + ncx = $1 |
| 196 | + noldf = nocase[$1] |
| 197 | + } |
| 198 | + } |
| 199 | + END { |
| 200 | + if( cx ) { |
| 201 | + output(wcase, cx, common(wcase)) |
| 202 | + } else if( ncx ) output(nocase, ncx, common(nocase)) |
| 203 | + }' $z_datafile) |
| 204 | + |
| 205 | + [ $status -gt 0 ]; and return |
| 206 | + [ "$cd" ]; and cd "$cd" |
| 207 | + end |
| 208 | + end |
| 209 | +end |
0 commit comments