aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2020-04-08 23:58:37 -0700
committerBruce Hill <bruce@bruce-hill.com>2020-04-08 23:58:37 -0700
commite0a30fe02bf1c75ebd211dd8f3defa6241c5b82b (patch)
treee7bc00e607ba762ade236865ab6b6dcbee421133
parentadcb0a43e02cd34a289fd66f290d87f9ac91197c (diff)
Some overhaul and refactoring. bbstartup.sh and bindings.bb have been
moved into scripts/bbstartup and scripts/bbbindkeys alongside the other scripts. Some minor bugs have been fixed as well, and a few bindings added.
-rw-r--r--Makefile3
-rw-r--r--README.md28
-rw-r--r--bb.c16
-rw-r--r--bb.h2
-rw-r--r--bindings.bb215
-rw-r--r--bterm.h1
-rwxr-xr-xscripts/bbbindkeys318
-rwxr-xr-xscripts/bbstartup6
8 files changed, 348 insertions, 241 deletions
diff --git a/Makefile b/Makefile
index 7eb39b5..04ab040 100644
--- a/Makefile
+++ b/Makefile
@@ -25,7 +25,7 @@ install: $(NAME)
[ ! "$$prefix" ] && prefix="/usr/local"; \
[ ! "$$sysconfdir" ] && sysconfdir=/etc; \
mkdir -pv -m 755 "$$prefix/share/man/man1" "$$prefix/bin" "$$sysconfdir/xdg/bb" \
- && cp -rv bindings.bb scripts/* "$$sysconfdir/xdg/bb/" \
+ && cp -rv scripts/* "$$sysconfdir/xdg/bb/" \
&& cp -v bb.1 bbcmd.1 "$$prefix/share/man/man1/" \
&& rm -f "$$prefix/bin/$(NAME)" \
&& cp -v $(NAME) "$$prefix/bin/"
@@ -42,3 +42,4 @@ uninstall:
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\n"
+.PHONY: all, clean, install, uninstall
diff --git a/README.md b/README.md
index d8de638..a00ee98 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ The core idea behind `bb` is that `bb` is a file **browser**, not a file
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
+`~/.config/bb/bbbindkeys`. For example, `D` is bound to a script that prints a
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
@@ -59,20 +59,18 @@ run the shell command `rm -rf file1 file2` and then tell `bb` to deselect all
## Customizing bb
-`bb` runs a script at startup (by default [bbstartup.sh](bbstartup.sh), installed
-to `/etc/xdg/bb/bbstartup.sh`) that sets up `bb`'s key bindings and a few other
-minor things. You can override this with your own custom startup script by creating
-a file at `~/.config/bb/bbstartup.sh`. The default startup script loads key bindings
-from (in order) `/etc/xdg/bb/bindings.bb` and `~/.config/bb/bindings.bb` (or if
-neither exists, from the local directory).
-
-`bb` comes with a bunch of pre-defined bindings for basic actions in
-[bindings.bb](bindings.bb) (installed to `/etc/xdg/bb/bindings.bb`). It's very
-easy to add new bindings for whatever custom scripts you want to run, just
-create a file called `~/.config/bb/bindings.bb` and put your bindings there.
-You can also create bindings at runtime by hitting `Ctrl-b`, pressing the key
-you want to bind, and then entering in a script to run (in case you want to set
-up an easy way to repeat some custom workflow).
+When `bb` launches, it first updates `bb`'s `$PATH` environment variable to
+include, in order, `~/.config/bb` and `/etc/xdg/bb`. Then, `bb` will run the
+command `bbstartup` (the default implementation is found at
+[scripts/bbstartup](scripts/bbstartup), along with other default `bb` commands).
+`bbstartup` will call `bbbindkeys` and may also set up configuration options like
+which columns to display and what sort order to use. All of these behaviors can
+be customized by creating custom local versions of these files in `~/.config/bb/`.
+The default versions can be found in `/etc/xdg/bb/`.
+
+You can also create temporary bindings at runtime by hitting `Ctrl-b`, pressing
+the key you want to bind, and then entering in a script to run (in case you
+want to set up an easy way to repeat some custom workflow).
### API
diff --git a/bb.c b/bb.c
index 7320f04..26cfcb0 100644
--- a/bb.c
+++ b/bb.c
@@ -534,6 +534,7 @@ void print_bindings(int fd)
*/
void run_bbcmd(bb_t *bb, const char *cmd)
{
+ while (*cmd == ' ' || *cmd == '\n') ++cmd;
if (strncmp(cmd, "bbcmd ", strlen("bbcmd ")) == 0) cmd = &cmd[strlen("bbcmd ")];
const char *value = strchr(cmd, ':');
if (value) ++value;
@@ -563,8 +564,15 @@ void run_bbcmd(bb_t *bb, const char *cmd)
for (size_t i = 0; i < sizeof(bindings)/sizeof(bindings[0]); i++) {
if (bindings[i].script && (bindings[i].key != keyval || is_section))
continue;
- binding_t binding = {keyval, memcheck(strdup(script)),
- memcheck(strdup(description))};
+ char *script2;
+ if (is_simple_bbcmd(script)) {
+ script2 = memcheck(strdup(script));
+ } else {
+ const char *prefix = "set -e\n";
+ script2 = memcheck(calloc(strlen(prefix) + strlen(script) + 1, 1));
+ sprintf(script2, "%s%s", prefix, script);
+ }
+ binding_t binding = {keyval, script2, memcheck(strdup(description))};
if (bindings[i].key == keyval) {
free(bindings[i].description);
free(bindings[i].script);
@@ -1194,9 +1202,9 @@ int main(int argc, char *argv[])
struct sigaction sa_winch = {.sa_handler = &update_term_size};
sigaction(SIGWINCH, &sa_winch, NULL);
update_term_size(0);
- // Wait 10ms at a time for terminal to initialize if necessary
+ // Wait 100us at a time for terminal to initialize if necessary
while (winsize.ws_row == 0)
- usleep(10000);
+ usleep(100);
// Set up environment variables
// Default values
diff --git a/bb.h b/bb.h
index 3e1a270..2b99f88 100644
--- a/bb.h
+++ b/bb.h
@@ -26,7 +26,7 @@
#include "bterm.h"
// Macros:
-#define BB_VERSION "0.26.0"
+#define BB_VERSION "0.27.0"
#ifndef PATH_MAX
#define PATH_MAX 4096
diff --git a/bindings.bb b/bindings.bb
deleted file mode 100644
index 3688228..0000000
--- a/bindings.bb
+++ /dev/null
@@ -1,215 +0,0 @@
-# This file defines the key bindings for bb
-# The format is: <key>(,<key>)*:[ ]*#<description>(\n[ ]+script)+
-Section: BB Commands
-?,F1: # Show Help menu
- bbcmd help
-q,Q: # Quit
- bbcmd quit
-Ctrl-c: # Send interrupt signal
- kill -INT $PPID
-Ctrl-z: # Suspend
- kill -TSTP $PPID
-Ctrl-\: # Quit and generate core dump
- kill -QUIT $PPID
-
-Section: File Navigation
-j,Down: # Next file
- bbcmd move:+1
-k,Up: # Previous file
- bbcmd move:-1
-h,Left: # Parent directory
- bbcmd cd:..
-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' | bbpick "Find: "
- )" && bbcmd goto:"$file"
-/: # Pick a file
- file="$(printf "%s\0" $BBGLOB | bbpick "Pick: ")" || exit
- bbcmd goto:"$file"
-*: # Set the glob
- glob="$(bbask "Show files matching: ")" && bbcmd glob:"$glob"
-Ctrl-g: # Go to directory
- 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
- 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' | bbpick "Jump to: ")" &&
- bbcmd cd:"$(readlink -f "$XDG_CONFIG_HOME"/bb/marks/"$mark")"
--,Backspace: # Go to previous directory
- [ "$BBPREVPATH" ] && bbcmd cd:"$BBPREVPATH"
-;: # Show selected files
- bbcmd cd:'<selection>'
-0: # Go to intitial directory
- bbcmd cd:"$BBINITIALPATH"
-g,Home: # Go to first file
- bbcmd move:0
-G,End: # Go to last file
- bbcmd move:100%n
-PgDn: # Page down
- bbcmd scroll:+100%
-PgUp: # Page up
- bbcmd scroll:-100%
-Ctrl-d: # Half page down
- bbcmd scroll:+50%
-Ctrl-u: # Half page up
- bbcmd scroll:-50%
-Mouse wheel down: # Scroll down
- bbcmd scroll:+3
-Mouse wheel up: # Scroll up
- bbcmd scroll:-3
-
-Section: File Selection
-v,V,Space: # Toggle selection at cursor
- bbcmd toggle:"$BBCURSOR"
-Escape: # Clear selection
- bbcmd deselect
-S: # Select pattern
- patt="$(bbask "Select: ")" && bbcmd select: $patt
-U: # Unselect pattern
- patt="$(bbask "Unselect: ")" && bbcmd deselect: $patt
-Ctrl-s: # Save the selection
- [ $# -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 ] && ! 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
-K: # Spread selection up
- bbcmd spread:-1
-Shift-Home: # Spread the selection to the top
- bbcmd spread:0
-Shift-End: # Spread the selection to the bottom
- bbcmd spread:100%n
-Ctrl-a: # Select all files here
- bbcmd select
-
-Section: File Actions
-Left click: # Move cursor to file
- if [ "$BBCLICKED" = "<column label>" ]; then
- bbcmd sort:"~$BBMOUSECOL"
- elif [ "$BBCLICKED" -a "$BBMOUSECOL" = "*" ]; then
- bbcmd toggle:"$BBCLICKED"
- elif [ "$BBCLICKED" ]; then
- bbcmd goto:"$BBCLICKED"
- fi
-Enter,Double left click: # Open file/directory
- 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
- else
- if expr "$(file -bi "$BBCURSOR")" : '\(text/\|inode/x-empty\)' >/dev/null; then $EDITOR "$BBCURSOR"
- else xdg-open "$BBCURSOR"; fi
- fi
-e: # Edit file in $EDITOR
- $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" | 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
- 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 | bbpick "Create new: ")" in
- File)
- name="$(bbask "New File: ")" && touch -- "$name"
- ;;
- Directory)
- name="$(bbask "New Directory: ")" && mkdir -- "$name"
- ;;
- *) exit
- ;;
- esac && bbcmd goto:"$name" refresh || bbpause
-p: # Page through a file with `less`
- less -XK "$BBCURSOR"
-r,F2: # Rename files
- case "$(bbtargets "$BBCURSOR" "$@")" in
- cursor) set -- "$BBCURSOR";;
- both) set -- "$BBCURSOR" "$@";;
- esac
- for f; do
- 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 "
- && bbconfirm && { rm -rf "$r" || { bbpause; exit; }; }
- mv "$f" "$r" || { bbpause; exit; }
- bbcmd deselect:"$f" select:"$r"
- [ "$f" = "$BBCURSOR" ] && bbcmd goto:"$r"
- done
- bbcmd refresh
-~: # Regex rename files
- if ! command -v rename >/dev/null; then
- printf '\033[31;1mThe `rename` command is not installed. Please install it to use this key binding.\033[0m\n'
- bbpause; exit 1
- fi
- if [ $# -eq 0 ]; then set -- $BBGLOB; fi
- set -e
- 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
- if [ $# -eq 0 ]; then set -- *; [ -e "$1" ] || exit 1; fi
- rename -i "$patt" "$rep" "$@"
- bbcmd deselect refresh
-
-Section: Shell Commands
-:: # Run a command
- cmd="$(bbask ':')" && sh -c "$cmd" -- "$@"; bbcmd refresh; bbpause
-|: # Pipe selected files to a command
- 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
- bbcmd fg
-
-Section: Viewing Options
-s: # Sort by...
- sort="$(bbask -1 "Sort (n)ame (s)ize (m)odification (c)reation (a)ccess (r)andom (p)ermissions: ")" &&
- bbcmd sort:"~$sort"
----,#: # Set columns
- 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
- bbcmd glob:"*"
- else
- bbcmd glob:".* *"
- fi
-i: # Toggle interleaving files and directories
- bbcmd interleave
-F5,Ctrl-l: # Refresh view
- bbcmd refresh
-Ctrl-b: # Bind a key to a script
- key="$(bbask -1 "Press key to bind...")" && echo && script="$(bbask "Bind script: ")" &&
- bbcmd bind:"$key":"{ $script; } || bbpause" || bbpause
-
-Section: User Bindings
diff --git a/bterm.h b/bterm.h
index 1ef7d38..79183a1 100644
--- a/bterm.h
+++ b/bterm.h
@@ -118,6 +118,7 @@ static keyname_t key_names[] = {
{KEY_CTRL_2, "Ctrl-2"}, {KEY_CTRL_3, "Ctrl-3"}, {KEY_CTRL_4, "Ctrl-4"},
{KEY_CTRL_5, "Ctrl-5"}, {KEY_CTRL_6, "Ctrl-6"}, {KEY_CTRL_7, "Ctrl-7"},
{KEY_CTRL_5, "Ctrl-8"}, {KEY_CTRL_6, "Ctrl-9"},
+ {':', "Colon"},
};
int bgetkey(FILE *in, int *mouse_x, int *mouse_y);
diff --git a/scripts/bbbindkeys b/scripts/bbbindkeys
new file mode 100755
index 0000000..8ffadc0
--- /dev/null
+++ b/scripts/bbbindkeys
@@ -0,0 +1,318 @@
+#!/bin/sh
+# This file defines the key bindings for bb. It works by a very hacky script on
+# the line below that prints out the rest of this file, converted into `bbcmd
+# bind:` commands. Thanks to the hackiness, the code below is also valid shell
+# code, so syntax highlighting should work normally.
+#
+# The format is: ## <key>(,<key>)*:[ ]+<description>(\n script)+
+#
+# May God have mercy on my soul for creating this abomination:
+sed '1,'$LINENO'd; /^$/d; s/^## \(.*\): \(.*\)/\x00bind:\1:# \2/; $a\\x00' "$0" | bbcmd; exit
+
+## Section: BB Commands
+## ?,F1: Show Help menu
+bbcmd help
+
+## q,Q: Quit
+bbcmd quit
+
+## Ctrl-c: Send interrupt signal
+kill -INT $PPID
+
+## Ctrl-z: Suspend
+kill -TSTP $PPID
+
+## Ctrl-\: Quit and generate core dump
+kill -QUIT $PPID
+
+## Section: File Navigation
+## j,Down: Next file
+bbcmd move:+1
+
+## k,Up: Previous file
+bbcmd move:-1
+
+## h,Left: Parent directory
+bbcmd cd:..
+
+## 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' | bbpick "Find: ")"
+bbcmd goto:"$file"
+
+## /: Pick a file
+file="$(printf "%s\0" $BBGLOB | bbpick "Pick: ")"
+bbcmd goto:"$file"
+
+## *: Set the glob
+glob="$(bbask "Show files matching: ")"
+bbcmd glob:"$glob"
+
+## Ctrl-g: Go to directory
+dir="$(bbask "Go to directory: ")"
+bbcmd cd:"$dir"
+
+## m: Mark this directory
+mkdir -p "$XDG_CONFIG_HOME/bb/marks"
+ln -sT "$2" "$XDG_CONFIG_HOME/bb/marks/$1" 2>/dev/null
+mark="$(bbask "Mark: ")"
+ln -s "$PWD" "$XDG_CONFIG_HOME"/bb/marks/"$mark"
+
+## ': Go to a marked directory
+[ -d "$XDG_CONFIG_HOME"/bb/marks ]
+mark="$(find "$XDG_CONFIG_HOME"/bb/marks/ -mindepth 1 -type l -printf '%P\0' | bbpick "Jump to: ")"
+mark="$(readlink -f "$XDG_CONFIG_HOME"/bb/marks/"$mark")"
+bbcmd cd:"$mark"
+
+## -,Backspace: Go to previous directory
+[ "$BBPREVPATH" ] && bbcmd cd:"$BBPREVPATH"
+
+## ;: Show selected files
+bbcmd cd:'<selection>'
+
+## 0: Go to intitial directory
+bbcmd cd:"$BBINITIALPATH"
+
+## g,Home: Go to first file
+bbcmd move:0
+
+## G,End: Go to last file
+bbcmd move:100%n
+
+## PgDn: Page down
+bbcmd scroll:+100%
+
+## PgUp: Page up
+bbcmd scroll:-100%
+
+## Ctrl-d: Half page down
+bbcmd scroll:+50%
+
+## Ctrl-u: Half page up
+bbcmd scroll:-50%
+
+## Mouse wheel down: Scroll down
+bbcmd scroll:+3
+
+## Mouse wheel up: Scroll up
+bbcmd scroll:-3
+
+
+## Section: File Selection
+## v,V,Space: Toggle selection at cursor
+bbcmd toggle:"$BBCURSOR"
+
+## Escape: Clear selection
+bbcmd deselect
+
+## S: Select pattern
+patt="$(bbask "Select: ")"
+bbcmd select: $patt
+
+## U: Unselect pattern
+patt="$(bbask "Unselect: ")"
+bbcmd deselect: $patt
+
+## Ctrl-s: Save the selection
+[ $# -gt 0 ]
+savename="$(bbask "Save selection as: ")"
+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 ]
+[ $# -gt 0 ] && bbconfirm "The current selection will be discarded. "
+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
+
+## K: Spread selection up
+bbcmd spread:-1
+
+## Shift-Home: Spread the selection to the top
+bbcmd spread:0
+
+## Shift-End: Spread the selection to the bottom
+bbcmd spread:100%n
+
+## Ctrl-a: Select all files here
+bbcmd select
+
+
+## Section: File Actions
+## Left click: Move cursor to file
+if [ "$BBCLICKED" = "<column label>" ]; then
+ bbcmd sort:"~$BBMOUSECOL"
+elif [ "$BBCLICKED" -a "$BBMOUSECOL" = "*" ]; then
+ bbcmd toggle:"$BBCLICKED"
+elif [ "$BBCLICKED" ]; then
+ bbcmd goto:"$BBCLICKED"
+fi
+
+## Enter,Double left click: Open file/directory
+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
+else
+ if expr "$(file -bi "$BBCURSOR")" : '\(text/\|inode/x-empty\)' >/dev/null; then $EDITOR "$BBCURSOR"
+ else xdg-open "$BBCURSOR"; fi
+fi
+
+## e: Edit file in $EDITOR
+$EDITOR "$BBCURSOR"
+
+## 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" | 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
+case "$(bbtargets "$BBCURSOR" "$@")" in
+ cursor) set -- "$BBCURSOR";;
+ both) set -- "$BBCURSOR" "$@";;
+esac
+[ $# -gt 0 ]
+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 | bbpick "Create new: ")" in
+ File)
+ name="$(bbask "New File: ")" && touch -- "$name"
+ ;;
+ Directory)
+ name="$(bbask "New Directory: ")" && mkdir -- "$name"
+ ;;
+ *) exit
+ ;;
+esac
+bbcmd goto:"$name" refresh
+
+## p: Page through a file with `less`
+less -XK "$BBCURSOR"
+
+## r,F2: Rename files
+case "$(bbtargets "$BBCURSOR" "$@")" in
+ cursor) set -- "$BBCURSOR";;
+ both) set -- "$BBCURSOR" "$@";;
+esac
+for f; do
+ 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 "
+ && bbconfirm && { rm -rf "$r" || { bbpause; exit; }; }
+ mv "$f" "$r" || { bbpause; exit; }
+ bbcmd deselect:"$f" select:"$r"
+ [ "$f" = "$BBCURSOR" ] && bbcmd goto:"$r"
+done
+bbcmd refresh
+
+## ~: Regex rename files
+if ! command -v rename >/dev/null; then
+ printf '\033[31;1mThe `rename` command is not installed. Please install it to use this key binding.\033[0m\n'
+ bbpause; exit 1
+fi
+if [ $# -eq 0 ]; then set -- $BBGLOB; fi
+patt="$(bbask "Replace pattern: ")"
+rep="$(bbask "Replacement: ")"
+printf "\033[1mRenaming:\n\033[33m$(rename -nv "$patt" "$rep" "$@")\033[0m" | bbunscroll | more
+bbconfirm
+rename -i "$patt" "$rep" "$@"
+bbcmd deselect refresh
+
+
+## Section: Shell Commands
+## Colon: Run a command
+cmd="$(bbask ':')"
+sh -c "$cmd" -- "$@" || true
+bbpause
+bbcmd refresh
+
+## |: Pipe selected files to a command
+cmd="$(bbask '|')"
+if [ $# -eq 0 ]; then set -- "$BBCURSOR"; fi
+printf '%s\n' "$@" | sh -c "$cmd" || true
+bbpause
+bbcmd refresh
+
+## @: Pass selected files as args to a command
+cmd="$(bbask '@')"
+if [ $# -eq 0 ]; then set -- "$BBCURSOR"; fi
+sh -c "$cmd \"\$@\"" -- "$@" || true
+bbpause
+bbcmd refresh
+
+## $: Pass the cursor as an argument to a command
+cmd="$(bbask '$')"
+sh -c "$cmd \"\$1\"" -- "$BBCURSOR" || true
+bbpause
+bbcmd refresh
+
+## >: Open a shell
+tput rmcup; tput cvvis
+$SHELL || true
+bbcmd refresh
+
+## f: Resume suspended process
+bbcmd fg
+
+
+## Section: Viewing Options
+## s: Sort by...
+sort="$(bbask -1 "Sort (n)ame (s)ize (m)odification (c)reation (a)ccess (r)andom (p)ermissions: ")"
+bbcmd sort:"~$sort"
+
+## ---,#: Set columns
+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
+ bbcmd glob:"*"
+else
+ bbcmd glob:".* *"
+fi
+
+## i: Toggle interleaving files and directories
+bbcmd interleave
+
+## F5,Ctrl-l: Refresh view
+bbcmd refresh
+
+## Ctrl-b: Bind a key to a script
+key="$(bbask -1 "Press key to bind...")"
+echo
+script="$(bbask "Bind script: ")"
+bbcmd bind:"$key":"{ $script; } || bbpause"
+
+
+## Section: User Bindings
diff --git a/scripts/bbstartup b/scripts/bbstartup
index 40de50b..21d04e3 100755
--- a/scripts/bbstartup
+++ b/scripts/bbstartup
@@ -4,11 +4,7 @@
[ ! -d "$XDG_DATA_HOME/bb" ] && mkdir -p "$XDG_DATA_HOME/bb"
# Load key bindings
-if [ "$BBPATH" ]; then
- cat "$BBPATH/bindings.bb" "$XDG_CONFIG_HOME/bb/bindings.bb"
-else
- cat "$sysconfdir/xdg/bb/bindings.bb" "$XDG_CONFIG_HOME/bb/bindings.bb"
-fi 2>/dev/null | awk '/^#/ {next} /^[^ ]/ {printf "\0bind:"} {print $0} END {printf "\0"}' >> "$BBCMD"
+bbbindkeys
if [ -e "$XDG_DATA_HOME/bb/settings.sh" ]; then
. "$XDG_DATA_HOME/bb/settings.sh"