commit b8c41045457f0e18a2eb62096c4035f1e54881ca
Author: rani <clagv.randomgames@gmail.com>
Date: Fri Jan 02 00:13:27 2026 +0000
diff --git a/include/dir.h b/include/dir.h
index 775a399..052b1ea 100644
--- a/include/dir.h
+++ b/include/dir.h
@@ -915 +916 @@ char dirent_longcrepr(const dirent_t * de); // like above, but for long mode
bool dirent_isexec(const dirent_t * de);
const char * dirent_prettymode(const dirent_t * de);
const char * dirent_size_unit(const dirent_t * de);
+size_t dirent_subfiles(const dirent_t * de); // number of sub entries in a dir
#endif /* DIR_H */
diff --git a/include/ui.h b/include/ui.h
index 01d3665..6568227 100644
--- a/include/ui.h
+++ b/include/ui.h
@@ -286 +289 @@ enum ui_color {
};
+// basically an array of strings (last entry is NULL)
+typedef const char * const prompt_opts_t[];
+
void ui_init(void);
void ui_deinit(void);
void ui_set_title(const char * title);
@@ -395 +427 @@ void ui_print_dir(const dir_t * dir, size_t cursor);
void ui_print_cursor(size_t cursor, size_t total);
void ui_resize(void);
const char * ui_readline(const char * prompt);
+const char * ui_prompt(const char * prompt, prompt_opts_t opts);
+bool ui_prompt_deletion(const dirent_t * de);
#endif /* UI_H */
diff --git a/src/dir.c b/src/dir.c
index 9f3049c..650c307 100644
--- a/src/dir.c
+++ b/src/dir.c
@@ -13 +14 @@
+#include <sys/resource.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <strings.h>
@@ -516 +643 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include <stdio.h>
#include <regex.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
+#include <ftw.h>
+
#include "dir.h"
#include "config.h"
#include "cvector.h"
+// S_ISVTX requires _XOPEN_SOURCE >= 500
+// I guess sticky bit isn't POSIX
+#ifndef S_ISVTX
+#define S_ISVTX 0
+#endif
+
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+#ifdef UNUSED
+#error UNUSED is already defined
+#undef UNUSED
+#endif
+#ifndef UNUSED
+# if __GNUC__ || defined(__clang__)
+# define UNUSED __attribute__((unused))
+# elif __STDC_VERSION__ >= 202311L
+# define UNUSED [[maybe_unused]]
+# else
+# define UNUSED
+# endif
+#endif /* UNUSED */
+
#define CWDSZ (PATH_MAX + 1)
#define LINKNAMESZ PATH_MAX
@@ -4013 +42986 @@ const char * dirent_size_unit(const dirent_t * de) {
if (!ret) return nounit;
return ret;
}
+
+static int dir_internal_ftw_(
+ int dfd,
+ int (*cb)(
+ int dirfd,
+ const char * path,
+ const struct stat * sb,
+ void * arg
+ ),
+ void * arg
+) {
+ int retval = 0;
+
+ DIR * dp = fdopendir(dfd);
+ if (!dp) return 0;
+
+ struct dirent * de;
+ while ((de = readdir(dp)) != NULL) {
+ int ret;
+
+ char * name = de->d_name;
+ if (!strcmp(name, ".") || !strcmp(name, "..")) continue;
+
+ struct stat statbuf;
+ ret = fstatat(dirfd(dp), name, &statbuf, AT_SYMLINK_NOFOLLOW);
+ struct stat * statbufp;
+ if (ret < 0) statbufp = NULL;
+ else statbufp = &statbuf;
+
+ ret = cb(dirfd(dp), name, statbufp, arg);
+ // if cb returns a negative value, stop the walk immediately
+ if (ret < 0) {
+ retval = -1;
+ goto done;
+ }
+
+ if (S_ISDIR(statbuf.st_mode)) {
+ int nextfd = openat(dfd, name, O_RDONLY | O_DIRECTORY);
+ ret = dir_internal_ftw_(nextfd, cb, arg);
+ if (ret < 0) {
+ retval = -1;
+ goto done;
+ }
+ }
+ }
+
+done:
+ closedir(dp);
+ return retval;
+}
+
+static int dir_internal_ftw(
+ const char * path,
+ int (*cb)(
+ int dirfd,
+ const char * path,
+ const struct stat * sb,
+ void * arg
+ ),
+ void * arg
+) {
+ DIR * d = opendir(path);
+ if (!d) return -1;
+ int ret = dir_internal_ftw_(dirfd(d), cb, arg);
+ closedir(d);
+ return ret;
+}
+
+static int dirent_subfiles_ftw_cb(
+ UNUSED int dirfd, UNUSED const char * path,
+ UNUSED const struct stat * sb, void * arg
+) {
+ (*(size_t*)arg)++;
+ return 0;
+}
+
+size_t dirent_subfiles(const dirent_t * de) {
+ if (de->type != DE_DIR) return 0;
+
+ size_t count = 0;
+ dir_internal_ftw(de->name, dirent_subfiles_ftw_cb, &count);
+ return count;
+}
diff --git a/src/main.c b/src/main.c
index 0bd5151..41e0551 100644
--- a/src/main.c
+++ b/src/main.c
@@ -26 +27 @@
#include <stdio.h>
#include <stddef.h>
#include <ncurses.h>
+#include <stdbool.h>
#include "ui.h"
#include "dir.h"
@@ -1426 +14314 @@ int main(int argc, char ** argv) {
}
break;
}
+ case 'd': {
+ ui_status_info("");
+ ui_refresh();
+ bool del = ui_prompt_deletion(cur_de);
+ if (del) ui_status_info("Deleting!");
+ else ui_status_info("Not Deleting.");
+ break;
+ }
case 'q':
goto finished;
case KEY_RESIZE:
diff --git a/src/ui.c b/src/ui.c
index ac2a79c..b68a668 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -116 +119 @@
#include "config.h"
#include "cvector.h"
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+#define MIN(x, y) (((x) > (y)) ? (y) : (x))
+
#define TITLEWINSTARTY 0
#define TITLEWINLINES 1
#define TITLEWINCOLS COLS
@@ -276 +3011 @@
#define INPUTWINLINES 1
#define INPUTWINCOLS COLS
+#define PROMPTWINSTARTY (LINES / 2)
+#define PROMPTWINSTARTX (COLS / 2)
+#define PROMPTWINMAXLINES (LINES * 3 / 4)
+#define PROMPTWINMAXCOLS (COLS * 3 / 4)
+
#define INPUTBUFSZ 2048
static WINDOW * titlewin = NULL;
@@ -3623 +370132 @@ const char * ui_readline(const char * prompt) {
if (p == inputbuf) return NULL;
return inputbuf;
}
+
+const char * ui_prompt(const char * prompt, prompt_opts_t opts) {
+ size_t n_opts = 0;
+ for (const char * const * p = opts; *p; p++) {
+ n_opts++;
+ }
+
+ size_t prompt_len = strlen(prompt);
+ // find width of options, assuming they all fit on one line
+ // there are n_opts - 1 spaces in total between the options
+ size_t opts_len = n_opts - 1;
+ for (size_t i = 0; i < n_opts; i++) {
+ opts_len += strlen(opts[i]);
+ }
+
+ /* top mid btm prompt opts */
+ size_t oglines = 1 + 1 + 1 + 1 + 1;
+ /* border left right */
+ size_t ogcols = 2 + 1 + 1 + MAX(prompt_len, opts_len);
+ size_t lines, cols;
+
+ lines = MIN(oglines, (unsigned)PROMPTWINMAXLINES);
+ cols = MIN(ogcols, (unsigned)PROMPTWINMAXCOLS);
+
+ WINDOW * promptwin = newwin(
+ lines,
+ cols,
+ PROMPTWINSTARTY - lines / 2,
+ PROMPTWINSTARTX - cols / 2
+ );
+
+ keypad(promptwin, TRUE);
+ wrefresh(promptwin);
+
+ size_t cursor = 0;
+
+ bool done = false;
+ const char * ret = NULL;
+ while (!done) {
+ werase(promptwin);
+ box(promptwin, 0, 0);
+
+ wmove(promptwin, 1, cols / 2 - prompt_len / 2);
+ waddstr(promptwin, prompt);
+
+ wmove(promptwin, 3, cols / 2 - opts_len / 2);
+ for (size_t i = 0; i < n_opts; i++) {
+ int attr = 0;
+ if (cursor == i) attr = A_REVERSE;
+ wattron(promptwin, attr);
+ waddstr(promptwin, opts[i]);
+ wattroff(promptwin, attr);
+ if (i < n_opts - 1) waddch(promptwin, ' ');
+ }
+ wrefresh(promptwin);
+
+ int c = wgetch(promptwin);
+ if (n_opts == 0) {
+ ret = NULL;
+ done = true;
+ } else switch (c) {
+ case KEY_LEFT:
+ if (cursor > 0) cursor--;
+ break;
+ case KEY_RIGHT:
+ if (cursor < n_opts - 1) cursor++;
+ break;
+ case '\n':
+ case KEY_ENTER:
+ ret = opts[cursor];
+ done = true;
+ break;
+ case KEY_RESIZE:
+ ui_resize();
+ ui_refresh();
+
+ lines = MIN(oglines, (unsigned)PROMPTWINMAXLINES);
+ cols = MIN(ogcols, (unsigned)PROMPTWINMAXCOLS);
+
+ wresize(promptwin, lines, cols);
+ mvwin(promptwin,
+ PROMPTWINSTARTY - lines / 2,
+ PROMPTWINSTARTX - cols / 2
+ );
+ wrefresh(promptwin);
+ break;
+ case 'q':
+ case KEY_ESC:
+ ret = NULL;
+ done = true;
+ break;
+ }
+ }
+
+ delwin(promptwin);
+ return ret;
+}
+
+bool ui_prompt_deletion(const dirent_t * de) {
+ const char * no = "No";
+ const char * yes = "Yes";
+
+ size_t nfiles = 0;
+ if (de->type == DE_DIR) {
+ ui_status_info("listing...");
+ ui_refresh();
+ nfiles = dirent_subfiles(de);
+ ui_status_info("");
+ ui_refresh();
+ }
+
+ char * prompt;
+ if (nfiles != 0) {
+ char * fstr = "Delete '%s' and all %zu files inside it?";
+ size_t len = snprintf(NULL, 0, fstr, de->name, nfiles);
+ prompt = malloc(len + 1);
+ sprintf(prompt, fstr, de->name, nfiles);
+ } else {
+ char * fstr = "Delete '%s'?";
+ size_t len = snprintf(NULL, 0, fstr, de->name);
+ prompt = malloc(len + 1);
+ sprintf(prompt, fstr, de->name);
+ }
+
+ const char * ret = ui_prompt(prompt, (prompt_opts_t){no, yes, NULL});
+ free(prompt);
+ if (ret == yes) return true;
+ return false;
+}