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 (firstselected) { + const char *p = firstselected->d_fullname; 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 (*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; + 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 = (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; + 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': - goto done; - case KEY_CTRL_D: - cursor = MIN(nfiles - 1, cursor + (height - 3) / 2); - if (nfiles <= height - 3) - goto redraw; - scroll += (height - 3)/2; - if (scroll > nfiles - (height - 3)) - scroll = nfiles - (height - 3); - goto redraw; - case KEY_CTRL_U: - cursor = MAX(0, cursor - (height - 3) / 2); - if (nfiles <= height - 3) - goto redraw; - scroll -= (height - 3)/2; - if (scroll < 0) - scroll = 0; - goto redraw; - case ' ': case '\r': - picked = 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]; - } 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; - } - } - 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; - selectedcapacity = nselected + 100; - selected = realloc(selected, selectedcapacity); + case 'q': case 'Q': case KEY_CTRL_C: + goto done; + + case KEY_CTRL_D: + state.cursor = MIN(state.nfiles - 1, state.cursor + (height - 3) / 2); + if (state.nfiles <= height - 3) + goto redraw; + state.scroll += (height - 3)/2; + if (state.scroll > state.nfiles - (height - 3)) + state.scroll = state.nfiles - (height - 3); + goto redraw; + + case KEY_CTRL_U: + state.cursor = MAX(0, state.cursor - (height - 3) / 2); + if (state.nfiles <= height - 3) + goto redraw; + state.scroll -= (height - 3)/2; + if (state.scroll < 0) + state.scroll = 0; + goto redraw; + + case ' ': + picked = state.cursor; + toggle: + 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 { + toggle_on:; + entry_t *e = state.files[picked]; + if (state.firstselected) { + e->next = state.firstselected; + state.firstselected->prev = e; + } + state.firstselected = e; + ++state.nselected; + } + goto redraw; + + 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 (state.cursor >= state.nfiles - 1) + goto skip_redraw; + ++state.cursor; + if (state.cursor > state.scroll + height - 4 - scrolloff && state.scroll < state.nfiles - (height - 3)) { + ++state.scroll; + } + goto redraw; + + case 'k': + if (state.cursor <= 0) + goto skip_redraw; + --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; + goto toggle_on; + } else { + picked = ++state.cursor; + goto toggle_off; } } - goto redraw; - case 'j': - if (cursor >= nfiles - 1) - goto skip_redraw; - ++cursor; - if (cursor > scroll + height - 4 - scrolloff && scroll < nfiles - (height - 3)) { - ++scroll; - } - goto redraw; - case 'k': - if (cursor <= 0) - goto skip_redraw; - --cursor; - if (cursor < scroll + scrolloff && scroll > 0) { - --scroll; - } - goto redraw; - case 'J': - if (cursor < nfiles - 1) { - ++cursor; - files[cursor].selected = files[cursor - 1].selected; - } - 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 cp"); - write_selection(fd, selected, nselected, ndeselected); + 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; + 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; }