sfm

Simple File Manager
git clone git://git.emmett1.my/sfm.git
Log | Files | Refs | LICENSE

commit fbc3227515f056b6dc0590bd59e91176734ca176
parent 02a8adadc48bc5b8c28a617d619b6f4a9403a387
Author: emmett1 <emmett1.2miligrams@protonmail.com>
Date:   Mon, 16 Mar 2026 00:36:44 +0800

added preview function

Diffstat:
Msfm | 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 ;;