From 792a39500ce926751f663eb54d2b58e3ed6fd427 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 10 Jun 2019 22:26:13 -0700 Subject: [PATCH] Simplified the make flags for fuzzy finding and asking. Cleaned up the readme a bit. --- Makefile | 17 +++++---- README.md | 99 +++++++++++++++++++++++----------------------------- config.def.h | 23 +++++------- 3 files changed, 60 insertions(+), 79 deletions(-) diff --git a/Makefile b/Makefile index 324207c..f4390c9 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,6 @@ CC=gcc CFLAGS=-O0 -std=gnu99 -D_XOPEN_SOURCE=500 -D_GNU_SOURCE -D_POSIX_C_SOURCE=200809L CWARN= -Wall -Wpedantic -Wno-unknown-pragmas -fsanitize=address -fno-omit-frame-pointer G=-g -PICKER= ifeq ($(shell uname),Darwin) CFLAGS += -D_DARWIN_C_SOURCE @@ -12,20 +11,20 @@ CWARN += -Weverything -Wno-missing-field-initializers -Wno-padded\ -Wno-missing-noreturn -Wno-cast-qual endif -ifneq (, $(shell which ask)) -ifeq (, $(ASKECHO)$(ASK)) -CFLAGS += -D'ASKECHO(prompt,initial)="ask --prompt=\"" prompt "\" --query=\"" initial "\""' +ifeq ($(PICKER),fzy) +CFLAGS += -D'PICK(prompt, initial)=" { printf \"\\033[3A\" >/dev/tty; fzy --lines=3 --prompt=\"" prompt "\" --query=\"" initial "\"; } "' endif -ifeq (, $(PICKER)) -PICKER=ask +ifeq ($(PICKER),fzf) +CFLAGS += -D'PICK(prompt, initial)=" { printf \"\\033[3A\" >/dev/tty; fzf --height=4 --prompt=\"" prompt "\" --query=\"" initial "\"; } "' endif +ifeq ($(PICKER),ask) +CFLAGS += -D'PICK(prompt, initial)=" ask --prompt=\"" prompt "\" --query=\"" initial "\" "' endif -ifneq (, $(PICKER)) -CFLAGS += -D'PICK(prompt, initial)="$(PICKER) --prompt=\"" prompt "\" --query=\"" initial "\""' +ifneq (, $(USE_ASK)) +CFLAGS += -D'USE_ASK=1' endif - all: $(NAME) clean: diff --git a/README.md b/README.md index b961f2a..b11926f 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,47 @@ create a new file, `N` to create a new directory, `:` to run a command with the selected files in `$@`, and `|` to pipe files to a command. Pressing `Ctrl-c` will cause `bb` to exit with a failure status and without printing anything. -## Using bb to Change Directory +# bb's Philosophy +The core idea behind `bb` is that `bb` is a file **browser**, not a file +**manager**, which means `bb` uses shell scripts to perform all modifications +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.h (the user's +version of [the defaults in config.def.h](config.def.h)). For example, `D` is +bound to `rm -rf "$@"`, which means selecting `file_foo` and `dir_baz`, then +pressing `D` will cause `bb` to run the shell command `rm -rf file_foo dir_baz`. + +## Customizing bb +`bb` comes with a bunch of pre-defined bindings for basic actions in +[config.def.h](config.def.h) (within `bb`, press `?` to see descriptions of the +bindings), but it's very easy to add new bindings for whatever custom scripts +you want to run, just add a new entry in `bindings` in config.h with the form +`{{keys...}, "", ""}` The description is shown when +pressing `?` within `bb`. + +### User Input in Scripts +If you need user input in a binding, you can use the `ASK(var, prompt, +initial)` and `ASKECHO(prompt, initial)` macros, which internally use the +`read` shell function (`initial` is discarded) or the `ask` tool, if `USE_ASK` +is set to 1. This is used in a few key bindings by default, including `n` and +`:`. + +### Fuzzy Finding +To select from a list of options with a fuzzy finder in a binding, you can use +the `PICK` macro. During the `make` process, you can use `PICKER=fzy`, +`PICKER=fzf`, or `PICKER=ask` to choose which fuzzy finder program `bb` will +use (and provide some default arguments). This is used in the `/` and `Ctrl-f` +key bindings by default. + +### API +`bb` also exposes an API that allows shell scripts to modify `bb`'s internal +state. To do this, call `bb +` from within `bb`. For example, by +default, `j` is bound to `bb '+move:+1'`, which has the effect of moving `bb`'s +cursor down one item. More details about the API can be found in [the config +file](config.def.h). + +## FAQ +### Using bb to Change Directory Applications cannot change the shell's working directory on their own, but you can define a shell function that uses the shell's builtin `cd` function on the output of `bb -d` (print directory on exit). For bash (sh, zsh, etc.), you can @@ -47,7 +87,7 @@ For [fish](https://fishshell.com/) (v3.0.0+), you can put this in your In both versions, `|| pwd` means the directory will not change if `bb` exits with failure (e.g. by pressing `Ctrl-c`). -## Launching bb with a Keyboard Shortcut +### Launching bb with a Keyboard Shortcut Using a keyboard shortcut to launch `bb` from the shell is something that is handled by your shell. Here are some examples for binding `Ctrl-b` to launch `bb` and change directory to `bb`'s directory (using the `bcd` function defined @@ -59,58 +99,5 @@ For fish, put this in your `~/.config/fish/functions/fish_user_key_bindings.fish bind \cB 'bcd; commandline -f repaint' -# bb's Philosophy -The core idea behind `bb` is that `bb` is a file **browser**, not a file -**manager**, which means `bb` uses shell scripts to perform all modifications -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.h (the user's -version of [the defaults in config.def.h](config.def.h)). For example, `D` is -bound to `rm -rf "$@"`, which means selecting `file_foo` and `dir_baz`, then -pressing `D` will cause `bb` to run the shell command `rm -rf file_foo dir_baz`. - -`bb` comes with a bunch of pre-defined bindings for basic actions in -[config.def.h](config.def.h) (within `bb`, press `?` to see descriptions of the -bindings), but it's very easy to add new bindings for whatever custom scripts -you want to run, just add a new entry in `bindings` in config.h with the form -`{{keys...}, "", ""}` The description is shown when -pressing `?` within `bb`. - -## User Input in Scripts -If you need user input in a script, you can just use the `read` shell function -like so: `read -p "Archive: " name && zip "$name" "$@"` However, `read` doesn't -support a lot of functionality (e.g. using the arrow keys), so I would recommnd -using [ask](https://bitbucket.org/spilt/ask) instead. If you have `ask` -isntalled, making `bb` will automatically detect it and the default key -bindings will use it instead of `read`. - -## 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 -essentially all they do is write ANSI escape sequences to the terminal. `bb` -does all of that by itself, just using basic calls to `write()`, with no -external libraries beyond the C standard library. Since `bb` only has to -support the terminal functionality that it uses itself, `bb`'s entire source -code is less than half the size of the source code for an extremely compact -library like termbox, and less than *half a percent* of the size of the source -code for ncurses. I hope anyone checking out this project can see it as a great -example of how you can build a full TUI without ncurses or any external -libraries as long as you're willing to hand-write a few escape sequences. - - -## Hacking - -If you want to customize `bb`, you can add or change the key bindings by -editing `config.h` and recompiling. In [suckless](https://suckless.org/) style, -customizing means editing source code, and compilation is extremely fast. -Key character constants are in `keys.h` and the rest of the code is in `bb.c`. - -## License - +# License `bb` is released under the MIT license. See the `LICENSE` file for full details. diff --git a/config.def.h b/config.def.h index 77fdd26..cef6d19 100644 --- a/config.def.h +++ b/config.def.h @@ -100,23 +100,18 @@ typedef struct { #define B(s) "\033[1m" s "\033[22m" // Macros for getting user input: -#ifndef ASK -#ifdef ASKECHO +#ifdef USE_ASK +#define ASKECHO(prompt, initial) "ask --prompt=\"" prompt "\" --query=\"" initial "\"" #define ASK(var, prompt, initial) var "=\"$(" ASKECHO(prompt, initial) ")\"" #else -#define ASK(var, prompt, initial) "read -p \"" prompt "\" " var " /dev/tty" -#endif +#define ASK(var, prompt, initial) "read -ep \"" prompt "\" " var " /dev/tty" +#define ASKECHO(prompt, initial) "read -ep \"" prompt "\" /dev/tty && echo \"$REPLY\"" #endif -#ifndef ASKECHO -#define ASKECHO(prompt, initial) ASK("REPLY", prompt, initial) " && echo \"$REPLY\"" -#endif - -// Get user input to choose an option (piped in). If you want to use -// a fuzzy finder like fzy or fzf, then this should be something like: -// "fzy --prompt=\"" prompt "\" --query=\"" initial "\"" +// Macros for picking from a list of options: #ifndef PICK -#define PICK(prompt, initial) "grep -i -m1 \"^$(" ASKECHO(prompt, initial) " | sed 's/./[^&]*[&]/g')\"" +#define PICK(prompt, initial) " { printf '\\033[A' >/dev/tty; awk '{print length, $1}' | sort -n | cut -d' ' -f2- | "\ + "grep -i -m1 \"^$(" ASKECHO(prompt, initial) " | sed 's/./[^&]*[&]/g')\"; } " #endif // Display a spinning indicator if command takes longer than 10ms: @@ -194,7 +189,7 @@ binding_t bindings[] = { {{KEY_ESC}, "+deselect:*", B("Clear")" selection"}, {{'e'}, "$EDITOR \"$@\" || "PAUSE, B("Edit")" file in $EDITOR"}, {{KEY_CTRL_F}, "bb \"+g:`find | "PICK("Find: ", "")"`\"", B("Search")" for file"}, - {{'/'}, "bb \"+g:`ls -pa | "PICK("Pick: ", "")"`\"", B("Pick")" file"}, + {{'/'}, "bb \"+g:`ls -A | "PICK("Pick: ", "")"`\"", B("Pick")" file"}, {{'d', KEY_DELETE}, "rm -rfi \"$@\" && bb '+deselect:*' +r ||" PAUSE, B("Delete")" files"}, {{'D'}, SPIN("rm -rf \"$@\"")" && bb '+deselect:*' +r ||" PAUSE, B("Delete")" files (without confirmation)"}, {{'M'}, SPIN("mv -i \"$@\" .")" && bb '+deselect:*' +r && for f; do bb \"+sel:`pwd`/`basename \"$f\"`\"; done || "PAUSE, @@ -245,7 +240,7 @@ binding_t bindings[] = { "&& bb \"+sort:+$sort\" +refresh"), B("Sort")" by..."}, {{'#'}, "bb \"+col:`"ASKECHO("Set columns: ", "")"`\"", "Set "B("columns")}, - {{'.'}, "bb +dotfiles", "Toggle "B("dotfiles")}, + {{'.'}, "+dotfiles", "Toggle "B("dotfiles")}, {{'g', KEY_HOME}, "+move:0", "Go to "B("first")" file"}, {{'G', KEY_END}, "+move:100%n", "Go to "B("last")" file"}, {{KEY_F5, KEY_CTRL_R}, "+refresh", B("Refresh")},