mirror of
https://github.com/koreader/koreader
synced 2024-11-04 12:00:25 +00:00
1187 lines
32 KiB
Plaintext
1187 lines
32 KiB
Plaintext
|
#!/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
|