commit fbc3227515f056b6dc0590bd59e91176734ca176
parent 02a8adadc48bc5b8c28a617d619b6f4a9403a387
Author: emmett1 <emmett1.2miligrams@protonmail.com>
Date: Mon, 16 Mar 2026 00:36:44 +0800
added preview function
Diffstat:
| M | sfm | | | 176 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------- |
1 file changed, 138 insertions(+), 38 deletions(-)
diff --git a/sfm b/sfm
@@ -83,6 +83,7 @@ INFO_MSG="" # ephemeral message shown in botbar
SHOW_HIDDEN=0 # 1 = show dotfiles
SORT_MODE="name" # name | size | date
SHOW_DETAILS=0 # 1 = show size+date column
+SHOW_PREVIEW=0 # 1 = show preview pane on right
TRASH_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/sfm-trash"
mkdir -p "$TRASH_DIR"
PREV_CWD="" # last visited directory, for jumping back
@@ -432,6 +433,85 @@ render_row() {
fi
}
+draw_preview() {
+ _rows=$1; _cols=$2; _px=$3; _pw=$4
+ entry=$(get_entry "$SEL")
+ # clear preview area first
+ _r=2
+ while [ "$_r" -le $((_rows - 1)) ]; do
+ goto "$_r" "$_px"
+ printf '\033[K'
+ _r=$((_r + 1))
+ done
+ [ -z "$entry" ] && return
+ _path=$(joinpath "${entry%/}"); _path="${_path%@}"
+
+ # draw vertical divider
+ _r=2
+ while [ "$_r" -le $((_rows - 1)) ]; do
+ goto "$_r" $((_px - 1))
+ printf '%s|%s' "${CYAN}" "${RESET}"
+ _r=$((_r + 1))
+ done
+
+ case "$entry" in
+ */)
+ # directory: show entry count
+ _dc=$(ls -1 "$_path" 2>/dev/null | wc -l | tr -d '[:space:]')
+ goto 2 "$_px"
+ printf '%s[dir] %s items%s' "${CYAN}" "${_dc}" "${RESET}"
+ ;;
+ *@)
+ # symlink
+ _tgt=$(readlink "$_path" 2>/dev/null || echo "?")
+ goto 2 "$_px"
+ printf '%s[symlink]%s' "${MAGENTA}${BOLD}" "${RESET}"
+ goto 3 "$_px"
+ printf '%s-> %s%s' "${WHITE}" "${_tgt}" "${RESET}"
+ ;;
+ *)
+ # detect if text via extension or mime
+ _ext="${entry##*.}"
+ _ext=$(printf '%s' "$_ext" | tr '[:upper:]' '[:lower:]')
+ _is_text=0
+ case "$_ext" in
+ txt|md|markdown|rst|log|conf|cfg|ini|toml|yaml|yml|\
+ sh|bash|zsh|py|rb|pl|lua|js|ts|json|xml|html|htm|\
+ css|c|h|cpp|rs|go|java|php|r|sql|vim|env|gitignore|\
+ diff|patch|csv|tsv|lock|mod|sum) _is_text=1 ;;
+ esac
+ if [ "$_is_text" = "0" ] && command -v file >/dev/null 2>&1; then
+ case "$(file --mime-type -b "$_path" 2>/dev/null)" in
+ text/*) _is_text=1 ;;
+ esac
+ fi
+ if [ "$_is_text" = "1" ]; then
+ _max_lines=$((_rows - 3))
+ _line_n=0
+ while IFS= read -r _line && [ "$_line_n" -lt "$_max_lines" ]; do
+ # truncate to preview width
+ if [ "${#_line}" -gt "$((_pw - 1))" ]; then
+ _line=$(printf '%s' "$_line" | cut -c1-$((_pw - 2)))
+ _line="${_line}~"
+ fi
+ goto "$((_line_n + 2))" "$_px"
+ printf '%s%s%s' "${WHITE}" "$_line" "${RESET}"
+ _line_n=$((_line_n + 1))
+ done < "$_path"
+ if [ "$_line_n" -eq 0 ]; then
+ goto 2 "$_px"
+ printf '%s(empty file)%s' "${WHITE}" "${RESET}"
+ fi
+ else
+ # binary / unknown
+ goto 2 "$_px"
+ _sz=$(ls -lh "$_path" 2>/dev/null | awk '{print $5}')
+ printf '%s[binary] %s%s' "${YELLOW}" "${_sz}" "${RESET}"
+ fi
+ ;;
+ esac
+}
+
draw_topbar() {
_cols=$1
spaces=$(printf '%*s' "$_cols" '')
@@ -494,6 +574,15 @@ draw() {
COLS=$(term_cols)
LIST_ROWS=$((ROWS - 2)) # row 1 = topbar, row ROWS = botbar
+ # split layout when preview enabled
+ if [ "$SHOW_PREVIEW" = "1" ]; then
+ LIST_COLS=$((COLS / 2))
+ PREV_COL=$((LIST_COLS + 2))
+ PREV_WIDTH=$((COLS - LIST_COLS - 2))
+ else
+ LIST_COLS=$COLS
+ fi
+
# clamp selection
[ "$SEL" -lt 0 ] && SEL=0
[ "$COUNT" -gt 0 ] && [ "$SEL" -ge "$COUNT" ] && SEL=$((COUNT - 1))
@@ -509,7 +598,6 @@ draw() {
[ "$OFFSET" != "$PREV_OFFSET" ] && NEED_FULL_REDRAW=1
if [ "$NEED_FULL_REDRAW" = "1" ]; then
- # --- full repaint: move to top without erasing, overwrite every row ---
printf '\033[H'
draw_topbar "$COLS"
@@ -517,7 +605,7 @@ draw() {
idx=$OFFSET
while [ "$row" -le $((LIST_ROWS + 1)) ] && [ "$idx" -lt "$COUNT" ]; do
sel=0; [ "$idx" -eq "$SEL" ] && sel=1
- render_row "$row" "$idx" "$sel" "$COLS"
+ render_row "$row" "$idx" "$sel" "$LIST_COLS"
row=$((row + 1))
idx=$((idx + 1))
done
@@ -527,18 +615,19 @@ draw() {
printf '%s (empty)%s%s' "${WHITE}" "${ERASE_LINE}" "${RESET}"
row=3
fi
- # blank leftover rows (e.g. after entering a smaller directory)
+ # blank leftover rows
while [ "$row" -le $((LIST_ROWS + 1)) ]; do
goto "$row" 1
printf '%s' "${ERASE_LINE}"
row=$((row + 1))
done
+ [ "$SHOW_PREVIEW" = "1" ] && draw_preview "$ROWS" "$COLS" "$PREV_COL" "$PREV_WIDTH"
+
draw_botbar "$ROWS" "$COLS"
NEED_FULL_REDRAW=0
else
- # --- fast path: only touch the two rows whose highlight state changed ---
- # if nothing moved, skip entirely — no flicker at boundaries
+ # --- fast path ---
if [ "$SEL" = "$PREV_SEL" ] && [ "$OFFSET" = "$PREV_OFFSET" ]; then
return 2>/dev/null || true
fi
@@ -546,12 +635,14 @@ draw() {
new_row=$(( (SEL - OFFSET) + 2 ))
if [ "$prev_row" -ge 2 ] && [ "$prev_row" -le $((LIST_ROWS + 1)) ]; then
- render_row "$prev_row" "$PREV_SEL" 0 "$COLS"
+ render_row "$prev_row" "$PREV_SEL" 0 "$LIST_COLS"
fi
if [ "$new_row" -ge 2 ] && [ "$new_row" -le $((LIST_ROWS + 1)) ]; then
- render_row "$new_row" "$SEL" 1 "$COLS"
+ render_row "$new_row" "$SEL" 1 "$LIST_COLS"
fi
+ [ "$SHOW_PREVIEW" = "1" ] && draw_preview "$ROWS" "$COLS" "$PREV_COL" "$PREV_WIDTH"
+
draw_botbar "$ROWS" "$COLS"
fi
@@ -765,7 +856,7 @@ do_help() {
bw=$((COLS - 4))
[ "$bw" -gt 52 ] && bw=52
[ "$bw" -lt 36 ] && bw=36
- bh=41
+ bh=42
_help_bx=$(( (COLS - bw) / 2 )); [ "$_help_bx" -lt 1 ] && _help_bx=1
_help_by=$(( (ROWS - bh) / 2 )); [ "$_help_by" -lt 1 ] && _help_by=1
_help_iw=$((bw - 2))
@@ -787,32 +878,33 @@ do_help() {
help_row 11 " i file info in status bar"
help_row 12 " s cycle sort: name/size/date"
help_row 13 " T toggle size/date details"
- help_sep 14
- help_row 15 " space toggle multi-select"
- help_row 16 " a select all / deselect all"
- help_row 17 " y yank/copy (works on selection)"
- help_row 18 " x cut (works on selection)"
- help_row 19 " p paste"
- help_row 20 " d delete (works on selection)"
- help_sep 21
- help_row 22 " r rename R refresh"
- help_row 23 " m make directory"
- help_row 24 " n new file"
- help_row 25 " u trash file (safe delete)"
- help_row 26 " U open trash directory"
- help_row 27 " ! drop to shell in CWD"
- help_row 28 " o open with custom program"
- help_sep 29
- help_row 30 " b bookmark current dir"
- help_row 31 " B open bookmark picker"
- help_row 32 " c copy path to clipboard"
- help_row 33 " ~ go to home directory"
- help_row 34 " \` jump to previous directory"
- help_sep 35
- help_row 36 " q quit"
- help_row 37 " ? this help"
- help_row 38 ""
- help_row 39 " press any key to close..."
+ help_row 14 " P toggle preview pane"
+ help_sep 15
+ help_row 16 " space toggle multi-select"
+ help_row 17 " a select all / deselect all"
+ help_row 18 " y yank/copy (works on selection)"
+ help_row 19 " x cut (works on selection)"
+ help_row 20 " p paste"
+ help_row 21 " d delete (works on selection)"
+ help_sep 22
+ help_row 23 " r rename R refresh"
+ help_row 24 " m make directory"
+ help_row 25 " n new file"
+ help_row 26 " u trash file (safe delete)"
+ help_row 27 " U open trash directory"
+ help_row 28 " ! drop to shell in CWD"
+ help_row 29 " o open with custom program"
+ help_sep 30
+ help_row 31 " b bookmark current dir"
+ help_row 32 " B open bookmark picker"
+ help_row 33 " c copy path to clipboard"
+ help_row 34 " ~ go to home directory"
+ help_row 35 " \` jump to previous directory"
+ help_sep 36
+ help_row 37 " q quit"
+ help_row 38 " ? this help"
+ help_row 39 ""
+ help_row 40 " press any key to close..."
goto "$(( _help_by + bh ))" "$_help_bx"
printf '%s+%s+%s' "${BOLD}${CYAN}" "$_help_hl" "${RESET}"
@@ -877,11 +969,18 @@ do_toggle_hidden() {
do_toggle_details() {
if [ "$SHOW_DETAILS" = "0" ]; then
- SHOW_DETAILS=1
- INFO_MSG="details on"
+ SHOW_DETAILS=1; INFO_MSG="details on"
+ else
+ SHOW_DETAILS=0; INFO_MSG="details off"
+ fi
+ NEED_FULL_REDRAW=1
+}
+
+do_toggle_preview() {
+ if [ "$SHOW_PREVIEW" = "0" ]; then
+ SHOW_PREVIEW=1; INFO_MSG="preview on"
else
- SHOW_DETAILS=0
- INFO_MSG="details off"
+ SHOW_PREVIEW=0; INFO_MSG="preview off"
fi
NEED_FULL_REDRAW=1
}
@@ -1385,6 +1484,7 @@ while true; do
/) do_search ;;
'.') do_toggle_hidden ;;
T) do_toggle_details ;;
+ P) do_toggle_preview ;;
i) do_info ;;
'!') do_shell ;;
o) do_open_with ;;