aboutsummaryrefslogtreecommitdiff
path: root/bb.c
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2019-05-21 02:17:11 -0700
committerBruce Hill <bruce@bruce-hill.com>2019-05-21 02:17:11 -0700
commit9786f9a525c7fffea49379aae05c14af0db1107b (patch)
treeb6deab42734f5d36999bf36e10336f59bc02fc3f /bb.c
parent12e366c735fca4b96aa13230e1b40eaee198387a (diff)
Mostly working version with linked lists and regular arrays
Diffstat (limited to 'bb.c')
-rw-r--r--bb.c639
1 files changed, 357 insertions, 282 deletions
diff --git a/bb.c b/bb.c
index be3497f..14079f1 100644
--- a/bb.c
+++ b/bb.c
@@ -24,6 +24,7 @@
#define MIN(a,b) ((a) > (b) ? (b) : (a))
#define MAX_PATH 4096
#define writez(fd, str) write(fd, str, strlen(str))
+#define IS_SELECTED(p) ((p)->next || (p)->prev)
static const int SCROLLOFF = 5;
@@ -32,6 +33,27 @@ static int termfd;
static int width, height;
static int mouse_x, mouse_y;
+typedef struct entry_s {
+ struct entry_s *next, *prev;
+ int visible : 1;
+ ino_t d_ino;
+ __uint16_t d_reclen;
+ __uint8_t d_type;
+ __uint8_t d_namlen;
+ char *d_name;
+ char d_fullname[0];
+} entry_t;
+
+typedef struct {
+ char *path;
+ entry_t *firstselected, **files;
+ size_t nselected, nfiles;
+ int scroll, cursor;
+ struct timespec lastclick;
+} bb_state_t;
+
+static void err(const char *msg, ...);
+
static void update_term_size(void)
{
struct winsize sz = {0};
@@ -56,16 +78,20 @@ static void init_term()
tios.c_cc[VTIME] = 0;
tcsetattr(termfd, TCSAFLUSH, &tios);
// xterm-specific:
- writez(termfd, "\033[?1049h");
+ writez(termfd, "\e[?1049h");
update_term_size();
// Initiate mouse tracking:
writez(termfd, "\e[?1000h\e[?1002h\e[?1015h\e[?1006h");
+ // hide cursor
+ writez(termfd, "\e[?25l");
}
static void close_term()
{
// xterm-specific:
- writez(termfd, "\033[?1049l");
+ writez(termfd, "\e[?1049l");
+ // Show cursor:
+ writez(termfd, "\e[?25h");
tcsetattr(termfd, TCSAFLUSH, &orig_termios);
close(termfd);
}
@@ -75,7 +101,7 @@ static void err(const char *msg, ...)
close_term();
va_list args;
va_start(args, msg);
- fprintf(stderr, msg, args);
+ int len = fprintf(stderr, msg, args);
va_end(args);
if (errno)
fprintf(stderr, "\n%s", strerror(errno));
@@ -83,32 +109,42 @@ static void err(const char *msg, ...)
_exit(1);
}
-static pid_t run_cmd(int *readable_fd, int *writable_fd, const char *cmd, ...)
+static pid_t run_cmd(int *child_out, int *child_in, const char *cmd, ...)
{
- int fd[2];
+ int child_outfds[2], child_infds[2];
pid_t child;
- pipe(fd);
+ if (child_out)
+ pipe(child_outfds);
+ if (child_in)
+ pipe(child_infds);
if ((child = fork())) {
if (child == -1)
err("Failed to fork");
- if (writable_fd) *writable_fd = fd[0];
- else close(fd[0]);
- if (readable_fd) *readable_fd = fd[1];
- else close(fd[1]);
+ 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 {
- close(fd[0]);
- if (writable_fd)
- dup2(fd[1], STDIN_FILENO);
- if (readable_fd)
- dup2(fd[0], STDOUT_FILENO);
+ 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);
- vasprintf(&formatted_cmd, cmd, args);
+ int len = vasprintf(&formatted_cmd, cmd, args);
va_end(args);
if (formatted_cmd)
execlp("sh", "sh", "-c", formatted_cmd);
- err("Failed to execute command: %s", formatted_cmd);
+ err("Failed to execute command %d: '%s'", len, formatted_cmd);
_exit(0);
}
return child;
@@ -122,98 +158,75 @@ static void term_move(int x, int y)
write(termfd, buf, len);
}
-static int term_write(const char *str, ...)
-{
- char buf[1024] = {0};
- va_list args;
- va_start(args, str);
- int len = snprintf(buf, sizeof(buf), str, args);
- va_end(args);
- if (len > 0)
- write(termfd, buf, len);
- return len;
-
- /*
- char *formatted = NULL;
- va_list args;
- va_start(args, str);
- int ret = asprintf(&formatted, str, args);
- va_end(args);
- if (!formatted)
- err("failed to allocate for string format");
-
- write(termfd, formatted, ret);
- free(formatted);
- return ret;
- */
-}
-
-typedef struct {
- struct dirent entry;
- const char *path;
- int selected : 1;
-} entry_t;
-
-static void render(const char *path, entry_t *files, size_t nfiles, int cursor, int scroll, size_t nselected)
+static void render(bb_state_t *state)
{
writez(termfd, "\e[2J\e[0;1m"); // Clear, reset color + bold
term_move(0,0);
- writez(termfd, path);
+ writez(termfd, state->path);
writez(termfd, "\e[0m"); // Reset color
char fullpath[MAX_PATH];
- size_t pathlen = strlen(path);
- strncpy(fullpath, path, pathlen + 1);
- for (int i = scroll; i < scroll + height - 3 && i < nfiles; i++) {
+ size_t pathlen = strlen(state->path);
+ strncpy(fullpath, state->path, pathlen + 1);
+ entry_t **files = state->files;
+ for (int i = state->scroll; i < state->scroll + height - 3 && i < state->nfiles; i++) {
+ entry_t *entry = files[i];
+
int x = 0;
- int y = i - scroll + 1;
+ int y = i - state->scroll + 1;
term_move(x, y);
// Selection box:
- if (files[i].selected)
+ if (IS_SELECTED(entry))
writez(termfd, "\e[43m \e[0m"); // Yellow BG
else
writez(termfd, " ");
- if (i != cursor && files[i].entry.d_type & DT_DIR) {
+ if (i != state->cursor && entry->d_type & DT_DIR) {
writez(termfd, "\e[34m"); // Blue FG
}
- if (i == cursor) {
+ if (i == state->cursor) {
writez(termfd, "\e[7m"); // Reverse color
}
- // Filesize:
- struct stat info;
+ struct stat info = {0};
fullpath[pathlen] = '/';
- strncpy(fullpath + pathlen + 1, files[i].entry.d_name, files[i].entry.d_namlen);
- fullpath[pathlen + 1 + files[i].entry.d_namlen] = '\0';
+ strncpy(fullpath + pathlen + 1, entry->d_name, entry->d_namlen);
+ fullpath[pathlen + 1 + entry->d_namlen] = '\0';
lstat(fullpath, &info);
- int j = 0;
- const char* units[] = {"B", "K", "M", "G", "T", "P", "E", "Z", "Y"};
- int bytes = info.st_size;
- while (bytes > 1024) {
- bytes /= 1024;
- j++;
+ {
+ // Filesize:
+ int j = 0;
+ const char* units = "BKMGTPEZY";
+ double bytes = (double)info.st_size;
+ while (bytes > 1024) {
+ bytes /= 1024;
+ j++;
+ }
+ char buf[16] = {0};
+ sprintf(buf, "%6.*f%c ", j > 0 ? 1 : 0, bytes, units[j]);
+ writez(termfd, buf);
}
- //term_write("%10.*f%s ", j, bytes, units[j]);
- // Date:
- char buf[64];
- strftime(buf, sizeof(buf), "%l:%M%p %b %e %Y", localtime(&(info.st_mtime)));
- writez(termfd, buf);
- writez(termfd, " ");
+ {
+ // Date:
+ char buf[64];
+ strftime(buf, sizeof(buf), "%l:%M%p %b %e %Y", localtime(&(info.st_mtime)));
+ writez(termfd, buf);
+ writez(termfd, " ");
+ }
// Name:
- write(termfd, files[i].entry.d_name, files[i].entry.d_namlen);
+ write(termfd, entry->d_name, entry->d_namlen);
- if (files[i].entry.d_type & DT_DIR) {
+ if (entry->d_type & DT_DIR) {
writez(termfd, "/");
}
- if (files[i].entry.d_type == DT_LNK) {
+ if (entry->d_type == DT_LNK) {
char linkpath[MAX_PATH] = {0};
ssize_t pathlen;
- if ((pathlen = readlink(files[i].entry.d_name, linkpath, sizeof(linkpath))) < 0)
+ if ((pathlen = readlink(entry->d_name, linkpath, sizeof(linkpath))) < 0)
err("readlink() failed");
writez(termfd, "\e[36m -> "); // Cyan FG
write(termfd, linkpath, pathlen);
@@ -223,23 +236,23 @@ static void render(const char *path, entry_t *files, size_t nfiles, int cursor,
term_move(0, height - 1);
char buf[32] = {0};
- int len = snprintf(buf, sizeof(buf), "%lu selected", nselected);
+ int len = snprintf(buf, sizeof(buf), "%lu selected", state->nselected);
write(termfd, buf, len);
}
static int compare_alpha(const void *v1, const void *v2)
{
- const entry_t *f1 = (const entry_t*)v1, *f2 = (const entry_t*)v2;
+ const entry_t *f1 = *((const entry_t**)v1), *f2 = *((const entry_t**)v2);
int diff;
- diff = (f1->entry.d_type & DT_DIR) - (f2->entry.d_type & DT_DIR);
+ diff = (f1->d_type & DT_DIR) - (f2->d_type & DT_DIR);
if (diff) return -diff;
- const char *p1 = f1->entry.d_name, *p2 = f2->entry.d_name;
+ const char *p1 = f1->d_name, *p2 = f2->d_name;
while (*p1 && *p2) {
int diff = (*p1 - *p2);
if ('0' <= *p1 && *p1 <= '9' && '0' <= *p2 && *p2 <= '9') {
long n1 = strtol(p1, (char**)&p1, 10);
long n2 = strtol(p2, (char**)&p2, 10);
- diff = ((p1 - f1->entry.d_name) - (p2 - f2->entry.d_name)) || (n1 - n2);
+ diff = ((p1 - f1->d_name) - (p2 - f2->d_name)) || (n1 - n2);
if (diff) return diff;
} else if (diff) {
return diff;
@@ -250,34 +263,20 @@ static int compare_alpha(const void *v1, const void *v2)
return *p1 - *p2;
}
-static void write_selection(int fd, entry_t *selected, size_t nselected, size_t ndeselected)
+static void write_selection(int fd, entry_t *firstselected)
{
- for (int i = 0; i < nselected + ndeselected; i++) {
- if (!selected[i].selected)
- continue;
-
- const char *p = selected[i].path;
- while (*p) {
- const char *p2 = strchr(p, '\n');
- if (!p2) p2 = p + strlen(p) + 1;
- write(fd, p, p2 - p);
- if (*p2 == '\n')
- write(fd, "\\", 1);
- p = p2;
- }
-
- write(fd, "/", 1);
-
- p = selected[i].entry.d_name;
+ while (firstselected) {
+ const char *p = firstselected->d_fullname;
while (*p) {
const char *p2 = strchr(p, '\n');
- if (!p2) p2 = p + strlen(p) + 1;
+ if (!p2) p2 = p + strlen(p);
write(fd, p, p2 - p);
if (*p2 == '\n')
write(fd, "\\", 1);
p = p2;
}
write(fd, "\n", 1);
+ firstselected = firstselected->next;
}
}
@@ -286,140 +285,171 @@ static int term_get_event()
char c;
if (read(termfd, &c, 1) != 1)
return -1;
+
+ if (c != '\x1b')
+ return c;
+
+ // Actual escape key:
+ if (read(termfd, &c, 1) != 1)
+ return KEY_ESC;
+
switch (c) {
- case '\x1b':
+ case '[':
if (read(termfd, &c, 1) != 1)
return -1;
switch (c) {
- case '[':
- if (read(termfd, &c, 1) != 1)
- return -1;
- switch (c) {
- case 'H': return KEY_HOME;
- case 'F': return KEY_END;
- case 'M':
- {
- char buf[7] = {0};
- if (read(termfd, buf, 6) != 6)
- return -1;
- unsigned char buttons, x, y;
- if (sscanf(buf, "%c%c%c", &buttons, &x, &y) != 3)
- return -1;
-
- mouse_x = (int)x - 32, mouse_y = (int)y - 32;
- switch (buttons) {
- case 0: return KEY_MOUSE_LEFT;
- case 1: return KEY_MOUSE_RIGHT;
- case 2: return KEY_MOUSE_MIDDLE;
- case 3: return KEY_MOUSE_RELEASE;
- default: return -1;
- }
- }
- break;
- default:
- break;
+ case 'H': return KEY_HOME;
+ case 'F': return KEY_END;
+ case '<': // Mouse clicks
+ {
+ int buttons = 0, x = 0, y = 0;
+ char buf;
+ while (read(termfd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
+ buttons = buttons * 10 + (buf - '0');
+ if (buf != ';') return -1;
+ while (read(termfd, &buf, 1) == 1 && '0' <= buf && buf <= '9')
+ x = x * 10 + (buf - '0');
+ if (buf != ';') return -1;
+ while (read(termfd, &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 0: return KEY_MOUSE_LEFT;
+ case 1: return KEY_MOUSE_RIGHT;
+ case 2: return KEY_MOUSE_MIDDLE;
+ default: return -1;
+ }
}
- return '\x1b';
+ break;
default:
- return '\x1b';
+ break;
}
- break;
+ return -1;
default:
- return c;
+ return -1;
}
return -1;
}
-static void explore(const char *path)
+static void explore(char *path)
{
- static entry_t *selected;
- static size_t nselected = 0, ndeselected = 0, selectedcapacity = 0;
+ char *tmp = path;
+ path = malloc(strlen(tmp) + 1);
+ strcpy(path, tmp);
+ tmp = NULL;
char to_select[MAX_PATH] = {0};
- char _path[MAX_PATH];
- tail_call:;
- DIR *dir = opendir(path);
+ bb_state_t state = {0};
+ memset(&state, 0, sizeof(bb_state_t));
+
+ tail_call:
+
+ state.path = path;
+
+ DIR *dir = opendir(state.path);
if (!dir)
- err("Couldn't open dir: %s", path);
- if (chdir(path) != 0)
- err("Couldn't chdir into %s", path);
+ err("Couldn't open dir: %s", state.path);
+ if (chdir(state.path) != 0)
+ err("Couldn't chdir into %s", state.path);
struct dirent *dp;
- size_t filecap = 0, nfiles = 0;
- entry_t *files = NULL;
-
- // Hash inode -> inode with linear probing
- size_t hashsize = 2 * nselected;
- ino_t *selecthash = calloc(hashsize, sizeof(ino_t));
+ // Hash inode -> entry_t with linear probing
+ 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);
- for (int i = nselected + ndeselected - 1; i >= 0; i--) {
- if (!selected[i].selected) continue;
- ino_t inode = selected[i].entry.d_ino;
- int probe = ((int)inode) % hashsize;
+ for (entry_t *p = state.firstselected; p; p = p->next) {
+ int probe = ((int)p->d_ino) % hashsize;
while (selecthash[probe])
probe = (probe + 1) % hashsize;
- selecthash[probe] = inode;
+ selecthash[probe] = p;
}
+ if (state.files) {
+ for (int i = 0; i < state.nfiles; i++) {
+ entry_t *e = state.files[i];
+ e->visible = 0;
+ if (!IS_SELECTED(e))
+ free(e);
+ }
+ free(state.files);
+ state.files = NULL;
+ }
+ size_t pathlen = strlen(state.path);
+ size_t filecap = 0;
+ state.nfiles = 0;
while ((dp = readdir(dir)) != NULL) {
if (dp->d_name[0] == '.' && dp->d_name[1] == '\0')
continue;
- if (nfiles >= filecap) {
- filecap += 100;
- if ((files = realloc(files, sizeof(entry_t)*filecap)) == NULL)
- err("Alloc fail");
- }
- int selected = 0;
- if (nselected) {
+ // Hashed lookup from selected:
+ if (state.firstselected) {
for (int probe = ((int)dp->d_ino) % hashsize; selecthash[probe]; probe = (probe + 1) % hashsize) {
- if (selecthash[probe] == dp->d_ino) {
- selected = 1;
- break;
+ if (selecthash[probe]->d_ino == dp->d_ino) {
+ selecthash[probe]->visible = 1;
+ state.files[state.nfiles++] = selecthash[probe];
+ goto next_file;
}
}
}
- entry_t file = {*dp, path, selected};
- files[nfiles++] = file;
+ entry_t *entry = malloc(sizeof(entry_t) + pathlen + dp->d_namlen + 2);
+ strncpy(entry->d_fullname, state.path, pathlen);
+ entry->d_fullname[pathlen] = '/';
+ entry->d_name = &entry->d_fullname[pathlen + 1];
+ strncpy(entry->d_name, dp->d_name, dp->d_namlen + 1);
+ entry->d_ino = dp->d_ino;
+ entry->d_reclen = dp->d_reclen;
+ entry->d_type = dp->d_type;
+ entry->d_namlen = dp->d_namlen;
+ entry->next = NULL, entry->prev = NULL;
+
+ if (state.nfiles >= filecap) {
+ filecap += 100;
+ state.files = realloc(state.files, filecap*sizeof(entry_t*));
+ }
+ state.files[state.nfiles++] = entry;
+ next_file:;
}
free(selecthash);
- if (nfiles == 0) {
- err("No files found (not even '..')");
- }
- qsort(files, nfiles, sizeof(entry_t), compare_alpha);
+ if (state.nfiles == 0) err("No files found (not even '..')");
+ qsort(state.files, state.nfiles, sizeof(entry_t*), compare_alpha);
closedir(dir);
- int cursor = 0;
- int scroll = 0;
+ state.cursor = 0;
+ state.scroll = 0;
if (to_select[0]) {
- for (int i = 0; i < nfiles; i++) {
- if (strcmp(to_select, files[i].entry.d_name) == 0) {
- cursor = i;
+ for (int i = 0; i < state.nfiles; i++) {
+ if (strcmp(to_select, state.files[i]->d_name) == 0) {
+ state.cursor = i;
break;
}
}
}
- struct timespec lastclick;
- clock_gettime(CLOCK_MONOTONIC, &lastclick);
+ clock_gettime(CLOCK_MONOTONIC, &state.lastclick);
int picked, scrolloff;
while (1) {
redraw:
- render(path, files, nfiles, cursor, scroll, nselected);
+ render(&state);
skip_redraw:
scrolloff = MIN(SCROLLOFF, height/2);
//sleep(2);
//if (1) goto done;
- switch (term_get_event()) {
+ int key = term_get_event();
+ switch (key) {
case KEY_MOUSE_LEFT: {
struct timespec clicktime;
clock_gettime(CLOCK_MONOTONIC, &clicktime);
- double dt_ms = 1e3*(double)(clicktime.tv_sec - lastclick.tv_sec);
- dt_ms += 1e-6*(double)(clicktime.tv_nsec - lastclick.tv_nsec);
- lastclick = clicktime;
- if (mouse_y > 0 && scroll + (mouse_y - 1) < nfiles) {
- int clicked = scroll + (mouse_y - 1);
+ double dt_ms = 1e3*(double)(clicktime.tv_sec - state.lastclick.tv_sec);
+ dt_ms += 1e-6*(double)(clicktime.tv_nsec - state.lastclick.tv_nsec);
+ state.lastclick = clicktime;
+ if (mouse_y > 0 && state.scroll + (mouse_y - 1) < state.nfiles) {
+ int clicked = state.scroll + (mouse_y - 1);
if (dt_ms > 200) {
// Single click
if (mouse_x == 0) {
@@ -427,7 +457,8 @@ static void explore(const char *path)
picked = clicked;
goto toggle;
} else {
- cursor = clicked;
+ state.cursor = clicked;
+ goto redraw;
}
} else {
// Double click
@@ -435,122 +466,133 @@ static void explore(const char *path)
goto open_file;
}
}
+ break;
}
- case KEY_ESC: case 'q': case 'Q':
+
+ case 'q': case 'Q': case KEY_CTRL_C:
goto done;
+
case KEY_CTRL_D:
- cursor = MIN(nfiles - 1, cursor + (height - 3) / 2);
- if (nfiles <= height - 3)
+ state.cursor = MIN(state.nfiles - 1, state.cursor + (height - 3) / 2);
+ if (state.nfiles <= height - 3)
goto redraw;
- scroll += (height - 3)/2;
- if (scroll > nfiles - (height - 3))
- scroll = nfiles - (height - 3);
+ state.scroll += (height - 3)/2;
+ if (state.scroll > state.nfiles - (height - 3))
+ state.scroll = state.nfiles - (height - 3);
goto redraw;
+
case KEY_CTRL_U:
- cursor = MAX(0, cursor - (height - 3) / 2);
- if (nfiles <= height - 3)
+ state.cursor = MAX(0, state.cursor - (height - 3) / 2);
+ if (state.nfiles <= height - 3)
goto redraw;
- scroll -= (height - 3)/2;
- if (scroll < 0)
- scroll = 0;
+ state.scroll -= (height - 3)/2;
+ if (state.scroll < 0)
+ state.scroll = 0;
goto redraw;
- case ' ': case '\r':
- picked = cursor;
+
+ case ' ':
+ picked = state.cursor;
toggle:
- files[picked].selected ^= 1;
- if (files[picked].selected) {
- if (nselected + ndeselected + 1 > selectedcapacity) {
- selectedcapacity += 100;
- selected = realloc(selected, selectedcapacity);
- }
- selected[nselected++] = files[picked];
+ if (IS_SELECTED(state.files[picked])) {
+ toggle_off:;
+ entry_t *e = state.files[picked];
+ if (state.firstselected == e)
+ state.firstselected = e->next;
+ if (e->prev) e->prev->next = e->next;
+ if (e->next) e->next->prev = e->prev;
+ e->next = NULL;
+ e->prev = NULL;
+ --state.nselected;
} else {
- // Find and destroy
- for (int i = nselected + ndeselected - 1; i >= 0; i--) {
- if (!selected[i].selected) continue;
- if (selected[i].entry.d_ino == files[picked].entry.d_ino) {
- selected[i].selected = 0;
- --nselected;
- // Leave a hole to clean up later
- if (i == nselected + ndeselected) {
- goto redraw;
- }
- ++ndeselected;
- goto found_it;
- }
+ toggle_on:;
+ entry_t *e = state.files[picked];
+ if (state.firstselected) {
+ e->next = state.firstselected;
+ state.firstselected->prev = e;
}
- err("Didn't find selection");
- found_it:
- // Coalesce removals:
- if (selectedcapacity > nselected + 100) {
- entry_t *first = &selected[0];
- entry_t *last = &selected[nselected + ndeselected - 1];
- entry_t *p = first;
- while (first != last) {
- if (first->selected) {
- *p = *first;
- ++p;
- }
- ++first;
- }
- ndeselected = 0;
+ state.firstselected = e;
+ ++state.nselected;
+ }
+ goto redraw;
- selectedcapacity = nselected + 100;
- selected = realloc(selected, selectedcapacity);
- }
+ case KEY_ESC:
+ for (entry_t *e = state.firstselected; e; e = e->next) {
+ e->next = NULL;
+ e->prev = NULL;
+ if (!e->visible) free(e);
}
+ state.firstselected = NULL;
+ state.nselected = 0;
goto redraw;
+
case 'j':
- if (cursor >= nfiles - 1)
+ if (state.cursor >= state.nfiles - 1)
goto skip_redraw;
- ++cursor;
- if (cursor > scroll + height - 4 - scrolloff && scroll < nfiles - (height - 3)) {
- ++scroll;
+ ++state.cursor;
+ if (state.cursor > state.scroll + height - 4 - scrolloff && state.scroll < state.nfiles - (height - 3)) {
+ ++state.scroll;
}
goto redraw;
+
case 'k':
- if (cursor <= 0)
+ if (state.cursor <= 0)
goto skip_redraw;
- --cursor;
- if (cursor < scroll + scrolloff && scroll > 0) {
- --scroll;
+ --state.cursor;
+ if (state.cursor < state.scroll + scrolloff && state.scroll > 0) {
+ --state.scroll;
}
goto redraw;
+
case 'J':
- if (cursor < nfiles - 1) {
- ++cursor;
- files[cursor].selected = files[cursor - 1].selected;
+ if (state.cursor < state.nfiles - 1) {
+ if (IS_SELECTED(state.files[state.cursor])) {
+ picked = ++state.cursor;
+ goto toggle_on;
+ } else {
+ picked = ++state.cursor;
+ goto toggle_off;
+ }
}
- goto redraw;
+ goto skip_redraw;
+
case 'K':
- if (cursor > 0) {
- --cursor;
- files[cursor].selected = files[cursor + 1].selected;
+ if (state.cursor > 0) {
+ if (IS_SELECTED(state.files[state.cursor])) {
+ picked = --state.cursor;
+ goto toggle_on;
+ } else {
+ picked = --state.cursor;
+ goto toggle_off;
+ }
}
- goto redraw;
+ goto skip_redraw;
+
case 'h':
- if (strcmp(path, "/") != 0) {
- char *p = strrchr(path, '/');
+ if (strcmp(state.path, "/") != 0) {
+ char *p = strrchr(state.path, '/');
if (p) strcpy(to_select, p+1);
else to_select[0] = '\0';
- char tmp[MAX_PATH];
- strcpy(tmp, path);
+ char tmp[MAX_PATH], tmp2[MAX_PATH];
+ strcpy(tmp, state.path);
strcat(tmp, "/");
strcat(tmp, "..");
- if (!realpath(tmp, _path))
+ if (!realpath(tmp, tmp2))
err("realpath failed");
- path = _path;
+ free(path);
+ path = malloc(strlen(tmp2) + 1);
+ strcpy(path, tmp2);
goto tail_call;
}
break;
- case 'l':
- picked = cursor;
+
+ case 'l': case '\r':
+ picked = state.cursor;
open_file:
{
- int is_dir = files[picked].entry.d_type & DT_DIR;
- if (files[picked].entry.d_type == DT_LNK) {
+ int is_dir = state.files[picked]->d_type & DT_DIR;
+ if (state.files[picked]->d_type == DT_LNK) {
char linkpath[MAX_PATH];
- if (readlink(files[picked].entry.d_name, linkpath, sizeof(linkpath)) < 0)
+ if (readlink(state.files[picked]->d_name, linkpath, sizeof(linkpath)) < 0)
err("readlink() failed");
DIR *dir = opendir(linkpath);
if (dir) {
@@ -560,27 +602,30 @@ static void explore(const char *path)
}
}
if (is_dir) {
- if (strcmp(files[picked].entry.d_name, "..") == 0) {
- char *p = strrchr(path, '/');
+ 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];
- strcpy(tmp, path);
+
+ char tmp[MAX_PATH], tmp2[MAX_PATH];
+ strcpy(tmp, state.path);
strcat(tmp, "/");
- strcat(tmp, files[picked].entry.d_name);
- if (!realpath(tmp, _path))
+ strcat(tmp, state.files[picked]->d_name);
+ if (!realpath(tmp, tmp2))
err("realpath failed");
- path = _path;
+ free(path);
+ path = malloc(strlen(tmp2) + 1);
+ strcpy(path, tmp2);
goto tail_call;
} else {
- char *name = files[picked].entry.d_name;
+ char *name = state.files[picked]->d_name;
close_term();
pid_t child = run_cmd(NULL, NULL,
#ifdef __APPLE__
- "if file -bI %s | grep '^text/'; then $EDITOR %s; else open %s; fi",
+ "if file -bI %s | grep '^text/' >/dev/null; then $EDITOR %s; else open %s; fi",
#else
- "if file -bi %s | grep '^text/'; then $EDITOR %s; else xdg-open %s; fi",
+ "if file -bi %s | grep '^text/' >/dev/null; then $EDITOR %s; else xdg-open %s; fi",
#endif
name, name, name);
waitpid(child, NULL, 0);
@@ -589,30 +634,60 @@ static void explore(const char *path)
}
break;
}
+
case 'm':
- if (nselected) {
+ if (state.nselected) {
int fd;
- run_cmd(NULL, &fd, "xargs mv");
- write_selection(fd, selected, nselected, ndeselected);
+ run_cmd(NULL, &fd, "xargs -I {} mv {} .");
+ write_selection(fd, state.firstselected);
close(fd);
}
break;
+
case 'd':
- if (nselected) {
+ if (state.nselected) {
int fd;
run_cmd(NULL, &fd, "xargs rm -rf");
- write_selection(fd, selected, nselected, ndeselected);
+ write_selection(fd, state.firstselected);
close(fd);
}
break;
+
case 'p':
- if (nselected) {
+ if (state.nselected) {
+ int fd;
+ run_cmd(NULL, &fd, "xargs -I {} cp {} .");
+ write_selection(fd, state.firstselected);
+ close(fd);
+ }
+ break;
+
+ case 'x':
+ if (state.nselected) {
+ close_term();
+
+ char *buf = NULL;
+ size_t bufsize = 0;
+ printf("> ");
+ fflush(stdout);
+ getline(&buf, &bufsize, stdin);
+
int fd;
- run_cmd(NULL, &fd, "xargs cp");
- write_selection(fd, selected, nselected, ndeselected);
+ pid_t child = run_cmd(NULL, &fd, "xargs %s", buf);
+ write_selection(fd, state.firstselected);
close(fd);
+ waitpid(child, NULL, 0);
+
+ printf("press enter to continue...");
+ fflush(stdout);
+ getline(&buf, &bufsize, stdin);
+ free(buf);
+
+ init_term();
+ goto redraw;
}
break;
+
default:
goto skip_redraw;
}
@@ -620,7 +695,7 @@ static void explore(const char *path)
}
done:
close_term();
- write_selection(STDOUT_FILENO, selected, nselected, ndeselected);
+ write_selection(STDOUT_FILENO, state.firstselected);
return;
}