Thumbnail

rani/cscroll.git

Clone URL: https://git.buni.party/rani/cscroll.git

commit b8c41045457f0e18a2eb62096c4035f1e54881ca Author: rani <clagv.randomgames@gmail.com> Date: Fri Jan 02 00:13:27 2026 +0000 Add file deletion prompt and recursive directory entry counting 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; +}