From 865092c656af70bd5317513d8e0ed87fdc1c9acd Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 24 Feb 2020 01:31:39 -0800 Subject: [PATCH] Major update: instead of defining all the helper functions as static C-strings prefixed to commands (modified by the Makefile), the helper functions are now standalone scripts in the helpers/ dir, which gets added to "$PATH" when running bb. --- API.md | 26 +++++------ Makefile | 49 ++------------------ bb.1 | 2 +- bb.c | 14 +++--- bb.h | 76 +------------------------------ bbcmd.1 | 2 +- bindings.bb | 111 +++++++++++++++++++++++---------------------- helpers/bbask | 31 +++++++++++++ helpers/bbcmd | 11 +++++ helpers/bbconfirm | 9 ++++ helpers/bbpause | 4 ++ helpers/bbpick | 39 ++++++++++++++++ helpers/bbtargets | 25 ++++++++++ helpers/bbunscroll | 5 ++ 14 files changed, 206 insertions(+), 198 deletions(-) create mode 100755 helpers/bbask create mode 100755 helpers/bbcmd create mode 100755 helpers/bbconfirm create mode 100755 helpers/bbpause create mode 100755 helpers/bbpick create mode 100755 helpers/bbtargets create mode 100755 helpers/bbunscroll diff --git a/API.md b/API.md index ec10f88..ce92b1c 100644 --- a/API.md +++ b/API.md @@ -9,18 +9,18 @@ internal state. ## Helper Functions -- `bb`: used for modifying `bb`'s internal state (see BB Commands). -- `ask`: get user input in a standardized and customizable way. The first - argument is a variable where the value is stored. The second argument is - a prompt. A third optional argument can provide a default value (may be - ignored). -- `ask1`: get a single character of user input. The first argument is a variable - where the input will be stored and the second argument is a prompt. -- `pause`: Display a "press any key to continue" message and wait for a keypress. -- `confirm`: Display a "Is this okay? [y/N]" prompt and exit with failure if - the user does not press 'y'. -- `spin`: Display a spinning icon while a slow command executes in the background. - (e.g. `spin sleep 5`). +- `bbask [-1] [prompt [initial]]`: get user input in a standardized and + customizable way and output it to `STDOUT`. +- `bbcmd *`: used for modifying `bb`'s internal state (see BB Commands). +- `bbconfirm [prompt]`: Display a "Is this okay? [y/N]" prompt and exit with + failure if the user does not press 'y'. +- `bbpause`: Display a "press any key to continue" message and wait for a keypress. +- `bbpick [prompt]`: Select one of `NULL`-delimited multiple inputs and print it. +- `bbtargets "$BBCMD" "$@"`: If `$BBCURSOR` is not currently among `$@` (the + selected files), this script prompts the user to ask whether they want to + perform an action on the selected files, or on the cursor. The result is + printed as `cursor` or `selected`. +- `bbunscroll`: Print text to the screen *above* the cursor instead of below it. ## Environment Variables @@ -80,7 +80,7 @@ glob is available in `$BBGLOB`, which can be used in scripts if left unquoted. ## Final Notes -Internally, `bbcmd` writes the commands (NUL terminated) to a file whose path is +Internally, `bbcmd` writes the commands (`NULL` terminated) to a file whose path is in`$BBCMD` and `bb` reads from that file when it resumes. These commands can also be passed to `bb` at startup as command line arugments starting with `+`, and will run immediately. E.g. `bbcmd +'col:n' +'sort:+r' .` will launch `bb` only diff --git a/Makefile b/Makefile index 606f8db..dae7e36 100644 --- a/Makefile +++ b/Makefile @@ -8,49 +8,6 @@ CWARN=-Wall -Wpedantic -Wextra -Wno-unknown-pragmas -Wno-missing-field-initializ #CFLAGS += -fsanitize=address -fno-omit-frame-pointer G= -ifeq ($(shell uname),Darwin) - CFLAGS += -D_DARWIN_C_SOURCE -endif - -ifneq (, $(SH)) - CFLAGS += -D'SH="$(SH)"' -endif - -PICKER_FLAG= -ifeq (, $(PICKER)) - PICKER=$(shell sh -c "(which fzf >/dev/null 2>/dev/null && echo 'fzf') || (which fzy >/dev/null 2>/dev/null && echo 'fzy') || (which pick >/dev/null 2>/dev/null && echo 'pick') || (which ask >/dev/null 2>/dev/null && echo 'ask')") -endif -ifneq (, $(PICKER)) - PICKER_FLAG=-D"PICK=\"$(PICKER) --prompt=\\\"$$1\\\"\"" - ifeq ($(shell which $(PICKER)),$(shell which fzf 2>/dev/null || echo '')) - PICKER_FLAG=-D'PICK="printf \"\\033[3A\\033[?25h\" >/dev/tty; fzf --read0 --height=4 --prompt=\"$$1\""' - endif - ifeq ($(shell which $(PICKER)),$(shell which fzy 2>/dev/null || echo '')) - PICKER_FLAG=-D'PICK="printf \"\\033[3A\\033[?25h\" >/dev/tty; tr "\\0" "\\n" | fzy --lines=3 --prompt=\"\033[1m$$1\033[0m\""' - endif - ifeq ($(shell which $(PICKER)),$(shell which ask 2>/dev/null || echo '')) - PICKER_FLAG=-D'PICK="/usr/bin/env ask --read0 --prompt=\"$$1\033[?25h\""' - endif - ifeq ($(shell which $(PICKER)),$(shell which pick 2>/dev/null || echo '')) - PICKER_FLAG=-D'PICK="printf \"\\033[?25h\" >/dev/tty; tr "\\0" "\\n" | pick"' - endif - ifeq ($(shell which $(PICKER)),$(shell which dmenu 2>/dev/null || echo '')) - PICKER_FLAG=-D'PICK="tr "\\0" "\\n" | dmenu -i -l 10 -p \"$$1\""' - endif -endif -CFLAGS += $(PICKER_FLAG) - -ifneq (, $(ASKER)) - PERCENT := % - ifeq ($(shell which $(ASKER)),$(shell which ask 2>/dev/null || echo '')) - CFLAGS += -D'ASK="eval \"$$1=\\$$(/usr/bin/env ask --history=bb.hist --prompt=\\\"$$2\033[?25h\\\" --query=\\\"$$3\\\")\""' - CFLAGS += -D'CONFIRM="/usr/bin/env ask -n \"$$1Is that okay?\033[?25h\""' - endif - ifeq ($(shell which $(ASKER)),$(shell which dmenu 2>/dev/null || echo '')) - CFLAGS += -D'ASK="eval \"$$1=\\$$(echo \"$$3\" | dmenu -p \"$$2\")\""' - endif -endif - all: $(NAME) clean: @@ -67,8 +24,8 @@ install: $(NAME) fi; \ [ ! "$$prefix" ] && prefix="/usr/local"; \ [ ! "$$sysconfdir" ] && sysconfdir=/etc; \ - mkdir -pv -m 755 "$$prefix/share/man/man1" "$$prefix/bin" "$$sysconfdir/xdg/bb" \ - && cp -v bbstartup.sh bindings.bb "$$sysconfdir/xdg/bb/" \ + mkdir -pv -m 755 "$$prefix/share/man/man1" "$$prefix/bin" "$$sysconfdir/xdg/bb" "$$sysconfdir/xdg/bb/helpers" \ + && cp -rv bbstartup.sh bindings.bb helpers "$$sysconfdir/xdg/bb/" \ && cp -v bb.1 bbcmd.1 "$$prefix/share/man/man1/" \ && rm -f "$$prefix/bin/$(NAME)" \ && cp -v $(NAME) "$$prefix/bin/" @@ -83,5 +40,5 @@ uninstall: [ ! "$$sysconfdir" ] && sysconfdir=/etc; \ echo "Deleting..."; \ rm -rvf "$$prefix/bin/$(NAME)" "$$prefix/share/man/man1/bb.1" "$$prefix/share/man/man1/bbcmd.1" "$$sysconfdir/xdg/bb"; \ - printf "\033[1mIf you created any config files in ~/.config/bb, you may want to delete them manually.\033[0m" + printf "\033[1mIf you created any config files in ~/.config/bb, you may want to delete them manually.\033[0m\n" diff --git a/bb.1 b/bb.1 index 42e450c..c474fba 100644 --- a/bb.1 +++ b/bb.1 @@ -1,6 +1,6 @@ .\" Manpage for bb. .\" Contact bruce@bruce-hill.com to correct errors or typos. -.TH man 1 "23 Feb 2020" "0.24" "bb manual page" +.TH man 1 "23 Feb 2020" "0.25" "bb manual page" .SH NAME bb \- A bitty browser for command line file management .SH SYNOPSIS diff --git a/bb.c b/bb.c index 8fce4d2..9f81830 100644 --- a/bb.c +++ b/bb.c @@ -904,12 +904,9 @@ int run_script(bb_t *bb, const char *cmd) err("Couldn't set pgrp"); char **args = memcheck(calloc(4 + (size_t)bb->nselected + 1, sizeof(char*))); int i = 0; - args[i++] = SH; + args[i++] = "sh"; args[i++] = "-c"; - char *fullcmd = calloc(strlen(cmd) + strlen(bbcmdfn) + 1, sizeof(char)); - strcpy(fullcmd, bbcmdfn); - strcat(fullcmd, cmd); - args[i++] = fullcmd; + args[i++] = (char*)cmd; args[i++] = "--"; // ensure files like "-i" are not interpreted as flags for sh // bb->selected is in most-recent order, so populate args in reverse to make sure // that $1 is the first selected, etc. @@ -924,7 +921,7 @@ int run_script(bb_t *bb, const char *cmd) ttyin = open("/dev/tty", O_RDONLY); dup2(ttyout, STDOUT_FILENO); dup2(ttyin, STDIN_FILENO); - execvp(SH, args); + execvp(args[0], args); err("Failed to execute command: '%s'", cmd); return -1; } @@ -1195,6 +1192,10 @@ int main(int argc, char *argv[]) sprintf(xdg_data_home, "%s/.local/share", getenv("HOME")); setenv("XDG_DATA_HOME", xdg_data_home, 0); setenv("sysconfdir", "/etc", 0); + char *newpath; + if (asprintf(&newpath, "%s/xdg/bb/helpers:%s/bb/helpers:%s", getenv("sysconfdir"), getenv("XDG_CONFIG_HOME"), getenv("PATH")) < 0) + err("Could not allocate memory"); + setenv("PATH", newpath, 1); setenv("SHELL", "bash", 0); setenv("EDITOR", "nano", 0); char *curdepth = getenv("BBDEPTH"); @@ -1202,7 +1203,6 @@ int main(int argc, char *argv[]) char depthstr[16]; sprintf(depthstr, "%d", depth + 1); setenv("BBDEPTH", depthstr, 1); - setenv("BBSHELLFUNC", bbcmdfn, 1); char full_initial_path[PATH_MAX]; getcwd(full_initial_path, PATH_MAX); normalize_path(full_initial_path, initial_path, full_initial_path); diff --git a/bb.h b/bb.h index c25ceae..21bbe89 100644 --- a/bb.h +++ b/bb.h @@ -26,7 +26,7 @@ #include "bterm.h" // Macros: -#define BB_VERSION "0.24.0" +#define BB_VERSION "0.25.0" #ifndef PATH_MAX #define PATH_MAX 4096 @@ -243,80 +243,6 @@ static const struct termios default_termios = { static const char *description_str = "bb - an itty bitty console TUI file browser\n"; static const char *usage_str = "Usage: bb (-h/--help | -v/--version | -s | -d | -0 | +command)* [[--] directory]\n"; -// Shell functions -static const char *bbcmdfn = "bbcmd() {\n" -" if test $# -eq 0; then cat >> $BBCMD; return; fi\n" -" for arg; do\n" -" shift;\n" -" if expr \"$arg\" : \"^[^:]*:$\" >/dev/null; then\n" -" if test $# -gt 0; then printf \"$arg%s\\0\" \"$@\" >> $BBCMD;\n" -" else sed \"s/\\([^\\x00]\\+\\)/$arg\\1/g\" >> $BBCMD; fi;\n" -" return;\n" -" fi;\n" -" printf \"%s\\0\" \"$arg\" >> $BBCMD;\n" -" done;\n" -"}\n" -// This shell function overwrites the lines above the cursor instead of scrolling the screen -"unscroll() {\n" -" input=\"$(cat)\"\n" -" printf \"\\033[$(echo \"$input\" | wc -l)A\\033[J\" >/dev/tty\n" -" echo \"$input\"\n" -"}\n" -"ask() {\n" -#ifdef ASK -ASK ";\n" -#else -" [ $# -lt 2 ] && printf '\033[31;1mNot enough args to ask!\033[0m\n' && return 1;\n" -" printf \"\033[1m%s\033[0m\" \"$2\" >/dev/tty;\n" -" tput cvvis >/dev/tty;\n" -#ifdef __APPLE__ -" read -e $1 /dev/tty;\n" -#else -" read $1 /dev/tty;\n" -#endif -#endif -"}\n" -"ask1() {\n" -#ifdef ASK1 -ASK1 ";\n" -#else -" tput civis >/dev/tty;\n" -" printf \"\033[1m%s\033[0m\" \"$2\" >/dev/tty;\n" -" stty -icanon -echo >/dev/tty 2>/dev/tty;\n" -#ifdef __APPLE__ -" read -n 1 $1 /dev/tty;\n" -#else -" eval \"$1=\\$(dd bs=1 count=1 2>/dev/null /dev/tty 2>/dev/tty;\n" -" tput cvvis >/dev/tty;\n" -#endif -"}\n" -"confirm() {\n" -#ifdef CONFIRM -CONFIRM ";\n" -#else -" ask1 REPLY \"$1\033[0;1mIs that okay? [y/N] \" && [ \"$REPLY\" = 'y' ];\n" -#endif -"}\n" -"pause() {\n" -" ask1 REPLY \"\033[0;2m Press any key to continue...\033[0m\";" -"}\n" -"pick() {\n" -#ifdef PICK -PICK ";\n" -#else -" ask query \"$1\" && awk '{print length, $1}' | sort -n | cut -d' ' -f2- |\n" -" grep -i -m1 \"$(echo \"$query\" | sed 's;.;[^/&]*[&];g')\";\n" -#endif -"}\n" -#ifdef SH -"alias sh="SH";\n" -#else -#define SH "sh" -#endif -; - static const char *runstartup = "for path in \"$XDG_CONFIG_HOME/bb\" \"$sysconfdir/xdg/bb\" .; do\n" " if [ -e \"$path/bbstartup.sh\" ]; then\n" diff --git a/bbcmd.1 b/bbcmd.1 index bc9345c..06794de 100644 --- a/bbcmd.1 +++ b/bbcmd.1 @@ -1,6 +1,6 @@ .\" Manpage for bbcmd. .\" Contact bruce@bruce-hill.com to correct errors or typos. -.TH man 1 "23 Feb 2020" "0.24" "bbcmd manual page" +.TH man 1 "23 Feb 2020" "0.25" "bbcmd manual page" .de TPx . TP 4n diff --git a/bindings.bb b/bindings.bb index 5e3a232..083183e 100644 --- a/bindings.bb +++ b/bindings.bb @@ -23,23 +23,22 @@ l,Right: # Enter directory if [ -d "$BBCURSOR" ]; then bbcmd cd:"$BBCURSOR"; fi Ctrl-f: # Search for file file="$( - find $BBGLOB -mindepth 1 -printf '%P\0' | pick "Find: " + find $BBGLOB -mindepth 1 -printf '%P\0' | bbpick "Find: " )" && bbcmd goto:"$file" /: # Pick a file - file="$(printf "%s\0" $BBGLOB | pick "Pick: ")" || exit + file="$(printf "%s\0" $BBGLOB | bbpick "Pick: ")" || exit bbcmd goto:"$file" *: # Set the glob - ask BBGLOB "Show files matching: " - bbcmd glob:"$BBGLOB" + glob="$(bbask "Show files matching: ")" && bbcmd glob:"$glob" Ctrl-g: # Go to directory - ask goto "Go to directory: " && bbcmd cd:"$goto" + cd="$(bbask "Go to directory: ")" && bbcmd cd:"$cd" m: # Mark this directory mkdir -p "$XDG_CONFIG_HOME/bb/marks" ln -sT "$2" "$XDG_CONFIG_HOME/bb/marks/$1" 2>/dev/null - ask mark "Mark: " && ln -s "$PWD" "$XDG_CONFIG_HOME"/bb/marks/"$mark" + mark="$(bbask "Mark: ") && ln -s "$PWD" "$XDG_CONFIG_HOME"/bb/marks/"$mark" ': # Go to a marked directory [ -d "$XDG_CONFIG_HOME"/bb/marks ] || exit - mark="$(find "$XDG_CONFIG_HOME"/bb/marks/ -mindepth 1 -type l -printf '%P\0' | pick "Jump to: ")" && + mark="$(find "$XDG_CONFIG_HOME"/bb/marks/ -mindepth 1 -type l -printf '%P\0' | bbpick "Jump to: ")" && bbcmd cd:"$(readlink -f "$XDG_CONFIG_HOME"/bb/marks/"$mark")" -,Backspace: # Go to previous directory [ "$BBPREVPATH" ] && bbcmd cd:"$BBPREVPATH" @@ -70,18 +69,18 @@ v,V,Space: # Toggle selection at cursor Escape: # Clear selection bbcmd deselect S: # Select pattern - ask patt "Select: " && bbcmd select: $patt + patt="$(bbask "Select: ")" && bbcmd select: $patt U: # Unselect pattern - ask patt "Unselect: " && eval bbcmd deselect: "$patt" + patt="$(bbask "Unselect: ")" && bbcmd deselect: $patt Ctrl-s: # Save the selection - [ $# -gt 0 ] && ask savename "Save selection as: " || exit 1 + [ $# -gt 0 ] && savename="$(bbask "Save selection as: ")" || exit 1 mkdir -p "$XDG_DATA_HOME"/bb if ! expr "$savename" : ".*\.sel" >/dev/null; then savename="$savename.sel"; fi printf '%s\0' "$@" > "$XDG_DATA_HOME"/bb/"$savename" Ctrl-o: # Open a saved selection [ -d "$XDG_DATA_HOME"/bb ] || exit - [ $# -gt 0 ] && ! confirm "The current selection will be discarded. " && exit 1 - loadpath="$(find "$XDG_DATA_HOME"/bb/ -mindepth 1 -name '*.sel' -printf '%P\0' | pick "Load selection: ")" && + [ $# -gt 0 ] && ! bbconfirm "The current selection will be discarded. " && exit 1 + loadpath="$(find "$XDG_DATA_HOME"/bb/ -mindepth 1 -name '*.sel' -printf '%P\0' | bbpick "Load selection: ")" && cat "$XDG_DATA_HOME"/bb/"$loadpath" | bbcmd deselect select: J: # Spread selection down bbcmd spread:+1 @@ -113,77 +112,79 @@ Enter,Double left click: # Open file/directory else xdg-open "$BBCURSOR"; fi fi e: # Edit file in $EDITOR - $EDITOR "$BBCURSOR" || pause -d,Delete: # Delete a file - printf "\033[1mDeleting \033[33m$BBCURSOR\033[0;1m...\033[0m " && confirm && - rm -rf "$BBCURSOR" && bbcmd deselect:"$BBCURSOR" refresh -D: # Delete all selected files - [ $# -gt 0 ] && printf "\033[1mDeleting the following:\n\033[33m$(printf ' %s\n' "$@")\033[0m" | unscroll | more && - confirm && rm -rf "$@" && bbcmd deselect refresh + $EDITOR "$BBCURSOR" || bbpause +d,Delete: # Delete + case "$(bbtargets "$BBCURSOR" "$@")" in + cursor) set -- "$BBCURSOR" ;; + both) set -- "$BBCURSOR" "$@" ;; + esac + printf "\033[1mDeleting the following:\n\033[33m$(printf ' %s\n' "$@")\033[0m" | bbunscroll | more && + bbconfirm && rm -rf "$@" && bbcmd deselect refresh Ctrl-v: # Move files here - printf "\033[1mMoving the following to here:\n\033[33m$(printf ' %s\n' "$@")\033[0m" | unscroll | more && - confirm && printf "\033[1G\033[KMoving..." && mv -i "$@" . && printf "done." && + printf "\033[1mMoving the following to here:\n\033[33m$(printf ' %s\n' "$@")\033[0m" | bbunscroll | more && + bbconfirm && printf "\033[1G\033[KMoving..." && mv -i "$@" . && printf "done." && bbcmd deselect refresh && for f; do bbcmd sel:"$(basename "$f")"; done c: # Copy a file - printf "\033[1mCreating copy of \033[33m$BBCURSOR\033[0;1m...\033[0m " && - confirm && cp -ri "$BBCURSOR" "$BBCURSOR.copy" && bbcmd refresh -C: # Copy all selected files here - [ $# -gt 0 ] && printf "\033[1mCopying the following to here:\n\033[33m$(printf ' %s\n' "$@")\033[0m" | unscroll | more && - confirm && printf "\033[1G\033[KCopying..." && + case "$(bbtargets "$BBCURSOR" "$@")" in + cursor) set -- "$BBCURSOR";; + both) set -- "$BBCURSOR" "$@";; + esac + [ $# -gt 0 ] || exit + printf "\033[1mCopying the following to here:\n\033[33m$(printf ' %s\n' "$@")\033[0m" | bbunscroll | more + bbconfirm && printf "\033[1G\033[KCopying..." && for f; do if [ "./$(basename "$f")" -ef "$f" ]; then cp -ri "$f" "$f.copy" || break; else cp -ri "$f" . || break; fi; done; printf 'done.' && bbcmd refresh Ctrl-n: # New file/directory - case "$(printf '%s\0' File Directory | pick "Create new: ")" in + case "$(printf '%s\0' File Directory | bbpick "Create new: ")" in File) - ask name "New File: " || exit - touch -- "$name" + name="$(bbask "New File: ")" && touch -- "$name" ;; Directory) - ask name "New Directory: " || exit - mkdir -- "$name" + name="$(bbask "New Directory: ")" && mkdir -- "$name" ;; *) exit ;; - esac && bbcmd goto:"$name" refresh || pause + esac && bbcmd goto:"$name" refresh || bbpause p: # Page through a file with `less` less -XK "$BBCURSOR" -r,F2: # Rename a file - ask newname "Rename $(printf "\033[33m%s\033[39m" "$(basename "$BBCURSOR")"): " "$(basename "$BBCURSOR")" || exit - r="$(dirname "$BBCURSOR")/$newname" || exit - [ "$r" = "$BBCURSOR" ] && exit - [ -e "$r" ] && printf "\033[31;1m$r already exists! It will be overwritten.\033[0m " && - confirm && { rm -rf "$r" || { pause; exit; }; } - mv "$BBCURSOR" "$r" && bbcmd refresh && - while [ $# -gt 0 ]; do "$1" = "$BBCURSOR" && bbcmd deselect:"$BBCURSOR" select:"$r"; shift; done && - bbcmd goto:"$r" || { pause; exit; } -R: # Rename all selected files +r,F2: # Rename files + case "$(bbtargets "$BBCURSOR" "$@")" in + cursor) set -- "$BBCURSOR";; + both) set -- "$BBCURSOR" "$@";; + esac for f; do - ask newname "Rename $(printf "\033[33m%s\033[39m" "$(basename "$f")"): " "$(basename "$f")" || break; + newname="$(bbask "Rename $(printf "\033[33m%s\033[39m" "$(basename "$f")"): " "$(basename "$f")")" || break; r="$(dirname "$f")/$newname"; [ "$r" = "$f" ] && continue; [ -e "$r" ] && printf "\033[31;1m$r already exists! It will be overwritten.\033[0m " - && confirm && { rm -rf "$r" || { pause; exit; }; } - mv "$f" "$r" || { pause; exit; } + && bbconfirm && { rm -rf "$r" || { bbpause; exit; }; } + mv "$f" "$r" || { bbpause; exit; } bbcmd deselect:"$f" select:"$r"; [ "$f" = "$BBCURSOR" ] && bbcmd goto:"$r"; done; bbcmd refresh Ctrl-r: # Regex rename files + case "$(bbtargets "$BBCURSOR" "$@")" in + cursor) set -- "$BBCURSOR";; + both) set -- "$BBCURSOR" "$@";; + esac command -v rename >/dev/null || - { printf '\033[31;1mThe `rename` command is not installed. Please install it to use this key binding.\033[0m\n'; pause; exit; }; - ask patt "Replace pattern: " && ask rep "Replacement: " && - printf "\033[1mRenaming:\n\033[33m$(if [ $# -gt 0 ]; then rename -nv "$patt" "$rep" "$@"; else rename -nv "$patt" "$rep" *; fi)\033[0m" | unscroll | more && - confirm || exit 1 + { printf '\033[31;1mThe `rename` command is not installed. Please install it to use this key binding.\033[0m\n'; bbpause; exit; }; + patt="$(bbask "Replace pattern: ") && rep="$(bbask "Replacement: ")" && + printf "\033[1mRenaming:\n\033[33m$(if [ $# -gt 0 ]; then rename -nv "$patt" "$rep" "$@"; else rename -nv "$patt" "$rep" *; fi)\033[0m" | bbunscroll | more && + bbconfirm || exit 1 if [ $# -eq 0 ]; then set -- *; ! [ -e "$1" ] && exit; fi rename -i "$patt" "$rep" "$@" bbcmd deselect refresh Section: Shell Commands :: # Run a command - ask cmd ':' && sh -c "$BBSHELLFUNC$cmd" -- "$@"; bbcmd refresh; pause + cmd="$(bbask ':')" && sh -c "$cmd" -- "$@"; bbcmd refresh; bbpause |: # Pipe selected files to a command - ask cmd '|' && printf '%s\n' "$@" | sh -c "$BBSHELLFUNC$cmd"; bbcmd refresh; pause + cmd="$(bbask '|')" && printf '%s\n' "$@" | sh -c "$cmd"; bbcmd refresh; bbpause +@: # Pipe selected files to a command + cmd="$(bbask '@')" && sh -c "$cmd \"$$@\"" -- "$@"; bbcmd refresh; bbpause >: # Open a shell tput rmcup; tput cvvis; $SHELL; bbcmd refresh f: # Resume suspended process @@ -191,10 +192,10 @@ f: # Resume suspended process Section: Viewing Options s: # Sort by... - ask1 sort "Sort (n)ame (s)ize (m)odification (c)reation (a)ccess (r)andom (p)ermissions: " && + sort="$(bbask -1 "Sort (n)ame (s)ize (m)odification (c)reation (a)ccess (r)andom (p)ermissions: ")" && bbcmd sort:"~$sort" ---,#: # Set columns - ask columns "Set columns (*)selected (a)ccessed (c)reated (m)odified (n)ame (p)ermissions (r)andom (s)ize: " && + columns="$(bbask "Set columns (*)selected (a)ccessed (c)reated (m)odified (n)ame (p)ermissions (r)andom (s)ize: ")" && bbcmd col:"$columns" .: # Toggle dotfile visibility if [ "$BBGLOB" = ".* *" ]; then @@ -207,7 +208,7 @@ i: # Toggle interleaving files and directories F5,Ctrl-l: # Refresh view bbcmd refresh Ctrl-b: # Bind a key to a script - ask1 key "Press key to bind..." && echo && ask script "Bind script: " && - bbcmd bind:"$key":"{ $script; } || pause" || pause + key="$(bbask -1 "Press key to bind...")" && echo && script="$(bbask "Bind script: ")" && + bbcmd bind:"$key":"{ $script; } || bbpause" || bbpause Section: User Bindings diff --git a/helpers/bbask b/helpers/bbask new file mode 100755 index 0000000..ad446ec --- /dev/null +++ b/helpers/bbask @@ -0,0 +1,31 @@ +#!/bin/sh +# A simple user-input asker. Result is output to stdout. +# Usage: ask [-1] [prompt [initial query]] +if [ "$1" = '-1' ]; then + # Get one character of input + tput civis >/dev/tty; + printf '\033[1m%s\033[0m' "$2" >/dev/tty; + stty -icanon -echo >/dev/tty 2>/dev/tty; + if [ "$(uname)" = "Darwin" ]; then + read -n 1 REPLY /dev/tty; + echo $REPLY + else + dd bs=1 count=1 2>/dev/null /dev/tty 2>/dev/tty + tput cvvis >/dev/tty +else + # Get a line of input + if command -v ask >/dev/null; then + ask --history=bb.hist --prompt="$(printf '%s\033[?25h' "$1")" --query="$2" + else + printf "\033[1m%s\033[0m" "$1" >/dev/tty + tput cvvis >/dev/tty + if [ "$(uname)" = "Darwin" ]; then + read -e REPLY /dev/tty + else + read REPLY /dev/tty + fi + echo $REPLY + fi +fi diff --git a/helpers/bbcmd b/helpers/bbcmd new file mode 100755 index 0000000..fdc4e78 --- /dev/null +++ b/helpers/bbcmd @@ -0,0 +1,11 @@ +#!/bin/sh +if [ $# -eq 0 ]; then cat >> $BBCMD; exit; fi +for arg; do + shift + if expr "$arg" : '^[^:]*:$' >/dev/null; then + if [ $# -gt 0 ]; then printf "$arg%s\\0" "$@" >> $BBCMD + else sed "s/\([^\\x00]\+\)/$arg\1/g" >> $BBCMD; fi + exit + fi + printf "%s\\0" "$arg" >> $BBCMD +done diff --git a/helpers/bbconfirm b/helpers/bbconfirm new file mode 100755 index 0000000..cae1153 --- /dev/null +++ b/helpers/bbconfirm @@ -0,0 +1,9 @@ +#!/bin/sh +# Ask for user confirmation +set -e +if command -v ask >/dev/null; then + ask -n "$(printf "$1Is that okay?\033[?25h")" +else + reply="$(bbask -1 "$(printf "$1\033[0;1mIs that okay? [y/N] ")")" + [ "$reply" != 'y' ] +fi diff --git a/helpers/bbpause b/helpers/bbpause new file mode 100755 index 0000000..6604fbd --- /dev/null +++ b/helpers/bbpause @@ -0,0 +1,4 @@ +#!/bin/sh +# Pause before continuing +printf '\033[0;2m Press any key to continue...\033[0m' >/dev/tty +bbask -1 >/dev/null diff --git a/helpers/bbpick b/helpers/bbpick new file mode 100755 index 0000000..3316484 --- /dev/null +++ b/helpers/bbpick @@ -0,0 +1,39 @@ +#!/bin/sh +# Pick from the provided input +if [ -z "$PICKER" ]; then + if command -v fzf >/dev/null; then + PICKER=fzf + elif command -v fzy >/dev/null; then + PICKER=fzy + elif command -v ask >/dev/null; then + PICKER=ask + elif command -v dmenu >/dev/null; then + PICKER=dmenu + elif command -v pick >/dev/null; then + PICKER=pick + fi +fi + +case "$PICKER" in + fzf) + printf '\033[3A\033[?25h' >/dev/tty + fzf --read0 --height=4 --prompt="$(printf "$1")" + ;; + fzy) + printf '\033[3A\033[?25h' >/dev/tty + tr '\0' '\n' | fzy --lines=3 --prompt="$(printf "\033[1m$1\033[0m")" + ;; + ask) + ask --read0 --prompt="$(printf "$1\033[?25h")" + ;; + dmenu) + tr '\0' '\n' | dmenu -i -l 10 -p "$(printf "$1")" + ;; + pick) + printf '\033[?25h' >/dev/tty + tr '\0' '\n' | pick + ;; + *) + query="$(bbask "$1")" && grep -i -m1 "$(echo "$query" | sed 's;.;[^/&]*[&];g')" + ;; +esac diff --git a/helpers/bbtargets b/helpers/bbtargets new file mode 100755 index 0000000..1862ca0 --- /dev/null +++ b/helpers/bbtargets @@ -0,0 +1,25 @@ +#!/bin/sh +# If the user is doing something ambiguous, like selecting a bunch of files, +# moving the cursor off of those files, then doing an action, this will ask +# what they mean to target, then output either 'cursor' or 'selected'. +# Usage: targets "$BBCURSOR" "$@" +cursor="$1" +shift +if [ $# -gt 0 ]; then + for f in "$@"; do + if [ "$f" = "$cursor" ]; then + intended='Selected files' + break + fi + done +else + intended='Cursor file' +fi + +[ -z "$intended" ] && intended="$(printf '%s\0' 'Cursor file' 'Selected files' 'Both' | bbpick 'Which do you want to delete? ')" + +case "$intended" in + Cursor*) echo cursor ;; + Selected*) echo selected ;; + Both) echo both ;; +esac diff --git a/helpers/bbunscroll b/helpers/bbunscroll new file mode 100755 index 0000000..15102c4 --- /dev/null +++ b/helpers/bbunscroll @@ -0,0 +1,5 @@ +#!/bin/sh +# Display text from the current line upward (instead of downward) +input="$(cat)" +printf "\\033[$(echo "$input" | wc -l)A\\033[J" >/dev/tty +echo "$input"