diff options
author | Hsieh Chin Fan <typebrook@gmail.com> | 2021-10-12 10:55:02 +0800 |
---|---|---|
committer | Hsieh Chin Fan <typebrook@gmail.com> | 2021-10-12 10:55:02 +0800 |
commit | 8b1a586da07488b4ca67204a33cb5349666ca9f6 (patch) | |
tree | 8abd6ac345d79e3392348540874326af8751a559 /zsh/deer | |
parent | 6bdea812676fa5c9f6425b24c663ca2aeec522cc (diff) |
update
Diffstat (limited to 'zsh/deer')
-rw-r--r-- | zsh/deer | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/zsh/deer b/zsh/deer new file mode 100644 index 0000000..4b0d883 --- /dev/null +++ b/zsh/deer | |||
@@ -0,0 +1,489 @@ | |||
1 | # -*- mode: shell-script -*- | ||
2 | # vim: set ft=zsh : | ||
3 | ######################################################################### | ||
4 | # Copyright (C) 2014-2015 Wojciech Siewierski # | ||
5 | # # | ||
6 | # This program is free software: you can redistribute it and/or modify # | ||
7 | # it under the terms of the GNU General Public License as published by # | ||
8 | # the Free Software Foundation, either version 3 of the License, or # | ||
9 | # (at your option) any later version. # | ||
10 | # # | ||
11 | # This program is distributed in the hope that it will be useful, # | ||
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | ||
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | ||
14 | # GNU General Public License for more details. # | ||
15 | # # | ||
16 | # You should have received a copy of the GNU General Public License # | ||
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | ||
18 | ######################################################################### | ||
19 | |||
20 | zstyle -s ":deer:" height DEER_HEIGHT || DEER_HEIGHT=22 | ||
21 | zstyle -b ":deer:" show_hidden DEER_SHOW_HIDDEN | ||
22 | |||
23 | |||
24 | typeset -Ag DEER_KEYS | ||
25 | function () | ||
26 | { | ||
27 | while [ -n "$2" ]; do | ||
28 | DEER_KEYS[$1]=${DEER_KEYS[$1]:-$2} | ||
29 | shift 2 | ||
30 | done | ||
31 | } down j \ | ||
32 | page_down J \ | ||
33 | up k \ | ||
34 | page_up K \ | ||
35 | enter l \ | ||
36 | leave h \ | ||
37 | next_parent ']' \ | ||
38 | prev_parent '[' \ | ||
39 | search / \ | ||
40 | filter f \ | ||
41 | toggle_hidden H \ | ||
42 | quit q \ | ||
43 | append_path a \ | ||
44 | append_abs_path A \ | ||
45 | insert_path i \ | ||
46 | insert_abs_path I \ | ||
47 | multi_insert_dwim s \ | ||
48 | multi_insert_abs S \ | ||
49 | chdir c \ | ||
50 | chdir_selected C \ | ||
51 | rifle r \ | ||
52 | edit e \ | ||
53 | |||
54 | |||
55 | # Select the Nth next file. Pass a negative argument for the previous file. | ||
56 | deer-move() | ||
57 | { | ||
58 | local FILES MOVEMENT INDEX | ||
59 | MOVEMENT=$1 | ||
60 | |||
61 | FILES=($DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-/:t) | ||
62 | $DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-^/:t)) | ||
63 | |||
64 | INDEX=${(k)FILES[(re)$DEER_BASENAME[$DEER_DIRNAME]]} | ||
65 | |||
66 | if (( INDEX+MOVEMENT <= 0 )); then | ||
67 | DEER_BASENAME[$DEER_DIRNAME]=$FILES[1] | ||
68 | elif (( INDEX+MOVEMENT > $#FILES )); then | ||
69 | DEER_BASENAME[$DEER_DIRNAME]=$FILES[$#FILES] | ||
70 | else | ||
71 | DEER_BASENAME[$DEER_DIRNAME]=$FILES[$INDEX+$MOVEMENT] | ||
72 | fi | ||
73 | } | ||
74 | |||
75 | # Select the first visible directory (or file if there are no | ||
76 | # directories) in the current directory. Useful when changing the file | ||
77 | # filter. | ||
78 | deer-refocus() | ||
79 | { | ||
80 | local TMP | ||
81 | TMP=($DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-/:t) | ||
82 | $DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-^/:t)) | ||
83 | DEER_BASENAME[$DEER_DIRNAME]=$TMP[1] | ||
84 | |||
85 | [ -n "$DEER_BASENAME[$DEER_DIRNAME]" ] # Return if there were any files at all. | ||
86 | } | ||
87 | |||
88 | # Enter the selected directory | ||
89 | deer-enter() | ||
90 | { | ||
91 | # Abort if there is no file focused at all or if it is not a | ||
92 | # directory. | ||
93 | [ -n "$DEER_BASENAME[$DEER_DIRNAME]" -a \ | ||
94 | -d "$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]" ] || return | ||
95 | |||
96 | DEER_DIRNAME=${DEER_DIRNAME%/}/$DEER_BASENAME[$DEER_DIRNAME] | ||
97 | |||
98 | if [ -z $DEER_BASENAME[$DEER_DIRNAME] ]; then | ||
99 | deer-refocus | ||
100 | fi | ||
101 | } | ||
102 | |||
103 | # Move to the parent directory | ||
104 | deer-leave() | ||
105 | { | ||
106 | [ $DEER_DIRNAME = / ] && return | ||
107 | DEER_BASENAME[$DEER_DIRNAME:h]=$DEER_DIRNAME:t | ||
108 | DEER_DIRNAME=$DEER_DIRNAME:h | ||
109 | } | ||
110 | |||
111 | # Display a given prompt, read a string and save it into $BUFFER. | ||
112 | deer-prompt() | ||
113 | { | ||
114 | BUFFER="" | ||
115 | PREDISPLAY="$1/ " | ||
116 | POSTDISPLAY="" | ||
117 | |||
118 | local region_highlight | ||
119 | region_highlight=("P0 $#1 fg=green") | ||
120 | zle recursive-edit | ||
121 | } | ||
122 | |||
123 | # Read a pattern and select the first matching file. | ||
124 | deer-search() | ||
125 | { | ||
126 | deer-prompt "search" | ||
127 | |||
128 | local TMP | ||
129 | TMP=($DEER_DIRNAME/${~BUFFER}${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-:t)) | ||
130 | [ -n "$TMP[1]" ] && DEER_BASENAME[$DEER_DIRNAME]=$TMP[1] | ||
131 | } | ||
132 | |||
133 | # Read a pattern and use it as a new filter. | ||
134 | deer-filter() | ||
135 | { | ||
136 | deer-prompt "filter" | ||
137 | |||
138 | if [ -n "$BUFFER" ] && [[ ! $BUFFER == *\** ]]; then | ||
139 | BUFFER=*$BUFFER* | ||
140 | fi | ||
141 | |||
142 | deer-apply-filter $BUFFER || deer-apply-filter | ||
143 | } | ||
144 | |||
145 | deer-apply-filter() | ||
146 | { | ||
147 | DEER_FILTER[$DEER_DIRNAME]=$1 | ||
148 | deer-refocus | ||
149 | } | ||
150 | |||
151 | # Draw an arrow pointing to the selected file. | ||
152 | deer-mark-file-list() | ||
153 | { | ||
154 | local MARKED=$1 | ||
155 | shift | ||
156 | |||
157 | print -l -- "$@" \ | ||
158 | | grep -Fx -B5 -A$DEER_HEIGHT -- "$MARKED" \ | ||
159 | | perl -pe 'BEGIN{$name = shift} | ||
160 | if ($name."\n" eq $_) { | ||
161 | $_="-> $_" | ||
162 | } else { | ||
163 | $_=" $_" | ||
164 | }' -- "$MARKED" | ||
165 | } | ||
166 | |||
167 | # Draw the file lists in the form of Miller columns. | ||
168 | deer-refresh() | ||
169 | { | ||
170 | local FILES PREVIEW PARENTFILES OUTPUT REL_DIRNAME | ||
171 | local SEPARATOR="------" | ||
172 | |||
173 | PREDISPLAY=$OLD_LBUFFER | ||
174 | REL_DIRNAME=${${DEER_DIRNAME%/}#$DEER_STARTDIR}/ | ||
175 | [ -n "$DEER_STARTDIR" ] && REL_DIRNAME=${REL_DIRNAME#/} | ||
176 | LBUFFER=$REL_DIRNAME$DEER_BASENAME[$DEER_DIRNAME] | ||
177 | RBUFFER="" | ||
178 | local TMP_FILTER | ||
179 | TMP_FILTER=${DEER_FILTER[$DEER_DIRNAME]} | ||
180 | POSTDISPLAY=${TMP_FILTER:+ filt:$TMP_FILTER} | ||
181 | region_highlight=("P0 $#PREDISPLAY fg=black,bold" | ||
182 | "0 $#REL_DIRNAME fg=blue,bold" | ||
183 | "$#BUFFER $[$#BUFFER+$#POSTDISPLAY] fg=yellow,bold") | ||
184 | |||
185 | |||
186 | FILES=($DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-/:t) | ||
187 | $SEPARATOR | ||
188 | $DEER_DIRNAME/${~DEER_FILTER[$DEER_DIRNAME]:-'*'}(N$DEER_GLOBFLAGS-^/:t)) | ||
189 | PARENTFILES=($DEER_DIRNAME:h/${~DEER_FILTER[$DEER_DIRNAME:h]:-'*'}(N$DEER_GLOBFLAGS-/:t)) | ||
190 | |||
191 | local IFS=$'\n' | ||
192 | FILES=($(deer-mark-file-list "$DEER_BASENAME[$DEER_DIRNAME]" $FILES)) | ||
193 | PARENTFILES=($(deer-mark-file-list "$DEER_DIRNAME:t" $PARENTFILES)) | ||
194 | unset IFS | ||
195 | |||
196 | FILES=(${(F)FILES[1,$DEER_HEIGHT]}) | ||
197 | PARENTFILES=(${(F)PARENTFILES[1,$DEER_HEIGHT]}) | ||
198 | |||
199 | |||
200 | if [ -f $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]; then | ||
201 | if file $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] | grep -Fq text; then | ||
202 | PREVIEW="--- Preview: ---"$'\n'$(head -n$DEER_HEIGHT $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]) | ||
203 | |||
204 | # Replace '/' with '∕' (division slash, U+2215) to allow using it as a | ||
205 | # paste(1)/column(1) separator. | ||
206 | PREVIEW=${PREVIEW//\//∕} | ||
207 | else | ||
208 | PREVIEW="--- Binary file, preview unavailable ---" | ||
209 | fi | ||
210 | else | ||
211 | # I'm really sorry about what you see below. | ||
212 | # It basically means: PREVIEW=(directories separator files) | ||
213 | PREVIEW=($DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]/${~DEER_FILTER[$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]]:-'*'}(N$DEER_GLOBFLAGS-/:t) | ||
214 | $SEPARATOR | ||
215 | $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]/${~DEER_FILTER[$DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME]]:-'*'}(N$DEER_GLOBFLAGS-^/:t)) | ||
216 | PREVIEW=${(F)PREVIEW[1,$DEER_HEIGHT]} | ||
217 | fi | ||
218 | |||
219 | OUTPUT="$(paste -d/ <(<<< $PARENTFILES \ | ||
220 | | awk '{print substr($0,1,16)}') \ | ||
221 | <(<<< $FILES) \ | ||
222 | <(<<< $PREVIEW) \ | ||
223 | | sed 's,/, / ,g' \ | ||
224 | | column -t -s/ 2> /dev/null \ | ||
225 | | awk -v width=$COLUMNS '{print substr($0,1,width-1)}')" | ||
226 | zle -M -- $OUTPUT | ||
227 | zle -R | ||
228 | } | ||
229 | |||
230 | # Run `deer-add' with the same arguments, restore the shell state and | ||
231 | # then exit. | ||
232 | deer-restore() | ||
233 | { | ||
234 | deer-add "$@" | ||
235 | PREDISPLAY="" | ||
236 | POSTDISPLAY="" | ||
237 | region_highlight=() | ||
238 | LBUFFER=$OLD_LBUFFER | ||
239 | RBUFFER=$OLD_RBUFFER | ||
240 | zle reset-prompt | ||
241 | zle -M "" | ||
242 | } | ||
243 | |||
244 | # Add the given string before or after the cursor. | ||
245 | deer-add() | ||
246 | { | ||
247 | case $1 in | ||
248 | --append) | ||
249 | OLD_LBUFFER+=$2 | ||
250 | shift 2 | ||
251 | ;; | ||
252 | --insert) | ||
253 | OLD_RBUFFER=$2$OLD_RBUFFER | ||
254 | shift 2 | ||
255 | ;; | ||
256 | esac | ||
257 | } | ||
258 | |||
259 | # Get the quoted relative path from the absolute unquoted path. | ||
260 | deer-get-relative() | ||
261 | { | ||
262 | local TMP | ||
263 | TMP=${1:-${DEER_DIRNAME%/}/$DEER_BASENAME[$DEER_DIRNAME]} | ||
264 | TMP="`python -c ' | ||
265 | import sys, os | ||
266 | print(os.path.relpath(sys.argv[1], sys.argv[2])) | ||
267 | ' $TMP ${DEER_STARTDIR:-$PWD}`" | ||
268 | print -R $TMP:q | ||
269 | } | ||
270 | |||
271 | # Tries to guess a directory to start in from the current argument. | ||
272 | deer-set-initial-directory() | ||
273 | { | ||
274 | autoload -U split-shell-arguments modify-current-argument | ||
275 | local REPLY REPLY2 reply | ||
276 | local DIRECTORY | ||
277 | |||
278 | ((--CURSOR)) | ||
279 | split-shell-arguments | ||
280 | ((++CURSOR)) | ||
281 | |||
282 | # Find the longest existing directory path in the current argument. | ||
283 | DEER_STARTDIR=${(Q)${${reply[$REPLY]%%[[:space:]]#}:a}%/} | ||
284 | while [ -n "$DEER_STARTDIR" -a \ | ||
285 | ! -d "$DEER_STARTDIR" ]; do | ||
286 | DEER_STARTDIR=${DEER_STARTDIR%/*} | ||
287 | done | ||
288 | |||
289 | DEER_DIRNAME=${DEER_STARTDIR:-$PWD} | ||
290 | } | ||
291 | |||
292 | # The main entry function. | ||
293 | deer-launch() | ||
294 | { | ||
295 | emulate -L zsh | ||
296 | setopt extended_glob | ||
297 | local DEER_DIRNAME DEER_STARTDIR DEER_GLOBFLAGS | ||
298 | local -A DEER_FILTER DEER_BASENAME | ||
299 | local REPLY OLD_LBUFFER OLD_RBUFFER | ||
300 | |||
301 | local GREP_OPTIONS | ||
302 | GREP_OPTIONS="" | ||
303 | |||
304 | OLD_LBUFFER=$LBUFFER | ||
305 | OLD_RBUFFER=$RBUFFER | ||
306 | |||
307 | deer-set-initial-directory | ||
308 | |||
309 | if [ "$DEER_SHOW_HIDDEN" = yes ]; then | ||
310 | DEER_GLOBFLAGS=D | ||
311 | else | ||
312 | DEER_GLOBFLAGS="" | ||
313 | fi | ||
314 | |||
315 | if [ -n "$NUMERIC" ]; then | ||
316 | for i in {1..$NUMERIC}; do | ||
317 | deer-leave | ||
318 | done | ||
319 | else | ||
320 | # Don't change cwd but initialize the variables. | ||
321 | deer-leave | ||
322 | deer-enter | ||
323 | fi | ||
324 | |||
325 | deer-refresh | ||
326 | while read -k; do | ||
327 | case $REPLY in | ||
328 | # Movement | ||
329 | $DEER_KEYS[up]) | ||
330 | deer-move -1 | ||
331 | deer-refresh | ||
332 | ;; | ||
333 | $DEER_KEYS[page_up]) | ||
334 | deer-move -5 | ||
335 | deer-refresh | ||
336 | ;; | ||
337 | $DEER_KEYS[down]) | ||
338 | deer-move 1 | ||
339 | deer-refresh | ||
340 | ;; | ||
341 | $DEER_KEYS[page_down]) | ||
342 | deer-move 5 | ||
343 | deer-refresh | ||
344 | ;; | ||
345 | $DEER_KEYS[enter]) | ||
346 | deer-enter | ||
347 | deer-refresh | ||
348 | ;; | ||
349 | $DEER_KEYS[leave]) | ||
350 | deer-leave | ||
351 | deer-refresh | ||
352 | ;; | ||
353 | $DEER_KEYS[next_parent]) | ||
354 | deer-leave | ||
355 | deer-move 1 | ||
356 | deer-enter | ||
357 | deer-refresh | ||
358 | ;; | ||
359 | $DEER_KEYS[prev_parent]) | ||
360 | deer-leave | ||
361 | deer-move -1 | ||
362 | deer-enter | ||
363 | deer-refresh | ||
364 | ;; | ||
365 | # Search | ||
366 | $DEER_KEYS[search]) | ||
367 | deer-search | ||
368 | deer-refresh | ||
369 | ;; | ||
370 | # Filter | ||
371 | $DEER_KEYS[filter]) | ||
372 | deer-filter | ||
373 | deer-refresh | ||
374 | ;; | ||
375 | $DEER_KEYS[toggle_hidden]) | ||
376 | if [ -z $DEER_GLOBFLAGS ]; then | ||
377 | DEER_GLOBFLAGS="D" # show hidden files | ||
378 | else | ||
379 | DEER_GLOBFLAGS="" | ||
380 | fi | ||
381 | # make sure the focus is on a visible file | ||
382 | DEER_BASENAME[$DEER_DIRNAME]= | ||
383 | deer-leave | ||
384 | deer-enter | ||
385 | deer-refresh | ||
386 | ;; | ||
387 | # Quit | ||
388 | $DEER_KEYS[quit]) | ||
389 | deer-restore | ||
390 | break | ||
391 | ;; | ||
392 | # Insert the path and quit. | ||
393 | $DEER_KEYS[append_path]) | ||
394 | deer-restore --append "`deer-get-relative` " | ||
395 | break | ||
396 | ;; | ||
397 | $DEER_KEYS[append_abs_path]) | ||
398 | deer-restore --append "${${DEER_DIRNAME%/}:q}/${DEER_BASENAME[$DEER_DIRNAME]:q} " | ||
399 | break | ||
400 | ;; | ||
401 | $DEER_KEYS[insert_path]) | ||
402 | deer-restore --insert " `deer-get-relative`" | ||
403 | break | ||
404 | ;; | ||
405 | $DEER_KEYS[insert_abs_path]) | ||
406 | deer-restore --insert " ${${DEER_DIRNAME%/}:q}/${DEER_BASENAME[$DEER_DIRNAME]:q}" | ||
407 | break | ||
408 | ;; | ||
409 | # Insert the path and don't quit yet. | ||
410 | $DEER_KEYS[multi_insert_dwim]) | ||
411 | if [ "$OLD_LBUFFER[-1]" = "/" ]; then | ||
412 | OLD_LBUFFER+="{" | ||
413 | fi | ||
414 | # replacement used to insert ',' instead of '{' as a separator in {foo,bar,...} lists | ||
415 | deer-add --append "`deer-get-relative`"${${OLD_LBUFFER[-1]/\{/,}:- } | ||
416 | deer-move 1 | ||
417 | deer-refresh | ||
418 | ;; | ||
419 | # Insert the absolute path and don't quit yet. | ||
420 | $DEER_KEYS[multi_insert_abs]) | ||
421 | deer-add --append " ${${DEER_DIRNAME%/}:q}/${DEER_BASENAME[$DEER_DIRNAME]:q}" | ||
422 | deer-move 1 | ||
423 | deer-refresh | ||
424 | ;; | ||
425 | # Quit and change the shell's current directory to the selected one. | ||
426 | $DEER_KEYS[chdir]) | ||
427 | deer-leave | ||
428 | ;& | ||
429 | $DEER_KEYS[chdir_selected]) | ||
430 | if [[ -d $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] && \ | ||
431 | -x $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]]; then | ||
432 | cd -- $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] | ||
433 | deer-restore | ||
434 | break | ||
435 | fi | ||
436 | ;; | ||
437 | $DEER_KEYS[edit]) | ||
438 | if [[ -f $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]]; then | ||
439 | "${EDITOR:-vim}" $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] | ||
440 | fi | ||
441 | ;; | ||
442 | # See rifle(1) manpage (included with ranger(1)). | ||
443 | $DEER_KEYS[rifle]) | ||
444 | if [[ -f $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] ]]; then | ||
445 | rifle $DEER_DIRNAME/$DEER_BASENAME[$DEER_DIRNAME] | ||
446 | fi | ||
447 | ;; | ||
448 | # Arrow keys | ||
449 | $'\e') | ||
450 | read -k | ||
451 | case $REPLY in | ||
452 | '[') | ||
453 | read -k | ||
454 | case $REPLY in | ||
455 | 'A') | ||
456 | deer-move -1 | ||
457 | deer-refresh | ||
458 | ;; | ||
459 | 'B') | ||
460 | deer-move 1 | ||
461 | deer-refresh | ||
462 | ;; | ||
463 | 'C') | ||
464 | deer-enter | ||
465 | deer-refresh | ||
466 | ;; | ||
467 | 'D') | ||
468 | deer-leave | ||
469 | deer-refresh | ||
470 | ;; | ||
471 | esac | ||
472 | ;; | ||
473 | esac | ||
474 | ;; | ||
475 | esac | ||
476 | done | ||
477 | } | ||
478 | |||
479 | if zle; then | ||
480 | deer-launch | ||
481 | else | ||
482 | deer() | ||
483 | { | ||
484 | deer-launch "$@" | ||
485 | } | ||
486 | fi | ||
487 | |||
488 | zle -N deer | ||
489 | bindkey '\ek' deer | ||