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.
This commit is contained in:
Bruce Hill 2020-02-24 01:31:39 -08:00
parent a8fd174319
commit 865092c656
14 changed files with 206 additions and 198 deletions

26
API.md
View File

@ -9,18 +9,18 @@ internal state.
## Helper Functions ## Helper Functions
- `bb`: used for modifying `bb`'s internal state (see BB Commands). - `bbask [-1] [prompt [initial]]`: get user input in a standardized and
- `ask`: get user input in a standardized and customizable way. The first customizable way and output it to `STDOUT`.
argument is a variable where the value is stored. The second argument is - `bbcmd <cmd>*`: used for modifying `bb`'s internal state (see BB Commands).
a prompt. A third optional argument can provide a default value (may be - `bbconfirm [prompt]`: Display a "Is this okay? [y/N]" prompt and exit with
ignored). failure if the user does not press 'y'.
- `ask1`: get a single character of user input. The first argument is a variable - `bbpause`: Display a "press any key to continue" message and wait for a keypress.
where the input will be stored and the second argument is a prompt. - `bbpick [prompt]`: Select one of `NULL`-delimited multiple inputs and print it.
- `pause`: Display a "press any key to continue" message and wait for a keypress. - `bbtargets "$BBCMD" "$@"`: If `$BBCURSOR` is not currently among `$@` (the
- `confirm`: Display a "Is this okay? [y/N]" prompt and exit with failure if selected files), this script prompts the user to ask whether they want to
the user does not press 'y'. perform an action on the selected files, or on the cursor. The result is
- `spin`: Display a spinning icon while a slow command executes in the background. printed as `cursor` or `selected`.
(e.g. `spin sleep 5`). - `bbunscroll`: Print text to the screen *above* the cursor instead of below it.
## Environment Variables ## Environment Variables
@ -80,7 +80,7 @@ glob is available in `$BBGLOB`, which can be used in scripts if left unquoted.
## Final Notes ## 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 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 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 will run immediately. E.g. `bbcmd +'col:n' +'sort:+r' .` will launch `bb` only

View File

@ -8,49 +8,6 @@ CWARN=-Wall -Wpedantic -Wextra -Wno-unknown-pragmas -Wno-missing-field-initializ
#CFLAGS += -fsanitize=address -fno-omit-frame-pointer #CFLAGS += -fsanitize=address -fno-omit-frame-pointer
G= 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 '<none>'))
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 '<none>'))
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 '<none>'))
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 '<none>'))
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 '<none>'))
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 '<none>'))
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 '<none>'))
CFLAGS += -D'ASK="eval \"$$1=\\$$(echo \"$$3\" | dmenu -p \"$$2\")\""'
endif
endif
all: $(NAME) all: $(NAME)
clean: clean:
@ -67,8 +24,8 @@ install: $(NAME)
fi; \ fi; \
[ ! "$$prefix" ] && prefix="/usr/local"; \ [ ! "$$prefix" ] && prefix="/usr/local"; \
[ ! "$$sysconfdir" ] && sysconfdir=/etc; \ [ ! "$$sysconfdir" ] && sysconfdir=/etc; \
mkdir -pv -m 755 "$$prefix/share/man/man1" "$$prefix/bin" "$$sysconfdir/xdg/bb" \ mkdir -pv -m 755 "$$prefix/share/man/man1" "$$prefix/bin" "$$sysconfdir/xdg/bb" "$$sysconfdir/xdg/bb/helpers" \
&& cp -v bbstartup.sh bindings.bb "$$sysconfdir/xdg/bb/" \ && cp -rv bbstartup.sh bindings.bb helpers "$$sysconfdir/xdg/bb/" \
&& cp -v bb.1 bbcmd.1 "$$prefix/share/man/man1/" \ && cp -v bb.1 bbcmd.1 "$$prefix/share/man/man1/" \
&& rm -f "$$prefix/bin/$(NAME)" \ && rm -f "$$prefix/bin/$(NAME)" \
&& cp -v $(NAME) "$$prefix/bin/" && cp -v $(NAME) "$$prefix/bin/"
@ -83,5 +40,5 @@ uninstall:
[ ! "$$sysconfdir" ] && sysconfdir=/etc; \ [ ! "$$sysconfdir" ] && sysconfdir=/etc; \
echo "Deleting..."; \ echo "Deleting..."; \
rm -rvf "$$prefix/bin/$(NAME)" "$$prefix/share/man/man1/bb.1" "$$prefix/share/man/man1/bbcmd.1" "$$sysconfdir/xdg/bb"; \ 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"

