Overhaul of how binding commands works. It's now all handled through
bbstartup.sh, which loads bindings.bb and parses it to +bind:<keys>:<script> commands.
This commit is contained in:
parent
e341f51dc6
commit
7a666d5195
69
API.md
Normal file
69
API.md
Normal file
@ -0,0 +1,69 @@
|
||||
# BB's API
|
||||
In `bb`, all interaction (more or less) occurs through binding keypresses
|
||||
(and mouse events) to shell scripts. These shell scripts can perform external
|
||||
actions (like moving files around) or internal actions (like changing the
|
||||
directory `bb` is displaying). When a shell script runs, `bb` creates a
|
||||
temporary file, and scripts may write commands to this file to modify `bb`'s
|
||||
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`).
|
||||
|
||||
## Environment Variables
|
||||
For startup commands and key bindings, the following values are provided as
|
||||
environment variables:
|
||||
|
||||
- `$@` (the list of arguments): the full paths of the selected files
|
||||
- `$BBCURSOR`: the full path of the file under the cursor
|
||||
- `$BBDOTFILES`: "1" if files beginning with "." are visible in bb, otherwise ""
|
||||
- `$BB_DEPTH`: the number of `bb` instances deep (in case you want to run a
|
||||
shell and have that shell print something special in the prompt)
|
||||
- `$BBCMD`: a file to which `bb` commands can be written (used internally)
|
||||
|
||||
## BB Internal State Commands
|
||||
In order to modify bb's internal state, you can call `bb +cmd`, where "cmd"
|
||||
is one of the following commands (or a unique prefix of one):
|
||||
|
||||
- `.:[01]` Whether to show "." in each directory
|
||||
- `..:[01]` Whether to show ".." in each directory
|
||||
- `cd:<path>` Navigate to <path>
|
||||
- `columns:<columns>` Change which columns are visible, and in what order
|
||||
- `deselect:<filename>` Deselect <filename>
|
||||
- `dotfiles:[01]` Whether dotfiles are visible
|
||||
- `goto:<filename>` Move the cursor to <filename> (changing directory if needed)
|
||||
- `interleave:[01]` Whether or not directories should be interleaved with files in the display
|
||||
- `move:<num*>` Move the cursor a numeric amount
|
||||
- `quit` Quit bb
|
||||
- `refresh` Refresh the file listing
|
||||
- `scroll:<num*>` Scroll the view a numeric amount
|
||||
- `select:<filename>` Select <filename>
|
||||
- `sort:([+-]method)+` Set sorting method (+: normal, -: reverse), additional methods act as tiebreaker
|
||||
- `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> ...`).
|
||||
|
||||
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
|
||||
(ending with `%`). Scrolling and moving, `%` means percent of screen height,
|
||||
and `%n` means percent of number of files (e.g. `+50%` means half a screen
|
||||
height down, and `100%n` means the last file)
|
||||
|
||||
Internally, `bb` will write the commands (NUL terminated) to a file whose path
|
||||
is in`$BBCMD` and read the 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.
|
49
Makefile
49
Makefile
@ -22,21 +22,21 @@ ifeq (, $(PICKER))
|
||||
PICKER=$(shell sh -c "(which fzy >/dev/null 2>/dev/null && echo 'fzy') || (which fzf >/dev/null 2>/dev/null && echo 'fzf') || (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(prompt)=\"$(PICKER)\""
|
||||
PICKER_FLAG=-D"PICK=\"$(PICKER) --prompt=\\\"$$1\\\"\""
|
||||
ifeq ($(shell which $(PICKER)),$(shell which fzy 2>/dev/null || echo '<none>'))
|
||||
PICKER_FLAG=-D'PICK(prompt)="{ printf \"\\033[3A\" >/dev/tty; fzy --lines=3 --prompt=\"\033[1m" prompt "\033[0m\"; }"'
|
||||
PICKER_FLAG=-D'PICK="printf \"\\033[3A\" >/dev/tty; fzy --lines=3 --prompt=\"\033[1m$$1\033[0m\""'
|
||||
endif
|
||||
ifeq ($(shell which $(PICKER)),$(shell which fzf 2>/dev/null || echo '<none>'))
|
||||
PICKER_FLAG=-D'PICK(prompt)="{ printf \"\\033[3A\" >/dev/tty; fzf --height=4 --prompt=\"" prompt "\"; }"'
|
||||
PICKER_FLAG=-D'PICK="printf \"\\033[3A\" >/dev/tty; fzf --height=4 --prompt=\"$$1\""'
|
||||
endif
|
||||
ifeq ($(shell which $(PICKER)),$(shell which ask 2>/dev/null || echo '<none>'))
|
||||
PICKER_FLAG=-D'PICK(prompt)="ask --prompt=\"" prompt "\""'
|
||||
PICKER_FLAG=-D'PICK="/usr/bin/env ask --prompt=\"$$1\""'
|
||||
endif
|
||||
ifeq ($(shell which $(PICKER)),$(shell which pick 2>/dev/null || echo '<none>'))
|
||||
PICKER_FLAG=-D'PICK(prompt)="pick"'
|
||||
PICKER_FLAG=-D'PICK="pick"'
|
||||
endif
|
||||
ifeq ($(shell which $(PICKER)),$(shell which dmenu 2>/dev/null || echo '<none>'))
|
||||
PICKER_FLAG=-D'PICK(prompt)="dmenu -i -l 10 -p \"" prompt "\""'
|
||||
PICKER_FLAG=-D'PICK="dmenu -i -l 10 -p \"$$1\""'
|
||||
endif
|
||||
endif
|
||||
CFLAGS += $(PICKER_FLAG)
|
||||
@ -44,11 +44,11 @@ CFLAGS += $(PICKER_FLAG)
|
||||
ifneq (, $(ASKER))
|
||||
PERCENT := %
|
||||
ifeq ($(shell which $(ASKER)),$(shell which ask 2>/dev/null || echo '<none>'))
|
||||
CFLAGS += -D'ASK(var, prompt, initial)=var "=\"$$(ask --history=bb."STRINGIFY(__COUNTER__)".hist --prompt=\"" prompt "\" --query=\"" initial "\")\""'
|
||||
CFLAGS += -D'CONFIRM(action, files)=" { printf \"$(PERCENT)s\\n\" \""B(action)"\" \""files"\" | more; ask -n \"Is that okay?\"; } "'
|
||||
CFLAGS += -D'ASK="eval \"$$1=\\$$(/usr/bin/env ask --history=bb.hist --prompt=\\\"$$2\\\" --query=\\\"$$3\\\")\""'
|
||||
CFLAGS += -D'CONFIRM="/usr/bin/env ask -n \"Is that okay?\""'
|
||||
endif
|
||||
ifeq ($(shell which $(ASKER)),$(shell which dmenu 2>/dev/null || echo '<none>'))
|
||||
CFLAGS += -D'ASK(var, prompt, initial)=var "=\"$$(printf \"" initial "\" | dmenu -p \"" prompt "\")\""'
|
||||
CFLAGS += -D'ASK="eval \"$$1=\\$$(echo \"$$3\" | dmenu -p \"$$2\")\""'
|
||||
endif
|
||||
endif
|
||||
|
||||
@ -65,24 +65,27 @@ $(NAME): $(NAME).c bterm.h config.h
|
||||
|
||||
install: $(NAME)
|
||||
@prefix="$(PREFIX)"; \
|
||||
if test -z $$prefix; then \
|
||||
read -p $$'\033[1mWhere do you want to install? (default: /usr/local) \033[0m' prefix; \
|
||||
if [ ! "$$prefix" ]; then \
|
||||
printf '\033[1mWhere do you want to install? (default: /usr/local) \033[0m'; \
|
||||
read prefix; \
|
||||
fi; \
|
||||
if test -z $$prefix; then \
|
||||
prefix="/usr/local"; \
|
||||
fi; \
|
||||
mkdir -pv $$prefix/bin $$prefix/share/man/man1 \
|
||||
&& cp -v $(NAME) $$prefix/bin/ \
|
||||
&& cp -v $(NAME).1 $$prefix/share/man/man1/
|
||||
[ ! "$$prefix" ] && prefix="/usr/local"; \
|
||||
[ ! "$$sysconfdir" ] && sysconfdir=/etc; \
|
||||
mkdir -m 700 -pv "$$prefix/bin" "$$prefix/share/man/man1" "$$sysconfdir/bb" \
|
||||
&& cp -v $(NAME) "$$prefix/bin/" \
|
||||
&& cp -v $(NAME).1 "$$prefix/share/man/man1/" \
|
||||
&& cp -v bbstartup.sh bindings.bb "$$sysconfdir/bb/"
|
||||
|
||||
uninstall:
|
||||
@prefix="$(PREFIX)"; \
|
||||
if test -z $$prefix; then \
|
||||
read -p $$'\033[1mWhere do you want to uninstall from? (default: /usr/local) \033[0m' prefix; \
|
||||
fi; \
|
||||
if test -z $$prefix; then \
|
||||
prefix="/usr/local"; \
|
||||
if [ ! "$$prefix" ]; then \
|
||||
printf '\033[1mWhere do you want to uninstall from? (default: /usr/local) \033[0m'; \
|
||||
read prefix; \
|
||||
fi; \
|
||||
[ ! "$$prefix" ] && prefix="/usr/local"; \
|
||||
[ ! "$$sysconfdir" ] && sysconfdir=/etc; \
|
||||
[ ! "$$XDG_CONFIG_HOME" ] && XDG_CONFIG_HOME=~/.config; \
|
||||
echo "Deleting..."; \
|
||||
rm -rvf $$prefix/bin/$(NAME) $$prefix/share/man/man1/$(NAME).1
|
||||
rm -rvf "$$prefix/bin/$(NAME)" "$$prefix/share/man/man1/$(NAME).1" "$$sysconfdir/bb"; \
|
||||
printf '\033[1mIf you created any config files in $$XDG_CONFIG_HOME/bb, you may want to delete them manually.\033[0m'
|
||||
|
||||
|
@ -16,6 +16,10 @@ No dependencies besides `make` and a C compiler, just:
|
||||
make
|
||||
sudo make install
|
||||
|
||||
To run `bb`, it's expected that you have some basic unix tools:
|
||||
`basename`, `cat`, `cp`, `echo`, `find`, `grep`, `mkdir`, `more`, `mv`,
|
||||
`printf`, `read`, `rm`, `sed`, `sh`, `sleep`, `touch`, `tput`, `tr`, `wait`.
|
||||
|
||||
## Usage
|
||||
|
||||
Run `bb` to launch the file browser. `bb` also has the flags:
|
||||
@ -25,6 +29,8 @@ Run `bb` to launch the file browser. `bb` also has the flags:
|
||||
- `-s`: when `bb` exits successfully, print the files that were selected.
|
||||
- `-0`: use NULL-terminated strings instead of newline-separated strings with
|
||||
the `-s` flag.
|
||||
- `-h`: print usage
|
||||
- `-v`: print version
|
||||
|
||||
Within `bb`, press `?` for a full list of available key bindings. In short:
|
||||
`h`/`j`/`k`/`l` or arrow keys for navigation, `q` to quit, `<space>` to toggle
|
||||
|
218
bb.c
218
bb.c
@ -24,7 +24,7 @@
|
||||
#include "config.h"
|
||||
#include "bterm.h"
|
||||
|
||||
#define BB_VERSION "0.15.2"
|
||||
#define BB_VERSION "0.16.0"
|
||||
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX 4096
|
||||
@ -134,6 +134,8 @@ static void init_term(void);
|
||||
static entry_t* load_entry(bb_t *bb, const char *path, int clear_dots);
|
||||
static void* memcheck(void *p);
|
||||
static void normalize_path(const char *root, const char *path, char *pbuf, int clear_dots);
|
||||
static int is_simple_bbcmd(const char *s);
|
||||
static char *trim(char *s);
|
||||
static void populate_files(bb_t *bb, int samedir);
|
||||
static void print_bindings(int fd);
|
||||
static bb_result_t process_cmd(bb_t *bb, const char *cmd);
|
||||
@ -149,7 +151,6 @@ static void update_term_size(int sig);
|
||||
|
||||
// Config options
|
||||
extern binding_t bindings[];
|
||||
extern const char *startupcmds[];
|
||||
extern const column_t columns[128];
|
||||
|
||||
// Constants
|
||||
@ -162,13 +163,75 @@ static const char *bbcmdfn = "bb() {\n"
|
||||
" for arg; do\n"
|
||||
" shift;\n"
|
||||
" if echo \"$arg\" | grep \"^+[^:]*:$\" >/dev/null 2>/dev/null; then\n"
|
||||
" if test $# -gt 0; then printf \"$arg%s\\0\" \"$@\" >> $BBCMD\n"
|
||||
" if test $# -gt 0; then printf \"%s\\0\" \"$arg\" \"$@\" >> $BBCMD\n"
|
||||
" else sed \"s/\\([^\\x00]\\+\\)/$arg\\1/g\" >> $BBCMD; fi\n"
|
||||
" return\n"
|
||||
" fi\n"
|
||||
" printf \"$arg\\0\" >> $BBCMD\n"
|
||||
" printf \"%s\\0\" \"$arg\" >> $BBCMD\n"
|
||||
" done\n"
|
||||
"}\n";
|
||||
"}\n"
|
||||
"ask() {\n"
|
||||
#ifdef ASK
|
||||
ASK ";\n"
|
||||
#else
|
||||
" printf \"\033[1m%s\033[0m\" \"$2\" >/dev/tty;\n"
|
||||
" read $1 </dev/tty >/dev/tty\n"
|
||||
#endif
|
||||
"}\n"
|
||||
"ask1() {\n"
|
||||
#ifdef ASK1
|
||||
ASK1 ";\n"
|
||||
#else
|
||||
" printf \"\033[?25l\" >/dev/tty;\n"
|
||||
" printf \"\033[1m%s\033[0m\" \"$2\" >/dev/tty;\n"
|
||||
" stty -icanon -echo >/dev/tty;\n"
|
||||
" eval \"$1=\\$(dd bs=1 count=1 2>/dev/null </dev/tty)\";\n"
|
||||
" stty icanon echo >/dev/tty;\n"
|
||||
" printf \"\033[?25h\" >/dev/tty;\n"
|
||||
#endif
|
||||
"}\n"
|
||||
"confirm() {\n"
|
||||
#ifdef CONFIRM
|
||||
CONFIRM ";\n"
|
||||
#else
|
||||
" ask1 REPLY \"\033[1mIs that okay? [y/N] \" && [ \"$REPLY\" = 'y' ];\n"
|
||||
#endif
|
||||
"}\n"
|
||||
"pause() {\n"
|
||||
#ifdef PAUSE
|
||||
PAUSE ";\n"
|
||||
#else
|
||||
" ask1 REPLY \"\033[2mPress any key to continue...\033[0m\";"
|
||||
#endif
|
||||
"}\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"
|
||||
"spin() {\n"
|
||||
#ifdef SPIN
|
||||
SPIN ";\n"
|
||||
#else
|
||||
" eval \"$@\" &\n"
|
||||
" pid=$!;\n"
|
||||
" spinner='-\\|/';\n"
|
||||
" sleep 0.01;\n"
|
||||
" while kill -0 $pid 2>/dev/null; do\n"
|
||||
" printf '%c\\033[D' \"$spinner\" >/dev/tty;\n"
|
||||
" spinner=\"$(echo $spinner | sed 's/\\(.\\)\\(.*\\)/\\2\\1/')\";\n"
|
||||
" sleep 0.1;\n"
|
||||
" done;\n"
|
||||
" wait $pid;\n"
|
||||
#endif
|
||||
"}\n"
|
||||
#ifdef SH
|
||||
"alias sh=" SH";\n"
|
||||
#endif
|
||||
;
|
||||
|
||||
// Global variables
|
||||
static struct termios orig_termios, bb_termios;
|
||||
@ -299,6 +362,10 @@ void* memcheck(void *p)
|
||||
*/
|
||||
int run_script(bb_t *bb, const char *cmd)
|
||||
{
|
||||
char *fullcmd = calloc(strlen(cmd) + strlen(bbcmdfn) + 1, sizeof(char));
|
||||
strcpy(fullcmd, bbcmdfn);
|
||||
strcat(fullcmd, cmd);
|
||||
|
||||
pid_t child;
|
||||
void (*old_handler)(int) = signal(SIGINT, SIG_IGN);
|
||||
if ((child = fork()) == 0) {
|
||||
@ -309,9 +376,6 @@ int run_script(bb_t *bb, const char *cmd)
|
||||
size_t i = 0;
|
||||
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++] = "--"; // ensure files like "-i" are not interpreted as flags for sh
|
||||
for (entry_t *e = bb->firstselected; e; e = e->selected.next) {
|
||||
@ -874,6 +938,38 @@ void normalize_path(const char *root, const char *path, char *normalized, int cl
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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").
|
||||
*/
|
||||
static int is_simple_bbcmd(const char *s)
|
||||
{
|
||||
if (!s) return 0;
|
||||
while (*s == ' ') ++s;
|
||||
if (s[0] != '+' && strncmp(s, "bb +", 4) != 0)
|
||||
return 0;
|
||||
const char *special = ";$&<>|\n*?\\\"'";
|
||||
for (const char *p = special; *p; ++p) {
|
||||
if (strchr(s, *p))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Trim trailing whitespace by inserting '\0' and return a pointer to after the
|
||||
* first non-whitespace char
|
||||
*/
|
||||
static char *trim(char *s)
|
||||
{
|
||||
if (!s) return NULL;
|
||||
while (*s == ' ' || *s == '\n') ++s;
|
||||
char *end;
|
||||
for (end = &s[strlen(s)-1]; end >= s && (*end == ' ' || *end == '\n'); end--)
|
||||
*end = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
int cd_to(bb_t *bb, const char *path)
|
||||
{
|
||||
char pbuf[PATH_MAX], prev[PATH_MAX] = {0};
|
||||
@ -986,10 +1082,11 @@ void populate_files(bb_t *bb, int samedir)
|
||||
*/
|
||||
bb_result_t process_cmd(bb_t *bb, const char *cmd)
|
||||
{
|
||||
char *value = strchr(cmd, ':');
|
||||
if (cmd[0] == '+') ++cmd;
|
||||
else if (strncmp(cmd, "bb +", 4) == 0) cmd = &cmd[4];
|
||||
const char *value = strchr(cmd, ':');
|
||||
if (value) ++value;
|
||||
#define set_bool(target) do { if (!value) { target = !target; } else { target = value[0] == '1'; } } while (0)
|
||||
if (cmd[0] == '+') ++cmd;
|
||||
switch (cmd[0]) {
|
||||
case '.': { // +..:, +.:
|
||||
if (cmd[1] == '.') // +..:
|
||||
@ -999,6 +1096,44 @@ bb_result_t process_cmd(bb_t *bb, const char *cmd)
|
||||
populate_files(bb, 1);
|
||||
return BB_OK;
|
||||
}
|
||||
case 'b': { // +bind:<keys>:<script>
|
||||
if (!value || !value[0])
|
||||
return BB_INVALID;
|
||||
char *value_copy = memcheck(strdup(value));
|
||||
char *keys = trim(value_copy);
|
||||
if (!keys[0]) { free(value_copy); return BB_OK; }
|
||||
char *script = strchr(keys+1, ':');
|
||||
if (!script) { free(value_copy); return BB_INVALID; }
|
||||
*script = '\0';
|
||||
script = trim(script + 1);
|
||||
char *description;
|
||||
if (script[0] == '#') {
|
||||
description = trim(strsep(&script, "\n") + 1);
|
||||
if (!script) script = "";
|
||||
else script = trim(script);
|
||||
} else description = script;
|
||||
for (char *key; (key = strsep(&keys, ",")); ) {
|
||||
int is_section = strcmp(key, "Section") == 0;
|
||||
int keyval = strlen(key) == 1 ? key[0] : bkeywithname(key);
|
||||
if (keyval == -1 && !is_section) continue;
|
||||
for (int i = 0; i < sizeof(bindings)/sizeof(bindings[0]); i++) {
|
||||
if (bindings[i].key && (bindings[i].key != keyval || is_section))
|
||||
continue;
|
||||
binding_t binding = {keyval, memcheck(strdup(script)),
|
||||
memcheck(strdup(description))};
|
||||
if (bindings[i].key == keyval) {
|
||||
free(bindings[i].description);
|
||||
free(bindings[i].script);
|
||||
for (; i + 1 < sizeof(bindings)/sizeof(bindings[0]) && bindings[i+1].key; i++)
|
||||
bindings[i] = bindings[i+1];
|
||||
}
|
||||
bindings[i] = binding;
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(value_copy);
|
||||
return BB_OK;
|
||||
}
|
||||
case 'c': { // +cd:, +columns:
|
||||
switch (cmd[1]) {
|
||||
case 'd': { // +cd:
|
||||
@ -1097,7 +1232,7 @@ bb_result_t process_cmd(bb_t *bb, const char *cmd)
|
||||
if (!bb->nfiles) return BB_INVALID;
|
||||
oldcur = bb->cursor;
|
||||
isdelta = value[0] == '-' || value[0] == '+';
|
||||
n = (int)strtol(value, &value, 10);
|
||||
n = (int)strtol(value, (char**)&value, 10);
|
||||
if (*value == '%')
|
||||
n = (n * (value[1] == 'n' ? bb->nfiles : termheight)) / 100;
|
||||
if (isdelta) set_cursor(bb, bb->cursor + n);
|
||||
@ -1121,7 +1256,7 @@ bb_result_t process_cmd(bb_t *bb, const char *cmd)
|
||||
if (!value) return BB_INVALID;
|
||||
// TODO: figure out the best version of this
|
||||
int isdelta = value[0] == '+' || value[0] == '-';
|
||||
int n = (int)strtol(value, &value, 10);
|
||||
int n = (int)strtol(value, (char**)&value, 10);
|
||||
if (*value == '%')
|
||||
n = (n * (value[1] == 'n' ? bb->nfiles : termheight)) / 100;
|
||||
if (isdelta)
|
||||
@ -1172,15 +1307,17 @@ void bb_browse(bb_t *bb, const char *path)
|
||||
bb->scroll = 0;
|
||||
bb->cursor = 0;
|
||||
|
||||
for (int i = 0; startupcmds[i]; i++) {
|
||||
if (startupcmds[i][0] == '+')
|
||||
process_cmd(bb, startupcmds[i] + 1);
|
||||
else
|
||||
run_script(bb, startupcmds[i]);
|
||||
if (bb->should_quit)
|
||||
goto quit;
|
||||
}
|
||||
|
||||
const char *runstartup =
|
||||
"[ ! \"$XDG_CONFIG_HOME\" ] && XDG_CONFIG_HOME=~/.config;\n"
|
||||
"[ ! \"$sysconfdir\" ] && sysconfdir=/etc;\n"
|
||||
"if [ -e \"$XDG_CONFIG_HOME/bb/bbstartup.sh\" ]; then\n"
|
||||
" . \"$XDG_CONFIG_HOME/bb/bbstartup.sh\";\n"
|
||||
"elif [ -e \"$sysconfdir/xdg/bb/bbstartup.sh\" ]; then\n"
|
||||
" . \"$sysconfdir/xdg/bb/bbstartup.sh\";\n"
|
||||
"elif [ -e \"./bbstartup.sh\" ]; then\n"
|
||||
" . \"./bbstartup.sh\";\n"
|
||||
"fi\n";
|
||||
run_script(bb, runstartup);
|
||||
init_term();
|
||||
goto force_check_cmds;
|
||||
|
||||
@ -1290,20 +1427,10 @@ void bb_browse(bb_t *bb, const char *path)
|
||||
// Search user-defined key bindings from config.h:
|
||||
binding_t *binding;
|
||||
user_bindings:
|
||||
for (int i = 0; bindings[i].keys[0] >= 0; i++) {
|
||||
for (int j = 0; bindings[i].keys[j]; j++) {
|
||||
if (key == bindings[i].keys[j]) {
|
||||
// Move to front optimization:
|
||||
if (i > 2) {
|
||||
binding_t tmp;
|
||||
tmp = bindings[0];
|
||||
bindings[0] = bindings[i];
|
||||
bindings[i] = tmp;
|
||||
i = 0;
|
||||
}
|
||||
binding = &bindings[i];
|
||||
goto run_binding;
|
||||
}
|
||||
for (int i = 0; bindings[i].key != 0 && i < sizeof(bindings)/sizeof(bindings[0]); i++) {
|
||||
if (key == bindings[i].key) {
|
||||
binding = &bindings[i];
|
||||
goto run_binding;
|
||||
}
|
||||
}
|
||||
// Nothing matched
|
||||
@ -1312,8 +1439,8 @@ void bb_browse(bb_t *bb, const char *path)
|
||||
run_binding:
|
||||
if (cmdpos != 0)
|
||||
err("Command file still open");
|
||||
if (binding->script[0] == '+') {
|
||||
process_cmd(bb, binding->script + 1);
|
||||
if (is_simple_bbcmd(binding->script)) {
|
||||
process_cmd(bb, binding->script);
|
||||
} else {
|
||||
move_cursor(tty_out, 0, termheight-1);
|
||||
fputs(T_ON(T_SHOW_CURSOR), tty_out);
|
||||
@ -1341,17 +1468,19 @@ void bb_browse(bb_t *bb, const char *path)
|
||||
void print_bindings(int fd)
|
||||
{
|
||||
char buf[1000], buf2[1024];
|
||||
for (int i = 0; bindings[i].keys[0] >= 0; i++) {
|
||||
if (bindings[i].keys[0] == 0) {
|
||||
for (int i = 0; bindings[i].key != 0 && i < sizeof(bindings)/sizeof(bindings[0]); i++) {
|
||||
if (bindings[i].key == -1) {
|
||||
const char *label = bindings[i].description;
|
||||
sprintf(buf, "\n\033[33;1;4m\033[%dG%s\033[0m\n", (termwidth-(int)strlen(label))/2, label);
|
||||
write(fd, buf, strlen(buf));
|
||||
continue;
|
||||
}
|
||||
int to_skip = -1;
|
||||
char *p = buf;
|
||||
for (int j = 0; bindings[i].keys[j]; j++) {
|
||||
if (j > 0) p = stpcpy(p, ", ");
|
||||
int key = bindings[i].keys[j];
|
||||
for (int j = i; bindings[j].key && strcmp(bindings[j].description, bindings[i].description) == 0; j++) {
|
||||
if (j > i) p = stpcpy(p, ", ");
|
||||
++to_skip;
|
||||
int key = bindings[j].key;
|
||||
const char *name = bkeyname(key);
|
||||
if (name)
|
||||
p = stpcpy(p, name);
|
||||
@ -1363,10 +1492,11 @@ void print_bindings(int fd)
|
||||
*p = '\0';
|
||||
sprintf(buf2, "\033[1m\033[%dG%s\033[0m", termwidth/2 - 1 - (int)strlen(buf), buf);
|
||||
write(fd, buf2, strlen(buf2));
|
||||
sprintf(buf2, "\033[0m\033[%dG\033[34m%s\033[0m", termwidth/2 + 1,
|
||||
bindings[i].description ? bindings[i].description : bindings[i].script);
|
||||
sprintf(buf2, "\033[1m\033[%dG\033[34m%s\033[0m", termwidth/2 + 1,
|
||||
bindings[i].description);
|
||||
write(fd, buf2, strlen(buf2));
|
||||
write(fd, "\033[0m\n", strlen("\033[0m\n"));
|
||||
i += to_skip;
|
||||
}
|
||||
write(fd, "\n", 1);
|
||||
}
|
||||
|
31
bbstartup.sh
Executable file
31
bbstartup.sh
Executable file
@ -0,0 +1,31 @@
|
||||
#!/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 "$XDG_CONFIG_HOME/bb/marks/$1" "$2" 2>/dev/null
|
||||
}
|
||||
mark home ~
|
||||
mark root /
|
||||
mark config "$XDG_CONFIG_HOME"
|
||||
mark marks "$XDG_CONFIG_HOME/bb/marks"
|
||||
|
||||
# Set viewing options
|
||||
bb +sort:+n
|
||||
bb +col:'*smpn'
|
||||
|
||||
# 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" ]; then
|
||||
cat "$XDG_CONFIG_HOME/bb/bindings.bb"
|
||||
elif [ -e "$sysconfdir/xdg/bb/bindings.bb" ]; then
|
||||
cat "$sysconfdir/xdg/bb/bindings.bb"
|
||||
elif [ -e bindings.bb ]; then
|
||||
cat bindings.bb
|
||||
fi | sed -e '/^#/d' -e "s/^[^ ]/$(printf '\034')+bind:\\0/" | tr '\034' '\0' >> "$BBCMD"
|
165
bindings.bb
Normal file
165
bindings.bb
Normal file
@ -0,0 +1,165 @@
|
||||
# This file defines the key bindings for bb
|
||||
# The format is: <key>(,<key>)*:[ ]*#<description>(\n[ ]+script)+
|
||||
Section: BB Commands
|
||||
?,F1: # Show Help menu
|
||||
bb +help
|
||||
q,Q: # Quit
|
||||
bb +quit
|
||||
|
||||
Section: File Navigation
|
||||
j,Down: # Next file
|
||||
bb +move:+1
|
||||
k,Up: # Previous file
|
||||
bb +move:-1
|
||||
h,Left: # Parent directory
|
||||
bb +cd:..
|
||||
l,Right: # Enter directory
|
||||
[ -d "$BBCURSOR" ] && bb +cd:"$BBCURSOR"
|
||||
Ctrl-f: # Search for file
|
||||
bb +goto:"$(
|
||||
if [ $BBDOTFILES ]; then
|
||||
find -mindepth 1;
|
||||
else find -mindepth 1 ! -path '*/.*';
|
||||
fi | pick "Find: "
|
||||
)"
|
||||
/: # Pick a file
|
||||
bb +goto:"$(
|
||||
if [ $BBDOTFILES ]; then find -mindepth 1 -maxdepth 1;
|
||||
else find -mindepth 1 -maxdepth 1 ! -path '*/.*'; fi | pick "Pick: "
|
||||
)"
|
||||
Ctrl-g: # Go to directory
|
||||
ask goto "Go to directory: " && bb +cd:"$goto"
|
||||
m: # Mark this directory
|
||||
ask mark "Mark: " && ln -s "$PWD" ~/.config/bb/marks/"$mark"
|
||||
': # Go to a marked directory
|
||||
mark="$(ls ~/.config/bb/marks | pick "Jump to: ")" &&
|
||||
bb +cd:"$(readlink -f ~/.config/bb/marks/"$mark")"
|
||||
-,Backspace: # Go to previous directory
|
||||
[ $BBPREVPATH ] && bb +cd:"$BBPREVPATH"
|
||||
;: # Show selected files
|
||||
bb +cd:'<selection>'
|
||||
0: # Go to intitial directory
|
||||
bb +cd:"$BBINITIALPATH"
|
||||
g,Home: # Go to first file
|
||||
bb +move:0
|
||||
G,End: # Go to last file
|
||||
bb +move:100%n
|
||||
PgDn: # Page down
|
||||
bb +scroll:+100%
|
||||
PgUp: # Page up
|
||||
bb +scroll:-100%
|
||||
Ctrl-d: # Half page down
|
||||
bb +scroll:+50%
|
||||
Ctrl-u: # Half page up
|
||||
bb +scroll:-50%
|
||||
Mouse wheel down: # Scroll down
|
||||
bb +scroll:+3
|
||||
Mouse wheel up: # Scroll up
|
||||
bb +scroll:-3
|
||||
|
||||
Section: File Selection
|
||||
v,V,Space: # Toggle selection at cursor
|
||||
bb +toggle
|
||||
Escape: # Clear selection
|
||||
bb +deselect: "$@"
|
||||
Ctrl-s: # Save the selection
|
||||
[ $# -gt 0 ] && ask savename "Save selection as: " && printf '%s\0' "$@" > ~/.config/bb/"$savename"
|
||||
Ctrl-o: # Open a saved selection
|
||||
loadpath="$(find ~/.config/bb -maxdepth 1 -type f | pick "Load selection: ")" &&
|
||||
[ -e "$loadpath" ] && bb +deselect: "$@" &&
|
||||
while IFS= read -r -d $'\0'; do bb +select:"$REPLY"; done < "$loadpath"
|
||||
J: # Spread selection down
|
||||
bb +spread:+1
|
||||
K: # Spread selection up
|
||||
bb +spread:-1
|
||||
Ctrl-a: # Select all files here
|
||||
if [ $BBDOTFILES ]; then find -mindepth 1 -maxdepth 1 -print0;
|
||||
else find -mindepth 1 -maxdepth 1 ! -path '*/.*' -print0; fi | bb +sel:
|
||||
|
||||
Section: File Actions
|
||||
Enter: # Open file/directory
|
||||
if [ -d "$BBCURSOR ]; then bb +cd:"$BBCURSOR";
|
||||
elif file -bi "$BBCURSOR" | grep -q '^\(text/\|inode/empty\)'; then $EDITOR "$BBCURSOR";
|
||||
else open "$BBCURSOR"; fi
|
||||
e: # Edit file in $EDITOR
|
||||
$EDITOR "$BBCURSOR" || pause
|
||||
d: # Delete a file
|
||||
printf "\033[1mDeleting \033[33m$BBCURSOR\033[0;1m...\033[0m " && confirm &&
|
||||
rm -rf "$BBCURSOR" && bb +refresh && bb +deselect:"$BBCURSOR"
|
||||
D: # Delete all selected files
|
||||
[ $# -gt 0 ] && printf "\033[1mDeleting the following:\n \033[33m$(printf ' %s\n' "$@")\033[0m" | more &&
|
||||
confirm && rm -rf "$@" && bb +refresh && bb +deselect: "$@"
|
||||
Ctrl-v: # Move files here
|
||||
printf "\033[1mMoving the following to here:\n \033[33m$(printf ' %s\n' "$@")\033[0m" | more &&
|
||||
confirm && spin mv -i "$@" . && bb +refresh && bb +deselect "$@" &&
|
||||
for f; do bb +sel:"$(basename "$f")"; done ||
|
||||
pause
|
||||
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
|
||||
C: # Copy all selected files here
|
||||
[ $# -gt 0 ] && printf "\033[1mCopying the following to here:\n \033[33m$(printf ' %s\n' "$@")\033[0m" | more &&
|
||||
confirm &&
|
||||
for f; do if [ "./$(basename "$f")" -ef "$f" ]; then
|
||||
spin cp -ri "$f" "$f.copy";
|
||||
else spin cp -ri "$f" .; fi; done; bb +refresh
|
||||
Ctrl-n: # New file/directory
|
||||
case "$(printf '%s\n' File Directory | pick "Create new: ")" in
|
||||
File)
|
||||
ask name "New File: " || exit
|
||||
touch "$name"
|
||||
;;
|
||||
Directory)
|
||||
ask name "New Directory: " || exit
|
||||
mkdir "$name"
|
||||
;;
|
||||
*) exit
|
||||
;;
|
||||
esac && bb +goto:"$name" +refresh || pause
|
||||
p: # Page through a file with $PAGER
|
||||
$PAGER "$BBCURSOR"
|
||||
|: # Pipe selected files to a command
|
||||
ask cmd '|' && printf '%s\n' "$@" | sh -c "$BBSHELLFUNC$cmd"; bb +r; pause
|
||||
:: # Run a command
|
||||
ask cmd ':' && sh -c "$BBSHELLFUNC$cmd" -- "$@"; bb +r; pause
|
||||
>: # Open a shell
|
||||
tput rmcup >/dev/tty; $SHELL; bb +r
|
||||
r,F2: # Rename a file
|
||||
ask newname "Rename \033[33m$(basename "$BBCURSOR")\033[39m: " "$(basename "$BBCURSOR")" || exit
|
||||
r="$(dirname "$BBCURSOR")/$newname" || exit
|
||||
[ "$r" != "$BBCURSOR" ] && mv -i "$BBCURSOR" "$r" && bb +refresh &&
|
||||
while [ $# -gt 0 ]; do "$1" = "$BBCURSOR" && bb +deselect:"$BBCURSOR" +select:"$r"; shift; done &&
|
||||
bb +goto:"$r"
|
||||
R: # Rename all selected files
|
||||
bb +refresh;
|
||||
for f; do
|
||||
ask newname "Rename \033[33m$(basename "$f")\033[39m: " "$(basename "$f")" || break;
|
||||
r="$(dirname "$f")/$newname";
|
||||
[ "$r" != "$f" ] && mv -i "$f" "$r" && bb "+deselect:$f" "+select:$r";
|
||||
[ "$f" = "$BBCURSOR" ] && bb +goto:"$r";
|
||||
done
|
||||
Ctrl-r: # Regex rename files
|
||||
command -v rename >/dev/null || { echo 'The `rename` command is not installed. Please install it to use this key binding.'; 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" | more &&
|
||||
confirm &&
|
||||
if [ $# -gt 0 ]; then rename -i "$patt" "$rep" "$@"; else rename -i "$patt" "$rep" *; fi;
|
||||
bb +deselect: "$@";
|
||||
bb +refresh
|
||||
|
||||
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" +refresh
|
||||
#: # Set columns
|
||||
ask columns "Set columns (*)selected (a)ccessed (c)reated (m)odified (n)ame (p)ermissions (r)andom (s)ize: " &&
|
||||
bb +col:"$columns"
|
||||
.: # Toggle dotfile visibility
|
||||
bb +dotfiles
|
||||
i: # Toggle interleaving files and directories
|
||||
bb +interleave
|
||||
F5: # Refresh view
|
||||
bb +refresh
|
||||
Ctrl-b: # Bind a key to a script
|
||||
ask1 key "Press key to bind..." && echo && ask script "Bind script: " && bb +bind:"$(printf "$key:$script")"
|
||||
|
||||
Section: User Bindings
|
247
config.def.h
247
config.def.h
@ -7,66 +7,15 @@
|
||||
This file contains:
|
||||
- Global options, like which colors are used
|
||||
- Column formatting (width and title)
|
||||
- Startup commands
|
||||
- User key bindings
|
||||
|
||||
For startup commands and key bindings, the following values are provided as
|
||||
environment variables:
|
||||
|
||||
$@ (the list of arguments): the full paths of the selected files
|
||||
$BBCURSOR: the full path of the file under the cursor
|
||||
$BBDOTFILES: "1" if files beginning with "." are visible in bb, otherwise ""
|
||||
$BB_DEPTH: the number of `bb` instances deep (in case you want to run a
|
||||
shell and have that shell print something special in the prompt)
|
||||
$BBCMD: a file to which `bb` commands can be written (used internally)
|
||||
|
||||
In order to modify bb's internal state, you can call `bb +cmd`, where "cmd"
|
||||
is one of the following commands (or a unique prefix of one):
|
||||
|
||||
.:[01] Whether to show "." in each directory
|
||||
..:[01] Whether to show ".." in each directory
|
||||
cd:<path> Navigate to <path>
|
||||
columns:<columns> Change which columns are visible, and in what order
|
||||
deselect:<filename> Deselect <filename>
|
||||
dotfiles:[01] Whether dotfiles are visible
|
||||
goto:<filename> Move the cursor to <filename> (changing directory if needed)
|
||||
interleave:[01] Whether or not directories should be interleaved with files in the display
|
||||
move:<num*> Move the cursor a numeric amount
|
||||
quit Quit bb
|
||||
refresh Refresh the file listing
|
||||
scroll:<num*> Scroll the view a numeric amount
|
||||
select:<filename> Select <filename>
|
||||
sort:([+-]method)+ Set sorting method (+: normal, -: reverse), additional methods act as tiebreaker
|
||||
spread:<num*> Spread the selection state at the cursor
|
||||
toggle:<filename> Toggle the selection status of <filename>
|
||||
|
||||
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 (ending with '%'). Scrolling and moving, '%' means percent of
|
||||
screen height, and '%n' means percent of number of files (e.g. +50% means
|
||||
half a screen height down, and 100%n means the last file)
|
||||
|
||||
Internally, bb will write the commands (NUL terminated) to $BBCMD, if
|
||||
$BBCMD is set, and read the file when file browsing 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.
|
||||
|
||||
As a shorthand and performance optimization, commands that don't rely on any
|
||||
shell variables or scripting can be written as "+move:+1" instead of "bb '+move:+1'",
|
||||
which is a bit faster because internally it avoids writing to and reading from
|
||||
the $BBCMD file.
|
||||
|
||||
*/
|
||||
#include "bterm.h"
|
||||
|
||||
// Constants:
|
||||
#define MAX_REBINDINGS 8
|
||||
|
||||
// Types:
|
||||
typedef struct {
|
||||
int keys[MAX_REBINDINGS+1];
|
||||
const char *script;
|
||||
const char *description;
|
||||
int key;
|
||||
char *script;
|
||||
char *description;
|
||||
} binding_t;
|
||||
|
||||
typedef struct {
|
||||
@ -93,58 +42,9 @@ typedef struct {
|
||||
#define SH "sh"
|
||||
#endif
|
||||
|
||||
// Used for STRINGIFY(__COUNTER__) to embed the line number as a string
|
||||
// (as in "ask --history=bb."STRINGIFY(__COUNTER__)")
|
||||
#define STRINGIFY2(x) #x
|
||||
#define STRINGIFY(x) STRINGIFY2(x)
|
||||
|
||||
// Some handy macros for common shell script behaviors:
|
||||
// Bold text:
|
||||
#define B(s) "\033[1m" s "\033[22m"
|
||||
|
||||
// Macro for getting user input:
|
||||
#ifndef ASK
|
||||
#define ASK(var, prompt, initial) " read -p \"" B(prompt) "\" " var " </dev/tty >/dev/tty "
|
||||
#endif
|
||||
|
||||
#ifndef ASK1
|
||||
#define ASK1(var, prompt) " { printf \""prompt"\"; stty -icanon -echo; "var"=$(dd bs=1 count=1 2> /dev/null); stty icanon echo; } "
|
||||
#endif
|
||||
|
||||
#define PAUSE ASK1("REPLY", "\033[2mPress any key to continue...\033[0m\033[?25l")
|
||||
|
||||
// Macro for picking from a list of options:
|
||||
#ifndef PICK
|
||||
#define PICK(prompt) " { "ASK("query", prompt)" && awk '{print length, $1}' | sort -n | cut -d' ' -f2- | "\
|
||||
"grep -i -m1 \"$(echo \"$query\" | sed 's;.;[^/&]*[&];g')\"; } "
|
||||
#endif
|
||||
|
||||
// Display a spinning indicator if command takes longer than 10ms:
|
||||
#ifndef SPIN
|
||||
#define SPIN(cmd) "{ { " cmd "; } & " \
|
||||
"pid=$!; "\
|
||||
"spinner='-\\|/'; "\
|
||||
"sleep 0.01; "\
|
||||
"while kill -0 $pid 2>/dev/null; do "\
|
||||
" printf '%c\\033[D' \"$spinner\" >/dev/tty; "\
|
||||
" spinner=\"$(echo $spinner | sed 's/\\(.\\)\\(.*\\)/\\2\\1/')\"; "\
|
||||
" sleep 0.1; "\
|
||||
"done; "\
|
||||
"wait $pid; }"
|
||||
#endif
|
||||
|
||||
#ifndef CONFIRM
|
||||
#define CONFIRM(action, files) " { printf '%s\\n' \""B(action)"\" \""files"\" | more; " \
|
||||
ASK1("REPLY", B("Is that okay? [y/N] "))"; [ \"$REPLY\" = 'y' ]; } "
|
||||
#endif
|
||||
|
||||
#define SECTION(name) {{0}, NULL, name}
|
||||
|
||||
|
||||
// These commands will run at startup (before command-line arguments)
|
||||
extern const char *startupcmds[];
|
||||
extern const column_t columns[128];
|
||||
extern binding_t bindings[];
|
||||
extern binding_t bindings[1024];
|
||||
|
||||
// Column widths and titles:
|
||||
const column_t columns[128] = {
|
||||
@ -158,20 +58,6 @@ const column_t columns[128] = {
|
||||
['s'] = {9, " Size"},
|
||||
};
|
||||
|
||||
// This is a list of commands that runs when `bb` launches:
|
||||
const char *startupcmds[] = {
|
||||
// Set some default marks:
|
||||
"mkdir -p ~/.config/bb/marks",
|
||||
"ln -sT ~/.config/bb/marks ~/.config/bb/marks/marks 2>/dev/null",
|
||||
"ln -sT ~ ~/.config/bb/marks/home 2>/dev/null",
|
||||
"ln -sT / ~/.config/bb/marks/root 2>/dev/null",
|
||||
"ln -sT ~/.config ~/.config/bb/marks/config 2>/dev/null",
|
||||
"ln -sT ~/.local ~/.config/bb/marks/local 2>/dev/null",
|
||||
// Default column and sorting options:
|
||||
"+sort:+n", "+col:*smpn",
|
||||
NULL, // NULL-terminated array
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
* These are all the key bindings for bb.
|
||||
* The format is: {{keys,...}, "<script>", "<description>"}
|
||||
@ -183,129 +69,6 @@ const char *startupcmds[] = {
|
||||
* `vim -c 'set t_ti= t_te=' "$@"` to prevent momentarily seeing the shell
|
||||
* after editing.
|
||||
*****************************************************************************/
|
||||
binding_t bindings[] = {
|
||||
SECTION("Key Bindings"),
|
||||
{{'?', KEY_F1}, "+help", B("Help")" menu"},
|
||||
{{'q', 'Q'}, "+quit", B("Quit")},
|
||||
|
||||
SECTION("Navigation"),
|
||||
{{'j', KEY_ARROW_DOWN}, "+move:+1", B("Next")" file"},
|
||||
{{'k', KEY_ARROW_UP}, "+move:-1", B("Previous")" file"},
|
||||
{{'h', KEY_ARROW_LEFT}, "+cd:..", B("Parent")" directory"},
|
||||
{{'l', KEY_ARROW_RIGHT}, "[ -d \"$BBCURSOR\" ] && bb \"+cd:$BBCURSOR\"", B("Enter")" a directory"},
|
||||
{{KEY_CTRL_F}, "bb \"+goto:$(if [ $BBDOTFILES ]; then find -mindepth 1; else find -mindepth 1 ! -path '*/.*'; fi "
|
||||
"| "PICK("Find: ")")\"", B("Search")" for file"},
|
||||
{{'/'}, "bb \"+goto:$(if [ $BBDOTFILES ]; then find -mindepth 1 -maxdepth 1; else find -mindepth 1 -maxdepth 1 ! -path '*/.*'; fi "
|
||||
"| "PICK("Pick: ")")\"", B("Pick")" file"},
|
||||
{{KEY_CTRL_G}, ASK("goto", "Go to directory: ", "")" && bb +cd:\"$goto\"", B("Go to")" directory"},
|
||||
{{'m'}, ASK("mark", "Mark: ", "")" && ln -s \"$PWD\" ~/.config/bb/marks/\"$mark\"", B("Mark")" this directory"},
|
||||
{{'\''}, "mark=\"$(ls ~/.config/bb/marks | " PICK("Jump to: ") ")\" "
|
||||
"&& bb +cd:\"$(readlink -f ~/.config/bb/marks/\"$mark\")\"",
|
||||
"Go to a "B("marked")" directory"},
|
||||
{{'-', KEY_BACKSPACE, KEY_BACKSPACE2},
|
||||
"[ $BBPREVPATH ] && bb +cd:\"$BBPREVPATH\"", "Go to "B("previous")" directory"},
|
||||
{{';'}, "bb +cd:'<selection>'", "Show "B("selected files")},
|
||||
{{'0'}, "bb +cd:\"$BBINITIALPATH\"", "Go to "B("initial directory")},
|
||||
{{'g', KEY_HOME}, "+move:0", "Go to "B("first")" file"},
|
||||
{{'G', KEY_END}, "+move:100%n", "Go to "B("last")" file"},
|
||||
{{KEY_PGDN}, "+scroll:+100%", B("Page down")},
|
||||
{{KEY_PGUP}, "+scroll:-100%", B("Page up")},
|
||||
{{KEY_CTRL_D}, "+scroll:+50%", B("Half page down")},
|
||||
{{KEY_CTRL_U}, "+scroll:-50%", B("Half page up")},
|
||||
{{KEY_MOUSE_WHEEL_DOWN}, "+scroll:+3", B("Scroll down")},
|
||||
{{KEY_MOUSE_WHEEL_UP}, "+scroll:-3", B("Scroll up")},
|
||||
|
||||
SECTION("File Selection"),
|
||||
{{' ','v','V'}, "+toggle", B("Toggle")" selection at cursor"},
|
||||
{{KEY_ESC}, "bb +deselect: \"$@\"", B("Clear")" selection"},
|
||||
{{KEY_CTRL_S}, "[ $# -gt 0 ] && "ASK("savename", "Save selection as: ", "") " && printf '%s\\0' \"$@\" > ~/.config/bb/\"$savename\"",
|
||||
B("Save")" the selection"},
|
||||
{{KEY_CTRL_O}, "loadpath=\"$(find ~/.config/bb -maxdepth 1 -type f | " PICK("Load selection: ") ")\" "
|
||||
"&& [ -e \"$loadpath\" ] && bb +deselect:'*' "
|
||||
"&& while IFS= read -r -d $'\\0'; do bb +select:\"$REPLY\"; done < \"$loadpath\"",
|
||||
B("Open")" a saved selection"},
|
||||
{{'J'}, "+spread:+1", B("Spread")" selection down"},
|
||||
{{'K'}, "+spread:-1", B("Spread")" selection up"},
|
||||
{{KEY_CTRL_A},
|
||||
"if [ $BBDOTFILES ]; then find -mindepth 1 -maxdepth 1 -print0; "
|
||||
"else find -mindepth 1 -maxdepth 1 ! -path '*/.*' -print0; fi | bb +sel:",
|
||||
B("Select all")" files here"},
|
||||
|
||||
SECTION("Actions"),
|
||||
{{'\r', KEY_MOUSE_DOUBLE_LEFT},
|
||||
"if [ -d \"$BBCURSOR\" ]; then bb \"+cd:$BBCURSOR\"; "
|
||||
#ifdef __APPLE__
|
||||
"elif file -bI \"$BBCURSOR\" | grep -q '^\\(text/\\|inode/empty\\)'; then $EDITOR \"$BBCURSOR\"; "
|
||||
"else open \"$BBCURSOR\"; fi",
|
||||
#else
|
||||
"elif file -bi \"$BBCURSOR\" | grep -q '^\\(text/\\|inode/empty\\)'; then $EDITOR \"$BBCURSOR\"; "
|
||||
"else xdg-open \"$BBCURSOR\"; fi",
|
||||
#endif
|
||||
B("Open")" file/directory"},
|
||||
{{'e'}, "$EDITOR \"$BBCURSOR\" || "PAUSE, B("Edit")" file in $EDITOR"},
|
||||
{{'E'}, "[ $# -gt 0 ] && $EDITOR \"$@\" || "PAUSE, B("Edit")" selected files in $EDITOR"},
|
||||
{{'d'}, CONFIRM("The following will be deleted:", "$BBCURSOR") " && rm -rf \"$BBCURSOR\" && bb +refresh && bb +deselect: \"$BBCURSOR\"",
|
||||
B("Delete")" a file"},
|
||||
{{'D', KEY_DELETE},
|
||||
"[ $# -gt 0 ] && "CONFIRM("The following will be deleted:", "$@") " && rm -rf \"$@\" && bb +refresh && bb +deselect: \"$@\"",
|
||||
B("Delete")" selected files"},
|
||||
{{KEY_CTRL_V}, "[ $# -gt 0 ] && "
|
||||
CONFIRM("The following will be moved here:", "$@") " && { "
|
||||
SPIN("mv -i \"$@\" . && bb +refresh && bb +deselect: \"$@\" && for f; do bb \"+sel:$(basename \"$f\")\"; done")" || "PAUSE"; }",
|
||||
B("Move")" files here"},
|
||||
{{'c'}, CONFIRM("Copy file:", "$BBCURSOR")" && cp -ri \"$BBCURSOR\" \"$BBCURSOR.copy\" && bb +refresh",
|
||||
B("Copy")" a file"},
|
||||
{{'C'}, "[ $# -gt 0 ] && "CONFIRM("The following will be copied here:", "$@")
|
||||
" && for f; do if [ \"./$(basename \"$f\")\" -ef \"$f\" ]; then "
|
||||
SPIN("cp -ri \"$f\" \"$(basename \"$f\").copy\"")"; "
|
||||
"else "SPIN("cp -ri \"$f\" .")"; fi; done; bb +refresh",
|
||||
B("Copy")" the selected files here"},
|
||||
{{KEY_CTRL_N}, "type=\"$(printf '%s\\n' File Directory | "PICK("Create new: ")")\" "
|
||||
"&& "ASK("name", "New $type: ", "")" && "
|
||||
"{ if [ $type = File ]; then touch \"$name\"; else mkdir \"$name\"; fi "
|
||||
"&& bb \"+goto:$name\" +r || "PAUSE"; }", B("New")" file/directory"},
|
||||
{{'p'}, "$PAGER \"$BBCURSOR\"", B("Page")" through a file in $PAGER"},
|
||||
{{'P'}, "[ $# -gt 0 ] && $PAGER \"$@\"", B("Page")" through selected files in $PAGER"},
|
||||
{{'|'}, ASK("cmd", "|", "") " && printf '%s\\n' \"$@\" | "SH" -c \"$BBSHELLFUNC$cmd\"; " PAUSE "; bb +r",
|
||||
B("Pipe")" selected files to a command"},
|
||||
{{':'}, ASK("cmd", ":", "")" && "SH" -c \"$BBSHELLFUNC$cmd\" -- \"$@\"; " PAUSE "; bb +refresh",
|
||||
B("Run")" a command"},
|
||||
{{'>'}, "tput rmcup >/dev/tty; $SHELL; bb +r", "Open a "B("shell")},
|
||||
{{'r', KEY_F2},
|
||||
ASK("newname", "Rename \033[33m$(basename \"$BBCURSOR\")\033[39m: ", "$(basename \"$BBCURSOR\")")" && "
|
||||
"r=\"$(dirname \"$BBCURSOR\")/$newname\" && "
|
||||
"[ \"$r\" != \"$BBCURSOR\" ] && mv -i \"$BBCURSOR\" \"$r\" && bb +refresh && "
|
||||
"while [ $# -gt 0 ]; do [ \"$1\" = \"$BBCURSOR\" ] && bb \"+deselect:$BBCURSOR\" \"+select:$r\"; shift; done && "
|
||||
"bb +goto:\"$r\"",
|
||||
B("Rename")" a file"},
|
||||
{{'R'},
|
||||
"bb +refresh; "
|
||||
"for f; do "
|
||||
" "ASK("newname", "Rename \033[33m$(basename \"$f\")\033[39m: ", "$(basename \"$f\")")" || break; "
|
||||
" r=\"$(dirname \"$f\")/$newname\"; "
|
||||
" [ \"$r\" != \"$f\" ] && mv -i \"$f\" \"$r\" && bb \"+deselect:$f\" \"+select:$r\"; "
|
||||
" [ \"$f\" = \"$BBCURSOR\" ] && bb +goto:\"$r\"; "
|
||||
"done", B("Rename")" selected files"},
|
||||
{{KEY_CTRL_R},
|
||||
"command -v rename >/dev/null || { echo 'The `rename` command is not installed. Please install it to use this key binding.'; "PAUSE"; exit; }; "
|
||||
ASK("patt", "Replace pattern: ", "")" && "ASK("rep", "Replacement: ", "")" && "
|
||||
CONFIRM("Renaming:", "$(if [ $# -gt 0 ]; then rename -nv \"$patt\" \"$rep\" \"$@\"; else rename -nv \"$patt\" \"$rep\" *; fi)")" && "
|
||||
"if [ $# -gt 0 ]; then rename -i \"$patt\" \"$rep\" \"$@\"; else rename -i \"$patt\" \"$rep\" *; fi; bb +refresh",
|
||||
B("Regex rename")" files"},
|
||||
|
||||
SECTION("File Display"),
|
||||
{{'s'},
|
||||
ASK1("sort", B("Sort (n)ame (s)ize (m)odification (c)reation (a)ccess (r)andom (p)ermissions: "))
|
||||
" && bb +sort:\"~$sort\" +refresh",
|
||||
B("Sort")" by..."},
|
||||
{{'#'}, ASK("columns", "Set columns (*)selected (a)ccessed (c)reated (m)odified (n)ame (p)ermissions (r)andom (s)ize: ", "")
|
||||
" && bb +col:\"$columns\"",
|
||||
"Set "B("columns")},
|
||||
{{'.'}, "+dotfiles", "Toggle "B("dotfile")" visibility"},
|
||||
{{'i'}, "+interleave", "Toggle "B("interleaving")" files and directories"},
|
||||
{{KEY_F5}, "+refresh", B("Refresh")},
|
||||
|
||||
{{-1}} // Array must be -1-terminated
|
||||
};
|
||||
binding_t bindings[1024];
|
||||
|
||||
// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
|
||||
|
Loading…
Reference in New Issue
Block a user