#!/bin/sh #-------------------------------------------------------------------------------- ### For source code see: https://github.com/Sketch98/shfm # The MIT License (MIT) # # Copyright (c) 2020 Dylan Araps # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. #-------------------------------------------------------------------------------- # these are here to help shellcheck # they will be overwritten in main immediately y=1 tab_num=1 esc() { case $1 in # vt100 (IL is vt102) (DECTCEM is vt520) CUD) printf '%s[%sB' "$esc_c" "$2" ;; # cursor down CUP) printf '%s[%s;%sH' "$esc_c" "$2" "$3" ;; # cursor home CUU) printf '%s[%sA' "$esc_c" "$2" ;; # cursor up DECAWM) printf '%s[?7%s' "$esc_c" "$2" ;; # line wrap DECRC) printf '%s8' "$esc_c" ;; # cursor restore DECSC) printf '%s7' "$esc_c" ;; # cursor save DECSTBM) printf '%s[%s;%sr' "$esc_c" "$2" "$3" ;; # scroll region DECTCEM) printf '%s[?25%s' "$esc_c" "$2" ;; # cursor visible ED[0-2]) printf '%s[%sJ' "$esc_c" "${1#ED}" ;; # clear screen EL[0-2]) printf '%s[%sK' "$esc_c" "${1#EL}" ;; # clear line IL) printf '%s[%sL' "$esc_c" "$2" ;; # insert line SGR) printf '%s[%s;%sm' "$esc_c" "$2" "$3" ;; # colors # xterm (since 1988, supported widely) screen_alt) printf '%s[?1049%s' "$esc_c" "$2" ;; # alternate buffer esac } term_setup() { stty -icanon -echo esc screen_alt h esc DECAWM l esc DECTCEM l esc ED2 set -f # false-positive, behavior intentional, globbing is disabled. # shellcheck disable=2046 set +f -- $(IFS=' ' stty size) LINES=$1 COLUMNS=$2 # space for status_line bottom=$((LINES - 3)) esc DECSTBM 1 $bottom } term_resize() { set -f # false-positive, behavior intentional, globbing is disabled. # shellcheck disable=2046 set +f -- $(IFS=' ' stty size) LINES=$1 COLUMNS=$2 # space for status_line bottom=$((LINES - 3)) # tell main loop to redraw because we don't have files list $@ here set_type=resize resized=1 } term_reset() { esc DECAWM h esc DECTCEM h esc ED2 esc DECSTBM stty "$stty" } term_scroll_down() { [ $y -ge $# ] && return y=$((y + 1)) y2=$((y2 + 1 < bottom ? y2 + 1 : bottom)) line_print $((y - 1)) "$@" printf '\n' line_print $y "$@" status_line $# } term_scroll_up() { [ $y -le 1 ] && return y=$((y - 1)) line_print $((y + 1)) "$@" case $y2 in 1) esc IL ;; *) esc CUU; y2=$((y2 > 1 ? y2 - 1 : 1)) esac line_print $y "$@" status_line $# } cmd_run() { term_reset printf %s "$@" "$@" 2>&1 || : term_setup hist=2 } file_escape() { tmp=$1 safe= # loop over string char by char while c=${tmp%"${tmp#?}"}; do case $c in '') return ;; [[:cntrl:]]) safe=$safe\? ;; *) safe=$safe$c esac tmp=${tmp#?} done } hist_search() { hist=0 j=1 y=1 y2=1 # default values in case nothing found for file do case ${PWD%%/}/$file in "$old_pwd") y=$j min=$((bottom + y - $#)) mid=$((mid < min ? min : mid)) y2=$((j >= bottom ? mid : j)) cur=$file esac j=$((j + 1)) done } list_print() { esc ED2 esc CUP i=1 end=$((bottom + 1)) mid=$((bottom / 4 < 5 ? 1 : bottom / 4)) case $# in 0) set -- empty ;; 1) [ -e "$1" ] || [ "$1" = 'no results' ] || set -- empty esac case $hist in 2) # redraw after cmd run shift $((y - y2)) ;; 1) # redraw after go-to-parent hist_search "$@" shift $((y >= bottom ? y - mid : 0)) ;; *) # everything else shift $((y >= bottom ? y - bottom : 0)) ;; esac hist=0 for file do [ $i -eq $y2 ] && esc SGR 0 7 if [ $i -lt $end ]; then line_format "$file" esc CUD fi i=$((i + 1)) done esc CUP $y2 } redraw() { list_print "$@" status_line $# } status_line() { esc DECSC esc CUP $((LINES - 1)) ## lazy debug # esc SGR 32 32 # printf %s "$y $y2 $bottom ${#others} ${selected:-empty}" # esc EL0 # esc CUD # esc SGR 33 33 # printf '\r%s' "${others:-empty};$LINES;$COLUMNS;$count" # esc EL0 # esc CUD # esc SGR 0 0 printf '\r[ ' [ -n "$tab1" ] && esc SGR 36 36 && [ $tab_num -eq 1 ] && esc SGR 1 7 printf 1; esc SGR 0 0; printf ' ' [ -n "$tab2" ] && esc SGR 36 36 && [ $tab_num -eq 2 ] && esc SGR 1 7 printf 2; esc SGR 0 0; printf ' ' [ -n "$tab3" ] && esc SGR 36 36 && [ $tab_num -eq 3 ] && esc SGR 1 7 printf 3; esc SGR 0 0; printf ' ' [ -n "$tab4" ] && esc SGR 36 36 && [ $tab_num -eq 4 ] && esc SGR 1 7 printf 4; esc SGR 0 0; printf ' ] ' esc SGR 36 36 esc SGR 1 7 printf ' %s ' $((num_sel + num_others)) esc SGR 0 0 esc CUD case $USER in root) esc SGR 31 7 ;; *) esc SGR 36 7 ;; esac printf '\r%*s\r%s ' "$COLUMNS" "" "($y/$1)" case $ltype in '') printf %s "$PWD" ;; *) printf %s "$ltype" ;; esac esc SGR 0 0 esc DECRC } prompt() { esc DECSC esc CUP "$LINES" printf %s "$1" esc DECTCEM h esc EL0 stty icanon echo read -r ans ||: stty -icanon -echo esc DECRC esc DECTCEM l status_line "$2" [ -n "$ans" ] } yes_no() { esc DECSC esc CUP "$LINES" printf %s "$1" esc DECTCEM h esc EL0 key=$(dd ibs=1 count=1 2>/dev/null) esc DECRC esc DECTCEM l status_line "$2" [ "$key" = y ] } line_print() { offset=$1 [ "$offset" -eq $y ] && esc SGR 0 7 shift "$offset" [ "$offset" -eq $y ] && cur=$1 line_format "$1" } line_format() { check_selected "$1" && printf + || printf ' ' file_escape "$1" [ -d "$1" ] && esc SGR 1 31 printf %s "$safe" [ -d "$1" ] && printf / esc SGR 0 0 esc EL0 printf '\r' } to_tab() { old_info=$PWD$hidden$ltype$dirs_first old_pos=$y$y2 case $tab_num in 1) tab1=$PWD hidden1=$hidden ltype1=$ltype dirs_first1=$dirs_first y_1=$y y2_1=$y2 ;; 2) tab2=$PWD hidden2=$hidden ltype2=$ltype dirs_first2=$dirs_first y_2=$y y2_2=$y2 ;; 3) tab3=$PWD hidden3=$hidden ltype3=$ltype dirs_first3=$dirs_first y_3=$y y2_3=$y2 ;; 4) tab4=$PWD hidden4=$hidden ltype4=$ltype dirs_first4=$dirs_first y_4=$y y2_4=$y2 ;; esac case $1 in 1) tab_num=1 if [ -z "$tab1" ]; then tab1=$PWD hidden1=$hidden ltype1=$ltype dirs_first1=$dirs_first y_1=1 y2_1=1 fi nwd=$tab1 ;; 2) tab_num=2 if [ -z "$tab2" ]; then tab2=$PWD hidden2=$hidden ltype2=$ltype dirs_first2=$dirs_first y_2=1 y2_2=1 fi nwd=$tab2 ;; 3) tab_num=3 if [ -z "$tab3" ]; then tab3=$PWD hidden3=$hidden ltype3=$ltype dirs_first3=$dirs_first y_3=1 y2_3=1 fi nwd=$tab3 ;; 4) tab_num=4 if [ -z "$tab4" ]; then tab4=$PWD hidden4=$hidden ltype4=$ltype dirs_first4=$dirs_first y_4=1 y2_4=1 fi nwd=$tab4 ;; esac case $tab_num in 1) hidden=$hidden1 ltype=$ltype1 dirs_first=$dirs_first1 y=$y_1 y2=$y2_1 ;; 2) hidden=$hidden2 ltype=$ltype2 dirs_first=$dirs_first2 y=$y_2 y2=$y2_2 ;; 3) hidden=$hidden3 ltype=$ltype3 dirs_first=$dirs_first3 y=$y_3 y2=$y2_3 ;; 4) hidden=$hidden4 ltype=$ltype4 dirs_first=$dirs_first4 y=$y_4 y2=$y2_4 ;; esac switched_tabs=true changed=all [ "$nwd$hidden$ltype$dirs_first" = "$old_info" ] && changed=pos && [ "$y$y2" = "$old_pos" ] && changed= : } try_tabs() { unset nwd for tab do case $tab in 1) [ -z "$tab1" ] && continue ;; 2) [ -z "$tab2" ] && continue ;; 3) [ -z "$tab3" ] && continue ;; 4) [ -z "$tab4" ] && continue ;; esac to_tab "$tab" && return 0 done return 1 } next_tab() { ct=$tab_num nums= while true; do ct=$((ct == num_tabs ? 1 : ct + 1)) [ $ct -eq $tab_num ] && break [ -n "$nums" ] && nums="$nums $ct" || nums=$ct done try_tabs $nums } prev_tab() { ct=$tab_num nums= while true; do ct=$((ct == 1 ? num_tabs : ct - 1)) [ $ct -eq $tab_num ] && break [ -n "$nums" ] && nums="$nums $ct" || nums=$ct done try_tabs $nums } check_selected() { # return code denotes whether input is selected: 0 yes, 1 no IFS=/ set -f for s in $selected; do [ "$s" = "$1" ] && unset IFS && return 0 done set +f unset IFS return 1 } add_selected() { [ -e "$1" ] || return IFS=/ set -f for s in $selected; do [ "$s" = "$1" ] && unset IFS && return done set +f unset IFS selected=$selected$1/ num_sel=$((num_sel + 1)) } remove_selected() { # return code denotes whether input was removed: 0 yes, 1 no [ -z "$selected" ] && return 1 selected=/$selected beg=${selected%%/"$1"/*} end=${selected##*/"$1"/} if [ -z "$beg$end" ]; then selected= elif [ "$beg" = "$end" ]; then selected=${selected#/} return 1 else selected=$beg/$end selected=${selected#/} selected=${selected%/}/ fi num_sel=$((num_sel - 1)) } filter_inpwd() { IFS=/ unset cwd fn inpwd notinpwd num_others=$num_sel num_sel=0 set -f -- "$1//" for piece in $1; do if [ -z "$piece" ]; then [ -z "$fn" ] && continue if [ "$PWD" = "$cwd" ]; then inpwd=$inpwd$fn/ num_sel=$((num_sel + 1)) else notinpwd=$notinpwd$cwd/$fn/ num_others=$((num_others + 1)) fi unset cwd fn elif [ -n "$fn" ]; then cwd=$cwd/$fn fi fn=$piece done set +f unset IFS } add_paths() { IFS=/ with_paths= set -f for s in $selected; do [ -n "$s" ] && with_paths=$with_paths$PWD/$s/ done set +f unset IFS selected } switch_dir() { [ "$1" = '..' ] && [ "$PWD" = / ] || [ "$1" = "$PWD" ] && return 1 add_paths cd -- "$1" >/dev/null 2>&1 || return 1 filter_inpwd "$others" selected=$inpwd others=$notinpwd$with_paths } others_op() { case $1 in p) msg_line 'Copying Files' ;; v) msg_line 'Moving Files' ;; esac IFS=/ set -f unset cwd fn others=$others// for piece in $others; do if [ -z "$piece" ]; then if [ -n "$fn" ] && [ -e "$cwd/$fn" ]; then case $1 in p) if [ -d "$cwd/$fn" ]; then cp -r -- "$cwd/$fn" "$2" || : else cp -- "$cwd/$fn" "$2" || : fi ;; v) mv -- "$cwd/$fn" "$2" || : esac fi unset cwd fn elif [ -n "$fn" ]; then cwd=$cwd/$fn fi fn=$piece done set +f unset IFS others num_others=0 } get_cur() { shift "$1" cur=$1 } redraw_cur_select(){ esc SGR 0 7 printf %s "$1" esc SGR 0 0 printf '\r' } msg_line() { esc DECSC esc CUP "$LINES" case $USER in root) esc SGR 31 7 ;; *) esc SGR 36 7 ;; esac printf '\r%*s\r%s ' "$COLUMNS" "" "$1" esc SGR 0 0 esc DECRC } populate_dirs_and_files () { unset dirs files IFS= # globbing is intentional, word splitting is disabled. # shellcheck disable=2231 for item in $1 $2; do [ -d "$item" ] && dirs=$dirs$item/ || [ ! -e "$item" ] || files=$files$item/ done unset IFS } main() { set -e case $1 in -h|--help) printf 'shfm -[hv] \n' exit 0 ;; -v|--version) printf 'shfm 0.4.2\n' exit 0 ;; *) cd -- "${1:-"$PWD"}" esac trash="${XDG_DATA_HOME:=$HOME/.local/share}/shfm/trash" cache="${XDG_CACHE_HOME:=$HOME/.cache}/shfm" [ -e "$trash" ] || mkdir -p "$trash" [ -e "$cache" ] || mkdir -p "$cache" rename_file="$cache/bulk_rename$$" exit_file="$cache/exit" esc_c=$(printf '\033') bs_char=$(printf '\177') tab_char=$(printf '\011') stty=$(stty -g) term_setup trap 'term_reset; esc screen_alt l; printf "%s\n" "$PWD" > "$exit_file"' EXIT INT trap 'term_resize' WINCH tab1=$PWD num_tabs=4 tab_num=1 set_type=normal hidden=0 dirs_first=1 num_sel=0 num_others=0 state=0 resized=0 y=1 y2=1 while true; do case $set_type in keybinds) set -- ' j - down' \ ' k - up' \ ' l - open file or directory' \ ' h - go up level' \ ' g - go to top' \ ' G - go to bottom' \ ' d - toggle printing directories first' \ ' q - quit' \ ' : - cd to ' \ ' / - search current directory *' \ ' - - go to last directory' \ ' ~ - go home' \ ' ! - spawn shell' \ ' . - toggle hidden files' \ ' ? - show keybinds' \ ' tab - next tab (or prev tab if shift is held)' \ ' 1-4 - move to tab 1-4' \ 'space - select (or deselect) current item' \ ' p - copy selected items to current folder' \ ' v - move selected items to current folder' \ ' x - trash selected items (permanently delete if in trash)' \ ' t - go to trash' \ ' r - bulk rename' \ ' a - select all' \ ' A - invert selection' \ ' n - create new file or directory' ltype=keybinds ;; search) if [ $dirs_first -eq 0 ]; then IFS= # false positive, behavior intentional # shellcheck disable=2086 set -- $ans* unset IFS else populate_dirs_and_files "$ans*" IFS=/ set -f # word splitting intentional, globbing is disabled. # shellcheck disable=2086 set +f -- $dirs $files unset IFS fi case $1$# in "$ans*1") set -- 'no results' esac ltype="search $PWD/$ans*" ;; normal) if [ $dirs_first -eq 0 ]; then if [ $hidden -eq 0 ]; then set -- * else set -- .* * fi else if [ $hidden -eq 0 ]; then populate_dirs_and_files '*' else populate_dirs_and_files '.*' '*' fi IFS=/ set -f # word splitting intentional, globbing is disabled. # shellcheck disable=2086 set +f -- $dirs $files unset IFS fi ;; esac if [ -n "$set_type" ]; then [ "$1" = '.' ] && shift [ "$1" = '..' ] && shift if [ $# -eq 0 ]; then y=1 y2=1 cur= else if [ $y -gt $# ]; then y=$# [ $y2 -gt $y ] && y2=$y hist=2 fi get_cur $y "$@" fi # adjust y2 and scrollable area if window resized if [ "$resized" -ne 0 ]; then [ $y2 -gt $bottom ] && y2=$bottom [ $y2 -gt $y ] && y2=$y esc DECSTBM 1 $bottom hist=2 fi redraw "$@" set_type= fi key=$(dd ibs=1 count=1 2>/dev/null) case $key$state in 32) state=3 ;; 42) state=4 ;; 52) state=5 ;; 62) state=6 ;; k?|A2) state=0 term_scroll_up "$@" ;; j?|B2) state=0 term_scroll_down "$@" ;; l?|C2|"$state") # ARROW RIGHT state=0 [ "$ltype" = keybinds ] && continue if [ -d "$cur" ] && switch_dir "$cur"; then set_type=normal y=1 y2=1 ltype= elif [ -e "$cur" ]; then cmd_run "${SHFM_OPENER:="${EDITOR:=vi}"}" "$cur" redraw "$@" fi ;; h?|D2|"$bs_char"?) # ARROW LEFT state=0 old_pwd=$PWD case $ltype in '') switch_dir .. || continue ;; *) ltype= esac set_type=normal y=1 y2=1 hist=1 ;; g?|H2) # HOME state=0 [ $y -eq 1 ] && continue y=1 y2=1 cur=$1 redraw "$@" ;; G?|\~4) # END state=0 [ $# -eq 0 ] && continue y=$# y2=$(($# < bottom ? $# : bottom)) get_cur "$y" "$@" redraw "$@" ;; \~5) # PGUP state=0 [ $y -eq 1 ] || [ $# -eq 0 ] && continue y=$((y - bottom)) [ $y -lt 1 ] && y=1 [ $y -lt $y2 ] && y2=$y hist=2 get_cur $y "$@" redraw "$@" ;; \~6) # PGDOWN state=0 [ $y -eq $# ] || [ $# -eq 0 ] && continue y=$((y + bottom)) if [ $y -gt $# ]; then y=$# y2=$((bottom > $# ? $# : bottom)) else min=$((bottom + y - $#)) y2=$((y2 < min ? min : y2)) fi hist=2 get_cur $y "$@" redraw "$@" ;; ' '?) [ "$ltype" = keybinds ] || [ ! -e "$cur" ] && continue new_select='+' if remove_selected "$cur"; then new_select=' ' else selected=$selected$cur/ num_sel=$((num_sel + 1)) fi if [ $y = $# ]; then redraw_cur_select "$new_select" status_line $# else term_scroll_down "$@" fi ;; "$tab_char"?) next_tab || to_tab $((tab_num == num_tabs ? 1 : tab_num + 1)) ;; Z2) state=0 prev_tab || to_tab $((tab_num == 1 ? num_tabs : tab_num - 1)) ;; [1-9]?) [ "$key" -le $num_tabs ] && [ "$key" -ne $tab_num ] && to_tab "$key" ;; p?|v?) # false positive, behavior intentional # shellcheck disable=2015 [ -z "$ltype" ] && [ -n "$others" ] || continue others_op "$key" "$PWD" set_type=normal ;; x?|\~3) state=0 [ "$ltype" = keybinds ] || [ $# -eq 0 ] && continue del= [ -z "$ltype$others" ] && [ "$PWD" = "$trash" ] && del=true if [ -z "$selected$others" ]; then key=c elif check_selected "$cur"; then key=s else [ -z $del ] && msg="trash 's'elected 'c'ur" || msg="permanently delete 's'elected 'c'ur" yes_no "$msg" $# || : fi case $key in s) [ -z $del ] && msg="send selected to trash? y/n" || msg="permanently delete selected? y/n" yes_no "$msg" $# || continue if [ -n "$selected" ]; then y=1 y2=1 IFS=/ set -f # globbing disabled and word splitting intentional # shellcheck disable=2086 if [ -z $del ]; then msg_line 'Trashing Files' mv -- $selected "$trash" || : else msg_line 'Deleting Files' rm -rf -- $selected fi set +f unset IFS selected num_sel=0 fi [ -n "$others" ] && others_op v "$trash" ;; c) [ -z $del ] && msg="send $cur to trash? y/n" || msg="permanently delete $cur? y/n" # false positive, behavior intentional # shellcheck disable=2015 [ -e "$cur" ] && yes_no "$msg" $# || continue remove_selected "$cur" || : if [ -z $del ]; then msg_line 'Trashing File' mv -- "$cur" "$trash" || : else msg_line 'Deleting File' rm -rf -- "$cur" fi [ $y -eq $# ] && y=$((y - 1)) [ $y2 -eq $# ] && y2=$((y2 - 1)) [ $y -eq 0 ] && y=1 [ $y2 -eq 0 ] && y2=1 ;; *) continue esac [ -z "$ltype" ] && set_type=normal || set_type=search ;; d?) [ "$ltype" = keybinds ] && continue [ $dirs_first -eq 0 ] && dirs_first=1 || dirs_first=0 [ -z "$ltype" ] && set_type=normal || set_type=search y=1 y2=1 ;; t?) switch_dir "$trash" || continue set_type=normal y=1 y2=1 ltype= ;; r?) [ -n "$ltype" ] || [ $# -lt 1 ] && continue for w; do printf '%s\n' "$w" done > "$rename_file" cmd_run "${EDITOR:=vi}" "$rename_file" i=0 while IFS= read -r r; do i=$((i+1)) done < "$rename_file" [ "$i" -eq $# ] || continue renames= while IFS= read -r r; do [ -n "$r" ] && [ ! -e "$r" ] && [ "$r" = "${r#*/}" ] && renames="$renames$1/$r/" shift done < "$rename_file" IFS=/ old= for new in $renames; do [ -z "$old" ] && old="$new" && continue if [ ! -e "$new" ]; then # Don't need to call msg_line because there's no chance # of moving across disk boundary mv -- "$old" "$new" remove_selected "$old" && selected=$selected$new/ fi old= done rm "$rename_file" unset IFS set_type=normal ;; a?) [ $# -eq 0 ] || [ $num_sel -eq $# ] && continue IFS=/ selected=$*/ num_sel=$# unset IFS esc CUP i=1 last_item=$(($# + y2 - y > bottom ? bottom : $# + y2 - y)) shift $((y - y2)) esc SGR 0 0 while [ $i -le $last_item ]; do [ $i -eq $y2 ] && esc SGR 0 7 printf + [ $i -eq $y2 ] && esc SGR 0 0 printf '\r' esc CUD i=$((i + 1)) done esc CUP $y2 status_line $# ;; A?) [ $# -eq 0 ] && continue IFS=/ new_selected= esc CUP i=1 first_item=$((y - y2 + 1)) last_item=$(($# + y2 - y > bottom ? bottom : $# + y2 - y)) for file; do was_selected= for s in $selected; do if [ "$s" = "$file" ]; then was_selected=true break fi done [ -z "$was_selected" ] && new_selected="$new_selected$file/" if [ $i -ge $first_item ] && [ $i -le $last_item ]; then [ $i -eq $y ] && esc SGR 0 7 [ -z "$was_selected" ] && printf + || printf ' ' [ $i -eq $y ] && esc SGR 0 0 printf '\r' esc CUD fi i=$((i + 1)) done esc CUP $y2 unset IFS selected=$new_selected num_sel=$(($# - num_sel)) status_line $# ;; n?) [ -z "$ltype" ] || continue yes_no "create new 'd'ir 'f'ile" $# || : case $key in d) # false positive, behavior intentional # shellcheck disable=2015 prompt 'directory name: ' $# && [ ! -e "$ans" ] && [ "$ans" = "${ans#*/}" ] || continue mkdir "$ans" ;; f) # false positive, behavior intentional # shellcheck disable=2015 prompt 'file name: ' $# && [ ! -e "$ans" ] && [ "$ans" = "${ans#*/}" ] || continue touch "$ans" ;; *) continue esac set_type=normal ;; .?) [ -n "$ltype" ] && continue [ $hidden -eq 0 ] && hidden=1 || hidden=0 set_type=normal y=1 y2=1 ;; :?) prompt "cd: " $# || continue # false positive, behavior intentional # shellcheck disable=2088 case $ans in '~') ans=$HOME ;; '~/'*) ans=$HOME/${ans#"~/"} esac switch_dir "$ans" || [ -n "$ltype" ] || continue set_type=normal y=1 y2=1 ltype= ;; /?) [ "$ltype" = keybinds ] && continue prompt / $# || continue ans="${ans##/}" case $ans in ''|*//*) continue esac set_type=search y=1 y2=1 ;; -?) switch_dir "$OLDPWD" || [ -n "$ltype" ] || continue set_type=normal y=1 y2=1 ltype= ;; \~?) switch_dir "$HOME" || [ -n "$ltype" ] || continue set_type=normal y=1 y2=1 ltype= ;; \!?) export SHFM_LEVEL SHFM_LEVEL=$((SHFM_LEVEL + 1)) cmd_run "${SHELL:=/bin/sh}" redraw "$@" ;; \??) set_type=keybinds y=1 y2=1 ;; q?) if [ "$ltype" = keybinds ]; then set_type=normal y=1 y2=1 ltype= else old_tab=$tab_num prev_tab || exit 0 case $old_tab in 1) unset tab1 ;; 2) unset tab2 ;; 3) unset tab3 ;; 4) unset tab4 ;; esac fi ;; # handle keys which emit escape sequences "$esc_c"*) state=1 ;; '[1') state=2 ;; *) state=0 ;; esac if [ -n "$switched_tabs" ]; then switched_tabs= switch_dir "$nwd" || : case $changed in pos) if [ $# -eq 0 ]; then y=1 y2=1 cur= else [ "$y" -gt $# ] && y=$# [ "$y2" -gt "$y" ] && y2=$y hist=2 get_cur "$y" "$@" redraw "$@" fi ;; all) case $ltype in keybinds) set_type=keybinds ;; search*) set_type=search ans="${ltype#"search $PWD/"}" ans="${ans%'*'}" ;; '') set_type=normal esac ;; *) status_line $# esac fi done } main "$@" >/dev/tty