2
0
mirror of https://github.com/koreader/koreader synced 2024-11-16 06:12:56 +00:00
koreader/plugins/terminal.koplugin/shfm
zwim f2557a7aa6
Terminal emulator: full rewrite, real vt52 emulator (#8636)
New real terminal emulator, replacing the old plugin.
The emulator is basically a vt52 terminal (enriched with
some ANSI-sequences, as ash, vi and mksh don't behave well
on a vt52 term).
So far working: ash, mksh, bash, nano, vi, busybox, watch...
The input supports: tab-completion; cursor movement;
backspace; start of line, end of line (long press);
page up, page down (long press).
User scripts may be placed in the koterm.koplugin/scripts/
folder, aliases can be put in the file aliases and startup
command in the file profile.user in that folder.
2022-01-28 20:33:09 +01:00

1187 lines
32 KiB
Bash
Executable File

#!/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] <starting dir>\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 <input>' \
' / - search current directory <input>*' \
' - - 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