fe
terminal file explorer and picker
git clone https://9o.is/git/fe.git
commit 903d743937a852f7b80d0cb9b44e0eb5fd9214e9 parent 9bed3588514c4f35bd769057387c04859c19ca58 Author: Jul <jul@9o.is> Date: Mon, 26 Jan 2026 12:47:25 -0500 add 'd' shortcut to remove files/dirs with confirmation Diffstat:
| M | config.def.h | | | 1 | + |
| M | entries.c | | | 64 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | entries.h | | | 1 | + |
| M | tty_interface.c | | | 45 | +++++++++++++++++++++++++++++++++++++++++++++ |
| M | tty_interface.h | | | 1 | + |
5 files changed, 112 insertions(+), 0 deletions(-)
diff --git a/config.def.h b/config.def.h @@ -36,6 +36,7 @@ static const keybinding_t keybindings[] = { {KEY('G'), action_last, NULL}, /* G */ {KEY('~'), action_home, NULL}, /* ~ */ {KEY('.'), action_togglehidden, NULL}, /* . */ + {KEY('d'), action_remove, NULL}, /* d (delete/remove) */ {KEY('p'), action_run, "less %s/%s"}, /* p (preview) */ {KEY('t'), action_run, "tmux new-window -b 'vis %s/%s'"}, /* edit new tab */ {KEY('f'), action_setpath, "ag -g . %s | fzy"}, /* s (search) */ diff --git a/entries.c b/entries.c @@ -329,3 +329,67 @@ struct entry *entries_item(entries_t *entries, size_t n) { struct entry *entries_selected(entries_t *entries) { return entries_item(entries, entries->selection); } + +int entries_remove(entries_t *entries) { + char rmpath[PATH_MAX]; + char stack[PATH_MAX][1024]; + int stack_top = 0; + + mkpath(entries->path, entries->dents[entries->selection].name, rmpath, sizeof(rmpath)); + + if (filetype(rmpath) != S_IFDIR) { + return unlink(rmpath); + } + + strlcpy(stack[stack_top], rmpath, PATH_MAX); + stack_top++; + + while (stack_top > 0) { + stack_top--; + char current_path[PATH_MAX]; + strlcpy(current_path, stack[stack_top], PATH_MAX); + + DIR *dirp = opendir(current_path); + if (dirp == NULL) { + perror("opendir"); + continue; + } + + int is_empty = 1; + struct dirent *dp; + + while ((dp = readdir(dirp)) != NULL) { + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) + continue; + + char child_path[PATH_MAX]; + mkpath(current_path, dp->d_name, child_path, sizeof(child_path)); + + if (filetype(child_path) == S_IFDIR) { + if (stack_top < 1024) { + if (is_empty) { + strlcpy(stack[stack_top], current_path, PATH_MAX); + stack_top++; + } + strlcpy(stack[stack_top], child_path, PATH_MAX); + stack_top++; + } + is_empty = 0; + } else { + if (unlink(child_path) == -1) { + perror("unlink"); + } + } + } + + closedir(dirp); + + if (is_empty) { + if (rmdir(current_path) == -1) { + perror("rmdir"); + } + } + } + + return 0; +} diff --git a/entries.h b/entries.h @@ -34,5 +34,6 @@ void entries_togglehidden(); int entries_select(entries_t *entries); struct entry *entries_item(entries_t *entries, size_t n); struct entry *entries_selected(entries_t *entries); +int entries_remove(entries_t *entries); #endif diff --git a/tty_interface.c b/tty_interface.c @@ -25,6 +25,30 @@ static void clear(tty_interface_t *state) { tty_flush(tty); } +static int confirm_remove(tty_interface_t *state, const char *name) { + tty_t *tty = state->tty; + char fullpath[PATH_MAX]; + + if (strcmp(state->entries->path, "/") == 0) { + strlcpy(fullpath, "/", sizeof(fullpath)); + strlcat(fullpath, name, sizeof(fullpath)); + } else { + strlcpy(fullpath, state->entries->path, sizeof(fullpath)); + strlcat(fullpath, "/", sizeof(fullpath)); + strlcat(fullpath, name, sizeof(fullpath)); + } + + clear(state); + tty_unhide_cursor(tty); + tty_setcol(tty, 0); + tty_printf(tty, "Remove '%s'? [y/N] ", fullpath); + tty_flush(tty); + + char response = tty_getchar(tty); + + return (response == 'y' || response == 'Y'); +} + static void draw(tty_interface_t *state) { tty_t *tty = state->tty; entries_t *entries = state->entries; @@ -272,6 +296,27 @@ void action_setpath(tty_interface_t *state, const char *argv) { draw(state); } +void action_remove(tty_interface_t *state, const char *argv) { + (void)argv; + + const struct entry *entry = entries_selected(state->entries); + if (!entry) return; + + if (confirm_remove(state, entry->name)) { + if (entries_remove(state->entries) == 0) { + size_t selection = state->entries->selection; + entries_setpath(state->entries, state->entries->path); + + size_t pos = selection < state->entries->size + ? selection : state->entries->size - 1; + entries_position(state->entries, pos); + } + } + + tty_hide_cursor(state->tty); + draw(state); +} + void action_exit(tty_interface_t *state, const char *argv) { (void)argv; clear(state); diff --git a/tty_interface.h b/tty_interface.h @@ -32,6 +32,7 @@ void action_home(tty_interface_t *state, const char *argv); void action_togglehidden(tty_interface_t *state, const char *argv); void action_run(tty_interface_t *state, const char *argv); void action_setpath(tty_interface_t *state, const char *argv); +void action_remove(tty_interface_t *state, const char *argv); void action_exit(tty_interface_t *state, const char *argv); #endif