From 67a1c24a8661fdcbc48f72f7b65521772065d305 Mon Sep 17 00:00:00 2001 From: Dylan Araps Date: Sat, 28 Feb 2026 21:52:26 +0200 Subject: dfm: add ranger style bulk rename Also the ability to embed scripts within dfm. --- README.txt | 6 +++--- bin/embed | 32 ++++++++++++++++++++++++++++++++ config_cmd.h.in | 33 ++++++++++++++++++++++++++++++++- config_key.h.in | 1 + example/opener_ext | 27 --------------------------- example/opener_mime | 33 --------------------------------- script/bulk-rename | 47 +++++++++++++++++++++++++++++++++++++++++++++++ script/opener_ext | 27 +++++++++++++++++++++++++++ script/opener_mime | 33 +++++++++++++++++++++++++++++++++ 9 files changed, 175 insertions(+), 64 deletions(-) create mode 100755 bin/embed delete mode 100755 example/opener_ext delete mode 100755 example/opener_mime create mode 100755 script/bulk-rename create mode 100755 script/opener_ext create mode 100755 script/opener_mime diff --git a/README.txt b/README.txt index 50a13ae..0971a58 100644 --- a/README.txt +++ b/README.txt @@ -26,7 +26,7 @@ Initial Announcement: https://dylan.gr/1772192922 * UTF8 support (minus grapheme clusters and other unruly things) * Multiple view modes (name, size, permissions, mtime, ...) * Multiple sort modes (name, extension, size, mtime, reverse, ...) -* No temporary file usage +* Ranger-style bulk rename * Incremental as-you-type search * Bookmarks * Vim-like keybindings @@ -161,7 +161,7 @@ environment, default values are derived from the config.h.in file. bind act_cd_bookmark_[0-9] to the keys of your choosing) - DFM_OPENER (Opener script to use when opening files. This could be - xdg-open or a custom script (see the examples/ directory)) + xdg-open or a custom script (see the script/ directory)) - DFM_TRASH (Program to use when trashing files) @@ -228,7 +228,7 @@ All is the sum of the other view modes and gives an idea of what is shown: -rwxr-xr-x 16m 4.0K .git/ -rwxr-xr-x 2h 4.0K bin/ --rwxr-xr-x 4d 4.0K example/ +-rwxr-xr-x 4d 4.0K script/ -rwxr-xr-x 32m 4.0K lib/ -rwxr-xr-x 16h 4.0K platform/ -rw-r--r-- 16m 0B .config_macro.h diff --git a/bin/embed b/bin/embed new file mode 100755 index 0000000..5efe6d8 --- /dev/null +++ b/bin/embed @@ -0,0 +1,32 @@ +#!/bin/sed -f +# +# Convert input to 32bit hex encoded utf8. +# +# Copyright (c) 2026 Dylan Araps +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +/^[[:space:]]*#/d +s/\\/\\\\/g +s/"/\\"/g +s/$/\\n/ +s/^/"/ +s/$/"/ + diff --git a/config_cmd.h.in b/config_cmd.h.in index 5891957..3802760 100644 --- a/config_cmd.h.in +++ b/config_cmd.h.in @@ -25,7 +25,31 @@ // CMD_FILE_CURSOR = Ignore marks and add the name under the cursor to input. // CMD_EXEC_MARK = Skip interactive prompt only if marks exist.. // CMD_EXEC_ROOT = Skip interactive prompt even if root. -// +!! +## Shell cript can be embedded within dfm by using the embed command. +## The script will be run in $SHELL as the argument following '-c'. +## +## FM_CMD(cmd_bulk_rename, +## .prompt = CUT_NULL, +## .left = CUT($(embed script/bulk-rename)), +## .enter = fm_cmd_run_sh, +## .config = CMD_MARK_DIR | CMD_MUT | CMD_EXEC, +## ) +## +## Another useful example is embedding your opener script. +## +## FM_CMD(cmd_opener, +## .prompt = CUT_NULL, +## .left = CUT($(embed script/opener_ext)), +## .enter = fm_cmd_run_sh, +## .config = CMD_MARK_DIR | CMD_EXEC, +## ) +## +## Shell can also be typed directly but its cumbersone to add \\n to the end of +## each line and escape backslashes and double quotes. +## +## .left = CUT("echo \\"$@\\"\\n"), +!! // If the FM_CMD system is too limiting you can define your own functions and // bind them to keys. The same function signature is used in navigation and // input modes. @@ -154,3 +178,10 @@ FM_CMD(cmd_trash, .config = CMD_MARK_DIR | CMD_MUT | CMD_EXEC_MARK, ) +FM_CMD(cmd_bulk_rename, + .prompt = CUT_NULL, + .left = CUT($(embed script/bulk-rename)), + .enter = fm_cmd_run_sh, + .config = CMD_MARK_DIR | CMD_MUT | CMD_EXEC, +) + diff --git a/config_key.h.in b/config_key.h.in index fde7239..cfb4253 100644 --- a/config_key.h.in +++ b/config_key.h.in @@ -92,6 +92,7 @@ static inline void (*fm_key(u32 cp))(struct fm *) case 'z': return act_alt_buffer; case 'p': return cmd_chmod; case 'P': return cmd_chown; + case 'B': return cmd_bulk_rename; case '~': return act_cd_home; case 'Y': return cmd_copy_clipboard; diff --git a/example/opener_ext b/example/opener_ext deleted file mode 100755 index ce1699a..0000000 --- a/example/opener_ext +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh -feu -# -# Example DFM_OPENER script using file extension. -# - -case $1 in - *.mkv | *.webm | *.mp4 | *.avi) - exec mpv -- "$1" - ;; - - *.opus | *.mp3 | *.wav | *.flac | *.ogg) - exec mus --no-shuffle -- "$1" - ;; - - *.jpg | *.jpeg | *.gif | *.png | *.CR2) - exec mpv --pause -- "$1" - ;; - - *.svg) - exec inkscape "$1" - ;; - - *?*) - exec "${EDITOR:-vim}" "$1" - ;; -esac - diff --git a/example/opener_mime b/example/opener_mime deleted file mode 100755 index 2f7bd52..0000000 --- a/example/opener_mime +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/sh -feu -# -# Example DFM_OPENER script using mimetype. -# - -mime_type=$(file -bi "$1") - -case $mime_type in - audio/*) - exec mpv --no-video "$1" - ;; - - video/*) - exec mpv "$1" - ;; - - image/*) - exec gimp "$1" - ;; - - text/html* | application/pdf*) - exec firefox "$1" - ;; - - text/*) - exec "${EDITOR:=vi}" "$1" - ;; - - *?*) - printf 'error: unhandled mime-type %s\n' "$mime_type" >&2 - ;; -esac - diff --git a/script/bulk-rename b/script/bulk-rename new file mode 100755 index 0000000..3bd6c85 --- /dev/null +++ b/script/bulk-rename @@ -0,0 +1,47 @@ +#!/bin/sh -eu +# +# Interactive bulk file rename using $EDITOR. +# +# Copyright (c) 2026 Dylan Araps +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +case $# in 0) exit; esac +f=${TMPDIR:=/tmp}/.dfm-brn-$$-$# +e() { rm -f "$f" || :; exit "$1"; } +( + printf '%s\n' "$@" > "$f" && + "${EDITOR:=vi}" "$f" && + exec 3<"$f" && + for _ do + IFS= read -r l <&3 + set -- "$@" "mv -f -- '$1' '${l:?line mismatch in rename file}'" + shift + done && { + read -r l <&3 && { + echo "$0: 39: line mismatch in rename file" >&2 + return 1 + } + printf '%s\n' '# This file will be executed when the editor is closed.' \ + '# Clear the file to abort.' "$@" + } > "$f" && + "$EDITOR" "$f" && . "$f" && e 0 +) || e 1 + diff --git a/script/opener_ext b/script/opener_ext new file mode 100755 index 0000000..ce1699a --- /dev/null +++ b/script/opener_ext @@ -0,0 +1,27 @@ +#!/bin/sh -feu +# +# Example DFM_OPENER script using file extension. +# + +case $1 in + *.mkv | *.webm | *.mp4 | *.avi) + exec mpv -- "$1" + ;; + + *.opus | *.mp3 | *.wav | *.flac | *.ogg) + exec mus --no-shuffle -- "$1" + ;; + + *.jpg | *.jpeg | *.gif | *.png | *.CR2) + exec mpv --pause -- "$1" + ;; + + *.svg) + exec inkscape "$1" + ;; + + *?*) + exec "${EDITOR:-vim}" "$1" + ;; +esac + diff --git a/script/opener_mime b/script/opener_mime new file mode 100755 index 0000000..2f7bd52 --- /dev/null +++ b/script/opener_mime @@ -0,0 +1,33 @@ +#!/bin/sh -feu +# +# Example DFM_OPENER script using mimetype. +# + +mime_type=$(file -bi "$1") + +case $mime_type in + audio/*) + exec mpv --no-video "$1" + ;; + + video/*) + exec mpv "$1" + ;; + + image/*) + exec gimp "$1" + ;; + + text/html* | application/pdf*) + exec firefox "$1" + ;; + + text/*) + exec "${EDITOR:=vi}" "$1" + ;; + + *?*) + printf 'error: unhandled mime-type %s\n' "$mime_type" >&2 + ;; +esac + -- cgit v1.2.3