aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoremmett1 <me@emmett1.my>2026-05-29 11:32:58 +0800
committeremmett1 <me@emmett1.my>2026-05-29 11:32:58 +0800
commitbed888c3c3910aecaff19d63e0ea905316d23507 (patch)
treefbba6dd3f0feb977bf32b96a5eb6e27a6e2aea79
parent7c2f21d8ecf2438a98a5222c74c0e0086704acdb (diff)
downloadsfm-bed888c3c3910aecaff19d63e0ea905316d23507.tar.gz
sfm-bed888c3c3910aecaff19d63e0ea905316d23507.zip
fix and updates
-rwxr-xr-xsfm106
1 files changed, 56 insertions, 50 deletions
diff --git a/sfm b/sfm
index d440464..9b181c9 100755
--- a/sfm
+++ b/sfm
@@ -16,23 +16,6 @@ MAGENTA=$(tput_cmd setaf 5)
BLUE=$(tput_cmd setaf 4)
ERASE_LINE=$(tput_cmd el || printf '\033[K')
-# map a filename to a display colour based on extension
-file_colour() {
- _fc_name="$1"
- _fc_ext="${_fc_name##*.}"
- _fc_ext=$(printf '%s' "$_fc_ext" | tr '[:upper:]' '[:lower:]')
- # executable?
- [ -x "${CWD}/${_fc_name}" ] && { printf '%s' "${GREEN}${BOLD}" ; return; }
- # node device?
- [ -c "${CWD}/${_fc_name}" ] && { printf '%s' "${MAGENTA}${BOLD}"; return; }
- # block device?
- [ -b "${CWD}/${_fc_name}" ] && { printf '%s' "${MAGENTA}${BOLD}"; return; }
- # readable?
- [ -r "${CWD}/${_fc_name}" ] || { printf '%s' "${RED}${BOLD}" ; return; }
- # writable?
- [ -w "${CWD}/${_fc_name}" ] || { printf '%s' "${RED}${BOLD}" ; return; }
-}
-
goto() { printf '\033[%d;%dH' "$1" "$2"; }
hide_cursor() { printf '\033[?25l'; }
show_cursor() { printf '\033[?25h'; }
@@ -174,8 +157,8 @@ $_line"; fi ;;
# sort files using ls into a tmp file
if [ -n "$_files" ]; then
case "$SORT_MODE" in
- size) ls -1Sp "$CWD" 2>/dev/null | grep -v '/' > /tmp/_fm_sorted ;;
- date) ls -1tp "$CWD" 2>/dev/null | grep -v '/' > /tmp/_fm_sorted ;;
+ size) ls -1Sp "$CWD" 2>/dev/null | grep -v '/' > /tmp/_fm_sorted_$$ ;;
+ date) ls -1tp "$CWD" 2>/dev/null | grep -v '/' > /tmp/_fm_sorted_$$ ;;
esac
# only keep files that were already in our list (respects hidden filter)
_sorted=""
@@ -190,7 +173,7 @@ ${_n}
else _sorted="$_sorted
$_n"; fi ;;
esac
- done < /tmp/_fm_sorted
+ done < /tmp/_fm_sorted_$$
_files="$_sorted"
fi
@@ -265,16 +248,16 @@ $_line"; fi
done
fi
# write to tmp file for O(1) line access in get_entry
- printf '%s\n' "$ENTRIES" > /tmp/_sfm_list
+ printf '%s\n' "$ENTRIES" > /tmp/_sfm_list_$$
}
get_entry() {
- sed -n "$(($1 + 1))p" /tmp/_sfm_list
+ sed -n "$(($1 + 1))p" /tmp/_sfm_list_$$
}
# find index of entry by name (0-based), returns -1 if not found
find_entry() {
- _fe_n=$(grep -n "^${1}$" /tmp/_sfm_list 2>/dev/null | head -1 | cut -d: -f1)
+ _fe_n=$(grep -nFx "$1" /tmp/_sfm_list_$$ 2>/dev/null | head -1 | cut -d: -f1)
if [ -n "$_fe_n" ]; then
printf '%d' $((_fe_n - 1))
else
@@ -284,12 +267,7 @@ find_entry() {
# is entry name in SELECTED list?
is_selected() {
- case "
-${SELECTED}
-" in *"
-$1
-"*) return 0 ;; esac
- return 1
+ printf '%s\n' "$SELECTED" | grep -Fxq "$1"
}
count_selected() {
@@ -1033,7 +1011,7 @@ do_find() {
_query="$READ_LINE"
# run find and collect results into a tmp file
- _tmp=/tmp/_sfm_find
+ _tmp=/tmp/_sfm_find_$$
find "$CWD" -name "*${_query}*" 2>/dev/null | sort > "$_tmp"
_total=$(wc -l < "$_tmp" | tr -d '[:space:]')
@@ -1258,7 +1236,8 @@ do_delete() {
[ "$_next" = "$_rest" ] && _next=""
_rest="$_next"
[ -z "$_line" ] && continue
- _t="${CWD}/${_line%/}"
+ _name="${_line%@}"; _name="${_name%/}"
+ _t="${CWD}/${_name}"
if [ -d "$_t" ]; then rm -rf "$_t"; else rm -f "$_t"; fi
done
SELECTED=""
@@ -1271,7 +1250,8 @@ do_delete() {
else
# --- single delete ---
entry=$(get_entry "$SEL")
- target="${CWD}/${entry%/}"
+ _name="${entry%@}"; _name="${_name%/}"
+ target="${CWD}/${_name}"
restore_term; show_cursor
goto "$(term_rows)" 1
printf '%s%s Delete "%s"? [y/N] %s' "${ERASE_LINE}" "${RED}${BOLD}" "$entry" "${RESET}"
@@ -1311,7 +1291,8 @@ do_rename() {
goto "$(term_rows)" 1
printf '%s%s Rename "%s" to (esc=cancel): %s' "${ERASE_LINE}" "${YELLOW}${BOLD}" "$entry" "${RESET}"
if read_line && [ -n "$READ_LINE" ] && [ "$READ_LINE" != "$entry" ]; then
- mv "${CWD}/${entry%/}" "${CWD}/${READ_LINE}"
+ _name="${entry%@}"; _name="${_name%/}"
+ mv "${CWD}/${_name}" "${CWD}/${READ_LINE}"
load_entries
else
NEED_FULL_REDRAW=1; draw
@@ -1358,7 +1339,8 @@ do_chmod_nox() {
do_info() {
entry=$(get_entry "$SEL")
[ -z "$entry" ] && return
- target="${CWD}/${entry%/}"
+ _name="${entry%@}"; _name="${_name%/}"
+ target="${CWD}/${_name}"
# permissions + type
_perm=$(ls -ld "$target" 2>/dev/null | awk '{print $1}')
# size (human readable via du, fallback to ls)
@@ -1396,9 +1378,10 @@ do_sort() {
do_trash() {
entry=$(get_entry "$SEL")
[ -z "$entry" ] && return
- target="${CWD}/${entry%/}"
+ _name="${entry%@}"; _name="${_name%/}"
+ target="${CWD}/${_name}"
_ts=$(date '+%Y%m%d_%H%M%S' 2>/dev/null || date '+%s')
- _dest="${TRASH_DIR}/${_ts}_${entry%/}"
+ _dest="${TRASH_DIR}/${_ts}_${_name}"
if mv "$target" "$_dest"; then
INFO_MSG="trashed: ${entry}"
[ "$SEL" -ge "$((COUNT - 1))" ] && SEL=$((COUNT - 2))
@@ -1421,7 +1404,8 @@ do_open_trash() {
do_copy_path() {
entry=$(get_entry "$SEL")
[ -z "$entry" ] && return
- _path="${CWD}/${entry%/}"
+ _name="${entry%@}"; _name="${_name%/}"
+ _path="${CWD}/${_name}"
if command -v wl-copy >/dev/null 2>&1; then
printf '%s' "$_path" | wl-copy
elif command -v xclip >/dev/null 2>&1; then
@@ -1479,6 +1463,8 @@ do_bookmark_jump() {
_hl=$(printf '%*s' "$_iw" '' | tr ' ' '-')
_bsel=1
+ _bvis=$((_ph - 3))
+ _boff=1
while true; do
# draw picker inline
goto "$_py" "$_px"
@@ -1488,11 +1474,23 @@ do_bookmark_jump() {
printf '%s|%s%s%s%s|%s' "${BOLD}${CYAN}" "${RESET}" "$_t" "$_p" "${BOLD}${CYAN}" "${RESET}"
goto "$((_py+2))" "$_px"
printf '%s|%s|%s' "${BOLD}${CYAN}" "$_hl" "${RESET}"
+ # clear visible rows first
+ _bri=0
+ while [ "$_bri" -lt "$_bvis" ]; do
+ goto "$((_py + 3 + _bri))" "$_px"
+ _bep=$(printf '%*s' "$_iw" '')
+ printf '%s|%s|%s' "${BOLD}${CYAN}" "$_bep" "${RESET}"
+ _bri=$((_bri + 1))
+ done
+ # draw visible bookmarks
_bi=0
while IFS= read -r _bm; do
[ -z "$_bm" ] && continue
_bi=$((_bi+1))
- goto "$((_py+2+_bi))" "$_px"
+ [ "$_bi" -lt "$_boff" ] && continue
+ [ "$_bi" -ge $((_boff + _bvis)) ] && continue
+ _drow=$((_py + 2 + _bi - _boff + 1))
+ goto "$_drow" "$_px"
_bt="${_bi} ${_bm}"
if [ "${#_bt}" -gt "$_iw" ]; then _bt=$(printf '%s' "$_bt" | cut -c1-$((_iw-1))); _bt="${_bt}~"; fi
_bpl=$((_iw - ${#_bt})); _bp=$(printf '%*s' "$_bpl" '')
@@ -1506,14 +1504,18 @@ do_bookmark_jump() {
printf '%s+%s+%s' "${BOLD}${CYAN}" "$_hl" "${RESET}"
IFS= read -r -n1 _bk 2>/dev/null || IFS= read -r _bk
case "$_bk" in
- j) _bsel=$((_bsel+1)); [ "$_bsel" -gt "$_bc" ] && _bsel=$_bc ;;
- k) _bsel=$((_bsel-1)); [ "$_bsel" -lt 1 ] && _bsel=1 ;;
+ j) _bsel=$((_bsel+1)); [ "$_bsel" -gt "$_bc" ] && _bsel=$_bc
+ [ "$_bsel" -ge $((_boff + _bvis)) ] && _boff=$((_bsel - _bvis + 1)) ;;
+ k) _bsel=$((_bsel-1)); [ "$_bsel" -lt 1 ] && _bsel=1
+ [ "$_bsel" -lt "$_boff" ] && _boff=$_bsel ;;
"$(printf '\033')")
# use timeout to distinguish bare esc from arrow sequences
IFS= read -r -n2 -t 0.1 _seq 2>/dev/null || _seq=""
case "$_seq" in
- '[A') _bsel=$((_bsel-1)); [ "$_bsel" -lt 1 ] && _bsel=1 ;;
- '[B') _bsel=$((_bsel+1)); [ "$_bsel" -gt "$_bc" ] && _bsel=$_bc ;;
+ '[A') _bsel=$((_bsel-1)); [ "$_bsel" -lt 1 ] && _bsel=1
+ [ "$_bsel" -lt "$_boff" ] && _boff=$_bsel ;;
+ '[B') _bsel=$((_bsel+1)); [ "$_bsel" -gt "$_bc" ] && _bsel=$_bc
+ [ "$_bsel" -ge $((_boff + _bvis)) ] && _boff=$((_bsel - _bvis + 1)) ;;
'[C') # right — open
_chosen=$(awk -v n="$_bsel" 'NR==n&&NF{print;exit}' "$BOOKMARK_FILE")
if [ -n "$_chosen" ] && [ -d "$_chosen" ]; then
@@ -1589,7 +1591,8 @@ do_yank() {
if [ "$_c" -gt 0 ]; then
CLIPBOARD=$(printf '%s\n' "$SELECTED" | while IFS= read -r _e; do
[ -z "$_e" ] && continue
- printf '%s\n' "${CWD}/${_e%/}"
+ _yname="${_e%@}"; _yname="${_yname%/}"
+ printf '%s\n' "${CWD}/${_yname}"
done)
CLIP_MODE="copy"
INFO_MSG="yanked ${_c} items"
@@ -1597,7 +1600,8 @@ do_yank() {
else
entry=$(get_entry "$SEL")
[ -z "$entry" ] && return
- CLIPBOARD="${CWD}/${entry%/}"
+ _yname="${entry%@}"; _yname="${_yname%/}"
+ CLIPBOARD="${CWD}/${_yname}"
CLIP_MODE="copy"
INFO_MSG="yanked: ${entry}"
fi
@@ -1609,7 +1613,8 @@ do_cut() {
if [ "$_c" -gt 0 ]; then
CLIPBOARD=$(printf '%s\n' "$SELECTED" | while IFS= read -r _e; do
[ -z "$_e" ] && continue
- printf '%s\n' "${CWD}/${_e%/}"
+ _cname="${_e%@}"; _cname="${_cname%/}"
+ printf '%s\n' "${CWD}/${_cname}"
done)
CLIP_MODE="cut"
INFO_MSG="cut ${_c} items"
@@ -1617,7 +1622,8 @@ do_cut() {
else
entry=$(get_entry "$SEL")
[ -z "$entry" ] && return
- CLIPBOARD="${CWD}/${entry%/}"
+ _cname="${entry%@}"; _cname="${_cname%/}"
+ CLIPBOARD="${CWD}/${_cname}"
CLIP_MODE="cut"
INFO_MSG="cut: ${entry}"
fi
@@ -1630,7 +1636,6 @@ do_paste() {
NEED_FULL_REDRAW=1
return
fi
- _ok=0; _fail=0
printf '%s\n' "$CLIPBOARD" | while IFS= read -r _src; do
[ -z "$_src" ] && continue
_name="${_src##*/}"
@@ -1641,8 +1646,8 @@ do_paste() {
_dest="${CWD}/${_base}_copy${_ext}"
fi
case "$CLIP_MODE" in
- copy) cp -r "$_src" "$_dest" && _ok=$((_ok+1)) || _fail=$((_fail+1)) ;;
- cut) mv "$_src" "$_dest" && _ok=$((_ok+1)) || _fail=$((_fail+1)) ;;
+ copy) cp -r "$_src" "$_dest" ;;
+ cut) mv "$_src" "$_dest" ;;
esac
done
CLIPBOARD=""; CLIP_MODE=""
@@ -1651,7 +1656,7 @@ do_paste() {
}
# --- main ---
-trap 'restore_term; show_cursor; printf "\033[2J\033[H"; rm -f /tmp/_sfm_list /tmp/_sfm_find; exit 0' INT TERM EXIT
+trap 'restore_term; show_cursor; printf "\033[2J\033[H"; rm -f /tmp/_sfm_list_$$ /tmp/_sfm_find_$$; exit 0' INT QUIT TERM EXIT
setup_term
hide_cursor
@@ -1709,6 +1714,7 @@ while true; do
m) do_mkdir ;;
n) do_newfile ;;
q|Q) break ;;
+ '!') do_shell ;;
esac
done