aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--bb.c257
-rw-r--r--bterm.h431
-rw-r--r--config.def.h108
-rw-r--r--keys.h244
5 files changed, 636 insertions, 406 deletions
diff --git a/Makefile b/Makefile
index 5dcf77a..47d1795 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,7 @@ clean:
config.h:
cp config.def.h config.h
-$(NAME): $(NAME).c keys.h config.h
+$(NAME): $(NAME).c bterm.h config.h
$(CC) $(NAME).c $(LIBS) $(CFLAGS) $(G) -o $(NAME)
test: $(NAME)
diff --git a/bb.c b/bb.c
index a5b0ac4..bd61491 100644
--- a/bb.c
+++ b/bb.c
@@ -22,9 +22,9 @@
#include <unistd.h>
#include "config.h"
-#include "keys.h"
+#include "bterm.h"
-#define BB_VERSION "0.9.1"
+#define BB_VERSION "0.10.0"
#ifndef PATH_MAX
#define PATH_MAX 4096
@@ -34,23 +34,10 @@
#define MIN(a,b) ((a) > (b) ? (b) : (a))
#define IS_SELECTED(p) (((p)->atme) != NULL)
-// Terminal escape sequences:
-#define CSI "\033["
-#define T_WRAP "7"
-#define T_SHOW_CURSOR "25"
-#define T_MOUSE_XY "1000"
-#define T_MOUSE_CELL "1002"
-#define T_MOUSE_SGR "1006"
-#define T_ALT_SCREEN "1049"
-#define T_ON(opt) CSI "?" opt "h"
-#define T_OFF(opt) CSI "?" opt "l"
-
static const char *T_ENTER_BBMODE = T_OFF(T_SHOW_CURSOR) T_ON(T_MOUSE_XY ";" T_MOUSE_CELL ";" T_MOUSE_SGR);
static const char *T_LEAVE_BBMODE = T_OFF(T_MOUSE_XY ";" T_MOUSE_CELL ";" T_MOUSE_SGR ";" T_ALT_SCREEN);
static const char *T_LEAVE_BBMODE_PARTIAL = T_OFF(T_MOUSE_XY ";" T_MOUSE_CELL ";" T_MOUSE_SGR);
-#define move_cursor(f, x, y) fprintf((f), CSI "%d;%dH", (int)(y)+1, (int)(x)+1)
-
#define err(...) do { \
close_term(); \
fputs(T_OFF(T_ALT_SCREEN), stdout); \
@@ -92,11 +79,10 @@ typedef struct entry_s {
char *name, *linkname;
// TODO: inline only the relevant fields:
struct stat info;
+ mode_t linkedmode;
int refcount : 2; // Should only be between 0-2
- int isdir : 1;
- int islink : 1;
- int needs_esc : 1;
- int link_needs_esc : 1;
+ int no_esc : 1;
+ int link_no_esc : 1;
char fullname[1]; // Must be last
} entry_t;
@@ -111,6 +97,8 @@ typedef struct bb_s {
int colwidths[10];
} bb_t;
+typedef enum { BB_NOP = 0, BB_INVALID, BB_REFRESH, BB_QUIT } bb_result_t;
+
// Functions
static void update_term_size(int sig);
static void init_term(void);
@@ -118,6 +106,8 @@ static void cleanup_and_exit(int sig);
static void close_term(void);
static void* memcheck(void *p);
static int run_cmd_on_selection(bb_t *bb, const char *cmd);
+static int fputs_escaped(FILE *f, const char *str, const char *color);
+static const char *color_of(mode_t mode);
static void render(bb_t *bb, int lazy);
static int compare_files(void *r, const void *v1, const void *v2);
static int find_file(bb_t *bb, const char *name);
@@ -126,8 +116,10 @@ static int select_file(bb_t *bb, entry_t *e);
static int deselect_file(bb_t *bb, entry_t *e);
static void set_cursor(bb_t *bb, int i);
static void set_scroll(bb_t *bb, int i);
+static entry_t *load_entry(const char *path);
static void populate_files(bb_t *bb, const char *path);
static void sort_files(bb_t *bb);
+static bb_result_t execute_cmd(bb_t *bb, const char *cmd);
static void explore(bb_t *bb, const char *path);
static void print_bindings(int verbose);
@@ -169,7 +161,8 @@ void init_term(void)
bb_termios.c_cflag |= (unsigned long)CS8;
bb_termios.c_cc[VMIN] = 0;
bb_termios.c_cc[VTIME] = 0;
- tcsetattr(fileno(tty_out), TCSAFLUSH, &bb_termios);
+ if (tcsetattr(fileno(tty_out), TCSAFLUSH, &bb_termios) == -1)
+ err("Couldn't tcsetattr");
update_term_size(0);
signal(SIGWINCH, update_term_size);
// Initiate mouse tracking and disable text wrapping:
@@ -181,9 +174,6 @@ void cleanup_and_exit(int sig)
{
(void)sig;
close_term();
- fputs(T_OFF(T_ALT_SCREEN), stdout);
- fputs(T_ON(T_WRAP), stdout);
- fflush(stdout);
unlink(cmdfilename);
exit(EXIT_FAILURE);
}
@@ -191,17 +181,20 @@ void cleanup_and_exit(int sig)
void close_term(void)
{
if (tty_out) {
- fflush(tty_out);
tcsetattr(fileno(tty_out), TCSAFLUSH, &orig_termios);
+ fputs(T_LEAVE_BBMODE_PARTIAL, tty_out);
+ fputs(T_ON(T_WRAP), tty_out);
+ fflush(tty_out);
fclose(tty_out);
tty_out = NULL;
fclose(tty_in);
tty_in = NULL;
+ } else {
+ fputs(T_LEAVE_BBMODE_PARTIAL, stdout);
+ fputs(T_ON(T_WRAP), stdout);
+ fflush(stdout);
}
- fputs(T_LEAVE_BBMODE_PARTIAL, stdout);
- fputs(T_ON(T_WRAP), stdout);
- fflush(stdout);
- signal(SIGWINCH, SIG_IGN);
+ signal(SIGWINCH, SIG_DFL);
}
void* memcheck(void *p)
@@ -237,8 +230,8 @@ int run_cmd_on_selection(bb_t *bb, const char *cmd)
}
args[i] = NULL;
- setenv("BBCURSOR", bb->files[bb->cursor]->name, 1);
- setenv("BBFULLCURSOR", bb->files[bb->cursor]->fullname, 1);
+ setenv("BBSELECTED", bb->firstselected ? "1" : "", 1);
+ setenv("BBCURSOR", bb->files[bb->cursor]->fullname, 1);
execvp("sh", args);
err("Failed to execute command: '%s'", cmd);
@@ -255,7 +248,7 @@ int run_cmd_on_selection(bb_t *bb, const char *cmd)
return status;
}
-static int fputs_escaped(FILE *f, const char *str, const char *color)
+int fputs_escaped(FILE *f, const char *str, const char *color)
{
static const char *escapes = " abtnvfr e";
int escaped = 0;
@@ -273,6 +266,18 @@ static int fputs_escaped(FILE *f, const char *str, const char *color)
return escaped;
}
+const char *color_of(mode_t mode)
+{
+ if (S_ISDIR(mode))
+ return DIR_COLOR;
+ else if (S_ISLNK(mode))
+ return LINK_COLOR;
+ else if (mode & (S_IXUSR | S_IXGRP | S_IXOTH))
+ return EXECUTABLE_COLOR;
+ else
+ return NORMAL_COLOR;
+}
+
void render(bb_t *bb, int lazy)
{
static int lastcursor = -1, lastscroll = -1;
@@ -330,13 +335,14 @@ void render(bb_t *bb, int lazy)
if (!lazy) {
// Path
move_cursor(tty_out, 0, 0);
- const char *color = "\033[0;1;37m";
+ const char *color = TITLE_COLOR;
+ fputs(color, tty_out);
fputs_escaped(tty_out, bb->path, color);
fputs(" \033[K\033[0m", tty_out);
// Columns
move_cursor(tty_out, 0, 1);
- fputs("\033[41m \033[0;44;30m\033[K", tty_out);
+ fputs(" \033[0;44;30m\033[K", tty_out);
int x = 1;
for (int col = 0; col < cols; col++) {
move_cursor(tty_out, x, 1);
@@ -391,20 +397,9 @@ void render(bb_t *bb, int lazy)
entry_t *entry = files[i];
- if (IS_SELECTED(entry))
- fputs("\033[41m \033[0m", tty_out);
- else
- fputs(" ", tty_out);
-
- const char *color;
- if (i == bb->cursor)
- color = CURSOR_COLOR;
- else if (entry->islink)
- color = entry->isdir ? LINKDIR_COLOR : LINK_COLOR;
- else if (entry->isdir)
- color = DIR_COLOR;
- else
- color = NORMAL_COLOR;
+ fputs(IS_SELECTED(entry) ? SELECTED_INDICATOR : NOT_SELECTED_INDICATOR, tty_out);
+
+ const char *color = (i == bb->cursor) ? CURSOR_COLOR : color_of(entry->info.st_mode);
fputs(color, tty_out);
int x = 1;
@@ -459,20 +454,22 @@ void render(bb_t *bb, int lazy)
break;
case 'n': {
- if (entry->needs_esc)
- entry->needs_esc &= fputs_escaped(tty_out, entry->name, color) > 0;
- else fputs(entry->name, tty_out);
+ if (entry->no_esc) fputs(entry->name, tty_out);
+ else entry->no_esc |= !fputs_escaped(tty_out, entry->name, color);
- if (entry->isdir)
+ if (S_ISDIR(S_ISLNK(entry->info.st_mode) ? entry->linkedmode : entry->info.st_mode))
fputs("/", tty_out);
if (entry->linkname) {
- fputs("\033[2m -> ", tty_out);
- if (entry->link_needs_esc)
- entry->link_needs_esc &= fputs_escaped(tty_out, entry->linkname, color) > 0;
- else fputs(entry->linkname, tty_out);
-
- if (entry->isdir)
+ if (i != bb->cursor)
+ fputs("\033[37m", tty_out);
+ fputs("\033[2m -> \033[22;3m", tty_out);
+ color = i == bb->cursor ? CURSOR_COLOR : color_of(entry->linkedmode);
+ fputs(color, tty_out);
+ if (entry->link_no_esc) fputs(entry->linkname, tty_out);
+ else entry->link_no_esc |= !fputs_escaped(tty_out, entry->linkname, color);
+
+ if (S_ISDIR(entry->linkedmode))
fputs("/", tty_out);
fputs("\033[22m", tty_out);
@@ -527,8 +524,10 @@ int compare_files(void *v, const void *v1, const void *v2)
const entry_t *f1 = *((const entry_t**)v1), *f2 = *((const entry_t**)v2);
int diff = 0;
if (!bb->options['i']) {
- diff = -(f1->isdir - f2->isdir);
- if (diff) return -diff;
+ // Unless interleave mode is on, sort dirs before files
+ int d1 = S_ISDIR(f1->info.st_mode) || (S_ISLNK(f1->info.st_mode) && S_ISDIR(f1->linkedmode));
+ int d2 = S_ISDIR(f2->info.st_mode) || (S_ISLNK(f2->info.st_mode) && S_ISDIR(f2->linkedmode));
+ if (d1 != d2) return d2 - d1;
}
if (sort == SORT_NAME) {
const char *p1 = f1->name, *p2 = f2->name;
@@ -591,9 +590,10 @@ void clear_selection(bb_t *bb)
{
for (entry_t *next, *e = bb->firstselected; e; e = next) {
next = e->next;
+ *(e->atme) = NULL;
+ e->atme = NULL;
if (--e->refcount <= 0) free(e);
}
- bb->firstselected = NULL;
}
int select_file(bb_t *bb, entry_t *e)
@@ -663,6 +663,32 @@ void set_scroll(bb_t *bb, int newscroll)
if (bb->cursor < 0) bb->cursor = 0;
}
+entry_t *load_entry(const char *path)
+{
+ ssize_t linkpathlen = -1;
+ char linkbuf[PATH_MAX];
+ struct stat linkedstat, filestat;
+ if (lstat(path, &filestat) == -1) return NULL;
+ if (S_ISLNK(filestat.st_mode)) {
+ linkpathlen = readlink(path, linkbuf, sizeof(linkbuf));
+ if (linkpathlen < 0) err("Couldn't read link: '%s'", path);
+ linkbuf[linkpathlen] = 0;
+ if (stat(path, &linkedstat) == -1) memset(&linkedstat, 0, sizeof(linkedstat));
+ }
+ size_t entry_size = sizeof(entry_t) + (strlen(path) + 1) + (size_t)(linkpathlen + 1);
+ entry_t *entry = memcheck(calloc(entry_size, 1));
+ char *end = stpcpy(entry->fullname, path);
+ if (linkpathlen >= 0)
+ entry->linkname = strcpy(end + 1, linkbuf);
+ entry->name = strrchr(entry->fullname, '/');
+ if (!entry->name) err("No slash found in '%s'", entry->fullname);
+ ++entry->name;
+ if (S_ISLNK(filestat.st_mode))
+ entry->linkedmode = linkedstat.st_mode;
+ entry->info = filestat;
+ return entry;
+}
+
void populate_files(bb_t *bb, const char *path)
{
ino_t old_inode = 0;
@@ -707,7 +733,9 @@ void populate_files(bb_t *bb, const char *path)
err("Couldn't open dir: %s", bb->path);
size_t pathlen = strlen(bb->path);
size_t filecap = 0;
- char linkbuf[PATH_MAX+1];
+ char pathbuf[PATH_MAX];
+ strcpy(pathbuf, path);
+ pathbuf[pathlen] = '/';
for (struct dirent *dp; (dp = readdir(dir)) != NULL; ) {
if (dp->d_name[0] == '.' && dp->d_name[1] == '\0')
continue;
@@ -729,35 +757,10 @@ void populate_files(bb_t *bb, const char *path)
}
}
- ssize_t linkpathlen = -1;
- linkbuf[0] = 0;
- if (dp->d_type == DT_LNK) {
- linkpathlen = readlink(dp->d_name, linkbuf, sizeof(linkbuf));
- linkbuf[linkpathlen] = 0;
- }
-
- entry_t *entry = memcheck(calloc(sizeof(entry_t) + pathlen + strlen(dp->d_name) + 2 + (size_t)(linkpathlen + 1), 1));
- if (pathlen > PATH_MAX) err("Path is too big");
- strncpy(entry->fullname, bb->path, pathlen);
- entry->fullname[pathlen] = '/';
- entry->name = &entry->fullname[pathlen + 1];
- strcpy(entry->name, dp->d_name);
- if (linkpathlen >= 0) {
- entry->linkname = entry->name + strlen(dp->d_name) + 2;
- strncpy(entry->linkname, linkbuf, linkpathlen+1);
- }
- entry->needs_esc = 1; // populate on-the-fly
- entry->link_needs_esc = 1; // populate on-the-fly
- entry->isdir = dp->d_type == DT_DIR;
- entry->islink = dp->d_type == DT_LNK;
+ strcpy(&pathbuf[pathlen+1], dp->d_name);
+ entry_t *entry = load_entry(pathbuf);
+ if (!entry) err("Failed to load entry: '%s'", pathbuf);
++entry->refcount;
- if (!entry->isdir && entry->islink) {
- struct stat statbuf;
- if (stat(entry->fullname, &statbuf) == 0)
- entry->isdir = S_ISDIR(statbuf.st_mode);
- }
- entry->next = NULL; entry->atme = NULL;
- lstat(entry->fullname, &entry->info);
bb->files[bb->nfiles++] = entry;
next_file:
continue;
@@ -791,9 +794,12 @@ void sort_files(bb_t *bb)
if (bb->options['s'] == SORT_RANDOM) {
entry_t **files = &bb->files[1];
int ndirs = 0, nents = bb->nfiles - 1;
- for (int i = 0; i < nents; i++) {
- if (bb->files[i]->isdir) ++ndirs;
- else break;
+ if (!bb->options['i']) {
+ for (int i = 0; i < nents; i++) {
+ if (S_ISDIR(bb->files[i]->info.st_mode)
+ || (S_ISLNK(bb->files[i]->info.st_mode) && S_ISDIR(bb->files[i]->linkedmode)))
+ ++ndirs;
+ }
}
for (int i = 0; i < ndirs - 1; i++) {
int j = i + rand() / (RAND_MAX / (ndirs - 1 - i));
@@ -816,8 +822,7 @@ void sort_files(bb_t *bb)
}
}
-static enum { BB_NOP = 0, BB_INVALID, BB_REFRESH, BB_DIRTY, BB_QUIT }
-execute_cmd(bb_t *bb, const char *cmd)
+bb_result_t execute_cmd(bb_t *bb, const char *cmd)
{
char *value = strchr(cmd, ':');
if (value) ++value;
@@ -857,7 +862,7 @@ execute_cmd(bb_t *bb, const char *cmd)
if (f >= 0) select_file(bb, bb->files[f]);
// TODO: support selecting files in other directories
}
- return BB_DIRTY;
+ return BB_REFRESH;
}
case 'c': { // cd:
char pbuf[PATH_MAX];
@@ -898,7 +903,7 @@ execute_cmd(bb_t *bb, const char *cmd)
entry_t *e = bb->files[f];
if (IS_SELECTED(e)) deselect_file(bb, e);
else select_file(bb, e);
- return f == bb->cursor ? BB_NOP : BB_DIRTY;
+ return f == bb->cursor ? BB_NOP : BB_REFRESH;
}
case 'o': { // options:
if (!value) return BB_INVALID;
@@ -923,12 +928,12 @@ execute_cmd(bb_t *bb, const char *cmd)
if (!value) value = bb->files[bb->cursor]->name;
if (strcmp(value, "*") == 0) {
clear_selection(bb);
- return BB_DIRTY;
+ return BB_REFRESH;
} else {
int f = find_file(bb, value);
if (f >= 0)
select_file(bb, bb->files[f]);
- return f == bb->cursor ? BB_NOP : BB_DIRTY;
+ return f == bb->cursor ? BB_NOP : BB_REFRESH;
}
case 'g': { // goto:
@@ -987,7 +992,7 @@ execute_cmd(bb_t *bb, const char *cmd)
deselect_file(bb, bb->files[i]);
}
if (abs(oldcur - bb->cursor) > 1)
- return BB_DIRTY;
+ return BB_REFRESH;
}
return BB_NOP;
}
@@ -1002,7 +1007,7 @@ execute_cmd(bb_t *bb, const char *cmd)
}
return BB_INVALID;
}
- default: break;
+ default: err("UNKNOWN COMMAND: '%s'", cmd); break;
}
return BB_INVALID;
}
@@ -1066,9 +1071,9 @@ void explore(bb_t *bb, const char *path)
switch (execute_cmd(bb, cmd)) {
case BB_INVALID:
break;
- case BB_DIRTY:
- lazy = 0;
case BB_NOP:
+ free(cmd);
+ fclose(cmdfile);
goto redraw;
case BB_REFRESH:
free(cmd);
@@ -1090,7 +1095,7 @@ void explore(bb_t *bb, const char *path)
int key;
get_keyboard_input:
- key = term_getkey(fileno(tty_in), &mouse_x, &mouse_y, KEY_DELAY);
+ key = bgetkey(tty_in, &mouse_x, &mouse_y, KEY_DELAY);
switch (key) {
case KEY_MOUSE_LEFT: {
struct timespec clicktime;
@@ -1137,10 +1142,8 @@ void explore(bb_t *bb, const char *path)
goto quit; // Unreachable
case KEY_CTRL_Z:
+ fputs(T_OFF(T_ALT_SCREEN), tty_out);
close_term();
- fputs(T_OFF(T_ALT_SCREEN), stdout);
- fputs(T_ON(T_WRAP), stdout);
- fflush(stdout);
raise(SIGTSTP);
init_term();
fputs(T_ON(T_ALT_SCREEN), tty_out);
@@ -1150,11 +1153,11 @@ void explore(bb_t *bb, const char *path)
case KEY_CTRL_H: {
move_cursor(tty_out, 0,termheight-1);
fputs("\033[K\033[33;1mPress any key...\033[0m", tty_out);
- while ((key = term_getkey(fileno(tty_in), &mouse_x, &mouse_y, 1000)) == -1)
+ while ((key = bgetkey(tty_in, &mouse_x, &mouse_y, 1000)) == -1)
;
move_cursor(tty_out, 0,termheight-1);
fputs("\033[K\033[1m<\033[33m", tty_out);
- const char *name = keyname(key);
+ const char *name = bkeyname(key);
if (name) fputs(name, tty_out);
else if (' ' <= key && key <= '~')
fputc((char)key, tty_out);
@@ -1207,7 +1210,6 @@ void explore(bb_t *bb, const char *path)
if (binding->command[0] == '+') {
switch (execute_cmd(bb, binding->command + 1)) {
case BB_INVALID: break;
- case BB_DIRTY: lazy = 0;
case BB_NOP: goto redraw;
case BB_REFRESH: goto refresh;
case BB_QUIT: goto quit;
@@ -1215,14 +1217,15 @@ void explore(bb_t *bb, const char *path)
goto get_keyboard_input;
}
move_cursor(tty_out, 0, termheight-1);
- close_term();
if (binding->flags & NORMAL_TERM) {
- fputs(T_OFF(T_ALT_SCREEN), stdout);
- fputs(T_ON(T_WRAP), stdout);
- fflush(stdout);
+ fputs(T_OFF(T_ALT_SCREEN), tty_out);
+ fputs(T_ON(T_WRAP), tty_out);
}
- if (binding->flags & SHOW_CURSOR)
- fputs(T_ON(T_SHOW_CURSOR), stdout);
+ if (binding->flags & AT_CURSOR && !bb->firstselected) {
+ move_cursor(tty_out, 0, 3 + bb->cursor - bb->scroll);
+ fputs("\033[K", tty_out);
+ }
+ close_term();
run_cmd_on_selection(bb, binding->command);
init_term();
if (binding->flags & NORMAL_TERM)
@@ -1239,9 +1242,6 @@ void explore(bb_t *bb, const char *path)
populate_files(bb, NULL);
fputs(T_LEAVE_BBMODE, tty_out);
close_term();
- fputs(T_OFF(T_ALT_SCREEN), stdout);
- fputs(T_ON(T_WRAP), stdout);
- fflush(stdout);
}
void print_bindings(int verbose)
@@ -1259,7 +1259,7 @@ void print_bindings(int verbose)
for (int j = 0; bindings[i].keys[j]; j++) {
if (j > 0) *(p++) = ',';
int key = bindings[i].keys[j];
- const char *name = keyname(key);
+ const char *name = bkeyname(key);
if (name)
p = stpcpy(p, name);
else if (' ' <= key && key <= '~')
@@ -1327,6 +1327,17 @@ int main(int argc, char *argv[])
int i;
for (i = 1; i < argc; i++) {
+ if (argv[i][0] == '?') {
+ fclose(cmdfile);
+ init_term();
+ char *line = breadline(tty_in, tty_out, argv[i] + 1, argv[i+1]);
+ close_term();
+ if (!line) return 1;
+ fputs(line, stdout);
+ free(line);
+ fflush(stdout);
+ return 0;
+ }
if (argv[i][0] == '+') {
fwrite(argv[i]+1, sizeof(char), strlen(argv[i]+1)+1, cmdfile);
continue;
@@ -1371,9 +1382,13 @@ int main(int argc, char *argv[])
if (cmdfile)
fclose(cmdfile);
- if (!initial_path) {
+ if (!initial_path)
return 0;
- }
+
+ // Default values
+ setenv("SHELL", "bash", 0);
+ setenv("EDITOR", "nano", 0);
+ setenv("PAGER", "less", 0);
signal(SIGTERM, cleanup_and_exit);
signal(SIGINT, cleanup_and_exit);
diff --git a/bterm.h b/bterm.h
new file mode 100644
index 0000000..faf6efa
--- /dev/null
+++ b/bterm.h
@@ -0,0 +1,431 @@
+/*
+ * bterm.h
+ * Copyright 2019 Bruce Hill
+ * Released under the MIT License
+ *
+ * Definitions of some basic terminal stuff, like reading keys
+ * and some terminal escape sequences.
+ */
+
+#ifndef FILE__BTERM_H
+#define FILE__BTERM_H
+
+#include <poll.h>
+#include <stdio.h>
+
+#define KEY_F1 (0xFFFF-0)
+#define KEY_F2 (0xFFFF-1)
+#define KEY_F3 (0xFFFF-2)
+#define KEY_F4 (0xFFFF-3)
+#define KEY_F5 (0xFFFF-4)
+#define KEY_F6 (0xFFFF-5)
+#define KEY_F7 (0xFFFF-6)
+#define KEY_F8 (0xFFFF-7)
+#define KEY_F9 (0xFFFF-8)
+#define KEY_F10 (0xFFFF-9)
+#define KEY_F11 (0xFFFF-10)
+#define KEY_F12 (0xFFFF-11)
+#define KEY_INSERT (0xFFFF-12)
+#define KEY_DELETE (0xFFFF-13)
+#define KEY_HOME (0xFFFF-14)
+#define KEY_END (0xFFFF-15)
+#define KEY_PGUP (0xFFFF-16)
+#define KEY_PGDN (0xFFFF-17)
+#define KEY_ARROW_UP (0xFFFF-18)
+#define KEY_ARROW_DOWN (0xFFFF-19)
+#define KEY_ARROW_LEFT (0xFFFF-20)
+#define KEY_ARROW_RIGHT (0xFFFF-21)
+#define KEY_MOUSE_LEFT (0xFFFF-22)
+#define KEY_MOUSE_RIGHT (0xFFFF-23)
+#define KEY_MOUSE_MIDDLE (0xFFFF-24)
+#define KEY_MOUSE_RELEASE (0xFFFF-25)
+#define KEY_MOUSE_WHEEL_UP (0xFFFF-26)
+#define KEY_MOUSE_WHEEL_DOWN (0xFFFF-27)
+#define KEY_MOUSE_DOUBLE_LEFT (0xFFFF-28)
+
+/* These are all ASCII code points below SPACE character and a BACKSPACE key. */
+#define KEY_CTRL_TILDE 0x00
+#define KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */
+#define KEY_CTRL_A 0x01
+#define KEY_CTRL_B 0x02
+#define KEY_CTRL_C 0x03
+#define KEY_CTRL_D 0x04
+#define KEY_CTRL_E 0x05
+#define KEY_CTRL_F 0x06
+#define KEY_CTRL_G 0x07
+#define KEY_BACKSPACE 0x08
+#define KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */
+#define KEY_TAB 0x09
+#define KEY_CTRL_I 0x09 /* clash with 'TAB' */
+#define KEY_CTRL_J 0x0A
+#define KEY_CTRL_K 0x0B
+#define KEY_CTRL_L 0x0C
+#define KEY_ENTER 0x0D
+#define KEY_CTRL_M 0x0D /* clash with 'ENTER' */
+#define KEY_CTRL_N 0x0E
+#define KEY_CTRL_O 0x0F
+#define KEY_CTRL_P 0x10
+#define KEY_CTRL_Q 0x11
+#define KEY_CTRL_R 0x12
+#define KEY_CTRL_S 0x13
+#define KEY_CTRL_T 0x14
+#define KEY_CTRL_U 0x15
+#define KEY_CTRL_V 0x16
+#define KEY_CTRL_W 0x17
+#define KEY_CTRL_X 0x18
+#define KEY_CTRL_Y 0x19
+#define KEY_CTRL_Z 0x1A
+#define KEY_ESC 0x1B
+#define KEY_CTRL_LSQ_BRACKET 0x1B /* clash with 'ESC' */
+#define KEY_CTRL_3 0x1B /* clash with 'ESC' */
+#define KEY_CTRL_4 0x1C
+#define KEY_CTRL_BACKSLASH 0x1C /* clash with 'CTRL_4' */
+#define KEY_CTRL_5 0x1D
+#define KEY_CTRL_RSQ_BRACKET 0x1D /* clash with 'CTRL_5' */
+#define KEY_CTRL_6 0x1E
+#define KEY_CTRL_7 0x1F
+#define KEY_CTRL_SLASH 0x1F /* clash with 'CTRL_7' */
+#define KEY_CTRL_UNDERSCORE 0x1F /* clash with 'CTRL_7' */
+#define KEY_SPACE 0x20
+#define KEY_BACKSPACE2 0x7F
+#define KEY_CTRL_8 0x7F /* clash with 'BACKSPACE2' */
+
+
+// Terminal escape sequences:
+#define CSI "\033["
+#define T_WRAP "7"
+#define T_SHOW_CURSOR "25"
+#define T_MOUSE_XY "1000"
+#define T_MOUSE_CELL "1002"
+#define T_MOUSE_SGR "1006"
+#define T_ALT_SCREEN "1049"
+#define T_ON(opt) CSI "?" opt "h"
+#define T_OFF(opt) CSI "?" opt "l"
+
+#define move_cursor(f, x, y) fprintf((f), CSI "%d;%dH", (int)(y)+1, (int)(x)+1)
+
+
+#define ESC_DELAY 10
+
+int bgetkey(FILE *in, int *mouse_x, int *mouse_y, int timeout);
+const char *bkeyname(int key);
+char *breadline(FILE *in, FILE *out, const char *prompt, const char *initial);
+
+static inline int nextchar(int fd, int timeout)
+{
+ char c;
+ struct pollfd pfd = {fd, POLLIN, 0};
+ if (poll(&pfd, 1, timeout) > 0)
+ return read(fd, &c, 1) == 1 ? c : -1;
+ return -1;
+}
+
+/**
+ * Get one key of input from the given file. Returns -1 on failure.
+ *
+ * If mouse_x or mouse_y are non-null and a mouse event occurs, they will be
+ * set to the position of the mouse (0-indexed).
+ */
+int bgetkey(FILE *in, int *mouse_x, int *mouse_y, int timeout)
+{
+ int fd = fileno(in);
+ int numcode = 0, super = 0;
+ int c = nextchar(fd, timeout);
+ if (c == '\x1b')
+ goto escape;
+
+ return c;
+
+ escape:
+ c = nextchar(fd, ESC_DELAY);
+ // Actual escape key:
+ if (c < 0)
+ return KEY_ESC;
+
+ switch (c) {
+ case '\x1b': ++super; goto escape;
+ case '[': goto CSI_start;
+ case 'P': goto DCS;
+ case 'O': goto SS3;
+ default: return -1;
+ }
+
+ CSI_start:
+ c = nextchar(fd, ESC_DELAY);
+ if (c == -1)
+ return -1;
+
+ switch (c) {
+ case 'A': return KEY_ARROW_UP;
+ case 'B': return KEY_ARROW_DOWN;
+ case 'C': return KEY_ARROW_RIGHT;
+ case 'D': return KEY_ARROW_LEFT;
+ case 'F': return KEY_END;
+ case 'H': return KEY_HOME;
+ case '~':
+ switch (numcode) {
+ case 3: return KEY_DELETE;
+ case 5: return KEY_PGUP;
+ case 6: return KEY_PGDN;
+ case 15: return KEY_F5;
+ case 17: return KEY_F6;
+ case 18: return KEY_F7;
+ case 19: return KEY_F8;
+ case 20: return KEY_F9;
+ case 21: return KEY_F10;
+ case 23: return KEY_F11;
+ case 24: return KEY_F12;
+ }
+ return -1;
+ case '<': { // Mouse clicks
+ int buttons = 0, x = 0, y = 0;
+ char buf;
+ while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
+ buttons = buttons * 10 + (buf - '0');
+ if (buf != ';') return -1;
+ while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
+ x = x * 10 + (buf - '0');
+ if (buf != ';') return -1;
+ while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
+ y = y * 10 + (buf - '0');
+ if (buf != 'm' && buf != 'M') return -1;
+
+ if (mouse_x) *mouse_x = x - 1;
+ if (mouse_y) *mouse_y = y - 1;
+
+ if (buf == 'm')
+ return KEY_MOUSE_RELEASE;
+ switch (buttons) {
+ case 64: return KEY_MOUSE_WHEEL_UP;
+ case 65: return KEY_MOUSE_WHEEL_DOWN;
+ case 0: return KEY_MOUSE_LEFT;
+ case 1: return KEY_MOUSE_RIGHT;
+ case 2: return KEY_MOUSE_MIDDLE;
+ default: return -1;
+ }
+ break;
+ }
+ default:
+ if ('0' <= c && c <= '9') {
+ // Ps prefix
+ numcode = 10*numcode + (c - '0');
+ goto CSI_start;
+ }
+ }
+ return -1;
+
+ DCS:
+ return -1;
+
+ SS3:
+ switch (nextchar(fd, ESC_DELAY)) {
+ case 'P': return KEY_F1;
+ case 'Q': return KEY_F2;
+ case 'R': return KEY_F3;
+ case 'S': return KEY_F4;
+ default: break;
+ }
+ return -1;
+}
+
+/**
+ * Return the name of a key, if one exists and is different from the key itself
+ * (i.e. bkeyname('c') == NULL, bkeyname(' ') == "Space")
+ */
+const char *bkeyname(int key)
+{
+ // TODO: currently only the keys I'm using are named
+ switch (key) {
+ case KEY_F1: return "F1";
+ case KEY_F2: return "F2";
+ case KEY_F3: return "F3";
+ case KEY_F4: return "F4";
+ case KEY_F5: return "F5";
+ case KEY_F6: return "F6";
+ case KEY_F7: return "F7";
+ case KEY_F8: return "F8";
+ case KEY_F9: return "F9";
+ case KEY_F10: return "F10";
+ case KEY_F11: return "F11";
+ case KEY_F12: return "F12";
+ case KEY_INSERT: return "Insert";
+ case KEY_DELETE: return "Delete";
+ case KEY_HOME: return "Home";
+ case KEY_END: return "End";
+ case KEY_PGUP: return "PgUp";
+ case KEY_PGDN: return "PgDn";
+ case KEY_ARROW_UP: return "Up";
+ case KEY_ARROW_DOWN: return "Down";
+ case KEY_ARROW_LEFT: return "Left";
+ case KEY_ARROW_RIGHT: return "Right";
+ case KEY_MOUSE_LEFT: return "Left click";
+ case KEY_MOUSE_RIGHT: return "Right click";
+ case KEY_MOUSE_MIDDLE: return "Middle click";
+ case KEY_MOUSE_RELEASE: return "Mouse release";
+ case KEY_MOUSE_WHEEL_UP: return "Mouse wheel up";
+ case KEY_MOUSE_WHEEL_DOWN: return "Mouse wheel down";
+ case KEY_MOUSE_DOUBLE_LEFT: return "Mouse double left";
+ case KEY_CTRL_TILDE: return "Ctrl-[2~]";
+ case KEY_CTRL_A: return "Ctrl-a";
+ case KEY_CTRL_B: return "Ctrl-b";
+ case KEY_CTRL_C: return "Ctrl-c";
+ case KEY_CTRL_D: return "Ctrl-d";
+ case KEY_CTRL_E: return "Ctrl-e";
+ case KEY_CTRL_F: return "Ctrl-f";
+ case KEY_CTRL_G: return "Ctrl-g";
+ case KEY_BACKSPACE: return "Backspace";
+ case KEY_TAB: return "Tab";
+ case KEY_CTRL_J: return "Ctrl-j";
+ case KEY_CTRL_K: return "Ctrl-k";
+ case KEY_CTRL_L: return "Ctrl-l";
+ case KEY_ENTER: return "Enter";
+ case KEY_CTRL_N: return "Ctrl-n";
+ case KEY_CTRL_O: return "Ctrl-o";
+ case KEY_CTRL_P: return "Ctrl-p";
+ case KEY_CTRL_Q: return "Ctrl-q";
+ case KEY_CTRL_R: return "Ctrl-r";
+ case KEY_CTRL_S: return "Ctrl-s";
+ case KEY_CTRL_T: return "Ctrl-t";
+ case KEY_CTRL_U: return "Ctrl-u";
+ case KEY_CTRL_V: return "Ctrl-v";
+ case KEY_CTRL_W: return "Ctrl-w";
+ case KEY_CTRL_X: return "Ctrl-x";
+ case KEY_CTRL_Y: return "Ctrl-y";
+ case KEY_CTRL_Z: return "Ctrl-z";
+ case KEY_ESC: return "Esc";
+ case KEY_CTRL_BACKSLASH: return "Ctrl-[4\\]";
+ case KEY_CTRL_RSQ_BRACKET: return "Ctrl-[5]]";
+ case KEY_CTRL_6: return "Ctrl-6";
+ case KEY_CTRL_SLASH: return "Ctrl-[7/_]";
+ case KEY_SPACE: return "Space";
+ case KEY_BACKSPACE2: return "Backspace";
+ }
+ return NULL;
+}
+
+/**
+ * A basic readline implementation. No history, but all the normal editing
+ * key bindings like Ctrl-U to clear to the beginning of the line, backspace,
+ * arrow keys, etc.
+ *
+ * Takes an input file and output file, and an optional prompt and returns
+ * a malloc'd string containing the user's input or NULL if an error occurred
+ * or if the user hit Escape or Ctrl-c.
+ */
+char *breadline(FILE *in, FILE *out, const char *prompt, const char *initial)
+{
+ size_t cap = initial ? strlen(initial) + 100 : 100;
+ char *buf = calloc(cap, 1);
+ if (!buf) return NULL;
+ int i = 0, len = 0;
+ if (prompt) {
+ fputs("\033[1G\033[K\033[37;1m", out);
+ fputs(prompt, out);
+ fputs("\033[0m", out);
+ }
+ if (initial) {
+ strcpy(buf, initial);
+ len = (int)strlen(initial);
+ i = len;
+ fputs(initial, out);
+ }
+ // Show cursor and set to blinking line:
+ fputs("\033[?25h\033[5 q", out);
+ while (1) {
+ fflush(out);
+ int key = bgetkey(in, NULL, NULL, -1);
+ switch (key) {
+ case -1: case -2: case -3: continue;
+ case '\r':
+ // TODO: support backslash-enter
+ goto finished;
+ case KEY_CTRL_C: case KEY_ESC:
+ free(buf);
+ buf = NULL;
+ goto finished;
+ case KEY_CTRL_A:
+ if (i > 0) {
+ fprintf(out, "\033[%dD", i);
+ i = 0;
+ }
+ break;
+ case KEY_CTRL_E:
+ if (i < len) {
+ fprintf(out, "\033[%dC", len-i);
+ i = len;
+ }
+ break;
+ case KEY_CTRL_U: {
+ int to_clear = i;
+ if (to_clear) {
+ memmove(buf, buf+i, len-i);
+ buf[len -= i] = 0;
+ i = 0;
+ fprintf(out, "\033[%dD\033[K", to_clear);
+ if (len > 0)
+ fprintf(out, "%s\033[%dD", buf, len);
+ }
+ break;
+ }
+ case KEY_CTRL_K:
+ if (i < len) {
+ buf[len = i] = 0;
+ fputs("\033[K", out);
+ }
+ break;
+ case KEY_BACKSPACE: case KEY_BACKSPACE2:
+ if (i > 0) {
+ memmove(buf+i, buf+i+1, len-i);
+ --i;
+ buf[--len] = 0;
+ if (i == len) fputs("\033[D \033[D", out);
+ else fprintf(out, "\033[D%s\033[K\033[%dD", buf+i, len-i);
+ }
+ break;
+ case KEY_DELETE: case KEY_CTRL_D:
+ if (i < len) {
+ memmove(buf+i, buf+i+1, len-i);
+ buf[--len] = 0;
+ if (i == len) fputs(" \033[D", out);
+ else fprintf(out, "%s\033[K\033[%dD", buf+i, len-i);
+ }
+ break;
+ case KEY_ARROW_LEFT: case KEY_CTRL_B:
+ if (i > 0) {
+ --i;
+ fputs("\033[D", out);
+ }
+ break;
+ case KEY_ARROW_RIGHT: case KEY_CTRL_F:
+ if (i < len) {
+ ++i;
+ fputs("\033[C", out);
+ }
+ break;
+ default:
+ if (' ' <= key && key <= '~') {
+ if (len + 1 >= (int)cap) {
+ cap += 100;
+ buf = realloc(buf, cap);
+ if (!buf) goto finished;
+ }
+ if (i < len)
+ memmove(buf+i+1, buf+i, len-i+1);
+ buf[i++] = (char)key;
+ buf[++len] = 0;
+ if (i == len)
+ fputc(key, out);
+ else
+ fprintf(out, "%c%s\033[%dD", (char)key, buf+i, len-i);
+ }
+ break;
+ }
+ }
+ finished:
+ // Reset cursor to block
+ fputs("\033[1G\033[1 q\033[2K", out);
+ return buf;
+}
+
+#endif
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
diff --git a/config.def.h b/config.def.h
index 6258605..c9e7647 100644
--- a/config.def.h
+++ b/config.def.h
@@ -13,8 +13,7 @@
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
+ $BBCURSOR: the full 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)
@@ -62,28 +61,33 @@
half a screen height down, and 100%n means the last file)
*/
-#include "keys.h"
+#include "bterm.h"
// Configurable options:
#define KEY_DELAY 50
#define SCROLLOFF MIN(5, (termheight-4)/2)
+#define QUOTE(s) #s
+
#define CMDFILE_FORMAT "/tmp/bb.XXXXXX"
#define SORT_INDICATOR "↓"
#define RSORT_INDICATOR "↑"
+#define SELECTED_INDICATOR "\033[33;1m★\033[0m"
+#define NOT_SELECTED_INDICATOR "\033[1m \033[0m"
-#define NORMAL_COLOR "\033[0m"
-#define CURSOR_COLOR "\033[0;30;43m"
-#define LINKDIR_COLOR "\033[0;36m"
-#define DIR_COLOR "\033[0;34m"
-#define LINK_COLOR "\033[0;33m"
+#define TITLE_COLOR "\033[32;1m"
+#define NORMAL_COLOR "\033[37m"
+#define CURSOR_COLOR "\033[1;30;43m"
+#define LINK_COLOR "\033[35m"
+#define DIR_COLOR "\033[34m"
+#define EXECUTABLE_COLOR "\033[31m"
#define PIPE_SELECTION_TO " printf '%s\\n' \"$@\" | "
-#define PAUSE " read -n1 -p '\033[2m...press any key to continue...\033[0m\033[?25l'"
+#define PAUSE " read -n1 -p '\033[2mPress any key to continue...\033[0m\033[?25l'"
#define NORMAL_TERM (1<<0)
-#define SHOW_CURSOR (1<<1)
+#define AT_CURSOR (1<<1)
#define MAX_REBINDINGS 8
@@ -108,6 +112,8 @@ const char *startupcmds[] = {
NULL, // NULL-terminated array
};
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
extern binding_t bindings[];
binding_t bindings[] = {
//////////////////////////////////////////////////////////////////////////
@@ -115,7 +121,7 @@ binding_t bindings[] = {
// 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},
+ {{'?'}, "bb -b | $PAGER -r", "Show the help menu", NORMAL_TERM},
{{KEY_CTRL_H}, "<placeholder>", "Figure out what key does"},
{{'q', 'Q'}, "+quit", "Quit"},
{{'k', KEY_ARROW_UP}, "+move:-1", "Move up"},
@@ -126,42 +132,60 @@ binding_t bindings[] = {
{{'e'}, "$EDITOR \"$@\"", "Edit file in $EDITOR", NORMAL_TERM},
{{'\r', KEY_MOUSE_DOUBLE_LEFT},
#ifdef __APPLE__
-"if test -d \"$BBCURSOR\"; then bb \"+cd:$BBCURSOR\";\n\
-elif test -x \"$BBCURSOR\"; then \"$BBCURSOR\";\n\
- read -n1 -p '\n\033[2m...press any key to continue...\033[0m';\n\
-elif file -bI \"$BBCURSOR\" | grep '^\\(text/\\|inode/empty\\)' >/dev/null; then $EDITOR \"$BBCURSOR\";\n\
-else open \"$BBCURSOR\"; fi",
+ QUOTE(
+if test -d "$BBCURSOR"; then bb "+cd:$BBCURSOR";
+elif test -x "$BBCURSOR"; then "$BBCURSOR"; read -p 'Press any key to continue...' -n1;
+elif file -bI "$BBCURSOR" | grep '^\(text/\|inode/empty\)' >/dev/null; then $EDITOR "$BBCURSOR";
+else open "$BBCURSOR"; fi
+ )/*ENDQUOTE*/,
#else
-"if test -d \"$BBCURSOR\"; then bb \"+cd:$BBCURSOR\";\n\
-elif file -bi \"$BBCURSOR\" | grep '^\\(text/\\|inode/empty\\)' >/dev/null; then $EDITOR \"$BBCURSOR\";\n\
-else xdg-open \"$BBCURSOR\"; fi",
+ QUOTE(
+if test -d "$BBCURSOR"; then bb "+cd:$BBCURSOR";
+elif file -bi "$BBCURSOR" | grep '^\(text/\|inode/empty\)' >/dev/null; then $EDITOR "$BBCURSOR";
+else xdg-open "$BBCURSOR"; fi
+ )/*ENDQUOTE*/,
#endif
"Open file", NORMAL_TERM},
{{'f'}, "bb \"+g:`fzf`\"", "Fuzzy search for file", NORMAL_TERM},
{{'/'}, "bb \"+g:`ls -a|fzf`\"", "Fuzzy select file", NORMAL_TERM},
- {{'L'}, PIPE_SELECTION_TO "less", "List all selected files", NORMAL_TERM},
- {{'d', KEY_DELETE}, "rm -rfi \"$@\"; bb '+d:*' +r", "Delete files", SHOW_CURSOR},
+ {{'L'}, PIPE_SELECTION_TO "$PAGER", "List all selected files", NORMAL_TERM},
+ {{'d', KEY_DELETE}, "rm -rfi \"$@\"; bb '+d:*' +r", "Delete files", AT_CURSOR},
{{'D'}, "rm -rf \"$@\"; bb '+d:*' +r", "Delete files without confirmation"},
- {{'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},
+ {{'M'}, "mv -i \"$@\" .; bb '+d:*' +r", "Move files to current folder"},
+ {{'c'}, "cp -i \"$@\" .; bb +r", "Copy files to current folder"},
{{'C'}, "for f; do cp \"$f\" \"$f.copy\"; done; bb +r", "Clone files"},
- {{'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",
- "Run a command", SHOW_CURSOR},
- {{'>'}, "sh", "Open a shell", NORMAL_TERM},
- {{'m'}, "read -n1 -p 'Mark: ' m && bb \"+mark:$m;$PWD\"", "Set mark", SHOW_CURSOR},
- {{'\''}, "read -n1 -p 'Jump: ' j && bb \"+jump:$j\"", "Jump to mark", SHOW_CURSOR},
- {{'r'}, "for f; do read -p \"Rename $f: \" renamed && mv \"$f\" \"$renamed\"; done; "
- "bb '+d:*' +r",
- "Rename files", SHOW_CURSOR},
- {{'R'}, "read -p 'Rename pattern: ' patt && "
- "for f; do mv -i \"$f\" \"`echo \"$f\" | sed \"$patt\"`\"; done &&" PAUSE,
- "Regex rename files", SHOW_CURSOR},
+ {{'n'}, "name=`bb '?New file: '` && touch \"$name\"; bb +r \"+goto:$name\"", "New file"},
+ {{'N'}, "name=`bb '?New dir: '` && mkdir \"$name\"; bb +r \"+goto:$name\"", "New folder"},
+ {{'|'}, "cmd=`bb '?|'` && " PIPE_SELECTION_TO "sh -c \"$cmd\" && " PAUSE "; bb +r",
+ "Pipe selected files to a command"},
+ {{':'}, "$SHELL -c \"`bb '?:'`\" -- \"$@\"; " PAUSE "; bb +refresh",
+ "Run a command"},
+ {{'>'}, "$SHELL", "Open a shell", NORMAL_TERM},
+ {{'m'}, "read -n1 -p 'Mark: ' m && bb \"+mark:$m;$PWD\"", "Set mark"},
+ {{'\''}, "read -n1 -p 'Jump: ' j && bb \"+jump:$j\"", "Jump to mark"},
+
+ {{'r'}, QUOTE(
+bb '+deselect:*' +refresh;
+for f; do
+ if renamed="$(dirname "$f")/$(bb '?Rename: ' "$(basename "$f")")" &&
+ test "$f" != "$renamed" && mv -i "$f" "$renamed"; then
+ test $BBSELECTED && bb "+select:$renamed";
+ elif test $BBSELECTED; then bb "+select:$f"; fi
+done)/*ENDQUOTE*/, "Rename files", AT_CURSOR},
+
+ {{'R'}, QUOTE(
+if patt="`bb '?Rename pattern: ' 's/'`"; then true; else bb +r; exit; fi;
+if sed -E "$patt" </dev/null; then true; else read -p 'Press any key to continue...' -n1; bb +r; exit; fi;
+bb '+deselect:*' +refresh;
+for f; do
+ renamed="`dirname "$f"`/`basename "$f" | sed -E "$patt"`";
+ if test "$f" != "$renamed" && mv -i "$f" "$renamed"; then
+ test $BBSELECTED && bb "+select:$renamed";
+ elif test $BBSELECTED; then bb "+select:$f"; fi
+done)/*ENDQUOTE*/, "Regex rename files", AT_CURSOR},
+
// TODO debug:
- {{'P'}, "read -p 'Select pattern: ' patt && "
+ {{'P'}, "patt=`bb '?Select pattern: '` && "
"for f; do echo \"$f\" | grep \"$patt\" >/dev/null 2>/dev/null && bb \"+sel:$f\"; done",
"Regex select files"},
{{'J'}, "+spread:+1", "Spread selection down"},
@@ -170,7 +194,8 @@ else xdg-open \"$BBCURSOR\"; fi",
"\033[1m(s)\033[22mize \033[1m(m)\033[22modification \033[1m(c)\033[22mcreation "
"\033[1m(a)\033[22maccess \033[1m(r)\033[22mandom \033[1m(p)\033[22mermissions:\033[0m ' sort "
"&& bb \"+opt:s=$sort\"", "Sort by..."},
- {{'#'}, "read -p 'Set columns: ' cols && bb \"+opt:`echo $cols 0 | fold -w1 | sed 10q | nl -v0 -s= -w1 | paste -sd '\\0' -`\"",
+ {{'#'}, "cols=`bb '?Set columns: '` && "
+ "bb \"+opt:`echo $cols 0 | fold -w1 | sed 10q | nl -v0 -s= -w1 | paste -sd '\\0' -`\"",
"Set columns"},
{{'.'}, "+opt:.~", "Toggle dotfiles"},
{{'g', KEY_HOME}, "+move:0", "Go to first file"},
@@ -186,3 +211,6 @@ else xdg-open \"$BBCURSOR\"; fi",
{{KEY_MOUSE_WHEEL_UP}, "+scroll:-3", "Scroll up"},
{0}, // Array must be 0-terminated
};
+#pragma clang diagnostic pop
+
+// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1
diff --git a/keys.h b/keys.h
deleted file mode 100644
index a7cb0ce..0000000
--- a/keys.h
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Definitions of some key character constants
- */
-
-#ifndef FILE__KEYS_H
-#define FILE__KEYS_H
-
-#include <poll.h>
-
-#define KEY_F1 (0xFFFF-0)
-#define KEY_F2 (0xFFFF-1)
-#define KEY_F3 (0xFFFF-2)
-#define KEY_F4 (0xFFFF-3)
-#define KEY_F5 (0xFFFF-4)
-#define KEY_F6 (0xFFFF-5)
-#define KEY_F7 (0xFFFF-6)
-#define KEY_F8 (0xFFFF-7)
-#define KEY_F9 (0xFFFF-8)
-#define KEY_F10 (0xFFFF-9)
-#define KEY_F11 (0xFFFF-10)
-#define KEY_F12 (0xFFFF-11)
-#define KEY_INSERT (0xFFFF-12)
-#define KEY_DELETE (0xFFFF-13)
-#define KEY_HOME (0xFFFF-14)
-#define KEY_END (0xFFFF-15)
-#define KEY_PGUP (0xFFFF-16)
-#define KEY_PGDN (0xFFFF-17)
-#define KEY_ARROW_UP (0xFFFF-18)
-#define KEY_ARROW_DOWN (0xFFFF-19)
-#define KEY_ARROW_LEFT (0xFFFF-20)
-#define KEY_ARROW_RIGHT (0xFFFF-21)
-#define KEY_MOUSE_LEFT (0xFFFF-22)
-#define KEY_MOUSE_RIGHT (0xFFFF-23)
-#define KEY_MOUSE_MIDDLE (0xFFFF-24)
-#define KEY_MOUSE_RELEASE (0xFFFF-25)
-#define KEY_MOUSE_WHEEL_UP (0xFFFF-26)
-#define KEY_MOUSE_WHEEL_DOWN (0xFFFF-27)
-#define KEY_MOUSE_DOUBLE_LEFT (0xFFFF-28)
-
-/* These are all ASCII code points below SPACE character and a BACKSPACE key. */
-#define KEY_CTRL_TILDE 0x00
-#define KEY_CTRL_2 0x00 /* clash with 'CTRL_TILDE' */
-#define KEY_CTRL_A 0x01
-#define KEY_CTRL_B 0x02
-#define KEY_CTRL_C 0x03
-#define KEY_CTRL_D 0x04
-#define KEY_CTRL_E 0x05
-#define KEY_CTRL_F 0x06
-#define KEY_CTRL_G 0x07
-#define KEY_BACKSPACE 0x08
-#define KEY_CTRL_H 0x08 /* clash with 'CTRL_BACKSPACE' */
-#define KEY_TAB 0x09
-#define KEY_CTRL_I 0x09 /* clash with 'TAB' */
-#define KEY_CTRL_J 0x0A
-#define KEY_CTRL_K 0x0B
-#define KEY_CTRL_L 0x0C
-#define KEY_ENTER 0x0D
-#define KEY_CTRL_M 0x0D /* clash with 'ENTER' */
-#define KEY_CTRL_N 0x0E
-#define KEY_CTRL_O 0x0F
-#define KEY_CTRL_P 0x10
-#define KEY_CTRL_Q 0x11
-#define KEY_CTRL_R 0x12
-#define KEY_CTRL_S 0x13
-#define KEY_CTRL_T 0x14
-#define KEY_CTRL_U 0x15
-#define KEY_CTRL_V 0x16
-#define KEY_CTRL_W 0x17
-#define KEY_CTRL_X 0x18
-#define KEY_CTRL_Y 0x19
-#define KEY_CTRL_Z 0x1A
-#define KEY_ESC 0x1B
-#define KEY_CTRL_LSQ_BRACKET 0x1B /* clash with 'ESC' */
-#define KEY_CTRL_3 0x1B /* clash with 'ESC' */
-#define KEY_CTRL_4 0x1C
-#define KEY_CTRL_BACKSLASH 0x1C /* clash with 'CTRL_4' */
-#define KEY_CTRL_5 0x1D
-#define KEY_CTRL_RSQ_BRACKET 0x1D /* clash with 'CTRL_5' */
-#define KEY_CTRL_6 0x1E
-#define KEY_CTRL_7 0x1F
-#define KEY_CTRL_SLASH 0x1F /* clash with 'CTRL_7' */
-#define KEY_CTRL_UNDERSCORE 0x1F /* clash with 'CTRL_7' */
-#define KEY_SPACE 0x20
-#define KEY_BACKSPACE2 0x7F
-#define KEY_CTRL_8 0x7F /* clash with 'BACKSPACE2' */
-
-
-#define ESC_DELAY 10
-
-int term_getkey(int fd, int *mouse_x, int *mouse_y, int timeout);
-const char *keyname(int key);
-
-static inline int nextchar(int fd, int timeout)
-{
- char c;
- struct pollfd pfd = {fd, POLLIN, 0};
- if (poll(&pfd, 1, timeout) > 0)
- return read(fd, &c, 1) == 1 ? c : -1;
- return -1;
-}
-
-int term_getkey(int fd, int *mouse_x, int *mouse_y, int timeout)
-{
- int c = nextchar(fd, timeout);
- if (c == '\x1b')
- goto escape;
-
- return c;
-
- escape:
- c = nextchar(fd, ESC_DELAY);
- // Actual escape key:
- if (c == -1)
- return KEY_ESC;
-
- switch (c) {
- case '[': goto CSI;
- case 'P': goto DCS;
- case 'O': goto SS3;
- default: return -1;
- }
-
- CSI:
- c = nextchar(fd, ESC_DELAY);
- if (c == -1)
- return -1;
-
- switch (c) {
- case 'A': return KEY_ARROW_UP;
- case 'B': return KEY_ARROW_DOWN;
- case 'C': return KEY_ARROW_RIGHT;
- case 'D': return KEY_ARROW_LEFT;
- case 'F': return KEY_END;
- case 'H': return KEY_HOME;
- case '1':
- switch (nextchar(fd, ESC_DELAY)) {
- case '5':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_F5 : -1;
- case '7':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_F6 : -1;
- case '8':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_F7 : -1;
- case '9':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_F8 : -1;
- default: return -1;
- }
- break;
- case '2':
- switch (nextchar(fd, ESC_DELAY)) {
- case '0':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_F9 : -1;
- case '1':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_F10 : -1;
- case '3':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_F11 : -1;
- case '4':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_F12 : -1;
- default: return -1;
- }
- break;
- case '3':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_DELETE : -1;
- case '5':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_PGUP : -1;
- case '6':
- return nextchar(fd, ESC_DELAY) == '~' ? KEY_PGDN : -1;
- case '<': { // Mouse clicks
- int buttons = 0, x = 0, y = 0;
- char buf;
- while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
- buttons = buttons * 10 + (buf - '0');
- if (buf != ';') return -1;
- while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
- x = x * 10 + (buf - '0');
- if (buf != ';') return -1;
- while (read(fd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
- y = y * 10 + (buf - '0');
- if (buf != 'm' && buf != 'M') return -1;
-
- *mouse_x = x - 1;
- *mouse_y = y - 1;
-
- if (buf == 'm')
- return KEY_MOUSE_RELEASE;
- switch (buttons) {
- case 64: return KEY_MOUSE_WHEEL_UP;
- case 65: return KEY_MOUSE_WHEEL_DOWN;
- case 0: return KEY_MOUSE_LEFT;
- case 1: return KEY_MOUSE_RIGHT;
- case 2: return KEY_MOUSE_MIDDLE;
- default: return -1;
- }
- break;
- }
- }
- return -1;
-
- DCS:
- return -1;
-
- SS3:
- switch (nextchar(fd, ESC_DELAY)) {
- case 'P': return KEY_F1;
- case 'Q': return KEY_F2;
- case 'R': return KEY_F3;
- case 'S': return KEY_F4;
- default: break;
- }
- return -1;
-}
-
-const char *keyname(int key)
-{
- // TODO: currently only the keys I'm using are named
- switch (key) {
- case ' ': return "Space";
- case '\r': return "Enter";
- case KEY_MOUSE_DOUBLE_LEFT: return "Doubleclick";
- case KEY_ARROW_UP: return "Up";
- case KEY_ARROW_DOWN: return "Down";
- case KEY_ARROW_LEFT: return "Left";
- case KEY_ARROW_RIGHT: return "Right";
- case KEY_HOME: return "Home";
- case KEY_END: return "End";
- case KEY_DELETE: return "Delete";
- case KEY_ESC: return "Escape";
- case KEY_F5: return "F5";
- case KEY_CTRL_A: return "Ctrl-a";
- case KEY_CTRL_D: return "Ctrl-d";
- case KEY_CTRL_H: return "Ctrl-h";
- case KEY_CTRL_R: return "Ctrl-r";
- case KEY_CTRL_U: return "Ctrl-u";
- case KEY_CTRL_T: return "Ctrl-t";
- case KEY_CTRL_W: return "Ctrl-w";
- case KEY_PGDN: return "PgDn";
- case KEY_PGUP: return "PgUp";
- case KEY_MOUSE_WHEEL_DOWN: return "Scroll down";
- case KEY_MOUSE_WHEEL_UP: return "Scroll up";
- }
- return NULL;
-}
-
-#endif
-// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1