diff options
| -rw-r--r-- | README.md | 10 | ||||
| -rw-r--r-- | bb.c | 40 | ||||
| -rw-r--r-- | config.def.h | 82 |
3 files changed, 101 insertions, 31 deletions
@@ -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. @@ -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 }; |