2
bb.1
View File

@ -1,6 +1,6 @@
.\" Manpage for bb. .\" Manpage for bb.
.\" Contact bruce@bruce-hill.com to correct errors or typos. .\" 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 .SH NAME
bb \- A bitty browser for command line file management bb \- A bitty browser for command line file management
.SH SYNOPSIS .SH SYNOPSIS

14
bb.c
View File

@ -904,12 +904,9 @@ int run_script(bb_t *bb, const char *cmd)
err("Couldn't set pgrp"); err("Couldn't set pgrp");
char **args = memcheck(calloc(4 + (size_t)bb->nselected + 1, sizeof(char*))); char **args = memcheck(calloc(4 + (size_t)bb->nselected + 1, sizeof(char*)));
int i = 0; int i = 0;
args[i++] = SH; args[i++] = "sh";
args[i++] = "-c"; args[i++] = "-c";
char *fullcmd = calloc(strlen(cmd) + strlen(bbcmdfn) + 1, sizeof(char)); args[i++] = (char*)cmd;
strcpy(fullcmd, bbcmdfn);
strcat(fullcmd, cmd);
args[i++] = fullcmd;
args[i++] = "--"; // ensure files like "-i" are not interpreted as flags for sh 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 // bb->selected is in most-recent order, so populate args in reverse to make sure
// that $1 is the first selected, etc. // 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); ttyin = open("/dev/tty", O_RDONLY);
dup2(ttyout, STDOUT_FILENO); dup2(ttyout, STDOUT_FILENO);
dup2(ttyin, STDIN_FILENO); dup2(ttyin, STDIN_FILENO);
execvp(SH, args); execvp(args[0], args);
err("Failed to execute command: '%s'", cmd); err("Failed to execute command: '%s'", cmd);
return -1; return -1;
} }
@ -1195,6 +1192,10 @@ int main(int argc, char *argv[])
sprintf(xdg_data_home, "%s/.local/share", getenv("HOME")); sprintf(xdg_data_home, "%s/.local/share", getenv("HOME"));
setenv("XDG_DATA_HOME", xdg_data_home, 0); setenv("XDG_DATA_HOME", xdg_data_home, 0);
setenv("sysconfdir", "/etc", 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("SHELL", "bash", 0);
setenv("EDITOR", "nano", 0); setenv("EDITOR", "nano", 0);
char *curdepth = getenv("BBDEPTH"); char *curdepth = getenv("BBDEPTH");
@ -1202,7 +1203,6 @@ int main(int argc, char *argv[])
char depthstr[16]; char depthstr[16];
sprintf(depthstr, "%d", depth + 1); sprintf(depthstr, "%d", depth + 1);
setenv("BBDEPTH", depthstr, 1); setenv("BBDEPTH", depthstr, 1);
setenv("BBSHELLFUNC", bbcmdfn, 1);
char full_initial_path[PATH_MAX]; char full_initial_path[PATH_MAX];
getcwd(full_initial_path, PATH_MAX); getcwd(full_initial_path, PATH_MAX);
normalize_path(full_initial_path, initial_path, full_initial_path); normalize_path(full_initial_path, initial_path, full_initial_path);

76
bb.h
View File

@ -26,7 +26,7 @@
#include "bterm.h" #include "bterm.h"
// Macros: // Macros:
#define BB_VERSION "0.24.0" #define BB_VERSION "0.25.0"
#ifndef PATH_MAX #ifndef PATH_MAX
#define PATH_MAX 4096 #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 *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"; 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 >/dev/tty;\n"
#else
" read $1 </dev/tty >/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 >/dev/tty;\n"
#else
" eval \"$1=\\$(dd bs=1 count=1 2>/dev/null </dev/tty)\";\n"
#endif
" stty icanon echo >/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 = static const char *runstartup =
"for path in \"$XDG_CONFIG_HOME/bb\" \"$sysconfdir/xdg/bb\" .; do\n" "for path in \"$XDG_CONFIG_HOME/bb\" \"$sysconfdir/xdg/bb\" .; do\n"
" if [ -e \"$path/bbstartup.sh\" ]; then\n" " if [ -e \"$path/bbstartup.sh\" ]; then\n"

View File

@ -1,6 +1,6 @@
.\" Manpage for bbcmd. .\" Manpage for bbcmd.
.\" Contact bruce@bruce-hill.com to correct errors or typos. .\" 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 .de TPx
. TP 4n . TP 4n

View File

@ -23,23 +23,22 @@ l,Right: # Enter directory
if [ -d "$BBCURSOR" ]; then bbcmd cd:"$BBCURSOR"; fi if [ -d "$BBCURSOR" ]; then bbcmd cd:"$BBCURSOR"; fi
Ctrl-f: # Search for file Ctrl-f: # Search for file
file="$( file="$(
find $BBGLOB -mindepth 1 -printf '%P\0' | pick "Find: " find $BBGLOB -mindepth 1 -printf '%P\0' | bbpick "Find: "
)" && bbcmd goto:"$file" )" && bbcmd goto:"$file"
/: # Pick a file /: # Pick a file
file="$(printf "%s\0" $BBGLOB | pick "Pick: ")" || exit file="$(printf "%s\0" $BBGLOB | bbpick "Pick: ")" || exit
bbcmd goto:"$file" bbcmd goto:"$file"
*: # Set the glob *: # Set the glob
ask BBGLOB "Show files matching: " glob="$(bbask "Show files matching: ")" && bbcmd glob:"$glob"
bbcmd glob:"$BBGLOB"
Ctrl-g: # Go to directory 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 m: # Mark this directory
mkdir -p "$XDG_CONFIG_HOME/bb/marks" mkdir -p "$XDG_CONFIG_HOME/bb/marks"
ln -sT "$2" "$XDG_CONFIG_HOME/bb/marks/$1" 2>/dev/null 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 ': # Go to a marked directory
[ -d "$XDG_CONFIG_HOME"/bb/marks ] || exit [ -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")" bbcmd cd:"$(readlink -f "$XDG_CONFIG_HOME"/bb/marks/"$mark")"
-,Backspace: # Go to previous directory -,Backspace: # Go to previous directory
[ "$BBPREVPATH" ] && bbcmd cd:"$BBPREVPATH" [ "$BBPREVPATH" ] && bbcmd cd:"$BBPREVPATH"
@ -70,18 +69,18 @@ v,V,Space: # Toggle selection at cursor
Escape: # Clear selection Escape: # Clear selection
bbcmd deselect bbcmd deselect
S: # Select pattern S: # Select pattern
ask patt "Select: " && bbcmd select: $patt patt="$(bbask "Select: ")" && bbcmd select: $patt
U: # Unselect pattern U: # Unselect pattern
ask patt "Unselect: " && eval bbcmd deselect: "$patt" patt="$(bbask "Unselect: ")" && bbcmd deselect: $patt
Ctrl-s: # Save the selection 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 mkdir -p "$XDG_DATA_HOME"/bb
if ! expr "$savename" : ".*\.sel" >/dev/null; then savename="$savename.sel"; fi if ! expr "$savename" : ".*\.sel" >/dev/null; then savename="$savename.sel"; fi
printf '%s\0' "$@" > "$XDG_DATA_HOME"/bb/"$savename" printf '%s\0' "$@" > "$XDG_DATA_HOME"/bb/"$savename"
Ctrl-o: # Open a saved selection Ctrl-o: # Open a saved selection
[ -d "$XDG_DATA_HOME"/bb ] || exit [ -d "$XDG_DATA_HOME"/bb ] || exit
[ $# -gt 0 ] && ! confirm "The current selection will be discarded. " && exit 1 [ $# -gt 0 ] && ! bbconfirm "The current selection will be discarded. " && exit 1
loadpath="$(find "$XDG_DATA_HOME"/bb/ -mindepth 1 -name '*.sel' -printf '%P\0' | pick "Load selection: ")" && 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: cat "$XDG_DATA_HOME"/bb/"$loadpath" | bbcmd deselect select:
J: # Spread selection down J: # Spread selection down
bbcmd spread:+1 bbcmd spread:+1
@ -113,77 +112,79 @@ Enter,Double left click: # Open file/directory
else xdg-open "$BBCURSOR"; fi else xdg-open "$BBCURSOR"; fi
fi fi
e: # Edit file in $EDITOR e: # Edit file in $EDITOR
$EDITOR "$BBCURSOR" || pause $EDITOR "$BBCURSOR" || bbpause
d,Delete: # Delete a file d,Delete: # Delete
printf "\033[1mDeleting \033[33m$BBCURSOR\033[0;1m...\033[0m " && confirm && case "$(bbtargets "$BBCURSOR" "$@")" in
rm -rf "$BBCURSOR" && bbcmd deselect:"$BBCURSOR" refresh cursor) set -- "$BBCURSOR" ;;
D: # Delete all selected files both) set -- "$BBCURSOR" "$@" ;;
[ $# -gt 0 ] && printf "\033[1mDeleting the following:\n\033[33m$(printf ' %s\n' "$@")\033[0m" | unscroll | more && esac
confirm && rm -rf "$@" && bbcmd deselect refresh 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 Ctrl-v: # Move files here
printf "\033[1mMoving the following to here:\n\033[33m$(printf ' %s\n' "$@")\033[0m" | unscroll | more && printf "\033[1mMoving the following to here:\n\033[33m$(printf ' %s\n' "$@")\033[0m" | bbunscroll | more &&
confirm && printf "\033[1G\033[KMoving..." && mv -i "$@" . && printf "done." && bbconfirm && printf "\033[1G\033[KMoving..." && mv -i "$@" . && printf "done." &&
bbcmd deselect refresh && for f; do bbcmd sel:"$(basename "$f")"; done bbcmd deselect refresh && for f; do bbcmd sel:"$(basename "$f")"; done
c: # Copy a file c: # Copy a file
printf "\033[1mCreating copy of \033[33m$BBCURSOR\033[0;1m...\033[0m " && case "$(bbtargets "$BBCURSOR" "$@")" in
confirm && cp -ri "$BBCURSOR" "$BBCURSOR.copy" && bbcmd refresh cursor) set -- "$BBCURSOR";;
C: # Copy all selected files here both) set -- "$BBCURSOR" "$@";;
[ $# -gt 0 ] && printf "\033[1mCopying the following to here:\n\033[33m$(printf ' %s\n' "$@")\033[0m" | unscroll | more && esac
confirm && printf "\033[1G\033[KCopying..." && [ $# -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 for f; do if [ "./$(basename "$f")" -ef "$f" ]; then
cp -ri "$f" "$f.copy" || break; cp -ri "$f" "$f.copy" || break;
else cp -ri "$f" . || break; fi; done; printf 'done.' && bbcmd refresh else cp -ri "$f" . || break; fi; done; printf 'done.' && bbcmd refresh
Ctrl-n: # New file/directory 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) File)
ask name "New File: " || exit name="$(bbask "New File: ")" && touch -- "$name"
touch -- "$name"
;; ;;
Directory) Directory)
ask name "New Directory: " || exit name="$(bbask "New Directory: ")" && mkdir -- "$name"
mkdir -- "$name"
;; ;;
*) exit *) exit
;; ;;
esac && bbcmd goto:"$name" refresh || pause esac && bbcmd goto:"$name" refresh || bbpause
p: # Page through a file with `less` p: # Page through a file with `less`
less -XK "$BBCURSOR" less -XK "$BBCURSOR"
r,F2: # Rename a file r,F2: # Rename files
ask newname "Rename $(printf "\033[33m%s\033[39m" "$(basename "$BBCURSOR")"): " "$(basename "$BBCURSOR")" || exit case "$(bbtargets "$BBCURSOR" "$@")" in
r="$(dirname "$BBCURSOR")/$newname" || exit cursor) set -- "$BBCURSOR";;
[ "$r" = "$BBCURSOR" ] && exit both) set -- "$BBCURSOR" "$@";;
[ -e "$r" ] && printf "\033[31;1m$r already exists! It will be overwritten.\033[0m " && esac
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
for f; do 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="$(dirname "$f")/$newname";
[ "$r" = "$f" ] && continue; [ "$r" = "$f" ] && continue;
[ -e "$r" ] && printf "\033[31;1m$r already exists! It will be overwritten.\033[0m " [ -e "$r" ] && printf "\033[31;1m$r already exists! It will be overwritten.\033[0m "
&& confirm && { rm -rf "$r" || { pause; exit; }; } && bbconfirm && { rm -rf "$r" || { bbpause; exit; }; }
mv "$f" "$r" || { pause; exit; } mv "$f" "$r" || { bbpause; exit; }
bbcmd deselect:"$f" select:"$r"; bbcmd deselect:"$f" select:"$r";
[ "$f" = "$BBCURSOR" ] && bbcmd goto:"$r"; [ "$f" = "$BBCURSOR" ] && bbcmd goto:"$r";
done; done;
bbcmd refresh bbcmd refresh
Ctrl-r: # Regex rename files Ctrl-r: # Regex rename files
case "$(bbtargets "$BBCURSOR" "$@")" in
cursor) set -- "$BBCURSOR";;
both) set -- "$BBCURSOR" "$@";;
esac
command -v rename >/dev/null || 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; }; { printf '\033[31;1mThe `rename` command is not installed. Please install it to use this key binding.\033[0m\n'; bbpause; exit; };
ask patt "Replace pattern: " && ask rep "Replacement: " && 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" | unscroll | more && printf "\033[1mRenaming:\n\033[33m$(if [ $# -gt 0 ]; then rename -nv "$patt" "$rep" "$@"; else rename -nv "$patt" "$rep" *; fi)\033[0m" | bbunscroll | more &&
confirm || exit 1 bbconfirm || exit 1
if [ $# -eq 0 ]; then set -- *; ! [ -e "$1" ] && exit; fi if [ $# -eq 0 ]; then set -- *; ! [ -e "$1" ] && exit; fi
rename -i "$patt" "$rep" "$@" rename -i "$patt" "$rep" "$@"
bbcmd deselect refresh bbcmd deselect refresh
Section: Shell Commands Section: Shell Commands
:: # Run a command :: # 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 |: # 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 >: # Open a shell
tput rmcup; tput cvvis; $SHELL; bbcmd refresh tput rmcup; tput cvvis; $SHELL; bbcmd refresh
f: # Resume suspended process f: # Resume suspended process
@ -191,10 +192,10 @@ f: # Resume suspended process
Section: Viewing Options Section: Viewing Options
s: # Sort by... 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" bbcmd sort:"~$sort"
---,#: # Set columns ---,#: # 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" bbcmd col:"$columns"
.: # Toggle dotfile visibility .: # Toggle dotfile visibility
if [ "$BBGLOB" = ".* *" ]; then if [ "$BBGLOB" = ".* *" ]; then
@ -207,7 +208,7 @@ i: # Toggle interleaving files and directories
F5,Ctrl-l: # Refresh view F5,Ctrl-l: # Refresh view
bbcmd refresh bbcmd refresh
Ctrl-b: # Bind a key to a script Ctrl-b: # Bind a key to a script
ask1 key "Press key to bind..." && echo && ask script "Bind script: " && key="$(bbask -1 "Press key to bind...")" && echo && script="$(bbask "Bind script: ")" &&
bbcmd bind:"$key":"{ $script; } || pause" || pause bbcmd bind:"$key":"{ $script; } || bbpause" || bbpause
Section: User Bindings Section: User Bindings

31
helpers/bbask Executable file
View File

@ -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 >/dev/tty;
echo $REPLY
else
dd bs=1 count=1 2>/dev/null </dev/tty
fi
stty icanon echo >/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 >/dev/tty
else
read REPLY </dev/tty >/dev/tty
fi
echo $REPLY
fi
fi

11
helpers/bbcmd Executable file
View File

@ -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

9
helpers/bbconfirm Executable file
View File

@ -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

4
helpers/bbpause Executable file
View File

@ -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

39
helpers/bbpick Executable file
View File

@ -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

25
helpers/bbtargets Executable file
View File

@ -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

5
helpers/bbunscroll Executable file
View File

@ -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"