aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rw-r--r--bb.c40
-rw-r--r--config.def.h82
3 files changed, 101 insertions, 31 deletions
diff --git a/README.md b/README.md
index a0ea761..cc67312 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
`bb` (bitty browser) is a TUI console file browser that is:
-- Extremely lightweight (currently around 1.4K lines of code)
+- Extremely lightweight (under 2k lines of code)
- Highly interoperable with unix pipelines
- Highly customizable and hackable
- Without any build dependencies other than the C standard library (no ncurses)
@@ -25,6 +25,12 @@ some complicated one-time task, you can just hit `>` to drop to a shell and run
commands with the selected files available in `$@` (or use `|` to run a quick
one-liner command that gets the selected files piped as input).
+## API
+`bb` also exposes an API so that programs can modify `bb`'s internal state.
+For example, by default, `f` is bound to `bb "+goto:$(fzf)"`, which has the
+effect of moving `bb`'s cursor to whatever `fzf` (a fuzzy finder) prints out.
+More details about the API can be found in [the config file](config.def.h).
+
## Zero Dependencies
There's a lot of TUI libraries out there like ncurses and termbox, but
@@ -47,7 +53,7 @@ libraries as long as you're willing to hand-write a few escape sequences.
Just run `bb` to launch the file browser. 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 selection, `d` to delete, `c` to copy, `m` to
+`q` to quit, `<space>` to toggle selection, `d` to delete, `c` to copy, `M` to
move, `r` to rename, `n` to create a new file, `N` to create a new directory,
and `|` to pipe files to a command.
diff --git a/bb.c b/bb.c
index b0bf125..dba11fa 100644
--- a/bb.c
+++ b/bb.c
@@ -537,8 +537,11 @@ int compare_files(void *r, const void *v1, const void *v2)
if ('0' <= c1 && c1 <= '9' && '0' <= c2 && c2 <= '9') {
long n1 = strtol(p1, (char**)&p1, 10);
long n2 = strtol(p2, (char**)&p2, 10);
- diff = ((p1 - f1->d_name) - (p2 - f2->d_name)) || (n1 - n2);
- if (diff) return diff*sign;
+ diff = (int)(p1 - f1->d_name) - (int)(p2 - f2->d_name);
+ if (diff != 0)
+ return diff*sign;
+ if (n1 != n2)
+ return (n1 > n2 ? 1 : -1)*sign;
} else if (diff) {
return diff*sign;
} else {
@@ -682,16 +685,17 @@ void populate_files(bb_state_t *s, const char *path)
free(s->files);
s->files = NULL;
}
+ int old_cursor = s->cursor;
+ int old_scroll = s->scroll;
s->nfiles = 0;
s->cursor = 0;
if (path == NULL)
return;
- if (strcmp(path, s->path) != 0) {
- s->scroll = 0;
+ int samedir = strcmp(path, s->path) == 0;
+ if (!samedir)
strcpy(s->path, path);
- }
// Hash inode -> entry_t with linear probing
int nselected = 0;
@@ -776,15 +780,22 @@ void populate_files(bb_state_t *s, const char *path)
free(selecthash);
if (s->nfiles == 0) err("No files found (not even '..')");
- if (old_inode) {
- for (int i = 0; i < s->nfiles; i++) {
- if (s->files[i]->d_ino == old_inode) {
- set_cursor(s, i);
- break;
+ sort_files(s);
+ if (samedir) {
+ if (old_inode) {
+ for (int i = 0; i < s->nfiles; i++) {
+ if (s->files[i]->d_ino == old_inode) {
+ set_scroll(s, old_scroll);
+ set_cursor(s, i);
+ return;
+ }
}
}
+ set_cursor(s, old_cursor);
+ set_scroll(s, old_scroll);
+ } else {
+ set_cursor(s, 0);
}
- sort_files(s);
}
void sort_files(bb_state_t *state)
@@ -825,9 +836,10 @@ execute_cmd(bb_state_t *state, const char *cmd)
char *value = strchr(cmd, ':');
if (value) ++value;
switch (cmd[0]) {
- case 'r': // refresh
+ case 'r': { // refresh
populate_files(state, state->path);
return BB_REFRESH;
+ }
case 'q': // quit
return BB_QUIT;
case 's': // sort:, select:, scroll:, spread:
@@ -1323,6 +1335,7 @@ int main(int argc, char *argv[])
FILE *cmdfile = NULL;
if (initial_path) {
+ has_initial_path:
cmdfilename = memcheck(strdup(CMDFILE_FORMAT));
if (!mktemp(cmdfilename)) err("Couldn't create tmpfile\n");
cmdfile = fopen(cmdfilename, "a");
@@ -1337,7 +1350,8 @@ int main(int argc, char *argv[])
} else if (cmd_args > 0) {
char *parent_bbcmd = getenv("BBCMD");
if (!parent_bbcmd || parent_bbcmd[0] == '\0') {
- err("Couldn't find command file\n");
+ initial_path = ".";
+ goto has_initial_path;
}
cmdfile = fopen(parent_bbcmd, "a");
if (!cmdfile) err("Couldn't open cmdfile: '%s'\n", parent_bbcmd);
diff --git a/config.def.h b/config.def.h
index 5dcd264..722c6c7 100644
--- a/config.def.h
+++ b/config.def.h
@@ -1,5 +1,54 @@
/*
- * User-defined key bindings.
+ BB Key Bindings
+
+ User-defined key bindings go in config.h, which is created by running `make`
+ (config.def.h is for keeping the defaults around, just in case)
+
+ The basic structure is:
+ <list of keys to bind>
+ <program to run>
+ <description> (for the help menu)
+ <flags> (whether to run in a full terminal window or silently, etc.)
+
+ When the scripts are run, the following values are provided as environment variables:
+
+ $@ (the list of arguments): the full paths of the selected files
+ $BBCURSOR: the (short) name of the file under the cursor
+ $BBFULLCURSOR: the full path name of the file under the cursor
+ $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):
+
+ cd:<path> Navigate to <path>
+ columns:<columns> Change which columns are visible, and in what order
+ deselect:<filename> Deselect <filename>
+ dots[:yes|:no] Toggle whether dotfiles are visible
+ goto:<filename> Move the cursor to <filename> (changing directory if needed)
+ jump:<key> Jump to the mark associated with <key>
+ mark:<key>[;<path>] Associate <key> with <path> (or current dir, if blank)
+ 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> Change the sorting method (uppercase means reverse)
+ spread:<num*> Spread the selection state at the cursor
+ toggle:<filename> Toggle the selection status of <filename>
+
+ 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.
+
+ *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)
+
*/
#include "keys.h"
@@ -18,28 +67,29 @@ typedef struct {
int flags;
} binding_t;
+// These commands will run at startup (before command-line arguments)
extern const char *startupcmds[];
const char *startupcmds[] = {
- "+mark:0",
- "+mark:~;~",
- "+mark:h;~",
- "+mark:/;/",
- "+mark:c;~/.config",
+ //////////////////////////////////////////////
+ // User-defined startup commands can go here
+ //////////////////////////////////////////////
+ // Set some default marks:
+ "+mark:0", "+mark:~;~", "+mark:h;~", "+mark:/;/", "+mark:c;~/.config",
"+mark:l;~/.local",
- "+columns:smpn",
- "+sort:n",
- NULL,
+ // Default column and sorting options:
+ "+columns:smpn", "+sort:n",
+ NULL, // NULL-terminated array
};
extern binding_t bindings[];
binding_t bindings[] = {
- ////////////////////////////////////////////////////////////////////////
- // User-defined custom scripts go here
+ //////////////////////////////////////////////////////////////////////////
+ // User-defined custom scripts can go here
// Please note that these are sh scripts, not bash scripts, so bash-isms
// won't work unless you make your script use `bash -c "<your script>"`
- ////////////////////////////////////////////////////////////////////////
+ //////////////////////////////////////////////////////////////////////////
{{'?'}, "bb -b | less -r", "Show the help menu", NORMAL_TERM},
- {{KEY_CTRL_H}, "", "Figure out what key does"},
+ {{KEY_CTRL_H}, "<placeholder>", "Figure out what key does"},
{{'q', 'Q'}, "bb +q", "Quit"},
{{'k', KEY_ARROW_UP}, "+m:-1", "Move up"},
{{'j', KEY_ARROW_DOWN}, "+m:+1", "Move down"},
@@ -70,8 +120,8 @@ else xdg-open \"$BBCURSOR\"; fi",
{{'M'}, "mv -i \"$@\" .; bb '+d:*' +r", "Move files to current folder", SHOW_CURSOR},
{{'c'}, "cp -i \"$@\" .; bb +r", "Copy files to current folder", SHOW_CURSOR},
{{'C'}, "for f; do cp \"$f\" \"$f.copy\"; done; bb +r", "Clone files"},
- {{'n'}, "read -p 'New file: ' name && touch \"$name\"; bb +r", "New file", SHOW_CURSOR},
- {{'N'}, "read -p 'New dir: ' name && mkdir \"$name\"; bb +r", "New folder", SHOW_CURSOR},
+ {{'n'}, "read -p 'New file: ' name && touch \"$name\"; bb +r \"+goto:$name\"", "New file", SHOW_CURSOR},
+ {{'N'}, "read -p 'New dir: ' name && mkdir \"$name\"; bb +r \"+goto:$name\"", "New folder", SHOW_CURSOR},
{{'|'}, "read -p '| ' cmd && " PIPE_SELECTION_TO "sh -c \"$cmd\" && " PAUSE "; bb +r",
"Pipe selected files to a command", SHOW_CURSOR},
{{':'}, "read -p ': ' cmd && sh -c \"$cmd\" -- \"$@\"; " PAUSE "; bb +r",
@@ -108,5 +158,5 @@ else xdg-open \"$BBCURSOR\"; fi",
{{KEY_CTRL_U}, "bb '+scroll:-50%'", "Half page up"},
{{KEY_MOUSE_WHEEL_DOWN}, "bb '+scroll:+3'", "Scroll down"},
{{KEY_MOUSE_WHEEL_UP}, "bb '+scroll:-3'", "Scroll up"},
- {0},
+ {0}, // Array must be 0-terminated
};