Overhaul to use arguments instead of piping for commands
This commit is contained in:
parent
292d1953e8
commit
5f0e1bf0be
32
README.md
32
README.md
@ -6,24 +6,23 @@
|
||||
- Highly interoperable with unix pipelines
|
||||
- Highly customizable and hackable
|
||||
- Without any build dependencies other than the C standard library (no ncurses)
|
||||
- A good proof-of-concept for making a TUI without using any libraries
|
||||
- A good proof-of-concept for making a TUI from scratch
|
||||
|
||||
The core idea behind `bb` is that almost all actions are performed by piping
|
||||
selected files to external scripts, rather than hard-coding actions. Unix
|
||||
tools are very good at doing file management, the thing that `bb` adds is
|
||||
immediate visual feedback and rapid navigation.
|
||||
selected files to external scripts, rather than having the file manager itself
|
||||
try to anticipate and hard-code every possible user action. Unix tools are very
|
||||
good at doing file management, the thing that `bb` adds is immediate visual
|
||||
feedback and rapid navigation.
|
||||
|
||||
For example, normally on the command line, if you wanted to manually delete a
|
||||
handful of files, you would first get a listing of the files with `ls`, then
|
||||
type `rm` followed by typing out the names of the files (with some tab
|
||||
autocompletion). With `bb`, you can just launch `bb`, see all the files
|
||||
immediately, select the ones you want with a few keystrokes, and press `D` to
|
||||
delete them (or `d` for deleting with confirmation). The `D` key's behavior
|
||||
is defined in a single line of code in `config.h` to pipe the selected files
|
||||
to `xargs -0 rm -rf`. That's it! If you want to add a mapping to upload files to
|
||||
your server, you can just add a binding for `xargs scp user@example.com` or,
|
||||
if you have some complicated one-time task, you can just hit `|` and type in
|
||||
any arbitrary command and have the selected files piped to it.
|
||||
For example, instead of using `ls`, then `rm` and typing out file names, you can
|
||||
just open `bb`, scroll through the list of files, select the ones you want to
|
||||
delete, and hit `D`. The `D` key's behavior is defined in a single line of code
|
||||
in `config.h` as piping the selected files to `xargs -0 rm -rf`. That's it! If
|
||||
you want to add a mapping to upload files to your server, you can just add a
|
||||
binding for `xargs -0 scp user@example.com`. Want to zip files? Add a mapping for
|
||||
`xargs -0 zip "$(printf 'Zip file: ' >/dev/tty && head -n1 /dev/tty)"` or, if
|
||||
you have some complicated one-time task, you can just hit `|` and type in any
|
||||
arbitrary command and have the selected files piped to it.
|
||||
|
||||
## Zero Dependencies
|
||||
|
||||
@ -58,6 +57,9 @@ 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`.
|
||||
|
||||
If you want to get user input during a command (e.g. to get the name of a new file),
|
||||
you can use `/dev/tty` like so: `touch "$(printf 'New file: ' >/dev/tty && head -n1 /dev/tty)"`
|
||||
|
||||
## License
|
||||
|
||||
`bb` is released under the MIT license. See the `LICENSE` file for full details.
|
||||
|
70
bb.c
70
bb.c
@ -124,6 +124,49 @@ static void err(const char *msg, ...)
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
static int run_cmd_on_selection(bb_state_t *state, const char *cmd)
|
||||
{
|
||||
pid_t child;
|
||||
sig_t old_handler = signal(SIGINT, do_nothing);
|
||||
|
||||
if ((child = fork()) == 0) {
|
||||
// TODO: is there a max number of args? Should this be batched?
|
||||
char **const args = calloc(MAX(1, state->nselected) + 4, sizeof(char*));
|
||||
int i = 0;
|
||||
args[i++] = "sh";
|
||||
args[i++] = "-c";
|
||||
args[i++] = (char*)cmd;
|
||||
args[i++] = "--";
|
||||
entry_t *first = state->firstselected ? state->firstselected : state->files[state->cursor];
|
||||
for (entry_t *e = first; e; e = e->next) {
|
||||
args[i++] = e->d_name;
|
||||
}
|
||||
args[i] = NULL;
|
||||
|
||||
{ // Set environment variable to track shell nesting
|
||||
char *depthstr = getenv("BB_DEPTH");
|
||||
int depth = depthstr ? atoi(depthstr) : 0;
|
||||
char buf[64] = {0};
|
||||
snprintf(buf, sizeof(buf), "BB_DEPTH=%d", depth + 1);
|
||||
putenv(buf);
|
||||
}
|
||||
|
||||
execvp("sh", args);
|
||||
free(args);
|
||||
err("Failed to execute command: '%s'", cmd);
|
||||
_exit(0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (child == -1)
|
||||
err("Failed to fork");
|
||||
int status;
|
||||
waitpid(child, &status, 0);
|
||||
signal(SIGINT, old_handler);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static pid_t run_cmd(int *child_out, int *child_in, const char *cmd, ...)
|
||||
{
|
||||
int child_outfds[2], child_infds[2];
|
||||
@ -885,40 +928,23 @@ static void explore(char *path, int print_dir, int print_selection, char sep)
|
||||
for (int i = 0; bindings[i].key > 0; i++) {
|
||||
if (key == bindings[i].key) {
|
||||
term_move(0, height-1);
|
||||
writez(termfd, "\e[K");
|
||||
struct termios cur_tios;
|
||||
if (!(bindings[i].flags & ONSCREEN)) {
|
||||
close_term();
|
||||
} else {
|
||||
tcgetattr(termfd, &cur_tios);
|
||||
struct termios tios;
|
||||
memcpy(&tios, &orig_termios, sizeof(tios));
|
||||
tcsetattr(termfd, TCSAFLUSH, &tios);
|
||||
// Show cursor
|
||||
writez(termfd, "\e[?25h");
|
||||
tcsetattr(termfd, TCSAFLUSH, &orig_termios);
|
||||
writez(termfd, "\e[?25h"); // Show cursor
|
||||
}
|
||||
|
||||
int scriptinfd;
|
||||
pid_t child;
|
||||
sig_t old_handler = signal(SIGINT, do_nothing);
|
||||
child = run_cmd(NULL, &scriptinfd, bindings[i].command);
|
||||
if (!(bindings[i].flags & NO_FILES)) {
|
||||
char sep = (bindings[i].flags & NULL_SEP) ? '\0' : '\n';
|
||||
if (state.nselected > 0) {
|
||||
write_selection(scriptinfd, state.firstselected, sep);
|
||||
} else if (strcmp(state.files[state.cursor]->d_name, "..") != 0) {
|
||||
write(scriptinfd, state.files[state.cursor]->d_name, state.files[state.cursor]->d_namlen);
|
||||
}
|
||||
}
|
||||
close(scriptinfd);
|
||||
waitpid(child, NULL, 0);
|
||||
signal(SIGINT, old_handler);
|
||||
run_cmd_on_selection(&state, bindings[i].command);
|
||||
|
||||
if (!(bindings[i].flags & ONSCREEN)) {
|
||||
init_term();
|
||||
} else {
|
||||
tcsetattr(termfd, TCSAFLUSH, &cur_tios);
|
||||
// hide cursor
|
||||
writez(termfd, "\e[?25l");
|
||||
writez(termfd, "\e[?25l"); // Hide cursor
|
||||
}
|
||||
|
||||
if (bindings[i].flags & CLEAR_SELECTION)
|
||||
|
34
config.def.h
34
config.def.h
@ -4,13 +4,13 @@
|
||||
#include "keys.h"
|
||||
|
||||
#define PROG_FUZZY "fzf"
|
||||
#define PIPE_SELECTION_TO " printf '%s\\n' \"$@\" | "
|
||||
#define AND_PAUSE " && read -n1 -p '\n\e[2m...press any key to continue...\e[0m\e[?25l'"
|
||||
#define SCROLLOFF 5
|
||||
|
||||
#define NO_FILES (1<<0)
|
||||
#define NULL_SEP (1<<1)
|
||||
#define REFRESH (1<<2)
|
||||
#define CLEAR_SELECTION (1<<3)
|
||||
#define ONSCREEN (1<<4)
|
||||
#define REFRESH (1<<0)
|
||||
#define CLEAR_SELECTION (1<<1)
|
||||
#define ONSCREEN (1<<2)
|
||||
|
||||
struct {
|
||||
int key;
|
||||
@ -18,18 +18,18 @@ struct {
|
||||
int flags;
|
||||
} bindings[] = {
|
||||
// User-defined custom scripts go here:
|
||||
{'L', "less"},
|
||||
{'D', "xargs -0 rm -rf", CLEAR_SELECTION | REFRESH | ONSCREEN | NULL_SEP},
|
||||
{'d', "xargs -0 -I @ sh -c 'rm -rfi @ </dev/tty'", CLEAR_SELECTION | REFRESH | ONSCREEN | NULL_SEP},
|
||||
{'m', "xargs -0 -I @ mv -i @ . </dev/tty", CLEAR_SELECTION | REFRESH | ONSCREEN | NULL_SEP},
|
||||
{'c', "xargs -0 -I @ cp -i @ . </dev/tty", CLEAR_SELECTION | REFRESH | ONSCREEN | NULL_SEP},
|
||||
{'C', "xargs -0 -n1 -I @ cp @ @.copy", REFRESH | ONSCREEN | NULL_SEP},
|
||||
{'n', "touch \"`printf '\\033[33;1mNew file:\\033[0m ' >/dev/tty && head -n1 /dev/tty`\"", ONSCREEN | REFRESH | NO_FILES},
|
||||
{'N', "mkdir \"`printf '\\033[33;1mNew dir:\\033[0m ' >/dev/tty && head -n1 /dev/tty`\"", ONSCREEN | REFRESH | NO_FILES},
|
||||
{'|', "sh -c \"`printf '> ' >/dev/tty && head -n1 /dev/tty`\"", REFRESH},
|
||||
{'>', "sh -c \"`printf '> ' >/dev/tty && head -n1 /dev/tty`\"", NO_FILES | REFRESH},
|
||||
{'r', "xargs -0 -I @ -n1 sh -c 'mv \"@\" \"`printf \"\e[1mRename \e[1;33m%%s\e[0m: \" \"@\" >&2 && head -n1 </dev/tty`\"'",
|
||||
REFRESH | CLEAR_SELECTION | ONSCREEN | NULL_SEP},
|
||||
{'L', PIPE_SELECTION_TO "less"},
|
||||
{'D', "rm -rf \"$@\"", CLEAR_SELECTION | REFRESH | ONSCREEN},
|
||||
{'d', "rm -rfi \"$@\"", CLEAR_SELECTION | REFRESH | ONSCREEN},
|
||||
{'m', "mv -i \"$@\" .", CLEAR_SELECTION | REFRESH | ONSCREEN},
|
||||
{'c', "cp -i \"$@\" .", CLEAR_SELECTION | REFRESH | ONSCREEN},
|
||||
{'C', "for f; do cp \"$f\" \"$f.copy\"; done", REFRESH | ONSCREEN},
|
||||
{'n', "read -p '\e[33;1mNew file:\e[0m ' name && touch \"$name\"", ONSCREEN | REFRESH},
|
||||
{'N', "read -p '\e[33;1mNew dir:\e[0m ' name && mkdir \"$name\"", ONSCREEN | REFRESH},
|
||||
{'|', "read -p \"\e[33;1m>\e[0m \" cmd && " PIPE_SELECTION_TO "$SHELL -c \"$cmd\"" AND_PAUSE, REFRESH},
|
||||
{'>', "$SHELL", REFRESH},
|
||||
{'r', "for f; do read -p \"Rename $f: \" renamed && mv \"$f\" \"$renamed\"; done",
|
||||
REFRESH | CLEAR_SELECTION | ONSCREEN},
|
||||
|
||||
// Hard-coded behaviors (these are just placeholders for the help):
|
||||
{-1, "?\t\e[0;34mOpen help menu\e[0m"},
|
||||
|
Loading…
Reference in New Issue
Block a user