Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c2f21d8ec | ||
|
|
938955120b | ||
|
|
f374506215 | ||
|
|
fe57971b91 | ||
|
|
2f8d3e8d9e |
@@ -22,36 +22,15 @@ file_colour() {
|
|||||||
_fc_ext="${_fc_name##*.}"
|
_fc_ext="${_fc_name##*.}"
|
||||||
_fc_ext=$(printf '%s' "$_fc_ext" | tr '[:upper:]' '[:lower:]')
|
_fc_ext=$(printf '%s' "$_fc_ext" | tr '[:upper:]' '[:lower:]')
|
||||||
# executable?
|
# executable?
|
||||||
[ -x "${CWD}/${_fc_name}" ] && { printf '%s' "${RED}${BOLD}"; return; }
|
[ -x "${CWD}/${_fc_name}" ] && { printf '%s' "${GREEN}${BOLD}" ; return; }
|
||||||
case "$_fc_ext" in
|
# node device?
|
||||||
# images
|
[ -c "${CWD}/${_fc_name}" ] && { printf '%s' "${MAGENTA}${BOLD}"; return; }
|
||||||
jpg|jpeg|png|gif|bmp|tiff|tif|webp|svg|ico|heic|heif|avif)
|
# block device?
|
||||||
printf '%s' "${MAGENTA}" ;;
|
[ -b "${CWD}/${_fc_name}" ] && { printf '%s' "${MAGENTA}${BOLD}"; return; }
|
||||||
# video
|
# readable?
|
||||||
mp4|mkv|avi|mov|wmv|flv|webm|m4v|mpeg|mpg|3gp|ogv)
|
[ -r "${CWD}/${_fc_name}" ] || { printf '%s' "${RED}${BOLD}" ; return; }
|
||||||
printf '%s' "${MAGENTA}${BOLD}" ;;
|
# writable?
|
||||||
# audio
|
[ -w "${CWD}/${_fc_name}" ] || { printf '%s' "${RED}${BOLD}" ; return; }
|
||||||
mp3|flac|ogg|wav|aac|m4a|opus|wma|aiff)
|
|
||||||
printf '%s' "${CYAN}${BOLD}" ;;
|
|
||||||
# archives
|
|
||||||
zip|tar|gz|bz2|xz|zst|7z|rar|tgz|tbz2)
|
|
||||||
printf '%s' "${RED}" ;;
|
|
||||||
# documents / text
|
|
||||||
pdf|doc|docx|odt|xls|xlsx|ods|ppt|pptx|odp)
|
|
||||||
printf '%s' "${YELLOW}" ;;
|
|
||||||
# code / scripts
|
|
||||||
sh|bash|zsh|fish|py|rb|pl|lua|js|ts|jsx|tsx|\
|
|
||||||
c|h|cpp|cc|cxx|hpp|rs|go|java|kt|swift|cs|php|r)
|
|
||||||
printf '%s' "${GREEN}" ;;
|
|
||||||
# data / config
|
|
||||||
json|xml|yaml|yml|toml|ini|conf|cfg|env)
|
|
||||||
printf '%s' "${CYAN}" ;;
|
|
||||||
# markdown / text
|
|
||||||
md|markdown|rst|txt|log)
|
|
||||||
printf '%s' "${WHITE}" ;;
|
|
||||||
# default
|
|
||||||
*) printf '%s' "${WHITE}" ;;
|
|
||||||
esac
|
|
||||||
}
|
}
|
||||||
|
|
||||||
goto() { printf '\033[%d;%dH' "$1" "$2"; }
|
goto() { printf '\033[%d;%dH' "$1" "$2"; }
|
||||||
@@ -265,40 +244,42 @@ apply_filter() {
|
|||||||
if [ -z "$FILTER" ]; then
|
if [ -z "$FILTER" ]; then
|
||||||
ENTRIES="$ALL_ENTRIES"
|
ENTRIES="$ALL_ENTRIES"
|
||||||
COUNT="$ALL_COUNT"
|
COUNT="$ALL_COUNT"
|
||||||
return
|
else
|
||||||
fi
|
|
||||||
ENTRIES=""
|
ENTRIES=""
|
||||||
COUNT=0
|
COUNT=0
|
||||||
printf '%s\n' "$ALL_ENTRIES" | while IFS= read -r line; do
|
_rest="$ALL_ENTRIES"
|
||||||
case "$line" in
|
while [ -n "$_rest" ]; do
|
||||||
*"$FILTER"*) printf '%s\n' "$line" ;;
|
_line="${_rest%%
|
||||||
esac
|
*}"; _next="${_rest#*
|
||||||
done | {
|
|
||||||
while IFS= read -r line; do
|
|
||||||
ENTRIES="$ENTRIES
|
|
||||||
$line"
|
|
||||||
COUNT=$((COUNT + 1))
|
|
||||||
done
|
|
||||||
# write back via temp — subshell can't modify parent vars directly
|
|
||||||
printf '%s\n' "$COUNT" > /tmp/_fm_count
|
|
||||||
printf '%s' "$ENTRIES" > /tmp/_fm_entries
|
|
||||||
}
|
|
||||||
COUNT=$(cat /tmp/_fm_count 2>/dev/null || echo 0)
|
|
||||||
ENTRIES=$(cat /tmp/_fm_entries 2>/dev/null || echo "")
|
|
||||||
ENTRIES="${ENTRIES#
|
|
||||||
}"
|
}"
|
||||||
|
[ "$_next" = "$_rest" ] && _next=""
|
||||||
|
_rest="$_next"
|
||||||
|
[ -z "$_line" ] && continue
|
||||||
|
case "$_line" in
|
||||||
|
*"$FILTER"*)
|
||||||
|
if [ -z "$ENTRIES" ]; then ENTRIES="$_line"
|
||||||
|
else ENTRIES="$ENTRIES
|
||||||
|
$_line"; fi
|
||||||
|
COUNT=$((COUNT + 1)) ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
# write to tmp file for O(1) line access in get_entry
|
||||||
|
printf '%s\n' "$ENTRIES" > /tmp/_sfm_list
|
||||||
}
|
}
|
||||||
|
|
||||||
get_entry() {
|
get_entry() {
|
||||||
printf '%s\n' "$ENTRIES" | awk -v n="$(($1 + 1))" 'NR==n{print; exit}'
|
sed -n "$(($1 + 1))p" /tmp/_sfm_list
|
||||||
}
|
}
|
||||||
|
|
||||||
# find index of entry by name (0-based), returns -1 if not found
|
# find index of entry by name (0-based), returns -1 if not found
|
||||||
find_entry() {
|
find_entry() {
|
||||||
printf '%s\n' "$ENTRIES" | awk -v name="$1" '
|
_fe_n=$(grep -n "^${1}$" /tmp/_sfm_list 2>/dev/null | head -1 | cut -d: -f1)
|
||||||
{ if ($0 == name) { print NR-1; found=1; exit } }
|
if [ -n "$_fe_n" ]; then
|
||||||
END { if (!found) print -1 }
|
printf '%d' $((_fe_n - 1))
|
||||||
'
|
else
|
||||||
|
printf '%d' -1
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# is entry name in SELECTED list?
|
# is entry name in SELECTED list?
|
||||||
@@ -362,16 +343,22 @@ render_row() {
|
|||||||
entry=$(get_entry "$_idx")
|
entry=$(get_entry "$_idx")
|
||||||
|
|
||||||
case "$entry" in
|
case "$entry" in
|
||||||
*/) colour="${GREEN}${BOLD}" ;;
|
*/) colour="${BLUE}${BOLD}" ;;
|
||||||
*@)
|
*@)
|
||||||
_name="${entry%@}"
|
_name="${entry%@}"
|
||||||
# broken symlink if target doesn't exist
|
|
||||||
if [ -e "${CWD}/${_name}" ]; then
|
if [ -e "${CWD}/${_name}" ]; then
|
||||||
colour="${MAGENTA}${BOLD}"
|
colour="${CYAN}${BOLD}"
|
||||||
else
|
else
|
||||||
colour="${RED}${BOLD}"
|
colour="${RED}${BOLD}"
|
||||||
fi ;;
|
fi ;;
|
||||||
*) colour=$(file_colour "$entry") ;;
|
*)
|
||||||
|
_fc="${CWD}/${entry}"
|
||||||
|
if [ -x "$_fc" ]; then colour="${GREEN}${BOLD}"
|
||||||
|
elif [ -c "$_fc" ]; then colour="${MAGENTA}${BOLD}"
|
||||||
|
elif [ -b "$_fc" ]; then colour="${MAGENTA}${BOLD}"
|
||||||
|
elif [ ! -r "$_fc" ] || [ ! -w "$_fc" ]; then colour="${RED}${BOLD}"
|
||||||
|
else colour="${WHITE}"
|
||||||
|
fi ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# multi-select marker
|
# multi-select marker
|
||||||
@@ -403,17 +390,21 @@ render_row() {
|
|||||||
if [ "$SHOW_DETAILS" = "1" ]; then
|
if [ "$SHOW_DETAILS" = "1" ]; then
|
||||||
_path="${CWD}/${entry%/}"; _path="${_path%@}"
|
_path="${CWD}/${entry%/}"; _path="${_path%@}"
|
||||||
_info=$(ls -ldh "$_path" 2>/dev/null)
|
_info=$(ls -ldh "$_path" 2>/dev/null)
|
||||||
_sz=$(printf '%s' "$_info" | awk '{print $5}')
|
# parse ls output with parameter expansion — no awk fork
|
||||||
_dt=$(printf '%s' "$_info" | awk '{print $6, $7}')
|
set -- $_info
|
||||||
# fixed width: size=6, date=6 → total detail = 14 chars
|
_sz=$5; _dt="$6 $7"
|
||||||
_detail=$(printf ' %6s %-6s' "$_sz" "$_dt")
|
_detail=$(printf ' %6s %-6s' "$_sz" "$_dt")
|
||||||
|
set --
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# layout: marker(1) + name + spaces + detail
|
# layout: marker(1) + name + spaces + detail
|
||||||
_dlen=${#_detail}
|
_dlen=${#_detail}
|
||||||
maxw=$((_cols - 1 - _dlen - 1))
|
maxw=$((_cols - 2 - _dlen))
|
||||||
if [ "${#display}" -gt "$maxw" ]; then
|
if [ "${#display}" -gt "$maxw" ]; then
|
||||||
display=$(printf '%s' "$display" | cut -c1-$((_cols - _dlen - 4)))
|
_trunc=$((maxw - 3))
|
||||||
|
while [ "${#display}" -gt "$_trunc" ]; do
|
||||||
|
display="${display%?}"
|
||||||
|
done
|
||||||
display="${display}..."
|
display="${display}..."
|
||||||
fi
|
fi
|
||||||
_namew=$((${#display} + 1)) # 1 for marker
|
_namew=$((${#display} + 1)) # 1 for marker
|
||||||
@@ -471,7 +462,7 @@ draw_preview() {
|
|||||||
_dl=" ${_dn}/"
|
_dl=" ${_dn}/"
|
||||||
[ "${#_dl}" -gt "$((_pw - 1))" ] && _dl="$(printf '%s' "$_dl" | cut -c1-$((_pw-2)))~"
|
[ "${#_dl}" -gt "$((_pw - 1))" ] && _dl="$(printf '%s' "$_dl" | cut -c1-$((_pw-2)))~"
|
||||||
goto "$_pr" "$_px"
|
goto "$_pr" "$_px"
|
||||||
printf '%s%s%s' "${GREEN}${BOLD}" "$_dl" "${RESET}"
|
printf '%s%s%s' "${BLUE}${BOLD}" "$_dl" "${RESET}"
|
||||||
_pr=$((_pr + 1))
|
_pr=$((_pr + 1))
|
||||||
done
|
done
|
||||||
for _fe in "$_path"/*; do
|
for _fe in "$_path"/*; do
|
||||||
@@ -684,15 +675,21 @@ draw() {
|
|||||||
read_key() {
|
read_key() {
|
||||||
IFS= read -r -n1 key 2>/dev/null || IFS= read -r key
|
IFS= read -r -n1 key 2>/dev/null || IFS= read -r key
|
||||||
if [ "$key" = "$(printf '\033')" ]; then
|
if [ "$key" = "$(printf '\033')" ]; then
|
||||||
# peek at next char with a very short timeout
|
|
||||||
IFS= read -r -n1 -t 0.1 _k2 2>/dev/null || _k2=""
|
IFS= read -r -n1 -t 0.1 _k2 2>/dev/null || _k2=""
|
||||||
if [ -z "$_k2" ]; then
|
if [ -z "$_k2" ]; then
|
||||||
# nothing followed — bare esc
|
|
||||||
printf '\033'; return
|
printf '\033'; return
|
||||||
fi
|
fi
|
||||||
# got a second char — read one more for full sequence (e.g. [A [B [C [D)
|
|
||||||
IFS= read -r -n1 -t 0.1 _k3 2>/dev/null || _k3=""
|
IFS= read -r -n1 -t 0.1 _k3 2>/dev/null || _k3=""
|
||||||
|
# if sequence ends with ~, it may have more chars — read one more
|
||||||
|
case "${_k2}${_k3}" in
|
||||||
|
'['[0-9])
|
||||||
|
IFS= read -r -n1 -t 0.1 _k4 2>/dev/null || _k4=""
|
||||||
|
key="${key}${_k2}${_k3}${_k4}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
key="${key}${_k2}${_k3}"
|
key="${key}${_k2}${_k3}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
fi
|
fi
|
||||||
printf '%s' "$key"
|
printf '%s' "$key"
|
||||||
}
|
}
|
||||||
@@ -1654,7 +1651,7 @@ do_paste() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# --- main ---
|
# --- main ---
|
||||||
trap 'restore_term; show_cursor; printf "\033[2J\033[H"; 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 TERM EXIT
|
||||||
|
|
||||||
setup_term
|
setup_term
|
||||||
hide_cursor
|
hide_cursor
|
||||||
@@ -1674,6 +1671,7 @@ while true; do
|
|||||||
G) SEL=$((COUNT - 1)) ;;
|
G) SEL=$((COUNT - 1)) ;;
|
||||||
"$(printf '\033[6~')") SEL=$((SEL + $(term_rows) / 2)) ;;
|
"$(printf '\033[6~')") SEL=$((SEL + $(term_rows) / 2)) ;;
|
||||||
"$(printf '\033[5~')") SEL=$((SEL - $(term_rows) / 2)) ;;
|
"$(printf '\033[5~')") SEL=$((SEL - $(term_rows) / 2)) ;;
|
||||||
|
"$(printf '\033[3~')") do_delete ;;
|
||||||
"$(printf '\n')"|\
|
"$(printf '\n')"|\
|
||||||
"$(printf '\r')"|\
|
"$(printf '\r')"|\
|
||||||
"$(printf '\033[C')"|\
|
"$(printf '\033[C')"|\
|
||||||
|
|||||||
@@ -43,6 +43,16 @@ Go to home directory.
|
|||||||
.TP
|
.TP
|
||||||
.B \`
|
.B \`
|
||||||
Jump to previous directory (toggle).
|
Jump to previous directory (toggle).
|
||||||
|
.TP
|
||||||
|
.B :
|
||||||
|
Prompt for a path to jump to directly. Supports
|
||||||
|
.B ~
|
||||||
|
expansion. Shows an error if the path does not exist. Press esc to cancel.
|
||||||
|
.TP
|
||||||
|
.B f
|
||||||
|
Find files recursively under the current directory. Opens an interactive
|
||||||
|
picker. Press j/k or arrows to navigate results, enter or l to jump to the
|
||||||
|
selected result, esc/q/h to close.
|
||||||
|
|
||||||
.SS Search and Filter
|
.SS Search and Filter
|
||||||
.TP
|
.TP
|
||||||
@@ -58,7 +68,9 @@ Exit search mode while keeping the current filter active.
|
|||||||
.SS Display Toggles
|
.SS Display Toggles
|
||||||
.TP
|
.TP
|
||||||
.B .
|
.B .
|
||||||
Toggle hidden files (dotfiles).
|
Toggle hidden files (dotfiles). Hidden entries appear before regular
|
||||||
|
entries (hidden dirs, then regular dirs, then hidden files, then
|
||||||
|
regular files).
|
||||||
.TP
|
.TP
|
||||||
.B T
|
.B T
|
||||||
Toggle size/date detail column.
|
Toggle size/date detail column.
|
||||||
@@ -86,7 +98,7 @@ Create a new directory. Press esc to cancel.
|
|||||||
.B n
|
.B n
|
||||||
Create a new empty file. Press esc to cancel.
|
Create a new empty file. Press esc to cancel.
|
||||||
.TP
|
.TP
|
||||||
.B d
|
.B d " or " del
|
||||||
Delete selected entry or all multi-selected entries.
|
Delete selected entry or all multi-selected entries.
|
||||||
Non-empty directories require a second confirmation.
|
Non-empty directories require a second confirmation.
|
||||||
.TP
|
.TP
|
||||||
@@ -98,6 +110,14 @@ Open the trash directory.
|
|||||||
.TP
|
.TP
|
||||||
.B o
|
.B o
|
||||||
Open selected file with a custom program. Press esc to cancel.
|
Open selected file with a custom program. Press esc to cancel.
|
||||||
|
.TP
|
||||||
|
.B +
|
||||||
|
Make the selected file executable
|
||||||
|
.RB ( "chmod +x" ).
|
||||||
|
.TP
|
||||||
|
.B \-
|
||||||
|
Remove execute permission from the selected file
|
||||||
|
.RB ( "chmod -x" ).
|
||||||
|
|
||||||
.SS Clipboard
|
.SS Clipboard
|
||||||
.TP
|
.TP
|
||||||
@@ -109,9 +129,11 @@ Cut selected entry or all multi-selected entries.
|
|||||||
.TP
|
.TP
|
||||||
.B p
|
.B p
|
||||||
Paste clipboard contents into the current directory.
|
Paste clipboard contents into the current directory.
|
||||||
|
Auto-renames on conflict by appending
|
||||||
|
.IR _copy .
|
||||||
.TP
|
.TP
|
||||||
.B c
|
.B c
|
||||||
Copy the full path of selected entry to the system clipboard.
|
Copy the full path of the selected entry to the system clipboard.
|
||||||
Requires
|
Requires
|
||||||
.BR wl-copy ", " xclip ", " xsel ", or " pbcopy .
|
.BR wl-copy ", " xclip ", " xsel ", or " pbcopy .
|
||||||
|
|
||||||
@@ -122,6 +144,9 @@ Toggle multi-select on the current entry (cursor advances).
|
|||||||
.TP
|
.TP
|
||||||
.B a
|
.B a
|
||||||
Select all entries / deselect all.
|
Select all entries / deselect all.
|
||||||
|
.PP
|
||||||
|
Multi-select works with
|
||||||
|
.BR y ", " x ", and " d .
|
||||||
|
|
||||||
.SS Bookmarks
|
.SS Bookmarks
|
||||||
.TP
|
.TP
|
||||||
@@ -139,7 +164,8 @@ Drop into
|
|||||||
.B $SHELL
|
.B $SHELL
|
||||||
in the current directory. Type
|
in the current directory. Type
|
||||||
.I exit
|
.I exit
|
||||||
to return to sfm.
|
to return to sfm. The working directory is updated if you cd elsewhere
|
||||||
|
in the shell.
|
||||||
.TP
|
.TP
|
||||||
.B ?
|
.B ?
|
||||||
Show the keyboard shortcuts help overlay.
|
Show the keyboard shortcuts help overlay.
|
||||||
@@ -186,6 +212,9 @@ case "$1" in
|
|||||||
esac
|
esac
|
||||||
.fi
|
.fi
|
||||||
.PP
|
.PP
|
||||||
|
Make it executable with:
|
||||||
|
.B chmod +x ~/.config/sfm/opener
|
||||||
|
.PP
|
||||||
If the opener script does not exist, sfm falls back to its built-in
|
If the opener script does not exist, sfm falls back to its built-in
|
||||||
smart opener which detects file types by extension and MIME type.
|
smart opener which detects file types by extension and MIME type.
|
||||||
|
|
||||||
@@ -335,11 +364,13 @@ s() { cd "$(sfm)"; }
|
|||||||
.fi
|
.fi
|
||||||
|
|
||||||
.SH AUTHORS
|
.SH AUTHORS
|
||||||
Built interactively with Claude (Anthropic).
|
Emmett1 <me@emmett1.my> with help of AI.
|
||||||
|
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
|
.BR find (1),
|
||||||
.BR ls (1),
|
.BR ls (1),
|
||||||
.BR mv (1),
|
.BR mv (1),
|
||||||
.BR cp (1),
|
.BR cp (1),
|
||||||
.BR rm (1),
|
.BR rm (1),
|
||||||
|
.BR chmod (1),
|
||||||
.BR vi (1)
|
.BR vi (1)
|
||||||
|
|||||||
Reference in New Issue
Block a user