Thumbnail

rani/cscroll.git

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

commit e36b0fb8558c588613060549d9c1e2d7f7eeb34c Author: rani <clagv.randomgames@gmail.com> Date: Tue Dec 30 23:38:22 2025 +0000 Add: UI, basic file directory navigation diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..37cbb01 --- /dev/null +++ b/Makefile @@ -00 +121 @@ +BIN = cscroll +SRC = src/dir.c src/ui.c src/main.c +OBJ = ${SRC:.c=.o} + +CC ?= cc + +CFLAGS += -Iinclude -Wall -Wextra -O2 +LDFLAGS += -lncurses + +all: ${BIN} + +${BIN}: ${OBJ} + ${CC} ${CFLAGS} ${OBJ} -o $@ ${LDFLAGS} + +src/.c.o: + ${CC} -c ${CFLAGS} $< + +clean: + rm -f ${BIN} ${OBJ} + +.PHONY: all clean diff --git a/include/dir.h b/include/dir.h index 38f14d1..132915d 100644 --- a/include/dir.h +++ b/include/dir.h @@ -4914 +4921 @@ typedef struct {  typedef struct {   size_t len;   cvector(dirent_t) entries; + + size_t longest_uname; + size_t longest_gname; + size_t longest_size;  } dir_t;    int dir_list(const char * path, dir_t * dir);  void dir_free(dir_t * dir); +const char * dir_get_cwd(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  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); +int dir_cd_back(const char * cwd); +int dir_cd(const char * cwd, const char * next);    #endif /* DIR_H */ diff --git a/include/ui.h b/include/ui.h new file mode 100644 index 0000000..a59aacd --- /dev/null +++ b/include/ui.h @@ -00 +137 @@ +#ifndef UI_H +#define UI_H + +#include <ncurses.h> +#include <stdbool.h> +#include <stddef.h> + +#include "dir.h" + +enum ui_color { + COLOR_FILE = 1, + COLOR_DIR, + COLOR_FIFO, + COLOR_LINK, + COLOR_BLOCK, + COLOR_CHAR, + COLOR_SOCKET, + COLOR_UNKNOWN, + COLOR_EXEC, + + RED, + YELLOW, + MAGENTA, + WHITE, + +}; + +void ui_init(void); +void ui_deinit(void); +void ui_set_title(const char * title); +void ui_set_status(const char * status); +void ui_erase(void); +void ui_refresh(void); +void ui_print_dir(const dir_t * dir, size_t cursor, bool longmode); +void ui_print_cursor(size_t cursor, size_t total); + +#endif /* UI_H */ diff --git a/src/dir.c b/src/dir.c index 7e68625..247e502 100644 --- a/src/dir.c +++ b/src/dir.c @@ -116 +117 @@  #include "dir.h"  #include "cvector.h"   +#define CWDSZ (PATH_MAX + 1)  #define LINKNAMESZ PATH_MAX    static void dir_entry(int dirfd, const char * name, dirent_t * dirent); @@ -2211 +2323 @@ static void free_dirent(void * p) {   free(de->gname);   free(de->linkname);  } +static size_t ilen(size_t i, int base) { + if (i == 0) return 1; + size_t r = 0; + while (i != 0) { + r++; + i /= base; + } + return r; +}    int dir_list(const char * path, dir_t * dir) {   dir->len = 0;   dir->entries = NULL;   cvector_init(dir->entries, 1, free_dirent); + dir->longest_uname = 0; + dir->longest_gname = 0; + dir->longest_size = 0;     struct dirent * de;   DIR * dp = opendir(path); @@ -436 +5615 @@ int dir_list(const char * path, dir_t * dir) {   dir_entry(dirfd(dp), de->d_name, &dirent);   cvector_push_back(dir->entries, dirent);   dir->len++; + + size_t uname_len = 0; + size_t gname_len = 0; + size_t size_len = ilen(dirent.size, 10); + if (dirent.uname) uname_len = strlen(dirent.uname); + if (dirent.gname) gname_len = strlen(dirent.gname); + if (uname_len > dir->longest_uname) dir->longest_uname = uname_len; + if (gname_len > dir->longest_gname) dir->longest_gname = gname_len; + if (size_len > dir->longest_size) dir->longest_size = size_len;   }     closedir(dp); @@ -1923 +21445 @@ const char * dirent_prettymode(const dirent_t * de) {     return s;  } + +const char * dir_get_cwd(void) { + static char cwd[CWDSZ]; + + getcwd(cwd, CWDSZ); + return cwd; +} + +int dir_cd_back(const char * cwd) { + if (!*cwd) return -1; + if (!strcmp(cwd, "/")) return 0; + + char * newwd = strdup(cwd); + char * p = strrchr(newwd, '/'); + *p = 0; + if (*newwd == 0) { + *newwd = '/'; + *(newwd + 1) = 0; + } + int ret = chdir(newwd); + free(newwd); + + if (ret < 0) return -1; + return 0; +} + +int dir_cd(const char * cwd, const char * next) { + size_t cwdlen = strlen(cwd); + size_t nextlen = strlen(next); + + // extra byte for '/' character + char * newwd = malloc(cwdlen + nextlen + 1 + 1); + strcpy(newwd, cwd); + newwd[cwdlen] = '/'; + strcpy(newwd + cwdlen + 1, next); + + int ret = chdir(newwd); + free(newwd); + + if (ret < 0) return -1; + return 0; +} diff --git a/src/main.c b/src/main.c index 7b4a140..8948789 100644 --- a/src/main.c +++ b/src/main.c @@ -124 +171 @@  #include <stdio.h>  #include <stddef.h> +#include <ncurses.h>   +#include "ui.h"  #include "dir.h"    int main(int argc, char ** argv) { + ui_init(); + + const char * cwd = dir_get_cwd(); +   dir_t dir; - dir_list(argv[1], &dir); - - for (size_t i = 0; i < dir.len; i++) { - dirent_t * de = &dir.entries[i]; - char c = dirent_crepr(de); - if (!c) c = ' '; - const char * pmode = dirent_prettymode(de); - printf("%s %s %s %lu %ld %s%c", pmode, de->uname, de->gname, de->size, de->mtime, de->name, c); - if (de->linkname) { - c = dirent_creprl(de); - if (!c) c = ' '; - printf(" -> %s%c\n", de->linkname, c); - } else putchar('\n'); + dir_list(cwd, &dir); + + size_t cursor = 0; + ui_set_status(""); + for (;;) { + ui_erase(); + + ui_set_title(cwd); + + ui_print_dir(&dir, cursor, true); + ui_print_cursor(cursor, dir.len); + + ui_refresh(); + + dirent_t * cur_de = &dir.entries[cursor]; + switch (getch()) { + case KEY_UP: + if (cursor > 0) cursor--; + break; + case KEY_DOWN: + if (cursor < dir.len - 1) cursor++; + break; + case KEY_LEFT: + if (dir_cd_back(cwd) >= 0) { + cursor = 0; + cwd = dir_get_cwd(); + dir_free(&dir); + dir_list(cwd, &dir); + } + break; + case KEY_RIGHT: + if (cur_de->type == DE_DIR) { + if (dir_cd(cwd, cur_de->name) >= 0) { + cursor = 0; + cwd = dir_get_cwd(); + dir_free(&dir); + dir_list(cwd, &dir); + } + } + break; + case KEY_HOME: + case 'g': + cursor = 0; + break; + case KEY_END: + case 'G': + cursor = dir.len - 1; + break; + case 'q': + goto finished; + default: break; + }   }   +finished:   dir_free(&dir); + ui_deinit();  } diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..8100059 --- /dev/null +++ b/src/ui.c @@ -00 +1204 @@ +#include <ncurses.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <locale.h> +#include <time.h> + +#include "ui.h" +#include "dir.h" + +static WINDOW * titlewin = NULL; +static WINDOW * statuswin = NULL; +static WINDOW * filewin = NULL; + +static void win_set(WINDOW * win, const char * str, int attrs); +static int ui_dirent_color(const dirent_t * de); +static void ui_print_dirent(const dirent_t * de, size_t pos, bool selected, bool longmode, const dir_t * dir); +static void ui_wlpadstr(WINDOW * win, const char * s, size_t len); +static void ui_get_first_last(size_t n_dirents, size_t cursor, size_t * first, size_t * last); + +void ui_init(void) { + setlocale(LC_CTYPE, ""); + + initscr(); + keypad(stdscr, TRUE); + curs_set(0); + noecho(); + + titlewin = newwin(1, COLS, 0, 0); + statuswin = newwin(1, COLS, 1, 0); + filewin = newwin(LINES - 2, COLS, 2, 0); + + clear(); + refresh(); + + use_default_colors(); + start_color(); + + init_pair(COLOR_FILE, -1, -1); + init_pair(COLOR_DIR, COLOR_BLUE, -1); + init_pair(COLOR_FIFO, COLOR_YELLOW, -1); + init_pair(COLOR_LINK, COLOR_CYAN, -1); + init_pair(COLOR_BLOCK, COLOR_YELLOW, -1); + init_pair(COLOR_CHAR, COLOR_YELLOW, -1); + init_pair(COLOR_SOCKET, COLOR_MAGENTA, -1); + init_pair(COLOR_UNKNOWN, COLOR_RED, -1); + init_pair(COLOR_EXEC, COLOR_GREEN, -1); + + init_pair(RED, COLOR_RED, -1); + init_pair(YELLOW, COLOR_YELLOW, -1); + init_pair(MAGENTA, COLOR_MAGENTA, -1); + init_pair(WHITE, -1, -1); +} + +void ui_deinit(void) { + endwin(); +} + +void win_set(WINDOW * win, const char * str, int attrs) { + werase(win); + wattron(win, attrs); + waddstr(win, str); + wattroff(win, attrs); +} + +void ui_set_title(const char * title) { + win_set(titlewin, title, 0); +} + +void ui_set_status(const char * status) { + win_set(statuswin, status, 0); +} + +void ui_erase(void) { + werase(filewin); +} + +void ui_refresh(void) { + wnoutrefresh(titlewin); + wnoutrefresh(statuswin); + wnoutrefresh(filewin); + doupdate(); +} + +static void ui_wlpadstr(WINDOW * win, const char * s, size_t len) { + size_t slen = strlen(s); + + for (size_t i = slen; i < len; i++) { + waddch(win, ' '); + } + + waddstr(win, s); +} + +void ui_print_dirent(const dirent_t * de, size_t pos, bool selected, bool longmode, const dir_t * dir) { + static const enum ui_color mode_colors[] = { + ['r'] = RED, ['w'] = MAGENTA, ['x'] = COLOR_EXEC, + ['s'] = YELLOW, ['S'] = YELLOW, ['t'] = RED, + ['T'] = RED, ['-'] = WHITE, ['?'] = RED, + + ['.'] = WHITE, + ['d'] = COLOR_DIR, ['b'] = COLOR_BLOCK, ['c'] = COLOR_CHAR, + ['l'] = COLOR_LINK, ['|'] = COLOR_FIFO, ['='] = COLOR_SOCKET, + }; + + static char stime[128]; + static char isbuf[40]; // should be big enough to hold any integer + + wmove(filewin, pos, 0); + + if (longmode) { + const char * pmode = dirent_prettymode(de); + for (const char * p = pmode; *p; p++) { + int color = COLOR_PAIR(mode_colors[(unsigned)*p]); + if (*p == '-' || *p == '.') color |= A_DIM; + wattron(filewin, color); + waddch(filewin, *p); + wattroff(filewin, color); + } + + if (de->uname) ui_wlpadstr(filewin, de->uname, dir->longest_uname + 1); + else waddstr(filewin, " ?"); + + if (de->gname) ui_wlpadstr(filewin, de->gname, dir->longest_gname + 1); + else waddstr(filewin, " ?"); + + snprintf(isbuf, sizeof(isbuf), "%lu", de->size); + ui_wlpadstr(filewin, isbuf, dir->longest_size + 1); + + strftime(stime, sizeof(stime), "%b %d %H:%M %Y", localtime(&de->mtime)); + wprintw(filewin, " %s ", stime); + } + + int color = COLOR_PAIR(ui_dirent_color(de)); + if (selected) color |= A_REVERSE; + wattron(filewin, color); + waddstr(filewin, de->name); + wattroff(filewin, color); + char c = dirent_crepr(de); + if (c) waddch(filewin, c); +} + +static int ui_dirent_color(const dirent_t * de) { + int color; + switch (de->type) { + case DE_FILE: color = COLOR_FILE; break; + case DE_DIR: color = COLOR_DIR; break; + case DE_FIFO: color = COLOR_FIFO; break; + case DE_LINK: color = COLOR_LINK; break; + case DE_BLOCK: color = COLOR_BLOCK; break; + case DE_CHAR: color = COLOR_CHAR; break; + case DE_SOCKET: color = COLOR_SOCKET; break; + case DE_UNKNOWN: color = COLOR_UNKNOWN; break; + default: color = WHITE; break; + } + + if (de->type == DE_FILE && dirent_isexec(de)) { + color = COLOR_EXEC; + } + + return color; +} + +void ui_print_dir(const dir_t * dir, size_t cursor, bool longmode) { + size_t first; + size_t last; + ui_get_first_last(dir->len, cursor, &first, &last); + + for (size_t i = first; i < last; i++) { + size_t pos = i - first; + ui_print_dirent(&dir->entries[i], pos, cursor == i, longmode, dir); + } +} + +void ui_print_cursor(size_t cursor, size_t total) { + size_t lines, cols; + getmaxyx(filewin, lines, cols); + if (total < lines - 2) wmove(filewin, total + 1, 0); + else wmove(filewin, lines - 1, 0); + + wprintw(filewin, "%zu/%zu", cursor + 1, total); +} + +static void ui_get_first_last(size_t dir_len, size_t cursor, size_t * first, size_t * last) { + size_t lines, cols; + getmaxyx(filewin, lines, cols); + lines -= 2; + // try to keep cursor in the middle of the window + if (dir_len < lines) { + *first = 0; + *last = dir_len; + } else { + size_t off = lines / 2; + if (cursor < off) { + *first = 0; + if (dir_len > lines) *last = lines; + else *last = dir_len; + } else { + *first = cursor - off; + if (dir_len > cursor + off) *last = cursor + off; + else *last = dir_len; + } + } +}