Refactored bb +... to bbcmd ... within bb bindings. This makes

things a lot less ambiguous. Also removed the default marks created in
bbstartup.sh and ensured that `$XDG_DATA_HOME` and `$XDG_CONFIG_HOME`
always get set as environment variables.
This commit is contained in:
Bruce Hill 2019-11-11 12:29:40 -08:00
parent af3f642150
commit b7ad0e93a2
6 changed files with 100 additions and 103 deletions

25
API.md
View File

@ -36,7 +36,7 @@ environment variables:
## BB Internal State Commands
In order to modify bb's internal state, you can call `bb +cmd`, where "cmd"
In order to modify bb's internal state, you can call `bbcmd <cmd>`, where "cmd"
is one of the following commands (or a unique prefix of one):
- `.:[01]` Whether to show "." in each directory
@ -57,10 +57,10 @@ is one of the following commands (or a unique prefix of one):
- `spread:<num*>` Spread the selection state at the cursor
- `toggle:<filename>` Toggle the selection status of <filename>
For any of these commands (e.g. `+select`), an empty parameter followed by
additional arguments (e.g. `bb +select: <file1> <file2> ...`) is equivalent to
repeating the command with each argument (e.g. `bb +select:<file1>
+select:<file2> ...`).
For any of these commands (e.g. `select`), an empty parameter followed by
additional arguments (e.g. `bbcmd select: <file1> <file2> ...`) is equivalent to
repeating the command with each argument (e.g. `bbcmd select:<file1>
select:<file2> ...`).
Note: for numeric-based commands (like scroll), the number can be either an
absolute value or a relative value (starting with `+` or `-`), and/or a percent
@ -70,12 +70,13 @@ height down, and `100%n` means the last file)
## Final Notes
Internally, `bb` writes the commands (NUL terminated) to a file whose path is
in`$BBCMD` and reads from that file when `bb` resumes. These commands can also
be passed to bb at startup, and will run immediately. E.g. `bb '+col:n'
'+sort:+r' .` will launch `bb` only showing the name column, randomly sorted.
Internally, `bbcmd` writes the commands (NUL 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
showing the name column, randomly sorted.
`bb` also optimizes any scripts that only contain just a `bb` command and no
shell variables, other commands, etc. (e.g. `bb +move:+1`) These
`bb`-command-only scripts directly modify `bb`'s internal state without
`bb` also optimizes any scripts that only contain just a `bbcmd` command and no
shell variables, other commands, etc. (e.g. `bbcmd move:+1`) These
`bbcmd`-command-only scripts directly modify `bb`'s internal state without
spawning a shell, so they're much faster and avoid flickering the screen.

View File

@ -49,7 +49,7 @@ to the filesystem (passing selected files as arguments), rather than
reinventing the wheel by hard-coding operations like `rm`, `mv`, `cp`, `touch`,
and so on. Shell scripts can be bound to keypresses in
`~/.config/bb/bindings.bb`. For example, `D` is bound to a script that prints a
confirmation message, then runs `rm -rf "$@" && bb +deselect +refresh`,
confirmation message, then runs `rm -rf "$@" && bbcmd deselect refresh`,
which means selecting `file1` and `file2`, then pressing `D` will cause `bb` to
run the shell command `rm -rf file1 file2` and then tell `bb` to deselect all
(now deleted) files and refresh.
@ -74,8 +74,8 @@ up an easy way to repeat some custom workflow).
### API
`bb` also exposes an API that allows shell scripts to modify `bb`'s internal
state. To do this, call `bb +<your command>` from within `bb`. For example, by
default, `j` is bound to `bb +move:+1`, which has the effect of moving `bb`'s
state. To do this, call `bbcmd <your command>` from within `bb`. For example, by
default, `j` is bound to `bbcmd move:+1`, which has the effect of moving `bb`'s
cursor down one item. More details about the API can be found in [the API
documentation](API.md).

15
bb.c
View File

