Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b729ffe8a | ||
|
|
25a329aba9 | ||
|
|
6f9feaf9b4 | ||
|
|
fbc3227515 | ||
|
|
02a8adadc4 | ||
|
|
1e6fdd9e29 | ||
|
|
12c85202f9 | ||
|
|
7f41df91f1 |
+94
-51
@@ -1,15 +1,15 @@
|
||||
sfm - Simple File Manager
|
||||
==========================
|
||||
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
sfm is a lightweight, terminal-based file manager written entirely in
|
||||
POSIX sh. It runs in your terminal with no external dependencies beyond
|
||||
standard Unix tools (ls, awk, tput, stty, mv, cp, rm). Designed to be
|
||||
fast, flicker-free, and keyboard-driven with a vim-inspired key layout.
|
||||
|
||||
|
||||
|
||||
|
||||
REQUIREMENTS
|
||||
------------
|
||||
- A POSIX-compatible shell (sh, dash, bash, etc.)
|
||||
@@ -17,35 +17,42 @@ REQUIREMENTS
|
||||
- Optional: wl-copy / xclip / xsel / pbcopy (for clipboard support)
|
||||
- Optional: mpv, vlc, feh, zathura, etc. (for smart file opening)
|
||||
- Optional: readlink (for symlink display)
|
||||
|
||||
|
||||
- Optional: file (for MIME type detection)
|
||||
|
||||
|
||||
INSTALLATION
|
||||
------------
|
||||
Using make (recommended):
|
||||
|
||||
|
||||
make install
|
||||
|
||||
|
||||
This installs sfm to /usr/local/bin by default. To change the prefix:
|
||||
|
||||
|
||||
make install PREFIX=/usr
|
||||
|
||||
|
||||
To uninstall:
|
||||
|
||||
|
||||
make uninstall
|
||||
|
||||
|
||||
Manual installation:
|
||||
|
||||
|
||||
cp sfm /usr/local/bin/sfm
|
||||
chmod +x /usr/local/bin/sfm
|
||||
|
||||
|
||||
|
||||
|
||||
USAGE
|
||||
-----
|
||||
sfm
|
||||
|
||||
sfm will opens in the current directory.
|
||||
|
||||
|
||||
sfm [path]
|
||||
|
||||
If no path is given, sfm opens in the current directory.
|
||||
|
||||
Examples:
|
||||
sfm # open in current directory
|
||||
sfm /mnt/data # open at a specific path
|
||||
sfm ~ # open home directory
|
||||
sfm .. # open parent directory
|
||||
|
||||
|
||||
NAVIGATION
|
||||
----------
|
||||
j / k or up/down arrows Move up / down
|
||||
@@ -55,24 +62,59 @@ NAVIGATION
|
||||
G Jump to bottom of list
|
||||
~ Go to home directory
|
||||
` (backtick) Jump to previous directory
|
||||
|
||||
|
||||
|
||||
|
||||
SEARCH & FILTER
|
||||
---------------
|
||||
/ Enter search mode (filters listing as you type)
|
||||
esc Clear filter and exit search mode
|
||||
enter Exit search mode but keep filter active
|
||||
|
||||
|
||||
|
||||
|
||||
DISPLAY TOGGLES
|
||||
---------------
|
||||
. Toggle hidden files (dotfiles)
|
||||
T Toggle size/date detail column
|
||||
P Toggle preview pane (right side)
|
||||
s Cycle sort mode: name -> size -> date
|
||||
i Show file info (permissions, size, date) in status bar
|
||||
R Refresh current directory listing
|
||||
|
||||
|
||||
|
||||
|
||||
PREVIEW PANE
|
||||
------------
|
||||
Press P to toggle the preview pane on the right side of the screen.
|
||||
The list pane takes the left half, preview takes the right half.
|
||||
|
||||
Text files Shows file contents line by line
|
||||
Directories Shows directory contents
|
||||
Symlinks Shows link target
|
||||
Binary files Shows file size
|
||||
|
||||
|
||||
CUSTOM OPENER
|
||||
-------------
|
||||
sfm checks for a user-defined opener script at:
|
||||
|
||||
~/.config/sfm/opener
|
||||
|
||||
If the file exists and is executable, sfm passes the selected file
|
||||
path to it instead of using the built-in smart opener. Example:
|
||||
|
||||
#!/bin/sh
|
||||
case "$1" in
|
||||
*.jpg|*.jpeg) imv "$1" ;;
|
||||
*.mp4) mpv "$1" ;;
|
||||
*.html|*.pdf) firefox "$1" ;;
|
||||
*) echo "no program set for this file type" ;;
|
||||
esac
|
||||
|
||||
Make it executable: chmod +x ~/.config/sfm/opener
|
||||
|
||||
If the opener script does not exist, sfm falls back to its built-in
|
||||
smart opener automatically.
|
||||
|
||||
|
||||
FILE OPERATIONS
|
||||
---------------
|
||||
r Rename selected entry
|
||||
@@ -82,45 +124,45 @@ FILE OPERATIONS
|
||||
u Move to trash (safe delete)
|
||||
U Open trash directory
|
||||
o Open with custom program
|
||||
|
||||
|
||||
|
||||
|
||||
CLIPBOARD
|
||||
---------
|
||||
y Yank / copy (works on multi-selection)
|
||||
x Cut (works on multi-selection)
|
||||
p Paste into current directory
|
||||
c Copy full path of selected entry to system clipboard
|
||||
|
||||
|
||||
|
||||
|
||||
MULTI-SELECT
|
||||
------------
|
||||
space Toggle multi-select on current entry (cursor advances)
|
||||
a Select all / deselect all
|
||||
|
||||
|
||||
|
||||
|
||||
BOOKMARKS
|
||||
---------
|
||||
b Bookmark current directory (press again to remove)
|
||||
B Open bookmark picker (j/k navigate, enter jump)
|
||||
|
||||
|
||||
Bookmarks are saved to: ~/.config/sfm/bookmarks
|
||||
|
||||
|
||||
|
||||
|
||||
SHELL & UTILITIES
|
||||
-----------------
|
||||
! Drop into $SHELL in current directory (type "exit" to return)
|
||||
|
||||
|
||||
|
||||
|
||||
HELP
|
||||
----
|
||||
? Show keyboard shortcuts overlay
|
||||
|
||||
|
||||
|
||||
|
||||
QUIT
|
||||
----
|
||||
q Quit sfm
|
||||
|
||||
|
||||
|
||||
|
||||
STATUS BAR INDICATORS
|
||||
---------------------
|
||||
[hidden] Hidden files are visible
|
||||
@@ -129,13 +171,13 @@ STATUS BAR INDICATORS
|
||||
[cut] A file is in the clipboard (cut mode)
|
||||
[sel:N] N items are currently selected
|
||||
[details] Size/date column is visible
|
||||
|
||||
|
||||
|
||||
|
||||
SMART FILE OPENER
|
||||
-----------------
|
||||
When you open a file, sfm detects the file type by extension and
|
||||
MIME type, then picks an appropriate program automatically:
|
||||
|
||||
|
||||
Text / code $EDITOR (or vi)
|
||||
Images imv, feh, sxiv, eog, gimp
|
||||
Video mpv, vlc, mplayer, totem
|
||||
@@ -143,25 +185,26 @@ MIME type, then picks an appropriate program automatically:
|
||||
PDF zathura, evince, okular, mupdf
|
||||
Office docs libreoffice
|
||||
Archives atool / bsdtar (lists contents in pager)
|
||||
|
||||
|
||||
Falls back to xdg-open, open (macOS), or $EDITOR if nothing matches.
|
||||
Use 'o' to manually specify any program.
|
||||
|
||||
|
||||
|
||||
|
||||
TRASH
|
||||
-----
|
||||
Files deleted with 'u' are moved to:
|
||||
|
||||
|
||||
~/.local/share/sfm-trash/
|
||||
|
||||
|
||||
Files are prefixed with a timestamp: YYYYMMDD_HHMMSS_filename
|
||||
Use U to browse the trash directory. To restore a file, rename it
|
||||
(press r) to remove the timestamp prefix, then cut (x) and paste (p)
|
||||
it back to the desired location.
|
||||
|
||||
|
||||
|
||||
|
||||
DATA FILES
|
||||
----------
|
||||
~/.config/sfm/bookmarks Saved bookmarks (one path per line)
|
||||
~/.config/sfm/opener Custom file opener script (optional)
|
||||
~/.local/share/sfm-trash/ Trashed files
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/bin/sh
|
||||
# sfm - Simple File Manager in POSIX sh
|
||||
# sfm - Simple File Manager in POSIX sh (flicker-free)
|
||||
# version 0.4
|
||||
|
||||
# --- terminal control ---
|
||||
tput_cmd() { command -v tput >/dev/null 2>&1 && tput "$@"; }
|
||||
@@ -61,7 +62,11 @@ term_rows() { tput_cmd lines || echo 24; }
|
||||
term_cols() { tput_cmd cols || echo 80; }
|
||||
|
||||
# --- state ---
|
||||
CWD="$PWD"
|
||||
if [ -n "$1" ]; then
|
||||
CWD=$(cd "$1" 2>/dev/null && pwd) || { printf 'sfm: %s: no such directory\n' "$1" >&2; exit 1; }
|
||||
else
|
||||
CWD=$(cd "$PWD" 2>/dev/null && pwd) || CWD="/"
|
||||
fi
|
||||
SEL=0
|
||||
OFFSET=0
|
||||
PREV_SEL=0
|
||||
@@ -79,9 +84,20 @@ 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
|
||||
|
||||
# normalise CWD: resolve to absolute canonical path, no double slashes
|
||||
norm_cwd() {
|
||||
CWD=$(cd "$CWD" 2>/dev/null && pwd) || CWD="/"
|
||||
# squeeze any double slashes (some systems return // for //<path>)
|
||||
CWD=$(printf '%s' "$CWD" | tr -s '/')
|
||||
}
|
||||
|
||||
# safely join CWD with a name, avoiding double slash at root
|
||||
joinpath() { [ "$CWD" = "/" ] && printf '/%s' "$1" || printf '%s/%s' "$CWD" "$1"; }
|
||||
BOOKMARK_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/sfm/bookmarks"
|
||||
mkdir -p "$(dirname "$BOOKMARK_FILE")"
|
||||
[ -f "$BOOKMARK_FILE" ] || touch "$BOOKMARK_FILE"
|
||||
@@ -94,17 +110,19 @@ load_entries() {
|
||||
# build list of dir names then file names, including dotfiles if enabled
|
||||
for d in "$CWD"/*/; do
|
||||
[ -d "$d" ] || continue
|
||||
[ -L "${d%/}" ] && continue # symlinks handled separately
|
||||
name="${d%/}"; name="${name##*/}"
|
||||
[ "$name" = "*" ] && continue
|
||||
case "$name" in .*) continue ;; esac # skip dotdirs here, handle below
|
||||
case "$name" in .*) continue ;; esac
|
||||
ENTRIES="$ENTRIES
|
||||
${name}/"
|
||||
COUNT=$((COUNT + 1))
|
||||
done
|
||||
if [ "$SHOW_HIDDEN" = "1" ]; then
|
||||
for d in "$CWD"/./; do : ; done # no-op, just to avoid error
|
||||
for d in "$CWD"/./; do : ; done
|
||||
for d in "$CWD"/.*/; do
|
||||
[ -d "$d" ] || continue
|
||||
[ -L "${d%/}" ] && continue # symlinks handled separately
|
||||
name="${d%/}"; name="${name##*/}"
|
||||
[ "$name" = "." ] || [ "$name" = ".." ] && continue
|
||||
[ "$name" = ".*" ] && continue
|
||||
@@ -416,6 +434,111 @@ 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: list contents
|
||||
_max_lines=$((_rows - 3))
|
||||
_pr=2
|
||||
for _de in "$_path"/*/; do
|
||||
[ "$_pr" -gt $((_rows - 1)) ] && break
|
||||
[ -d "$_de" ] || continue
|
||||
_dn="${_de%/}"; _dn="${_dn##*/}"
|
||||
[ "$_dn" = "*" ] && continue
|
||||
_dl=" ${_dn}/"
|
||||
[ "${#_dl}" -gt "$((_pw - 1))" ] && _dl="$(printf '%s' "$_dl" | cut -c1-$((_pw-2)))~"
|
||||
goto "$_pr" "$_px"
|
||||
printf '%s%s%s' "${GREEN}${BOLD}" "$_dl" "${RESET}"
|
||||
_pr=$((_pr + 1))
|
||||
done
|
||||
for _fe in "$_path"/*; do
|
||||
[ "$_pr" -gt $((_rows - 1)) ] && break
|
||||
[ -e "$_fe" ] || continue
|
||||
[ -d "$_fe" ] && continue
|
||||
_fn="${_fe##*/}"
|
||||
[ "$_fn" = "*" ] && continue
|
||||
_fl=" ${_fn}"
|
||||
[ "${#_fl}" -gt "$((_pw - 1))" ] && _fl="$(printf '%s' "$_fl" | cut -c1-$((_pw-2)))~"
|
||||
goto "$_pr" "$_px"
|
||||
printf '%s%s%s' "${WHITE}" "$_fl" "${RESET}"
|
||||
_pr=$((_pr + 1))
|
||||
done
|
||||
if [ "$_pr" -eq 2 ]; then
|
||||
goto 2 "$_px"
|
||||
printf '%s(empty)%s' "${WHITE}" "${RESET}"
|
||||
fi
|
||||
;;
|
||||
*@)
|
||||
# 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" '')
|
||||
@@ -426,7 +549,7 @@ draw_topbar() {
|
||||
draw_botbar() {
|
||||
_rows=$1; _cols=$2
|
||||
if [ "$SEARCHING" = "1" ]; then
|
||||
info=" $((SEL + 1))/${COUNT} ${CWD}"
|
||||
info=" $([ "$COUNT" -eq 0 ] && echo 0 || echo $((SEL + 1)))/${COUNT} ${CWD}"
|
||||
keys=" search: ${FILTER} "
|
||||
maxk=$((_cols - ${#info} - 1))
|
||||
[ "${#keys}" -gt "$maxk" ] && keys=$(printf '%s' "$keys" | cut -c1-"$maxk")
|
||||
@@ -440,9 +563,8 @@ draw_botbar() {
|
||||
"${YELLOW}${keys}" \
|
||||
"${RESET}"
|
||||
elif [ -n "$INFO_MSG" ]; then
|
||||
info=" $((SEL + 1))/${COUNT} ${CWD}"
|
||||
msg=" ${INFO_MSG} "
|
||||
padlen=$((_cols - ${#info} - ${#msg}))
|
||||
info=" $([ "$COUNT" -eq 0 ] && echo 0 || echo $((SEL + 1)))/${COUNT} ${CWD}"
|
||||
msg=" ${INFO_MSG} " padlen=$((_cols - ${#info} - ${#msg}))
|
||||
[ "$padlen" -lt 0 ] && padlen=0
|
||||
pad=$(printf '%*s' "$padlen" '')
|
||||
goto "$_rows" 1
|
||||
@@ -461,7 +583,7 @@ draw_botbar() {
|
||||
[ "$SORT_MODE" != "name" ] && _ind="${_ind} [sort:${SORT_MODE}]"
|
||||
[ "$SHOW_DETAILS" = "1" ] && _ind="${_ind} [details]"
|
||||
hint=" press ? for help "
|
||||
info=" $((SEL + 1))/${COUNT} ${CWD}${_ind}"
|
||||
info=" $([ "$COUNT" -eq 0 ] && echo 0 || echo $((SEL + 1)))/${COUNT} ${CWD}${_ind}"
|
||||
padlen=$((_cols - ${#info} - ${#hint}))
|
||||
[ "$padlen" -lt 0 ] && padlen=0
|
||||
pad=$(printf '%*s' "$padlen" '')
|
||||
@@ -479,6 +601,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))
|
||||
@@ -494,7 +625,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"
|
||||
|
||||
@@ -502,22 +632,29 @@ 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
|
||||
# blank leftover rows (e.g. after entering a smaller directory)
|
||||
# show (empty) if directory has no entries
|
||||
if [ "$COUNT" -eq 0 ]; then
|
||||
goto 2 1
|
||||
printf '%s (empty)%s%s' "${WHITE}" "${ERASE_LINE}" "${RESET}"
|
||||
row=3
|
||||
fi
|
||||
# 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
|
||||
@@ -525,12 +662,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
|
||||
|
||||
@@ -567,6 +706,14 @@ _run_gui() { "$@" >/dev/null 2>&1 & }
|
||||
|
||||
open_file() {
|
||||
_f="$1"
|
||||
|
||||
# use user opener script if it exists and is executable
|
||||
_opener="${XDG_CONFIG_HOME:-$HOME/.config}/sfm/opener"
|
||||
if [ -x "$_opener" ]; then
|
||||
"$_opener" "$_f"
|
||||
return
|
||||
fi
|
||||
|
||||
_ext="${_f##*.}"
|
||||
_ext=$(printf '%s' "$_ext" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
@@ -744,7 +891,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))
|
||||
@@ -766,32 +913,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}"
|
||||
@@ -856,11 +1004,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"
|
||||
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_PREVIEW=0; INFO_MSG="preview off"
|
||||
fi
|
||||
NEED_FULL_REDRAW=1
|
||||
}
|
||||
@@ -871,7 +1026,7 @@ do_go_back() {
|
||||
SELECTED=""
|
||||
PREV_CWD="$CWD"
|
||||
LAST_CHILD="${CWD##*/}/" # name of current dir with trailing slash
|
||||
CWD=$(dirname "$CWD")
|
||||
CWD=$(cd "$(dirname "$CWD")" && pwd)
|
||||
SEL=0; OFFSET=0; PREV_SEL=0; PREV_OFFSET=0
|
||||
load_entries
|
||||
# restore selection to the directory we just came from
|
||||
@@ -888,20 +1043,20 @@ do_open() {
|
||||
*/)
|
||||
FILTER=""
|
||||
SELECTED=""
|
||||
_target="${CWD}/${entry%/}"
|
||||
_target=$(joinpath "${entry%/}")
|
||||
if [ ! -x "$_target" ] || [ ! -r "$_target" ]; then
|
||||
INFO_MSG="permission denied: ${entry%/}"
|
||||
NEED_FULL_REDRAW=1
|
||||
return
|
||||
fi
|
||||
PREV_CWD="$CWD"
|
||||
CWD=$(cd "$_target" && pwd)
|
||||
CWD=$(cd "$_target" && pwd); norm_cwd
|
||||
SEL=0; OFFSET=0; PREV_SEL=0; PREV_OFFSET=0
|
||||
load_entries
|
||||
;;
|
||||
*@)
|
||||
_name="${entry%@}"
|
||||
_target="${CWD}/${_name}"
|
||||
_target=$(joinpath "$_name")
|
||||
if [ -d "$_target" ]; then
|
||||
if [ ! -x "$_target" ] || [ ! -r "$_target" ]; then
|
||||
INFO_MSG="permission denied: ${_name}"
|
||||
@@ -910,7 +1065,7 @@ do_open() {
|
||||
fi
|
||||
FILTER=""; SELECTED=""
|
||||
PREV_CWD="$CWD"
|
||||
CWD=$(cd "$_target" && pwd)
|
||||
CWD=$(cd "$_target" && pwd); norm_cwd
|
||||
SEL=0; OFFSET=0; PREV_SEL=0; PREV_OFFSET=0
|
||||
load_entries
|
||||
else
|
||||
@@ -1057,7 +1212,7 @@ do_shell() {
|
||||
printf '%s(type "exit" to return to fm)%s\n' "${YELLOW}" "${RESET}"
|
||||
${SHELL:-sh}
|
||||
# restore CWD in case user cd'd around
|
||||
CWD="${PWD}"
|
||||
CWD="${PWD}"; norm_cwd
|
||||
setup_term; hide_cursor
|
||||
NEED_FULL_REDRAW=1
|
||||
}
|
||||
@@ -1093,7 +1248,7 @@ do_trash() {
|
||||
do_open_trash() {
|
||||
PREV_CWD="$CWD"
|
||||
FILTER=""; SELECTED=""
|
||||
CWD="$TRASH_DIR"
|
||||
CWD="$TRASH_DIR"; norm_cwd
|
||||
SEL=0; OFFSET=0; PREV_SEL=0; PREV_OFFSET=0
|
||||
load_entries
|
||||
}
|
||||
@@ -1122,7 +1277,7 @@ do_copy_path() {
|
||||
do_jump_back() {
|
||||
[ -z "$PREV_CWD" ] && { INFO_MSG="no previous directory"; NEED_FULL_REDRAW=1; return; }
|
||||
_tmp="$CWD"
|
||||
CWD="$PREV_CWD"
|
||||
CWD="$PREV_CWD"; norm_cwd
|
||||
PREV_CWD="$_tmp"
|
||||
FILTER=""; SELECTED=""
|
||||
SEL=0; OFFSET=0; PREV_SEL=0; PREV_OFFSET=0
|
||||
@@ -1197,7 +1352,7 @@ do_bookmark_jump() {
|
||||
'[C') # right — open
|
||||
_chosen=$(awk -v n="$_bsel" 'NR==n&&NF{print;exit}' "$BOOKMARK_FILE")
|
||||
if [ -n "$_chosen" ] && [ -d "$_chosen" ]; then
|
||||
PREV_CWD="$CWD"; CWD="$_chosen"
|
||||
PREV_CWD="$CWD"; CWD="$_chosen"; norm_cwd
|
||||
FILTER=""; SELECTED=""
|
||||
SEL=0; OFFSET=0; PREV_SEL=0; PREV_OFFSET=0
|
||||
load_entries
|
||||
@@ -1213,7 +1368,7 @@ do_bookmark_jump() {
|
||||
l)
|
||||
_chosen=$(awk -v n="$_bsel" 'NR==n&&NF{print;exit}' "$BOOKMARK_FILE")
|
||||
if [ -n "$_chosen" ] && [ -d "$_chosen" ]; then
|
||||
PREV_CWD="$CWD"; CWD="$_chosen"
|
||||
PREV_CWD="$CWD"; CWD="$_chosen"; norm_cwd
|
||||
FILTER=""; SELECTED=""
|
||||
SEL=0; OFFSET=0; PREV_SEL=0; PREV_OFFSET=0
|
||||
load_entries
|
||||
@@ -1364,6 +1519,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 ;;
|
||||
@@ -1371,7 +1527,7 @@ while true; do
|
||||
u) do_trash ;;
|
||||
U) do_open_trash ;;
|
||||
'`') do_jump_back ;;
|
||||
'~') PREV_CWD="$CWD"; CWD="$HOME"; FILTER=""; SELECTED=""
|
||||
'~') PREV_CWD="$CWD"; CWD=$(cd "$HOME" && pwd); FILTER=""; SELECTED=""
|
||||
SEL=0; OFFSET=0; PREV_SEL=0; PREV_OFFSET=0; load_entries ;;
|
||||
c) do_copy_path ;;
|
||||
"$(printf '\033')") do_clear_filter ;;
|
||||
|
||||
Reference in New Issue
Block a user