aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2019-05-23 00:57:25 -0700
committerBruce Hill <bruce@bruce-hill.com>2019-05-23 00:57:25 -0700
commite080490d515f5b674e1eda2c7a2f3540ac771d14 (patch)
tree6b469dc85eaa2d599776a21d109720331c98949d
parent566ee2f27233e6ced70417b6ac0355c5c4c8b762 (diff)
Working version of running (nearly) everything on bb IPC commands
-rw-r--r--bb.15
-rw-r--r--bb.c577
-rw-r--r--config.def.h76
3 files changed, 349 insertions, 309 deletions
diff --git a/bb.1 b/bb.1
index bbcef27..dc14dbc 100644
--- a/bb.1
+++ b/bb.1
@@ -8,6 +8,7 @@ bb \- A bitty browser for command line file management
[\fI-d\fR]
[\fI-s\fR]
[\fI-0\fR]
+[\fI-c commands... \fR]
[\fIdirectory\fR]
.SH DESCRIPTION
\fBbb\fR is a tiny TUI console application for browsing and managing files.
@@ -22,6 +23,10 @@ Print the selected files on exit.
If printing the selected files on exit, use NULL-terminated strings instead of
newline-separated.
+.B \-c commands...
+From within \fBbb\fR, running \fBbb -c ...\fR will execute an internal bb
+command for modifying bb's state.
+
.B directory
Open to this directory.
diff --git a/bb.c b/bb.c
index bcd5af4..34bfc09 100644
--- a/bb.c
+++ b/bb.c
@@ -25,10 +25,11 @@
#define MAX(a,b) ((a) < (b) ? (b) : (a))
#define MIN(a,b) ((a) > (b) ? (b) : (a))
#define MAX_PATH 4096
+#define startswith(str, start) (strncmp(str, start, strlen(start)) == 0)
#define writez(fd, str) write(fd, str, strlen(str))
#define IS_SELECTED(p) ((p)->atme)
-#define KEY_DELAY -1
+#define KEY_DELAY 50
static struct termios orig_termios;
static int termfd;
@@ -39,7 +40,7 @@ typedef enum {
SORT_ALPHA = 0,
SORT_SIZE,
SORT_BITS,
- SORT_DATE
+ SORT_TIME
} sortmethod_t;
typedef struct entry_s {
@@ -63,7 +64,12 @@ typedef struct {
sortmethod_t sortmethod;
} bb_state_t;
-static void err(const char *msg, ...);
+#define err(...) do { \
+ close_term(); \
+ fprintf(stderr, __VA_ARGS__); \
+ _err(); \
+} while (0)
+static void _err();
static void update_term_size(void)
{
@@ -73,6 +79,13 @@ static void update_term_size(void)
height = sz.ws_row;
}
+static inline int clamped(int x, int low, int high)
+{
+ if (x < low) return low;
+ if (x > high) return high;
+ return x;
+}
+
static void do_nothing(int _)
{
// Used as SIGINT handler
@@ -112,28 +125,34 @@ static void close_term()
writez(termfd, "\e[?1049l");
// Show cursor:
writez(termfd, "\e[?25h");
+ // Restore scrollable region
+ char buf[16];
+ sprintf(buf, "\e[1;%dr", height);
+ writez(termfd, buf);
+
tcsetattr(termfd, TCSAFLUSH, &orig_termios);
close(termfd);
}
-static void err(const char *msg, ...)
+static void _err()
{
- close_term();
- va_list args;
- va_start(args, msg);
- int len = fprintf(stderr, msg, args);
- va_end(args);
if (errno)
fprintf(stderr, "\n%s", strerror(errno));
fprintf(stderr, "\n");
exit(1);
}
+static char bb_tmpfile[] = "/tmp/bb.XXXXXX";
+
static int run_cmd_on_selection(bb_state_t *state, const char *cmd)
{
pid_t child;
sig_t old_handler = signal(SIGINT, do_nothing);
+ strcpy(bb_tmpfile, "/tmp/bb.XXXXXX");
+ if (!mktemp(bb_tmpfile))
+ err("Could not create temp file");
+
if ((child = fork()) == 0) {
// TODO: is there a max number of args? Should this be batched?
char **const args = calloc(MAX(1, state->nselected) + 5, sizeof(char*));
@@ -148,12 +167,15 @@ static int run_cmd_on_selection(bb_state_t *state, const char *cmd)
}
args[i] = NULL;
+ char bb_depth_str[64] = {0}, bb_ipc_str[64] = {0};
{ // 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);
+ snprintf(bb_depth_str, sizeof(bb_depth_str), "%d", depth + 1);
+ setenv("BB_DEPTH", bb_depth_str, 1);
+ setenv("BBCMD", bb_tmpfile, 1);
+ setenv("BBCURSOR", state->files[state->cursor]->d_name, 1);
+ setenv("BBFULLCURSOR", state->files[state->cursor]->d_fullname, 1);
}
execvp("sh", args);
@@ -163,53 +185,13 @@ static int run_cmd_on_selection(bb_state_t *state, const char *cmd)
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];
- pid_t child;
- if (child_out)
- pipe(child_outfds);
- if (child_in)
- pipe(child_infds);
- if ((child = fork())) {
- if (child == -1)
- err("Failed to fork");
- if (child_out) {
- *child_out = child_outfds[0];
- close(child_outfds[1]);
- }
- if (child_in) {
- *child_in = child_infds[1];
- close(child_infds[0]);
- }
- } else {
- if (child_out) {
- dup2(child_outfds[1], STDOUT_FILENO);
- close(child_outfds[0]);
- }
- if (child_in) {
- dup2(child_infds[0], STDIN_FILENO);
- close(child_infds[1]);
- }
- char *formatted_cmd;
- va_list args;
- va_start(args, cmd);
- int len = vasprintf(&formatted_cmd, cmd, args);
- va_end(args);
- if (formatted_cmd)
- execlp("sh", "sh", "-c", formatted_cmd);
- err("Failed to execute command %d: '%s'", len, formatted_cmd);
- }
- return child;
-}
-
static void term_move(int x, int y)
{
static char buf[32] = {0};
@@ -263,7 +245,7 @@ static void render(bb_state_t *state, int lazy)
{ // Column labels
char buf[] = "\e[32m Size Date Perm Name\e[0m";
buf[8] = state->sortmethod == SORT_SIZE ? (state->sort_reverse ? '-' : '+') : ' ';
- buf[21] = state->sortmethod == SORT_DATE ? (state->sort_reverse ? '-' : '+') : ' ';
+ buf[21] = state->sortmethod == SORT_TIME ? (state->sort_reverse ? '-' : '+') : ' ';
buf[36] = state->sortmethod == SORT_BITS ? (state->sort_reverse ? '-' : '+') : ' ';
buf[42] = state->sortmethod == SORT_ALPHA ? (state->sort_reverse ? '-' : '+') : ' ';
writez(termfd, buf);
@@ -460,6 +442,16 @@ static int compare_date(void *r, const void *v1, const void *v2)
return -(info1.st_mtimespec.tv_sec - info2.st_mtimespec.tv_sec)*sign;
}
+static entry_t *find_file(bb_state_t *state, const char *name)
+{
+ for (int i = 0; i < state->nfiles; i++) {
+ entry_t *e = state->files[i];
+ if (strcmp(name[0] == '/' ? e->d_fullname : e->d_name, name) == 0)
+ return e;
+ }
+ return NULL;
+}
+
static void write_selection(int fd, entry_t *firstselected, char sep)
{
while (firstselected) {
@@ -491,21 +483,46 @@ static void clear_selection(bb_state_t *state)
state->nselected = 0;
}
+static int select_file(bb_state_t *state, entry_t *e)
+{
+ if (IS_SELECTED(e)) return 0;
+ if (strcmp(e->d_name, "..") == 0) return 0;
+ if (state->firstselected)
+ state->firstselected->atme = &e->next;
+ e->next = state->firstselected;
+ e->atme = &state->firstselected;
+ state->firstselected = e;
+ ++state->nselected;
+ return 1;
+}
+
+static int deselect_file(bb_state_t *state, entry_t *e)
+{
+ if (!IS_SELECTED(e)) return 0;
+ if (e->next) e->next->atme = e->atme;
+ *(e->atme) = e->next;
+ e->next = NULL, e->atme = NULL;
+ --state->nselected;
+ return 1;
+}
+
static void explore(char *path, int print_dir, int print_selection, char sep)
{
- char *tmp = path;
- char *original_path = calloc(strlen(tmp) + 1, 1);
- if (!original_path) err("allocation failure");
- strcpy(original_path, path);
- path = calloc(strlen(tmp) + 1, 1);
+ char realpath_buf[MAX_PATH+1];
+ path = strdup(path);
if (!path) err("allocation failure");
- strcpy(path, tmp);
- tmp = NULL;
+ char *original_path = strdup(path);
+ if (!original_path) err("allocation failure");
+
char to_select[MAX_PATH+1] = {0};
bb_state_t state = {0};
memset(&state, 0, sizeof(bb_state_t));
tail_call:
+ if (!realpath(path, realpath_buf))
+ err("realpath failed on %s", path);
+ path = realloc(path, strlen(realpath_buf));
+ strcpy(path, realpath_buf);
if (state.files) {
for (int i = 0; i < state.nfiles; i++) {
@@ -525,7 +542,7 @@ static void explore(char *path, int print_dir, int print_selection, char sep)
size_t hashsize = 2 * state.nselected;
entry_t **selecthash = calloc(hashsize, sizeof(entry_t*));
if (!selecthash)
- err("Failed to allocate %d spaces for selecthash", hashsize);
+ err("Failed to allocate %ld spaces for selecthash", hashsize);
for (entry_t *p = state.firstselected; p; p = p->next) {
int probe = ((int)p->d_ino) % hashsize;
while (selecthash[probe])
@@ -596,7 +613,7 @@ static void explore(char *path, int print_dir, int print_selection, char sep)
sort_files:
cmp = compare_alpha;
if (state.sortmethod == SORT_SIZE) cmp = compare_size;
- if (state.sortmethod == SORT_DATE) cmp = compare_date;
+ if (state.sortmethod == SORT_TIME) cmp = compare_date;
if (state.sortmethod == SORT_BITS) cmp = compare_perm;
qsort_r(&state.files[1], state.nfiles-1, sizeof(entry_t*), &state.sort_reverse, cmp);
@@ -644,20 +661,24 @@ static void explore(char *path, int print_dir, int print_selection, char sep)
} else if (mouse_y >= 2 && state.scroll + (mouse_y - 2) < state.nfiles) {
int clicked = state.scroll + (mouse_y - 2);
if (dt_ms > 200) {
+ lazy = 1;
// Single click
if (mouse_x == 0) {
- // Toggle
- picked = clicked;
- goto toggle;
+ if (IS_SELECTED(state.files[clicked]))
+ deselect_file(&state, state.files[clicked]);
+ else
+ select_file(&state, state.files[clicked]);
+ goto redraw;
} else {
state.cursor = clicked;
- lazy = 1;
goto redraw;
}
} else {
// Double click
- picked = clicked;
- goto open_file;
+ // TODO: hacky
+ state.cursor = clicked;
+ key = '\n';
+ goto user_bindings;
}
}
break;
@@ -676,9 +697,6 @@ static void explore(char *path, int print_dir, int print_selection, char sep)
init_term();
goto redraw;
- case 'q': case 'Q':
- goto done;
-
case KEY_MOUSE_WHEEL_DOWN:
if (state.cursor >= state.nfiles - 1)
goto skip_redraw;
@@ -697,146 +715,28 @@ static void explore(char *path, int print_dir, int print_selection, char sep)
--state.scroll;
goto redraw;
- case KEY_CTRL_D: case KEY_PGDN:
- if (state.cursor == state.nfiles - 1)
- goto skip_redraw;
- lazy = 1;
- state.cursor = MIN(state.nfiles - 1, state.cursor + (height - 4) / 2);
- if (state.nfiles <= height - 4)
- goto redraw;
-
- state.scroll += (height - 4)/2;
- if (state.scroll > state.nfiles - (height - 4) - 1)
- state.scroll = state.nfiles - (height - 4) - 1;
- goto redraw;
-
- case KEY_CTRL_U: case KEY_PGUP:
- state.cursor = MAX(0, state.cursor - (height - 4) / 2);
- if (state.nfiles <= height - 4)
- goto redraw;
- lazy = 1;
- state.scroll -= (height - 4)/2;
- if (state.scroll < 0)
- state.scroll = 0;
- goto redraw;
-
- case 'g': case KEY_HOME:
- lazy = 1;
- state.cursor = 0;
- state.scroll = 0;
- goto redraw;
-
- case 'G': case KEY_END:
- lazy = 1;
- state.cursor = state.nfiles - 1;
- if (state.nfiles > height - 4)
- state.scroll = state.nfiles - (height - 4) - 1;
- goto redraw;
-
- case ' ':
- picked = state.cursor;
- toggle:
- lazy = 1;
- if (strcmp(state.files[picked]->d_name, "..") == 0)
- goto skip_redraw;
- if (IS_SELECTED(state.files[picked])) {
- toggle_off:;
- entry_t *e = state.files[picked];
- if (e->next) e->next->atme = e->atme;
- *(e->atme) = e->next;
- e->next = NULL, e->atme = NULL;
- --state.nselected;
- } else {
- toggle_on:;
- entry_t *e = state.files[picked];
- if (state.firstselected)
- state.firstselected->atme = &e->next;
- e->next = state.firstselected;
- e->atme = &state.firstselected;
- state.firstselected = e;
- ++state.nselected;
- }
- goto redraw;
-
- case KEY_CTRL_A:
- for (int i = 0; i < state.nfiles; i++) {
- entry_t *e = state.files[i];
- if (e->atme) continue;
- if (strcmp(e->d_name, "..") == 0)
- continue;
- if (state.firstselected)
- state.firstselected->atme = &e->next;
- e->next = state.firstselected;
- e->atme = &state.firstselected;
- state.firstselected = e;
- ++state.nselected;
- }
- goto redraw;
-
- case KEY_ESC:
- clear_selection(&state);
- goto redraw;
-
- case KEY_F5: case KEY_CTRL_R:
- strcpy(to_select, state.files[state.cursor]->d_name);
- goto tail_call;
-
- case 'j': case KEY_ARROW_DOWN:
- if (state.cursor >= state.nfiles - 1)
- goto skip_redraw;
- lazy = 1;
- ++state.cursor;
- if (state.cursor > state.scroll + (height - 4) - 1 - scrolloff
- && state.scroll < state.nfiles - (height - 4) - 1)
- ++state.scroll;
- goto redraw;
-
- case 'k': case KEY_ARROW_UP:
- if (state.cursor <= 0)
- goto skip_redraw;
- lazy = 1;
- --state.cursor;
- if (state.cursor < state.scroll + scrolloff && state.scroll > 0)
- --state.scroll;
- goto redraw;
-
case 'J':
if (state.cursor < state.nfiles - 1) {
- if (IS_SELECTED(state.files[state.cursor])) {
- picked = ++state.cursor;
- if (!IS_SELECTED(state.files[picked]))
- goto toggle_on;
- } else {
- picked = ++state.cursor;
- if (IS_SELECTED(state.files[picked]))
- goto toggle_off;
- }
lazy = 1;
+ if (IS_SELECTED(state.files[state.cursor]))
+ select_file(&state, state.files[++state.cursor]);
+ else
+ deselect_file(&state, state.files[++state.cursor]);
goto redraw;
}
goto skip_redraw;
case 'K':
if (state.cursor > 0) {
- if (IS_SELECTED(state.files[state.cursor])) {
- picked = --state.cursor;
- if (!IS_SELECTED(state.files[picked]))
- goto toggle_on;
- } else {
- picked = --state.cursor;
- if (IS_SELECTED(state.files[picked]))
- goto toggle_off;
- }
lazy = 1;
+ if (IS_SELECTED(state.files[state.cursor]))
+ select_file(&state, state.files[--state.cursor]);
+ else
+ deselect_file(&state, state.files[--state.cursor]);
goto redraw;
}
goto skip_redraw;
- case 'h': case KEY_ARROW_LEFT:
- // TODO: slightly hacky, depends on ".." being at 0 (currently always true)
- picked = 0;
- goto open_file;
-
case 's':
// Change sorting method:
term_move(0, height-1);
@@ -875,10 +775,10 @@ static void explore(char *path, int print_dir, int print_selection, char sep)
case 't': case 'T':
sort_date:
- if (state.sortmethod == SORT_DATE)
+ if (state.sortmethod == SORT_TIME)
state.sort_reverse ^= 1;
else {
- state.sortmethod = SORT_DATE;
+ state.sortmethod = SORT_TIME;
state.sort_reverse = 0;
}
break;
@@ -896,85 +796,12 @@ static void explore(char *path, int print_dir, int print_selection, char sep)
case '.':
state.showhidden ^= 1;
+ strcpy(to_select, state.files[state.cursor]->d_name);
goto tail_call;
- case 'l': case '\r': case KEY_ARROW_RIGHT:
- picked = state.cursor;
- open_file: {
- if (state.files[picked]->d_isdir) {
- if (strcmp(state.files[picked]->d_name, "..") == 0) {
- char *p = strrchr(state.path, '/');
- if (p) strcpy(to_select, p+1);
- else to_select[0] = '\0';
- } else to_select[0] = '\0';
-
- char tmp[MAX_PATH+1];
- if (!realpath(state.files[picked]->d_fullname, tmp))
- err("realpath failed");
- free(path);
- path = calloc(strlen(tmp) + 1, sizeof(char));
- strcpy(path, tmp);
- goto tail_call;
- } else {
- char *name = state.files[picked]->d_name;
- close_term();
- pid_t child = run_cmd(NULL, NULL,
-#ifdef __APPLE__
- "if file -bI %s | grep '^text/' >/dev/null; then $EDITOR %s; else open %s; fi",
-#else
- "if file -bi %s | grep '^text/' >/dev/null; then $EDITOR %s; else xdg-open %s; fi",
-#endif
- name, name, name);
- waitpid(child, NULL, 0);
- init_term();
- goto redraw;
- }
- break;
- }
-
- case 'f': case '/': {
- close_term();
- int fd;
- if (state.showhidden)
- run_cmd(&fd, NULL, "find | cut -d/ -f2- | " PROG_FUZZY);
- else
- run_cmd(&fd, NULL, "find -not -name '\\.*' -not -path '*/\\.*' | cut -d/ -f2- | " PROG_FUZZY);
- int len = 0, space = MAX_PATH, consumed;
- while ((consumed = read(fd, &to_select[len], space)) > 0) {
- if (consumed < 0) err("Error reading selection");
- to_select[len + consumed] = '\0';
- char *nl = strchr(&to_select[len], '\n');
- if (nl) {
- *nl = '\0';
- break;
- }
- len += consumed;
- space -= consumed;
- }
- close(fd);
- init_term();
-
- if (to_select[0] == '\0')
- goto redraw;
-
- char *pathend = strrchr(to_select, '/');
- if (pathend) {
- char *newpath = calloc(strlen(path) + 1 + (pathend - to_select), 1);
- strcpy(newpath, path);
- strcat(newpath, "/");
- strncat(newpath, to_select, pathend - to_select);
- free(path);
- path = newpath;
- strcpy(to_select, pathend + 1);
- }
- goto tail_call;
- }
-
case '?': {
- close_term();
- int fd;
- pid_t child = run_cmd(NULL, &fd, "less -r");
-
+ char tmpname[] = "/tmp/bb_help.XXXXXX";
+ int fd = mkstemp(tmpname);
writez(fd, "\n \e[33;1mKey Bindings:\e[0m\n");
for (int i = 0; bindings[i].key; i++) {
if (bindings[i].key > 0) continue;
@@ -990,17 +817,27 @@ static void explore(char *path, int print_dir, int print_selection, char sep)
writez(fd, "\n \e[33;1mScript Key Bindings:\e[0m\n");
for (int i = 0; bindings[i].key; i++) {
if (bindings[i].key <= 0) continue;
- writez(fd, "\e[1m");
- char buf[] = " X \e[0m";
- *strchr(buf, 'X') = bindings[i].key;
- writez(fd, buf);
- writez(fd, bindings[i].command);
+ writez(fd, "\e[1m ");
+ char buf[16];
+ buf[0] = bindings[i].key;
+ if (' ' <= buf[0] && buf[0] <= '~')
+ write(fd, buf, 1);
+ else {
+ sprintf(buf, "\e[31m%04X", bindings[i].key);
+ writez(fd, buf);
+ }
+ writez(fd, " \e[0m\t");
+ write_escaped(fd, bindings[i].command, strlen(bindings[i].command), "\e[0m");
writez(fd, "\e[0m\n");
}
writez(fd, "\n");
-
close(fd);
- waitpid(child, NULL, 0);
+
+ close_term();
+ char cmd[256];
+ sprintf(cmd, "less -r %s", tmpname);
+ system(cmd);
+ unlink(tmpname);
init_term();
goto redraw;
}
@@ -1010,36 +847,182 @@ static void explore(char *path, int print_dir, int print_selection, char sep)
default:
// Search user-defined key bindings from config.h:
+ user_bindings:
for (int i = 0; bindings[i].key > 0; i++) {
if (key == bindings[i].key) {
term_move(0, height-1);
- writez(termfd, "\e[K");
+ //writez(termfd, "\e[K");
+
struct termios cur_tios;
- if (!(bindings[i].flags & ONSCREEN)) {
+ if (bindings[i].flags & NORMAL_TERM) {
close_term();
} else {
+ { // Restore scrolling region
+ char buf[16];
+ sprintf(buf, "\e[1;%dr", height);
+ writez(termfd, buf);
+ }
+ /*
tcgetattr(termfd, &cur_tios);
tcsetattr(termfd, TCSAFLUSH, &orig_termios);
writez(termfd, "\e[?25h"); // Show cursor
+ */
}
run_cmd_on_selection(&state, bindings[i].command);
- if (!(bindings[i].flags & ONSCREEN)) {
+ if (bindings[i].flags & NORMAL_TERM) {
+ lazy = 0;
init_term();
} else {
+ lazy = 1;
+ /*
tcsetattr(termfd, TCSAFLUSH, &cur_tios);
writez(termfd, "\e[?25l"); // Hide cursor
+ */
+ { // Restore scrolling region
+ char buf[16];
+ sprintf(buf, "\e[3;%dr", height-1);
+ writez(termfd, buf);
+ }
}
- if (bindings[i].flags & CLEAR_SELECTION)
- clear_selection(&state);
+ // Scan for IPC requests
+ int needs_full_refresh = 0;
+ char newsort[3] = "";
+ FILE *tmpfile;
+ if ((tmpfile = fopen(bb_tmpfile, "r"))) {
+ char *line = NULL;
+ size_t capacity = 0;
+ ssize_t len;
+ while ((len = getline(&line, &capacity, tmpfile)) >= 0) {
+ if (len > 0 && line[len-1] == '\n') line[--len] = '\0';
+ if (strcmp(line, "refresh") == 0) {
+ needs_full_refresh = 1;
+ } else if (strcmp(line, "quit") == 0) {
+ goto done;
+ } else if (startswith(line, "sort:") && len >= 7) {
+ newsort[0] = line[strlen("sort:")];
+ newsort[1] = line[strlen("sort:") + 1];
+ } else if (startswith(line, "cd:")) {
+ free(path);
+ path = calloc(strlen(line+strlen("cd:")) + 1, sizeof(char));
+ strcpy(path, line+strlen("cd:"));
+ goto tail_call;
+ } else if (startswith(line, "toggle:")) {
+ lazy = 0;
+ entry_t *e = find_file(&state, line + strlen("select:"));
+ if (e) {
+ if (IS_SELECTED(e)) deselect_file(&state, e);
+ else select_file(&state, e);
+ }
+ } else if (startswith(line, "select:")) {
+ char *name = line + strlen("select:");
+ lazy = 0;
+ if (strcmp(name, "*") == 0) {
+ for (int i = 0; i < state.nfiles; i++)
+ select_file(&state, state.files[i]);
+ } else {
+ entry_t *e = find_file(&state, name);
+ if (e) select_file(&state, e);
+ }
+ } else if (startswith(line, "deselect:")) {
+ char *name = line + strlen("deselect:");
+ lazy = 0;
+ if (strcmp(name, "*") == 0) {
+ clear_selection(&state);
+ } else {
+ entry_t *e = find_file(&state, name);
+ if (e) select_file(&state, e);
+ }
+ } else if (startswith(line, "cursor:")) {
+ char *name = line + strlen("cursor:");
+ for (int i = 0; i < state.nfiles; i++) {
+ if (strcmp(name[0] == '/' ?
+ state.files[i]->d_fullname : state.files[i]->d_name,
+ name) == 0) {
+ state.cursor = i;
+ goto found_it;
+ }
+ }
+ free(path);
+ char *lastslash = strrchr(name, '/');
+ if (!lastslash) goto found_it;
+ size_t len = lastslash - name;
+ path = calloc(len + 1, sizeof(char));
+ memcpy(path, name, len);
+ strcpy(to_select, lastslash+1);
+ goto tail_call;
+ found_it:;
+ } else if (startswith(line, "move:")) {
+ char *value = line + strlen("move:");
+ int oldcur = state.cursor;
+ int isabs = value[0] != '-' && value[0] != '+';
+ long delta = strtol(value, &value, 10);
+ if (*value == '%') delta = (delta * height)/100;
+ if (isabs) state.cursor = delta;
+ else state.cursor += delta;
+
+ state.cursor = clamped(state.cursor, 0, state.nfiles-1);
+ delta = state.cursor - oldcur;
+
+ if (state.nfiles > height-4) {
+ if (delta > 0) {
+ if (state.cursor >= state.scroll + (height-4) - scrolloff)
+ state.scroll += delta;
+ } else if (delta < 0) {
+ if (state.cursor <= state.scroll + scrolloff)
+ state.scroll += delta;
+ }
+ //int target = clamped(state.scroll, state.cursor - (height-4) + scrolloff, state.cursor - scrolloff);
+ //state.scroll += (delta > 0 ? 1 : -1)*MIN(abs(target-state.scroll), abs((int)delta));
+ //state.scroll = target;
+ state.scroll = clamped(state.scroll, state.cursor - (height-4) + 1, state.cursor);
+ state.scroll = clamped(state.scroll, 0, state.nfiles-1 - (height-4));
+ }
+ } else if (startswith(line, "scroll:")) {
+ char *value = line + strlen("scroll:");
+ int oldscroll = state.scroll;
+ int isabs = value[0] != '-' && value[0] != '+';
+ long delta = strtol(value, &value, 10);
+ if (*value == '%') delta = (delta * height)/100;
+
+ if (state.nfiles > height-4) {
+ if (isabs) state.scroll = delta;
+ else state.scroll += delta;
+ state.scroll = clamped(state.scroll, 0, state.nfiles-1 - (height-4));
+ }
+
+ state.cursor = clamped(state.cursor + delta, 0, state.nfiles-1);
+ }
+ }
+ if (line) free(line);
+ fclose(tmpfile);
+ unlink(bb_tmpfile);
+ }
- if (bindings[i].flags & REFRESH) {
+ sortmethod_t oldmethod = state.sortmethod;
+ int oldreverse = state.sort_reverse;
+ if (newsort[0] == '+')
+ state.sort_reverse = 0;
+ else if (newsort[0] == '+')
+ state.sort_reverse = 1;
+ switch (newsort[1]) {
+ case 'a': state.sortmethod = SORT_ALPHA; break;
+ case 's': state.sortmethod = SORT_SIZE; break;
+ case 't': state.sortmethod = SORT_TIME; break;
+ case 'p': state.sortmethod = SORT_BITS; break;
+ default: break;
+ }
+
+ if (needs_full_refresh) {
strcpy(to_select, state.files[state.cursor]->d_name);
goto tail_call;
}
+ if (state.sortmethod != oldmethod || oldreverse != state.sort_reverse)
+ goto sort_files;
+
goto redraw;
}
}
@@ -1058,7 +1041,6 @@ done:
int main(int argc, char *argv[])
{
- char _realpath[MAX_PATH+1];
char *path = ".";
char sep = '\n';
int print_dir = 0, print_selection = 0;
@@ -1069,6 +1051,17 @@ int main(int argc, char *argv[])
printf("Usage: bb [-h/--help] [-s] [-b] [-0] [path]\n");
return 0;
}
+ if (strcmp(argv[i], "-c") == 0) {
+ char *bb_cmdfile = getenv("BBCMD");
+ if (!bb_cmdfile)
+ err("Can only execute bb commands from inside bb");
+ FILE *f = fopen(bb_cmdfile, "w");
+ for (i = i+1; i < argc; i++) {
+ fprintf(f, "%s\n", argv[i]);
+ }
+ fclose(f);
+ return 0;
+ }
if (argv[i][0] == '-' && argv[i][1] == '-')
continue;
if (argv[i][0] == '-') {
@@ -1094,8 +1087,6 @@ int main(int argc, char *argv[])
}
}
init_term();
- if (!realpath(path, _realpath))
- err("realpath failed");
- explore(_realpath, print_dir, print_selection, sep);
+ explore(path, print_dir, print_selection, sep);
return 0;
}
diff --git a/config.def.h b/config.def.h
index 2956703..0061490 100644
--- a/config.def.h
+++ b/config.def.h
@@ -8,9 +8,7 @@
#define AND_PAUSE " && read -n1 -p '\n\e[2m...press any key to continue...\e[0m\e[?25l'"
#define SCROLLOFF 5
-#define REFRESH (1<<0)
-#define CLEAR_SELECTION (1<<1)
-#define ONSCREEN (1<<2)
+#define NORMAL_TERM (1<<0)
struct {
int key;
@@ -22,19 +20,65 @@ struct {
// 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>"`
////////////////////////////////////////////////////////////////////////
- {'e', "$EDITOR \"$@\""},
- {'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},
+ {'e', "$EDITOR \"$@\"", NORMAL_TERM},
+ {'L', PIPE_SELECTION_TO "less", NORMAL_TERM},
+ {'D', "rm -rf \"$@\"; bb -c 'deselect:*' refresh"},
+ {'d', "rm -rfi \"$@\"; bb -c 'deselect:*' refresh"},
+ {'m', "mv -i \"$@\" .; bb -c 'deselect:*' refresh"},
+ {'c', "cp -i \"$@\" .; bb -c refresh"},
+ {'C', "for f; do cp \"$f\" \"$f.copy\"; done; bb -c refresh"},
+ {'n', "read -p '\e[33;1mNew file:\e[0m \e[K' name && touch \"$name\"; bb -c refresh"},
+ {'N', "read -p '\e[33;1mNew dir:\e[0m \e[K' name && mkdir \"$name\"; bb -c refresh"},
+ {'|', "read -p '\e[33;1m|>\e[0m \e[K' cmd && " PIPE_SELECTION_TO "$SHELL -c \"$cmd\"" AND_PAUSE},
+ {':', "read -p '\e[33;1m:>\e[0m \e[K' cmd && $SHELL -c \"$cmd\" -- \"$@\"" AND_PAUSE},
+ {'>', "$SHELL", NORMAL_TERM},
+ {'r', "for f; do read -p \"Rename $f: \e[K\" renamed && mv \"$f\" \"$renamed\"; done;"
+ " bb -c 'deselect:*' refresh"},
+
+
+ {'h', "bb -c \"cd:..\""},
+ {KEY_ARROW_LEFT, "bb -c 'cd:..'"},
+ {'j', "bb -c 'move:+1'"},
+ {KEY_ARROW_DOWN, "bb -c 'move:+1'"},
+ {'k', "bb -c 'move:-1'"},
+ {KEY_ARROW_UP, "bb -c 'move:-1'"},
+
+ {'l', "bb -c \"cd:$BBFULLCURSOR\""},
+ {KEY_ARROW_RIGHT, "bb -c \"cd:$BBFULLCURSOR\""},
+#ifdef __APPLE__
+ {'\r', "if test -x \"$BBCURSOR\"; then \"$BBCURSOR\"; "
+ "elif test -d \"$BBCURSOR\"; then bb -c \"cd:$BBFULLCURSOR\"; "
+ "elif file -bI \"$BBCURSOR\" | grep '^text/' >/dev/null; then $EDITOR \"$BBCURSOR\"; "
+ "else open \"$BBCURSOR\"; fi"},
+#else
+ {'\r', "if test -x \"$BBCURSOR\"; then \"$BBCURSOR\"; "
+ "elif test -d \"$BBCURSOR\"; then bb -c \"cd:$BBFULLCURSOR\"; "
+ "elif file -bi \"$BBCURSOR\" | grep '^text/' >/dev/null; then $EDITOR \"$BBCURSOR\"; "
+ "else xdg-open \"$BBCURSOR\"; fi"},
+#endif
+ {' ', "bb -c \"toggle:$BBCURSOR\""},
+ //{-1, "J\t\e[0;34mMove selection state down\e[0m"},
+ //{-1, "K\t\e[0;34mMove selection state up\e[0m"},
+ {'q', "bb -c quit"},
+ {'Q', "bb -c quit"},
+ {'g', "bb -c move:0"},
+ {KEY_HOME, "bb -c move:0"},
+ {'G', "bb -c move:9999999999"},
+ {KEY_END, "bb -c move:999999999"},
+ {'f', "bb -c \"cursor:`fzf`\"", NORMAL_TERM},
+ {'/', "bb -c \"cursor:`ls -a|fzf`\"", NORMAL_TERM},
+ {KEY_ESC, "bb -c 'deselect:*'"},
+ {KEY_F5, "bb -c refresh"},
+ {KEY_CTRL_R, "bb -c refresh"},
+ {KEY_CTRL_A, "bb -c 'select:*'"},
+ //{-1, "Ctrl-C\t\e[0;34mAbort and exit\e[0m"},
+ {KEY_PGDN, "bb -c 'scroll:+100%'"},
+ {KEY_CTRL_D, "bb -c 'scroll:+50%'"},
+ {KEY_PGUP, "bb -c 'scroll:-100%'"},
+ {KEY_CTRL_U, "bb -c 'scroll:-50%'"},
+ //{-1, "Ctrl-Z\t\e[0;34mSuspend\e[0m"},
+
+
// Hard-coded behaviors (these are just placeholders for the help):
{-1, "?\t\e[0;34mOpen help menu\e[0m"},