@ -263,13 +263,13 @@ void init_term(void)
/*
* Return whether or not 's' is a simple bb command that doesn't need
* a full shell instance (e.g. "bb +cd:.." or "bb +move:+1").
* a full shell instance (e.g. "bbcmd cd:.." or "bbcmd move:+1").
*/
static int is_simple_bbcmd(const char *s)
{
if (!s) return 0;
while (*s == ' ') ++s;
if (s[0] != '+' && strncmp(s, "bb +", 4) != 0)
if (strncmp(s, "bbcmd ", strlen("bbcmd ")) != 0)
return 0;
const char *special = ";$&<>|\n*?\\\"'";
for (const char *p = special; *p; ++p) {
@ -542,8 +542,7 @@ void print_bindings(int fd)
*/
void run_bbcmd(bb_t *bb, const char *cmd)
{
if (cmd[0] == '+') ++cmd;
else if (strncmp(cmd, "bb +", 4) == 0) cmd = &cmd[4];
if (strncmp(cmd, "bbcmd ", strlen("bbcmd ")) == 0) cmd = &cmd[strlen("bbcmd ")];
const char *value = strchr(cmd, ':');
if (value) ++value;
#define set_bool(target) do { if (!value) { target = !target; } else { target = value[0] == '1'; } } while (0)
@ -1212,6 +1211,12 @@ int main(int argc, char *argv[])
// Set up environment variables
// Default values
char xdg_config_home[PATH_MAX], xdg_data_home[PATH_MAX];
sprintf(xdg_config_home, "%s/.config", getenv("HOME"));
setenv("XDG_CONFIG_HOME", xdg_config_home, 0);
sprintf(xdg_data_home, "%s/.local/share", getenv("HOME"));
setenv("XDG_DATA_HOME", xdg_data_home, 0);
setenv("sysconfdir", "/etc", 0);
setenv("SHELL", "bash", 0);
setenv("EDITOR", "nano", 0);
char *curdepth = getenv("BB_DEPTH");
@ -1254,7 +1259,7 @@ int main(int argc, char *argv[])
write(cmdfd, "\0", 1);
for (int i = 0; i < argc; i++) {
if (argv[i][0] == '+') {
char *cmd = argv[i];
char *cmd = argv[i] + 1;
char *colon = strchr(cmd, ':');
if (colon && !colon[1]) {
for (++i; i < argc; i++) {

6
bb.h
View File

@ -25,7 +25,7 @@
#include "bterm.h"
// Macros:
#define BB_VERSION "0.19.2"
#define BB_VERSION "0.20.0"
#ifndef PATH_MAX
#define PATH_MAX 4096
@ -254,11 +254,11 @@ static const char *description_str = "bb - an itty bitty console TUI file browse
static const char *usage_str = "Usage: bb (-h/--help | -v/--version | -s | -d | -0 | +command | path)*\n";
// Shell functions
static const char *bbcmdfn = "bb() {\n"
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 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"

View File

@ -1,28 +1,14 @@
#!/bin/sh
# This file contains the script that is run when bb launches
# See API.md for details on bb's API.
# Default XDG values
[ ! "$XDG_CONFIG_HOME" ] && XDG_CONFIG_HOME=~/.config
[ ! "$sysconfdir" ] && sysconfdir=/etc
# Create some default marks:
mkdir -p "$XDG_CONFIG_HOME/bb/marks"
mark() {
ln -sT "$2" "$XDG_CONFIG_HOME/bb/marks/$1" 2>/dev/null
}
mark home ~
mark root /
mark config "$XDG_CONFIG_HOME"
mark marks "$XDG_CONFIG_HOME/bb/marks"
# See API.md for details on bb's command API.
# Load key bindings
# first check ~/.config/bb/bindings.bb, then /etc/xdg/bb/bindings.bb, then ./bindings.bb
if [ ! -e "$XDG_CONFIG_HOME/bb/bindings.bb" ] && [ ! -e "$sysconfdir/xdg/bb/bindings.bb" ]; then
cat "./bindings.bb" 2>/dev/null | sed -e '/^#/d' -e "s/^[^ ]/$(printf '\034')+bind:\\0/" | tr '\034' '\0' >> "$BBCMD"
cat "./bindings.bb" 2>/dev/null | sed -e '/^#/d' -e "s/^[^ ]/$(printf '\034')bind:\\0/" | tr '\034' '\0' >> "$BBCMD"
else
for path in "$sysconfdir/xdg/bb" "$XDG_CONFIG_HOME/bb"; do
cat "$path/bindings.bb" 2>/dev/null
done | sed -e '/^#/d' -e "s/^[^ ]/$(printf '\034')+bind:\\0/" | tr '\034' '\0' >> "$BBCMD"
done | sed -e '/^#/d' -e "s/^[^ ]/$(printf '\034')bind:\\0/" | tr '\034' '\0' >> "$BBCMD"
fi
printf '\0' >> "$BBCMD"

View File

@ -2,9 +2,9 @@
# The format is: <key>(,<key>)*:[ ]*#<description>(\n[ ]+script)+
Section: BB Commands
?,F1: # Show Help menu
bb +help
bbcmd help
q,Q: # Quit
bb +quit
bbcmd quit
Ctrl-c: # Send interrupt signal
kill -INT $PPID
Ctrl-z: # Suspend
@ -14,92 +14,97 @@ Ctrl-\: # Quit and generate core dump
Section: File Navigation
j,Down: # Next file
bb +move:+1
bbcmd move:+1
k,Up: # Previous file
bb +move:-1
bbcmd move:-1
h,Left: # Parent directory
bb +cd:..
bbcmd cd:..
l,Right: # Enter directory
if [ -d "$BBCURSOR" ]; then bb +cd:"$BBCURSOR"; fi
if [ -d "$BBCURSOR" ]; then bbcmd cd:"$BBCURSOR"; fi
Ctrl-f: # Search for file
file="$(
if [ $BBDOTFILES ]; then find -mindepth 1 -printf '%P\0';
else find -mindepth 1 ! -path '*/.*' -printf '%P\0';
fi | pick "Find: "
)" && bb +goto:"$file"
)" && bbcmd goto:"$file"
/: # Pick a file
file="$(find -mindepth 1 -maxdepth 1 -printf '%P\0' | pick "Pick: ")" || exit
expr "$file" : "\..*" >/dev/null && ! [ "$BBDOTFILES" ] && bb +dotfiles
bb +goto:"$file"
expr "$file" : "\..*" >/dev/null && ! [ "$BBDOTFILES" ] && bbcmd dotfiles
bbcmd goto:"$file"
Ctrl-g: # Go to directory
ask goto "Go to directory: " && bb +cd:"$goto"
ask goto "Go to directory: " && bbcmd cd:"$goto"
m: # Mark this directory
ask mark "Mark: " && ln -s "$PWD" ~/.config/bb/marks/"$mark"
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"
': # Go to a marked directory
mark="$(find ~/.config/bb/marks/ -mindepth 1 -printf '%P\0' | pick "Jump to: ")" &&
bb +cd:"$(readlink -f ~/.config/bb/marks/"$mark")"
[ -d "$XDG_CONFIG_HOME"/bb/marks ] || exit
mark="$(find "$XDG_CONFIG_HOME"/bb/marks/ -mindepth 1 -type l -printf '%P\0' | pick "Jump to: ")" &&
bbcmd cd:"$(readlink -f "$XDG_CONFIG_HOME"/bb/marks/"$mark")"
-,Backspace: # Go to previous directory
[ "$BBPREVPATH" ] && bb +cd:"$BBPREVPATH"
[ "$BBPREVPATH" ] && bbcmd cd:"$BBPREVPATH"
;: # Show selected files
bb +cd:'<selection>'
bbcmd cd:'<selection>'
0: # Go to intitial directory
bb +cd:"$BBINITIALPATH"
bbcmd cd:"$BBINITIALPATH"
g,Home: # Go to first file
bb +move:0
bbcmd move:0
G,End: # Go to last file
bb +move:100%n
bbcmd move:100%n
PgDn: # Page down
bb +scroll:+100%
bbcmd scroll:+100%
PgUp: # Page up
bb +scroll:-100%
bbcmd scroll:-100%
Ctrl-d: # Half page down
bb +scroll:+50%
bbcmd scroll:+50%
Ctrl-u: # Half page up
bb +scroll:-50%
bbcmd scroll:-50%
Mouse wheel down: # Scroll down
bb +scroll:+3
bbcmd scroll:+3
Mouse wheel up: # Scroll up
bb +scroll:-3
bbcmd scroll:-3
Section: File Selection
v,V,Space: # Toggle selection at cursor
bb +toggle
bbcmd toggle
Escape: # Clear selection
bb +deselect
bbcmd deselect
S: # Select pattern
ask patt "Select: " && eval bb +select: "$patt"
ask patt "Select: " && eval bbcmd select: "$patt"
U: # Unselect pattern
ask patt "Unselect: " && eval bb +deselect: "$patt"
ask patt "Unselect: " && eval bbcmd deselect: "$patt"
Ctrl-s: # Save the selection
[ $# -gt 0 ] && ask savename "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' "$@" > ~/.config/bb/"$savename"
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 ~/.config/bb/ -mindepth 1 -name '*.sel' -printf '%P\0' | pick "Load selection: ")" &&
cat ~/.config/bb/"$loadpath" | bb +deselect +select:
loadpath="$(find "$XDG_DATA_HOME"/bb/ -mindepth 1 -name '*.sel' -printf '%P\0' | pick "Load selection: ")" &&
cat "$XDG_DATA_HOME"/bb/"$loadpath" | bbcmd deselect select:
J: # Spread selection down
bb +spread:+1
bbcmd spread:+1
K: # Spread selection up
bb +spread:-1
bbcmd spread:-1
Shift-Home: # Spread the selection to the top
bb +spread:0
bbcmd spread:0
Shift-End: # Spread the selection to the bottom
bb +spread:100%n
bbcmd spread:100%n
Ctrl-a: # Select all files here
bb +select
bbcmd select
Section: File Actions
Left click: # Move cursor to file
if [ "$BBCLICKED" = "<column label>" ]; then
bb +sort:"~$BBMOUSECOL"
bbcmd sort:"~$BBMOUSECOL"
elif [ "$BBCLICKED" -a "$BBMOUSECOL" = "*" ]; then
bb +toggle:"$BBCLICKED"
bbcmd toggle:"$BBCLICKED"
elif [ "$BBCLICKED" ]; then
bb +goto:"$BBCLICKED"
bbcmd goto:"$BBCLICKED"
fi
Enter,Double left click: # Open file/directory
if [ -d "$BBCURSOR" ]; then bb +cd:"$BBCURSOR";
if [ -d "$BBCURSOR" ]; then bbcmd cd:"$BBCURSOR";
elif [ "$(uname)" = "Darwin" ]; then
if expr "$(file -bI "$BBCURSOR")" : '\(text/\|inode/empty\)' >/dev/null; then $EDITOR "$BBCURSOR";
else open "$BBCURSOR"; fi
@ -111,23 +116,23 @@ 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" && bb +deselect:"$BBCURSOR" +refresh
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 "$@" && bb +deselect +refresh
confirm && 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." &&
bb +deselect +refresh && for f; do bb +sel:"$(basename "$f")"; 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" && bb +refresh
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..." &&
for f; do if [ "./$(basename "$f")" -ef "$f" ]; then
cp -ri "$f" "$f.copy" || break;
else cp -ri "$f" . || break; fi; done; printf 'done.' && bb +refresh
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
File)
@ -140,7 +145,7 @@ Ctrl-n: # New file/directory
;;
*) exit
;;
esac && bb +goto:"$name" +refresh || pause
esac && bbcmd goto:"$name" refresh || pause
p: # Page through a file with `less`
less -XK "$BBCURSOR"
r,F2: # Rename a file
@ -149,9 +154,9 @@ r,F2: # Rename a file
[ "$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" && bb +refresh &&
while [ $# -gt 0 ]; do "$1" = "$BBCURSOR" && bb +deselect:"$BBCURSOR" +select:"$r"; shift; done &&
bb +goto:"$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
ask newname "Rename $(printf "\033[33m%s\033[39m" "$(basename "$f")"): " "$(basename "$f")" || break;
@ -160,10 +165,10 @@ R: # Rename all selected files
[ -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; }
bb +deselect:"$f" +select:"$r";
[ "$f" = "$BBCURSOR" ] && bb +goto:"$r";
bbcmd deselect:"$f" select:"$r";
[ "$f" = "$BBCURSOR" ] && bbcmd goto:"$r";
done;
bb +refresh
bbcmd refresh
Ctrl-r: # Regex rename files
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; };
@ -172,33 +177,33 @@ Ctrl-r: # Regex rename files
confirm || exit 1
if [ $# -eq 0 ]; then set -- *; ! [ -e "$1" ] && exit; fi
rename -i "$patt" "$rep" "$@"
bb +deselect +refresh
bbcmd deselect refresh
Section: Shell Commands
:: # Run a command
ask cmd ':' && sh -c "$BBSHELLFUNC$cmd" -- "$@"; bb +r; pause
ask cmd ':' && sh -c "$BBSHELLFUNC$cmd" -- "$@"; bbcmd refresh; pause
|: # Pipe selected files to a command
ask cmd '|' && printf '%s\n' "$@" | sh -c "$BBSHELLFUNC$cmd"; bb +r; pause
ask cmd '|' && printf '%s\n' "$@" | sh -c "$BBSHELLFUNC$cmd"; bbcmd refresh; pause
>: # Open a shell
tput rmcup; tput cvvis; $SHELL; bb +r
tput rmcup; tput cvvis; $SHELL; bbcmd refresh
f: # Resume suspended process
bb +fg
bbcmd fg
Section: Viewing Options
s: # Sort by...
ask1 sort "Sort (n)ame (s)ize (m)odification (c)reation (a)ccess (r)andom (p)ermissions: " &&
bb +sort:"~$sort"
bbcmd sort:"~$sort"
---,#: # Set columns
ask columns "Set columns (*)selected (a)ccessed (c)reated (m)odified (n)ame (p)ermissions (r)andom (s)ize: " &&
bb +col:"$columns"
bbcmd col:"$columns"
.: # Toggle dotfile visibility
bb +dotfiles
bbcmd dotfiles
i: # Toggle interleaving files and directories
bb +interleave
bbcmd interleave
F5: # Refresh view
bb +refresh
bbcmd refresh
Ctrl-b: # Bind a key to a script
ask1 key "Press key to bind..." && echo && ask script "Bind script: " &&
bb +bind:"$key":"{ $script; } || pause" || pause
bbcmd bind:"$key":"{ $script; } || pause" || pause
Section: User Bindings