Thumbnail

rani/cscroll.git

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

commit 247044fb9f76f2298a44a9985bb3a950b7fa13ad Author: rani <clagv.randomgames@gmail.com> Date: Thu Jan 15 13:12:00 2026 +0000 Add: marking/unmarking files diff --git a/Makefile b/Makefile index b7d8a73..406894a 100644 --- a/Makefile +++ b/Makefile @@ -15 +15 @@  BIN = cscroll -SRC = src/config.c src/dir.c src/ui.c src/main.c +SRC = src/hashmap.c src/config.c src/dir.c src/ui.c src/main.c  OBJ = ${SRC:.c=.o}    CC ?= cc diff --git a/include/dir.h b/include/dir.h index 9de5e89..7f8d3da 100644 --- a/include/dir.h +++ b/include/dir.h @@ -246 +249 @@  #define REGSEARCH_BAD_REGEX 1  #define REGSEARCH_NOT_FOUND 2   +#define TOGGLEMARK_PARENT_MARKED 1 +#define TOGGLEMARK_DIRENT_UNKNOWN 2 +  enum size_unit {   SIZE_B,   SIZE_KB, @@ -5813 +6115 @@ typedef struct {     char * linkname;   enum de_type linktype; + + bool marked;  } dirent_t;    typedef struct {   size_t len;   cvector(dirent_t) entries;   size_t nodots_len; - cvector(dirent_t) nodots; + cvector(dirent_t*) nodots;     size_t longest_uname;   size_t longest_gname; @@ -736 +789 @@ typedef struct {   size_t longest_size_unit;  } dir_t;   +void dir_init(void); +void dir_deinit(void); +  int dir_list(const char * path, dir_t * dir);  void dir_free(dir_t * dir);  const char * dir_get_cwd(void); @@ -829 +9011 @@ int dir_search_name(const dir_t * dir, const char * name, size_t * idx);  int dir_search_regex(const dir_t * dir, const char * regexstr, size_t * idx);  const char * dir_basename(const char * path);  size_t dir_len(const dir_t * dir); +dirent_t * dir_get_entry(const dir_t * dir, size_t cursor);  cvector(dirent_t) dir_entries(const dir_t * dir); -void dir_sort(const dir_t * dir); +void dir_sort(dir_t * dir);  const char * dir_get_home(void); +size_t dir_get_total_marked(void);    char dirent_crepr(const dirent_t * de); // character representing dir entry  char dirent_creprl(const dirent_t * de); // like above, but for the link @@ -955 +1056 @@ const char * dirent_size_unit(const dirent_t * de);  size_t dirent_subfiles(const dirent_t * de); // number of sub entries in a dir  int dirent_delete(const dirent_t * de);  int dirent_open(const dirent_t * de); +int dirent_togglemark(dirent_t * de);    #endif /* DIR_H */ diff --git a/include/hashmap.h b/include/hashmap.h new file mode 100644 index 0000000..73f7f46 --- /dev/null +++ b/include/hashmap.h @@ -00 +149 @@ +#ifndef HASHMAP_H +#define HASHMAP_H + +#include <stdint.h> +#include <stddef.h> + + +// collision list +struct hashmap_col_list { + struct hashmap_col_list * next; + struct hashmap_col_list * prev; + + unsigned long hash; + char * key; + void * val; +}; + + +struct hashmap_bucket { + struct hashmap_col_list * entries; + struct hashmap_col_list * last; +}; + + +typedef struct { + void (*destroyer)(void*); + struct hashmap_bucket ** buckets; + size_t len; // total entries + size_t max; +} hashmap; + + +typedef struct { + size_t curbuck; + struct hashmap_col_list * col; + + char * key; + void * val; +} hashmap_walk_state; + + +hashmap * hashmap_new(void (*destroyer)(void*)); +void hashmap_insert(hashmap *, char *, void *); +void * hashmap_get(const hashmap *, const char *); +void hashmap_remove(hashmap *, char *); +int hashmap_walk(hashmap *, hashmap_walk_state *); +void hashmap_destroy(hashmap *); + +#endif /* HASHMAP_H */ diff --git a/include/ui.h b/include/ui.h index e2fa624..1735fa4 100644 --- a/include/ui.h +++ b/include/ui.h @@ -627 +627 @@ void ui_status_error(const char * status);  void ui_erase(void);  void ui_refresh(void);  void ui_print_dir(const dir_t * dir, size_t cursor); -void ui_print_cursor(size_t cursor, size_t total); +void ui_print_cursor(size_t cursor, size_t total, size_t total_marked);  void ui_resize(void);  const char * ui_readline(const char * prompt);  const char * ui_prompt(const char * prompt, prompt_opts_t opts); diff --git a/src/dir.c b/src/dir.c index 56b30eb..7dd3748 100644 --- a/src/dir.c +++ b/src/dir.c @@ -196 +197 @@  #include "dir.h"  #include "config.h"  #include "cvector.h" +#include "hashmap.h"    // S_ISVTX requires _XOPEN_SOURCE >= 500  // I guess sticky bit isn't POSIX @@ -498 +5013 @@    static char cwd_buf[CWDSZ];  static char * cwd = NULL; +// marks is a map of sets +// keys are directories and values are sets of marked files in that directory +static hashmap * marks = NULL; +static size_t n_marks = 0;   -static void dir_entry(int dirfd, const char * name, dirent_t * dirent); +static void dir_entry(int dirfd, const char * name, dirent_t * dirent, const hashmap * dirmarks); +static void dir_fill_nodots(dir_t * dir);  static char de_crepr(enum de_type de_type);  static void free_dirent(void * p) {   dirent_t * de = (dirent_t*)p; @@ -686 +7426 @@ static size_t ilen(size_t i, int base) {   }   return r;  } +static void marks_set_destroyer(void * p) { + ; +} +static void marks_destroyer(void * p) { + hashmap_destroy((hashmap*)p); +} + +void dir_init(void) { + // read cwd + getcwd(cwd_buf, CWDSZ); + cwd = cwd_buf; + char * p = strrchr(cwd, '/'); + if (*(p + 1) == 0) *p = 0; + + marks = hashmap_new(marks_destroyer); +} + +void dir_deinit(void) { + hashmap_destroy(marks); +}    int dir_list(const char * path, dir_t * dir) {   dir->len = 0; @@ -8721 +11318 @@ int dir_list(const char * path, dir_t * dir) {   return -errno;   }   + hashmap * dirmarks = hashmap_get(marks, path); +   while ((de = readdir(dp))) {   if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {   continue;   }     dirent_t dirent; - dir_entry(dirfd(dp), de->d_name, &dirent); + dir_entry(dirfd(dp), de->d_name, &dirent, dirmarks);   cvector_push_back(dir->entries, dirent);   dir->len++;   - if (*de->d_name != '.') { - cvector_push_back(dir->nodots, dirent); - dir->nodots_len++; - } -   size_t uname_len = 0;   size_t gname_len = 0;   size_t size_len = ilen(dirent.size, 10); @@ -11911 +14225 @@ int dir_list(const char * path, dir_t * dir) {   }     closedir(dp); + dir_fill_nodots(dir);   dir_sort(dir);   return 0;  }   -void dir_entry(int dirfd, const char * name, dirent_t * dirent) { +static void dir_fill_nodots(dir_t * dir) { + cvector_clear(dir->nodots); + dir->nodots_len = 0; + + for (size_t i = 0; i < dir->len; i++) { + dirent_t * dirent = &dir->entries[i]; + if (*dirent->name != '.') { + cvector_push_back(dir->nodots, dirent); + dir->nodots_len++; + } + } +} + +void dir_entry(int dirfd, const char * name, dirent_t * dirent, const hashmap * dirmarks) {   memset(dirent, 0, sizeof(dirent_t));   dirent->name = strdup(name);   dirent->type = DE_UNKNOWN; @@ -1346 +1718 @@ void dir_entry(int dirfd, const char * name, dirent_t * dirent) {   return;   }   + if (dirmarks && hashmap_get(dirmarks, name)) dirent->marked = true; +   // convert the ugly st_mode to a nice de_type   switch (statbuf.st_mode & S_IFMT) {   case S_IFSOCK: dirent->type = DE_SOCKET; break; @@ -1997 +2389 @@ void dir_entry(int dirfd, const char * name, dirent_t * dirent) {    void dir_free(dir_t * dir) {   cvector_free(dir->entries); + dir->len = 0;   cvector_free(dir->nodots); + dir->nodots_len = 0;  }    char de_crepr(enum de_type de_type) { @@ -27512 +3166 @@ const char * dirent_prettymode(const dirent_t * de) {  }    const char * dir_get_cwd(void) { - if (!cwd) { - getcwd(cwd_buf, CWDSZ); - cwd = cwd_buf; - char * p = strrchr(cwd, '/'); - if (*(p + 1) == 0) *p = 0; - }   return cwd;  }   @@ -33610 +37110 @@ const char * dir_basename(const char * path) {    int dir_search_name(const dir_t * dir, const char * name, size_t * idx) {   size_t dirlen = dir_len(dir); - cvector(dirent_t) entries = dir_entries(dir);     for (size_t i = 0; i < dirlen; i++) { - if (!strcmp(entries[i].name, name)) { + dirent_t * de = dir_get_entry(dir, i); + if (!strcmp(de->name, name)) {   *idx = i;   return 0;   } @@ -35014 +38513 @@ int dir_search_name(const dir_t * dir, const char * name, size_t * idx) {    int dir_search_regex(const dir_t * dir, const char * regexstr, size_t * idx) {   size_t dirlen = dir_len(dir); - cvector(dirent_t) entries = dir_entries(dir);     regex_t regex;   int ret = regcomp(&regex, regexstr, REG_EXTENDED);   if (!!ret) return -REGSEARCH_BAD_REGEX;     for (size_t i = 0; i < dirlen; i++) { - dirent_t * de = &entries[i]; + dirent_t * de = dir_get_entry(dir, i);   ret = regexec(&regex, de->name, 0, NULL, 0);   if (!ret) {   *idx = i; @@ -3759 +40911 @@ size_t dir_len(const dir_t * dir) {   else return dir->nodots_len;  }   -cvector(dirent_t) dir_entries(const dir_t * dir) { - if (config.dots) return dir->entries; - else return dir->nodots; +dirent_t * dir_get_entry(const dir_t * dir, size_t cursor) { + if (dir_len(dir) == 0) return NULL; + + if (config.dots) return &dir->entries[cursor]; + else return dir->nodots[cursor];  }    static int dir_sort_cmp(const void * inpa, const void * inpb) { @@ -40912 +44512 @@ static int dir_sort_cmp(const void * inpa, const void * inpb) {   }  }   -void dir_sort(const dir_t * dir) { + +void dir_sort(dir_t * dir) {   if (config.dir_sort == DIR_SORT_UNSORTED) return;   if (dir->entries && dir->len > 0)   qsort(dir->entries, dir->len, sizeof(dir->entries[0]), dir_sort_cmp); - if (dir->nodots && dir->nodots_len > 0) - qsort(dir->nodots, dir->nodots_len, sizeof(dir->entries[0]), dir_sort_cmp); + dir_fill_nodots(dir);  }    const char * dirent_size_unit(const dirent_t * de) { @@ -6273 +663101 @@ const char * dir_get_home(void) {   }   return pw->pw_dir;  } + +size_t dir_get_total_marked(void) { + return n_marks; +} + +static size_t dir_get_total_marked_(void) { + size_t n_marks = 0; + + hashmap_walk_state marksws = {0}; + while (hashmap_walk(marks, &marksws)) { + hashmap * dirmarks = marksws.val; + hashmap_walk_state dirmarksws = {0}; + while (hashmap_walk(dirmarks, &dirmarksws)) + n_marks++; + } + + return n_marks; +} + +int dirent_togglemark(dirent_t * de) { + if (de->type == DE_UNKNOWN) return -TOGGLEMARK_DIRENT_UNKNOWN; + + if (de->marked) { + hashmap * dirmarks = hashmap_get(marks, cwd); + de->marked = false; + hashmap_remove(dirmarks, de->name); + if (dirmarks->len == 0) hashmap_remove(marks, cwd); + n_marks--; + } else { + // do not allow marking if an ancestor is already marked + bool bad = false; + char * ancestor = strdup(cwd); + do { + char * sep = strrchr(ancestor, '/'); + *sep = 0; + char * base = sep + 1; + + hashmap * ancestor_marks; + if (sep == ancestor) ancestor_marks = hashmap_get(marks, "/"); + else ancestor_marks = hashmap_get(marks, ancestor); + + if (!ancestor_marks) continue; + if (hashmap_get(ancestor_marks, base)) { + bad = true; + break; + } + } while (*ancestor); + free(ancestor); + if (bad) return -TOGGLEMARK_PARENT_MARKED; + + de->marked = true; + hashmap * dirmarks = hashmap_get(marks, cwd); + if (!dirmarks) { + dirmarks = hashmap_new(marks_set_destroyer); + hashmap_insert(marks, cwd, dirmarks); + } + hashmap_insert(dirmarks, de->name, (void*)true); + + // unmark all children if marking a directory + if (de->type == DE_DIR) { + const char * fmt; + if (!strcmp(cwd, "/")) fmt = "%s%s"; + else fmt = "%s/%s"; + + size_t dirpathlen = snprintf(NULL, 0, fmt, cwd, de->name); + // extra byte for trailing slash + char * dirpath = malloc(dirpathlen + 1 + 1); + sprintf(dirpath, fmt, cwd, de->name); + + // check any direct children + if (hashmap_get(marks, dirpath)) hashmap_remove(marks, dirpath); + + // check any subchildren + strcat(dirpath, "/"); + dirpathlen++; + bool deleted; + do { + deleted = false; + + // this is a horrible solution + hashmap_walk_state ws = {0}; + while (hashmap_walk(marks, &ws)) { + if (!strncmp(ws.key, dirpath, dirpathlen)) { + hashmap_remove(marks, ws.key); + deleted = true; + break; + } + } + } while (deleted); + + free(dirpath); + } + + n_marks = dir_get_total_marked_(); + } + + return 0; +} diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100644 index 0000000..34549a5 --- /dev/null +++ b/src/hashmap.c @@ -00 +1234 @@ +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include "hashmap.h" + +#define HASHMAP_RESIZE_LEN 128 +#define HASHMAP_RESIZE_FACT 75 // percent full + +// FNV-1a +static unsigned long hashs(const char * s) { + unsigned long hash = 2166136261UL; + int c; + while ((c = *s++)) { + hash *= 2166136261UL; + hash ^= c; + } + return hash; +} + + +static struct hashmap_bucket * new_bucket(void) { + struct hashmap_bucket * b = malloc(sizeof(struct hashmap_bucket)); + b->entries = NULL; + b->last = NULL; + return b; +} + + +static struct hashmap_col_list * new_col_list(void) { + struct hashmap_col_list * cl = malloc(sizeof(struct hashmap_col_list)); + cl->next = NULL; + cl->prev = NULL; + cl->hash = 0; + cl->key = NULL; + cl->val = NULL; + return cl; +} + + +static void destroy_col(struct hashmap_col_list * cl) { + free(cl->key); + free(cl); +} + + +static struct hashmap_col_list * append_col(struct hashmap_bucket * b) { + if (!b->entries) { + b->entries = new_col_list(); + b->last = b->entries; + return b->entries; + } + + b->last->next = new_col_list(); + b->last->next->prev = b->last; + b->last = b->last->next; + return b->last; +} + + +static struct hashmap_bucket * hashmap_get_bucket(const hashmap * hm, const char * key) { + unsigned long hash = hashs(key); + size_t index = hash % hm->max; + return hm->buckets[index]; +} + + +static struct hashmap_col_list * search_col_list(struct hashmap_bucket * b, const char * key) { + for (struct hashmap_col_list * cl = b->entries; cl; cl = cl->next) { + if (!strcmp(key, cl->key)) return cl; + } + return NULL; +} + + +hashmap * hashmap_new(void (*destroyer)(void*)) { + hashmap * hm = malloc(sizeof(hashmap)); + hm->destroyer = destroyer; + hm->buckets = malloc(sizeof(struct hashmap_bucket*) * HASHMAP_RESIZE_LEN); + hm->len = 0; + hm->max = HASHMAP_RESIZE_LEN; + + for (size_t i = 0; i < hm->max; i++) { + hm->buckets[i] = NULL; + } + + return hm; +} + + +static void hashmap_resize(hashmap * hm) { + size_t nmax = hm->max * 2; + struct hashmap_bucket ** nb = malloc(sizeof(struct hashmap_bucket*) * nmax); + + for (size_t i = 0; i < nmax; i++) { + nb[i] = NULL; + } + + for (size_t i = 0; i < hm->max; i++) { + struct hashmap_bucket * ob = hm->buckets[i]; + if (!ob || !ob->entries) continue; + + for (struct hashmap_col_list * cl = ob->entries; cl;) { + struct hashmap_col_list * next = cl->next; + + size_t nindex = cl->hash % nmax; + if (!nb[nindex]) { + nb[nindex] = new_bucket(); + nb[nindex]->entries = cl; + nb[nindex]->last = cl; + cl->prev = NULL; + cl->next = NULL; + } else { + struct hashmap_col_list * last = nb[nindex]->last; + last->next = cl; + cl->next = NULL; + cl->prev = last; + nb[nindex]->last = cl; + } + + cl = next; + } + + free(ob); + } + + free(hm->buckets); + hm->buckets = nb; + hm->max = nmax; +} + + +void hashmap_insert(hashmap * hm, char * key, void * val) { + if ((hm->len * 100) / hm->max >= HASHMAP_RESIZE_FACT) { + hashmap_resize(hm); + } + + struct hashmap_col_list * cl = NULL; + + unsigned long hash = hashs(key); + size_t index = hash % hm->max; + if (!hm->buckets[index]) { + hm->buckets[index] = new_bucket(); + } else { + for (struct hashmap_col_list * l = hm->buckets[index]->entries; l; l = l->next) { + // overrite the old collision entry + if (!strcmp(l->key, key)) { + cl = l; + if (hm->destroyer) hm->destroyer(cl->val); + cl->val = val; + } + } + } + + if (!cl) { + cl = append_col(hm->buckets[index]); + cl->hash = hash; + cl->key = strdup(key); + cl->val = val; + + hm->len++; + } +} + + +void * hashmap_get(const hashmap * hm, const char * key) { + struct hashmap_bucket * b = hashmap_get_bucket(hm, key); + if (!b) return NULL; + struct hashmap_col_list * cl = search_col_list(b, key); + if (!cl) return NULL; + return cl->val; +} + + +void hashmap_remove(hashmap * hm, char * key) { + struct hashmap_bucket * b = hashmap_get_bucket(hm, key); + if (!b) return; + + struct hashmap_col_list * cl = search_col_list(b, key); + if (!cl) return; + + struct hashmap_col_list * prev = cl->prev; + struct hashmap_col_list * next = cl->next; + + if (!prev) b->entries = next; + else prev->next = next; + if (next) next->prev = prev; + + if (hm->destroyer) hm->destroyer(cl->val); + destroy_col(cl); +} + + +int hashmap_walk(hashmap * hm, hashmap_walk_state * state) { + for (size_t i = state->curbuck; i < hm->max; i++) { + struct hashmap_bucket * b = hm->buckets[i]; + if (!b || !b->entries) continue; + + if (!state->col) state->col = b->entries; + for (struct hashmap_col_list * cl = state->col; cl; cl = cl->next) { + state->col = cl->next; + state->curbuck = i; + if (!state->col) state->curbuck++; + + state->key = cl->key; + state->val = cl->val; + return 1; + } + state->col = NULL; + } + + return 0; +} + + +void hashmap_destroy(hashmap * hm) { + for (size_t i = 0; i < hm->max; i++) { + struct hashmap_bucket * b = hm->buckets[i]; + if (!b) continue; + + // free each collision in the bucket + for (struct hashmap_col_list * cl = b->entries; cl;) { + struct hashmap_col_list * next = cl->next; + if (hm->destroyer) hm->destroyer(cl->val); + destroy_col(cl); + cl = next; + } + + free(b); + } + + free(hm->buckets); + free(hm); +} diff --git a/src/main.c b/src/main.c index 5abdfa7..1bad0ab 100644 --- a/src/main.c +++ b/src/main.c @@ -116 +117 @@  #include "cvector.h"    int main(int argc, char ** argv) { + dir_init();   const char * cwd = dir_get_cwd();   // dir_get_home may return a static field so it must be copied here   char * home = (char*)dir_get_home(); @@ -2626 +2723 @@ int main(int argc, char ** argv) {   ui_erase();     size_t dirlen = dir_len(&dir); - cvector(dirent_t) entries = dir_entries(&dir);     ui_set_title(cwd, home);   ui_print_dir(&dir, cursor); - ui_print_cursor(cursor, dirlen); + ui_print_cursor(cursor, dirlen, dir_get_total_marked());     ui_refresh();   - dirent_t * cur_de; - if (dirlen == 0) cur_de = NULL; - else cur_de = &entries[cursor]; + dirent_t * cur_de = dir_get_entry(&dir, cursor);   - switch (getch()) { + int inputc = getch(); + if (inputc != KEY_RESIZE) ui_status_info(""); + switch (inputc) {   UP_KEYS:   if (cursor > 0) cursor--; - ui_status_info("");   break;   DOWN_KEYS:   if (cursor < dirlen - 1) cursor++; - ui_status_info("");   break;   LEFT_KEYS: {   // the string from dir_basename(cwd) may be a static field @@ -6113 +5910 @@ int main(int argc, char ** argv) {   ret = dir_list(cwd, &dir);   if (ret < 0) {   ui_status_error(strerror(-ret)); - } else { - if (basename) { - size_t idx; - int i = dir_search_name(&dir, basename, &idx); - if (i >= 0) cursor = idx; - } - ui_status_info(""); + } else if (basename) { + size_t idx; + int i = dir_search_name(&dir, basename, &idx); + if (i >= 0) cursor = idx;   }   } else {   ui_status_error(strerror(-ret)); @@ -767 +716 @@ int main(int argc, char ** argv) {   break;   }   RIGHT_KEYS: { - ui_status_info("");   if (!cur_de) break;   if (cur_de->type == DE_DIR || (cur_de->type == DE_LINK   && cur_de->linktype == DE_DIR)) { @@ -10116 +9513 @@ int main(int argc, char ** argv) {   case KEY_HOME:   case 'g':   cursor = 0; - ui_status_info("");   break;   case KEY_END:   case 'G':   if (dirlen == 0) cursor = 0;   else cursor = dirlen - 1; - ui_status_info("");   break;   case '.': { - ui_status_info("");   config.dots = !config.dots;   if (!cur_de) cursor = 0;   else { @@ -1307 +1216 @@ int main(int argc, char ** argv) {   config.longmode = true;   config.longinline = true;   } - ui_status_info("");   break;   case '/': {   const char * input = ui_readline("/"); @@ -1397 +1296 @@ int main(int argc, char ** argv) {   int i = dir_search_regex(&dir, input, &idx);   if (i >= 0) {   cursor = idx; - ui_status_info("");   } else switch (-i) {   case REGSEARCH_BAD_REGEX:   ui_status_error("Bad RegEx"); @@ -1547 +1436 @@ int main(int argc, char ** argv) {   break;   }   case 'd': { - ui_status_info("");   ui_refresh();   bool del = ui_prompt_deletion(cur_de);   if (!del) { @@ -1806 +16822 @@ int main(int argc, char ** argv) {   else if (cursor > dirlen - 1) cursor = dirlen - 1;   break;   } + case 'm': { + if (!cur_de) break; + int ret = dirent_togglemark(cur_de); + if (ret < 0) switch (-ret) { + case TOGGLEMARK_PARENT_MARKED: + ui_status_error("Ancestor Already Marked"); + break; + case TOGGLEMARK_DIRENT_UNKNOWN: + ui_status_error("Cannot Mark Unknown Dirent"); + break; + default: + ui_status_error("Mark Failed"); + break; + } + break; + }   case 'q':   goto finished;   case KEY_RESIZE: @@ -1934 +1975 @@ finished:   free(home);   dir_free(&dir);   ui_deinit(); + dir_deinit();  } diff --git a/src/ui.c b/src/ui.c index 261d245..798f871 100644 --- a/src/ui.c +++ b/src/ui.c @@ -2207 +2208 @@ void ui_print_dirent(const dirent_t * de, size_t pos, bool selected, const dir_t     int color = COLOR_PAIR(ui_dirent_color(de));   if (selected) color |= A_REVERSE; - waddch(filewin, ' '); + if (de->marked) waddch(filewin, '+'); + else waddch(filewin, ' ');   wattron(filewin, color);   waddstr(filewin, de->name);   wattroff(filewin, color); @@ -35316 +35416 @@ void ui_print_dir(const dir_t * dir, size_t cursor) {   size_t first;   size_t last;   size_t len = dir_len(dir); - cvector(dirent_t) entries = dir_entries(dir);   ui_get_first_last(len, cursor, &first, &last);     for (size_t i = first; i < last; i++) {   size_t pos = i - first; - ui_print_dirent(&entries[i], pos, cursor == i, dir); + dirent_t * de = dir_get_entry(dir, i); + ui_print_dirent(de, pos, cursor == i, dir);   }  }   -void ui_print_cursor(size_t cursor, size_t total) { +void ui_print_cursor(size_t cursor, size_t total, size_t total_marked) {   size_t lines, cols;   getmaxyx(filewin, lines, cols);   if (config.longmode && !config.longinline) { @@ -3786 +37910 @@ void ui_print_cursor(size_t cursor, size_t total) {   } else {   waddstr(filewin, "0/0");   } + + if (total_marked > 0) { + wprintw(filewin, " | %zu Marked", total_marked); + }  }    static void ui_get_first_last(size_t dir_len, size_t cursor, size_t * first, size_t * last) {