From da6bb9176341c322aef8d184761c77a488a4f302 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 12 Jul 2019 16:19:31 -0700 Subject: Overhaul to use a shell function for internal commands instead of `bb` itself, which lets bb work when not yet installed. --- bb.1 | 4 --- bb.c | 103 +++++++++++++++++++++++++++++++++++------------------------ config.def.h | 21 ++++++------ 3 files changed, 71 insertions(+), 57 deletions(-) diff --git a/bb.1 b/bb.1 index d1719b7..1ec42ac 100644 --- a/bb.1 +++ b/bb.1 @@ -8,7 +8,6 @@ bb \- A bitty browser for command line file management [\fI-d\fR] [\fI-s\fR] [\fI-0\fR] -[\fI-b\rR] [\fI+command... \fR] [\fIdirectory\fR] .SH DESCRIPTION @@ -27,9 +26,6 @@ newline-separated. .B \-d Print the current directory on exit. -.B \-b -Print the bb key bindings and exit. - .B \+command From within \fBbb\fR, running \fBbb +command\fR will execute an internal bb command for modifying bb's state. diff --git a/bb.c b/bb.c index 5d6f3cd..982be2e 100644 --- a/bb.c +++ b/bb.c @@ -135,7 +135,7 @@ 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 void populate_files(bb_t *bb, int samedir); -static void print_bindings(void); +static void print_bindings(int fd); static bb_result_t process_cmd(bb_t *bb, const char *cmd); static void render(bb_t *bb); static int run_script(bb_t *bb, const char *cmd); @@ -157,6 +157,19 @@ static const char *T_ENTER_BBMODE = T_OFF(T_SHOW_CURSOR ";" T_WRAP) T_ON(T_ALT_S static const char *T_LEAVE_BBMODE = T_OFF(T_MOUSE_XY ";" T_MOUSE_CELL ";" T_MOUSE_SGR ";" T_ALT_SCREEN) T_ON(T_SHOW_CURSOR ";" T_WRAP); static const char *T_LEAVE_BBMODE_PARTIAL = T_OFF(T_MOUSE_XY ";" T_MOUSE_CELL ";" T_MOUSE_SGR) T_ON(T_WRAP); +static const char *bbcmdfn = "bb() {\n" +" if $# -eq 0; then cat >> $BBCMD; return; fi\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" +" else sed \"s/\\([^\\x00]\\+\\)/$arg\\1/g\" >> $BBCMD; fi\n" +" return\n" +" fi\n" +" printf \"$arg\\0\" >> $BBCMD\n" +" done\n" +"}\n"; + // Global variables static struct termios orig_termios, bb_termios; static FILE *tty_out = NULL, *tty_in = NULL; @@ -276,7 +289,10 @@ int run_script(bb_t *bb, const char *cmd) size_t i = 0; args[i++] = "sh"; args[i++] = "-c"; - args[i++] = (char*)cmd; + 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 entry_t *first = bb->firstselected ? bb->firstselected : (bb->nfiles ? bb->files[bb->cursor] : NULL); for (entry_t *e = first; e; e = e->selected.next) { @@ -1043,6 +1059,25 @@ bb_result_t process_cmd(bb_t *bb, const char *cmd) } else try_free_entry(e); return BB_OK; } + case 'h': { // +help + close_term(); + int fds[2]; + pipe(fds); + pid_t child = fork(); + if (child != 0) { + close(fds[0]); + print_bindings(fds[1]); + waitpid(child, NULL, 0); + } else { + close(fds[1]); + dup2(fds[0], STDIN_FILENO); + char *args[] = {"sh", "-c", "$PAGER -rX", NULL}; + execvp("sh", args); + } + init_term(); + bb->dirty = 1; + return BB_OK; + } case 'i': { // +interleave set_bool(bb->interleave_dirs); sort_files(bb); @@ -1325,16 +1360,12 @@ void bb_browse(bb_t *bb, const char *path) /* * Print the current key bindings */ -void print_bindings(void) +void print_bindings(int fd) { - struct winsize sz = {0}; - ioctl(STDOUT_FILENO, TIOCGWINSZ, &sz); - int width = sz.ws_col; - if (width == 0) width = 80; - - char buf[1024]; + char buf[1024], buf2[1024]; char *kb = "Key Bindings"; - printf("\n\033[33;1;4m\033[%dG%s\033[0m\n\n", (width-(int)strlen(kb))/2, kb); + sprintf(buf, "\n\033[33;1;4m\033[%dG%s\033[0m\n\n", (termwidth-(int)strlen(kb))/2, kb); + write(fd, buf, strlen(buf)); for (int i = 0; bindings[i].keys[0] > 0; i++) { char *p = buf; for (int j = 0; bindings[i].keys[j]; j++) { @@ -1349,11 +1380,13 @@ void print_bindings(void) p += sprintf(p, "\033[31m\\x%02X", key); } *p = '\0'; - printf("\033[1m\033[%dG%s\033[0m", width/2 - 1 - (int)strlen(buf), buf); - printf("\033[0m\033[%dG\033[34m%s\033[0m", width/2 + 1, bindings[i].description); - printf("\033[0m\n"); + 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); + write(fd, buf2, strlen(buf2)); + write(fd, "\033[0m\n", strlen("\033[0m\n")); } - printf("\n"); + write(fd, "\n", 1); } int main(int argc, char *argv[]) @@ -1362,10 +1395,8 @@ int main(int argc, char *argv[]) char sep = '\n'; int print_dir = 0, print_selection = 0; - int cmd_args = 0; for (int i = 1; i < argc; i++) { if (argv[i][0] == '+') { - ++cmd_args; char *colon = strchr(argv[i], ':'); if (colon && !colon[1]) break; continue; @@ -1377,29 +1408,19 @@ int main(int argc, char *argv[]) if (argv[i][0] != '-' && !initial_path) initial_path = argv[i]; } - if (!initial_path && cmd_args == 0) initial_path = "."; - - int cmdfd = -1; - if (initial_path) { - has_initial_path: - cmdfilename = memcheck(strdup(CMDFILE_FORMAT)); - if ((cmdfd = mkostemp(cmdfilename, O_APPEND)) == -1) - err("Couldn't create tmpfile: '%s'", CMDFILE_FORMAT); - // Set up environment variables - char *curdepth = getenv("BB_DEPTH"); - int depth = curdepth ? atoi(curdepth) : 0; - sprintf(depthstr, "%d", depth + 1); - setenv("BB_DEPTH", depthstr, 1); - setenv("BBCMD", cmdfilename, 1); - } else { - char *parent_bbcmd = getenv("BBCMD"); - if (!parent_bbcmd || parent_bbcmd[0] == '\0') { - initial_path = "."; - goto has_initial_path; - } - cmdfd = open(parent_bbcmd, O_WRONLY | O_APPEND | O_CREAT, 0644); - if (cmdfd == -1) err("Couldn't open cmdfile: '%s'\n", parent_bbcmd); - } + if (!initial_path) + initial_path = "."; + + cmdfilename = memcheck(strdup(CMDFILE_FORMAT)); + int cmdfd; + if ((cmdfd = mkostemp(cmdfilename, O_APPEND)) == -1) + err("Couldn't create tmpfile: '%s'", CMDFILE_FORMAT); + // Set up environment variables + char *curdepth = getenv("BB_DEPTH"); + int depth = curdepth ? atoi(curdepth) : 0; + sprintf(depthstr, "%d", depth + 1); + setenv("BB_DEPTH", depthstr, 1); + setenv("BBCMD", cmdfilename, 1); int i; for (i = 1; i < argc; i++) { @@ -1419,7 +1440,7 @@ int main(int argc, char *argv[]) if (strcmp(argv[i], "--help") == 0) { usage: printf("bb - an itty bitty console TUI file browser\n"); - printf("Usage: bb [-h/--help] [-s] [-b] [-d] [-0] (+command)* [path]\n"); + printf("Usage: bb [-h/--help] [-s] [-d] [-0] (+command)* [path]\n"); return 0; } if (strcmp(argv[i], "--version") == 0) { @@ -1442,8 +1463,6 @@ int main(int argc, char *argv[]) break; case 's': print_selection = 1; break; - case 'b': print_bindings(); - return 0; } } continue; diff --git a/config.def.h b/config.def.h index f8a8525..26edb97 100644 --- a/config.def.h +++ b/config.def.h @@ -105,14 +105,14 @@ typedef struct { #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 -ep \"" prompt "\" " var " /dev/tty" -#define ASKECHO(prompt, initial) "read -ep \"" prompt "\" /dev/tty && echo \"$REPLY\"" +#define ASK(var, prompt, initial) "read -p \"" prompt "\" " var " /dev/tty" +#define ASKECHO(prompt, initial) "read -p \"" prompt "\" REPLY /dev/tty && echo \"$REPLY\"" #endif // Macros for picking from a list of options: #ifndef PICK #define PICK(prompt, initial) " { awk '{print length, $1}' | sort -n | cut -d' ' -f2- | "\ - "grep -i -m1 \"^$(" ASKECHO(prompt, initial) " | sed 's/./[^&]*[&]/g')\"; } " + "grep -i -m1 \"$(" ASKECHO(prompt, initial) " | sed 's;.;[^/&]*[&];g')\"; } " #endif // Display a spinning indicator if command takes longer than 10ms: @@ -168,7 +168,7 @@ const char *startupcmds[] = { * after editing. *****************************************************************************/ binding_t bindings[] = { - {{'?', KEY_F1}, "bb -b | $PAGER -rX", B("Help")" menu"}, + {{'?', KEY_F1}, "+help", B("Help")" menu"}, {{'q', 'Q'}, "+quit", B("Quit")}, {{'j', KEY_ARROW_DOWN}, "+move:+1", B("Next")" file"}, {{'k', KEY_ARROW_UP}, "+move:-1", B("Previous")" file"}, @@ -191,9 +191,9 @@ binding_t bindings[] = { "| "PICK("Find: ", "")")\"", B("Search")" for file"}, {{'/'}, "bb \"+goto:$(if test $BBDOTFILES; then find -mindepth 1 -maxdepth 1; else find -mindepth 1 -maxdepth 1 ! -path '*/.*'; fi " "| "PICK("Pick: ", "")")\"", B("Pick")" file"}, - {{'d', KEY_DELETE}, "rm -rfi \"$@\" && bb +refresh +deselect: \"$@\" ||" PAUSE, B("Delete")" files"}, - {{'D'}, SPIN("rm -rf \"$@\"")" && bb +refresh +deselect: \"$@\" ||" PAUSE, B("Delete")" files (without confirmation)"}, - {{'M'}, SPIN("mv -i \"$@\" . && bb +refresh +deselect: \"$@\" && for f; do bb \"+sel:$(basename \"$f\")\"; done")" || "PAUSE, + {{'d', KEY_DELETE}, "rm -rfi \"$@\" && bb +refresh && bb +deselect: \"$@\" ||" PAUSE, B("Delete")" files"}, + {{'D'}, SPIN("rm -rf \"$@\"")" && bb +refresh && bb +deselect: \"$@\" ||" PAUSE, B("Delete")" files (without confirmation)"}, + {{'M'}, SPIN("mv -i \"$@\" . && bb +refresh && bb +deselect: \"$@\" && for f; do bb \"+sel:$(basename \"$f\")\"; done")" || "PAUSE, B("Move")" files to current directory"}, {{'c'}, SPIN("cp -ri \"$@\" .")" && bb +r || "PAUSE, B("Copy")" files to current directory"}, {{'C'}, "for f; do "SPIN("cp -ri \"$f\" \"$f.copy\"")"; done && bb +r || "PAUSE, @@ -205,7 +205,7 @@ binding_t bindings[] = { B("Pipe")" selected files to a command"}, {{':'}, "sh -c \"$(" ASKECHO(":", "") ")\" -- \"$@\"; " PAUSE "; bb +refresh", B("Run")" a command"}, - {{'>'}, "tput rmcup; $SHELL; bb +r", "Open a "B("shell")}, + {{'>'}, "tput rmcup >/dev/tty; $SHELL; bb +r", "Open a "B("shell")}, {{'m'}, "read -n1 -p 'Mark: ' m && bb \"+mark:$m;$PWD\"", "Set "B("mark")}, {{'\''}, "read -n1 -p 'Jump: ' j && bb \"+jump:$j\"", B("Jump")" to mark"}, {{'r'}, @@ -229,7 +229,7 @@ binding_t bindings[] = { " fi;" "done", B("Regex rename")" files"}, {{'S'}, - ASK("patt", "Select pattern: ", "")" && bb +sel: $patt", + ASK("patt", "Select pattern: ", "")" && bb +sel: \"$patt\"", B("Select")" file(s) by pattern"}, {{'J'}, "+spread:+1", B("Spread")" selection down"}, {{'K'}, "+spread:-1", B("Spread")" selection up"}, @@ -244,8 +244,7 @@ binding_t bindings[] = { {{'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")}, - {{KEY_CTRL_A}, "if test $BBDOTFILES; then find -mindepth 1 -maxdepth 1 -print0; else find -mindepth 1 -maxdepth 1 ! -path '*/.*' -print0; fi " - "| xargs -0 bb +sel:", + {{KEY_CTRL_A}, "if test $BBDOTFILES; then find -mindepth 1 -maxdepth 1 -print0; else find -mindepth 1 -maxdepth 1 ! -path '*/.*' -print0; fi | bb +sel:", B("Select all")" files in current directory"}, {{KEY_PGDN}, "+scroll:+100%", B("Page down")}, {{KEY_PGUP}, "+scroll:-100%", B("Page up")}, -- cgit v1.2.3