on 448bfaf help sign in
425 lines 9.1 kB history raw
1#!/usr/bin/env dash
3esc() {
4 case $1 in
5 # vt100 (IL is vt102) (DECTCEM is vt520)
6 CUD) printf '%s[%sB' "$esc_c" "$2" ;; # cursor down
7 CUP) printf '%s[%s;%sH' "$esc_c" "$2" "$3" ;; # cursor home
8 CUU) printf '%s[%sA' "$esc_c" "$2" ;; # cursor up
9 DECAWM) printf '%s[?7%s' "$esc_c" "$2" ;; # line wrap
10 DECRC) printf '%s8' "$esc_c" ;; # cursor restore
11 DECSC) printf '%s7' "$esc_c" ;; # cursor save
12 DECSTBM) printf '%s[%s;%sr' "$esc_c" "$2" "$3" ;; # scroll region
13 DECTCEM) printf '%s[?25%s' "$esc_c" "$2" ;; # cursor visible
14 ED[0-2]) printf '%s[%sJ' "$esc_c" "${1#ED}" ;; # clear screen
15 EL[0-2]) printf '%s[%sK' "$esc_c" "${1#EL}" ;; # clear line
16 IL) printf '%s[%sL' "$esc_c" "$2" ;; # insert line
17 SGR) printf '%s[%s;%sm' "$esc_c" "$2" "$3" ;; # colors
19 # xterm (since 1988, supported widely)
20 screen_alt) printf '%s[?1049%s' "$esc_c" "$2" ;; # alternate buffer
21 esac
24term_setup() {
25 stty=$(stty -g)
26 stty -icanon -echo
27 esc screen_alt h
28 esc DECAWM l
29 esc DECTCEM l
30 esc ED2
31 esc DECSTBM 1 "$((LINES - 2))"
34term_reset() {
35 esc DECAWM h >&2
36 esc DECTCEM h >&2
37 esc ED2 >&2
38 esc DECSTBM >&2
39 esc screen_alt l >&2
40 stty "$stty"
42 # needed for cd-on-exit
43 printf '%s\n' "$PWD" >&1
46term_resize() {
47 # false-positive, behavior intentional, globbing is disabled.
48 # shellcheck disable=2046
49 {
50 set -f
51 set +f -- $(stty size)
52 }
54 LINES=$1 COLUMNS=$2
56 # space for status_line
57 bottom=$((LINES - 2))
60term_scroll_down() {
61 case $((y - $#)) in
62 [0-9]*) return
63 esac
65 y=$((y + 1))
66 y2=$((y2 + 1 < bottom ? y2 + 1 : bottom))
68 line_print "$((y - 1))" "$@"
69 printf '\n'
70 line_print "$y" "$@"
71 status_line "$#"
74term_scroll_up() {
75 case $y in
76 -*|0|1) return
77 esac
79 y=$((y - 1))
81 line_print "$((y + 1))" "$@"
83 case $y2 in
84 1) esc IL ;;
85 *) esc CUU; y2=$((y2 > 1 ? y2 - 1 : 1))
86 esac
88 line_print "$y" "$@"
89 status_line "$#"
92cmd_run() {
93 stty "$stty"
94 esc DECTCEM h
95 esc DECSTBM
96 esc ED2
97 "$@" ||:
98 esc DECSTBM 1 "$((LINES - 2))"
99 esc DECTCEM l
100 stty -icanon -echo
101 hist=2
104file_escape() {
105 tmp=$1 safe=
107 # loop over string char by char
108 while c=${tmp%"${tmp#?}"}; do
109 case $c in
110 '') return ;;
111 [[:cntrl:]]) safe=$safe\? ;;
112 *) safe=$safe$c ;;
113 esac
115 tmp=${tmp#?}
116 done
119hist_search() {
120 hist=0 j=1
122 for file do
123 case ${PWD%%/}/$file in
124 "$old_pwd") y=$j y2=$((j > bottom ? mid : j)) cur=$file
125 esac
127 j=$((j + 1))
128 done
131list_print() {
132 esc ED2
133 esc CUP
135 i=1
136 end=$((bottom + 1))
137 mid=$((bottom / 4 < 5 ? 1 : bottom / 4))
139 case $# in
140 1) [ -e "$1" ] || set -- empty
141 esac
143 case $hist in
144 2) # redraw after cmd run
145 shift "$((y > y2 ? y - y2 : 0))"
146 ;;
148 1) # redraw after go-to-parent
149 hist_search "$@"
150 shift "$((y >= bottom ? y - mid : 0))"
151 ;;
153 *) # everything else
154 shift "$((y >= bottom ? y - bottom : 0))"
155 ;;
156 esac
158 for file do
159 case $i in
160 "$y2") esc SGR 0 7
161 esac
163 case $((i - end)) in
164 -*)
165 line_format "$file"
166 esc CUD
167 ;;
168 esac
170 i=$((i + 1))
171 done
173 esc CUP "$((y > y2 ? y2 : y))"
176redraw() {
177 list_print "$@"
178 status_line "$#"
181status_line() {
182 esc DECSC
183 esc CUP "$LINES"
185 case $USER in
186 root) esc SGR 31 7 ;;
187 *) esc SGR 34 7 ;;
188 esac
190 printf '%*s\r%s ' "$COLUMNS" "" "($y/$1)"
192 case $ltype in
193 '') printf %s "$PWD" ;;
194 *) printf %s "$ltype"
195 esac
197 esc SGR 0 0
198 esc DECRC
201prompt() {
202 esc DECSC
203 esc CUP "$LINES"
204 printf %s "$1"
205 esc DECTCEM h
206 esc EL0
208 case $2 in
209 r)
210 stty icanon echo
211 read -r ans ||:
212 stty -icanon -echo
213 ;;
214 esac
216 esc DECRC
217 esc DECTCEM l
218 status_line "($y/$#) $PWD"
221line_print() {
222 offset=$1
224 case $offset in
225 "$y") esc SGR 0 7
226 esac
228 shift "$offset"
230 case $offset in
231 "$y") cur=$1
232 esac
234 line_format "$1"
237line_format() {
238 file_escape "$1"
239 [ -d "$1" ] && esc SGR 1 31
240 printf %s "$safe"
241 [ -d "$1" ] && printf /
242 esc SGR 0 0
243 esc EL0
244 printf '\r'
247main() {
248 set -e
250 case $1 in
251 -h|--help)
252 printf 'shfm -[hv] <starting dir>\n'
253 exit 0
254 ;;
256 -v|--version)
257 printf 'shfm 0.4.2\n'
258 exit 0
259 ;;
261 *)
262 cd -- "${1:-"$PWD"}"
263 ;;
264 esac
266 esc_c=$(printf '\033')
267 bs_char=$(printf '\177')
269 set -- *
270 cur=$1
272 term_resize
273 term_setup
275 trap 'term_reset' EXIT INT
276 trap 'term_resize; term_setup; y=1 y2=1; redraw "$@"' WINCH
278 y=1 y2=1
279 redraw "$@"
281 while key=$(dd ibs=1 count=1 2>/dev/null); do
282 case $key${esc:=0} in
283 k?|A2)
284 term_scroll_up "$@"
285 ;;
287 j?|B2)
288 term_scroll_down "$@"
289 ;;
291 l?|C2|"$esc") # ARROW RIGHT
292 if [ -d "$cur" ] && cd -- "$cur" >/dev/null 2>&1; then
293 set -- *
294 y=1 y2=1 cur=$1 ltype=
295 redraw "$@"
297 elif [ -e "$cur" ]; then
298 cmd_run "${SHFM_OPENER:="${EDITOR:=vi}"}" "$cur"
299 redraw "$@"
300 fi
301 ;;
303 h?|D2|"$bs_char"?) # ARROW LEFT
304 old_pwd=$PWD
306 case $ltype in
307 '') cd .. || continue ;;
308 *) ltype= ;;
309 esac
311 set -- *
312 y=1 y2=1 cur=$1 hist=1
313 redraw "$@"
314 ;;
316 g?)
317 case $y in
318 1) continue
319 esac
321 y=1 y2=1 cur=$1
322 redraw "$@"
323 ;;
325 G?)
326 y=$#
327 y2=$(($# < bottom ? $# : bottom))
328 redraw "$@"
329 ;;
331 .?)
332 case ${hidden:=1} in
333 1) hidden=0; set -- .* ;;
334 0) hidden=1; set -- *
335 esac
337 y=1 y2=1 cur=$1
338 redraw "$@"
339 ;;
341 :?)
342 prompt "cd: " r
344 # false positive, behavior intentional
345 # shellcheck disable=2088
346 case $ans in
347 '~') ans=$HOME ;;
348 '~/'*) ans=$HOME/${ans#"~/"}
349 esac
351 cd -- "${ans:="$0"}" >/dev/null 2>&1|| continue
352 set -- *
353 y=1 y2=1 cur=$1
354 redraw "$@"
355 ;;
357 /?)
358 prompt / r
360 # word splitting and globbing intentional
361 # shellcheck disable=2086
362 set -- $ans*
364 case $1$# in
365 "$ans*1") set -- 'no results'
366 esac
368 y=1 y2=1 cur=$1 ltype="search $PWD/$ans*"
369 redraw "$@"
370 status_line "$#"
371 ;;
373 -?)
374 cd -- "$OLDPWD" >/dev/null 2>&1|| continue
375 set -- *
376 y=1 y2=1 cur=$1
377 redraw "$@"
378 ;;
380 \~?)
381 cd || continue
382 set -- *
383 y=1 y2=1 cur=$1
384 redraw "$@"
385 ;;
387 \!?)
388 export SHFM_LEVEL
389 SHFM_LEVEL=$((SHFM_LEVEL + 1))
390 cmd_run "${SHELL:=/bin/sh}"
391 redraw "$@"
392 ;;
394 \??)
395 set -- 'j - down' \
396 'k - up' \
397 'l - open file or directory' \
398 'h - go up level' \
399 'g - go to top' \
400 'G - go to bottom' \
401 'q - quit' \
402 ': - cd to <input>' \
403 '/ - search current directory <input>*' \
404 '- - go to last directory' \
405 '~ - go home' \
406 '! - spawn shell' \
407 '. - toggle hidden files' \
408 '? - show keybinds'
410 y=1 y2=1 cur=$1 ltype=keybinds
411 redraw "$@"
412 status_line "$#"
413 ;;
415 q?) exit 0 ;;
417 # handle keys which emit escape sequences
418 "$esc_c"*) esc=1 ;;
419 '[1') esc=2 ;;
420 *) esc=0 ;;
421 esac
422 done
425main "$@" >/dev/tty