commit 71ee162773e8955eb4d4ece8959a8fb005bba95d from: Marcel Rodrigues date: Fri Nov 07 13:54:20 2014 UTC Import files from skit. commit - /dev/null commit + 71ee162773e8955eb4d4ece8959a8fb005bba95d blob - /dev/null blob + d85ed884e19d232bd9354cd1886d757c53f870f3 (mode 644) --- /dev/null +++ .gitignore @@ -0,0 +1 @@ +rover blob - /dev/null blob + 48a221a49900cdc8a307e3e7027dd5a5e6905567 (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,13 @@ +CLINK = -lcurses +CFLAGS = -Wall -Wextra -Werror -std=c89 +DEBUG ?= 0 +ifeq ($(DEBUG), 1) + CFLAGS += -O0 -g +else + CFLAGS += -O2 +endif + +all : rover + +rover : rover.c config.h + $(CC) $(CFLAGS) -o $@ $< $(CLINK) blob - /dev/null blob + 2b49ec11645715470257939247bc337bd3b44606 (mode 644) --- /dev/null +++ config.h @@ -0,0 +1,12 @@ +#define RVK_QUIT 'q' +#define RVK_DOWN 'j' +#define RVK_UP 'k' +#define RVK_JUMP_DOWN 'J' +#define RVK_JUMP_UP 'K' +#define RVK_CD_DOWN 'l' +#define RVK_CD_UP 'h' +#define RVK_HOME 'H' +#define RVK_SHELL '\r' +#define RVK_EDIT ' ' + +#define RV_JUMP 10 blob - /dev/null blob + 9fde40875ba652b6f1a539db580e56a856eb91e2 (mode 644) --- /dev/null +++ rover.c @@ -0,0 +1,284 @@ +/* ToDo + * - incremental search inside directory; + * - filters (show/hide diretories/files, apply glob pattern) + * - tabs (only store paths?); + * - browsing history (use keys < & > to navigate); + */ + +/* POSIX 2008: http://pubs.opengroup.org/onlinepubs/9699919799/toc.htm */ + +#include +#include +#include /* ? */ +#include /* FILENAME_MAX */ +#include /* setlocale(), LC_ALL */ +#include /* chdir(), getcwd() */ +#include /* DIR, struct dirent, opendir(), ... */ +#include +#include /* waitpid() */ +#include + +#include "config.h" + +#define TXTBUFSZ 256 +char TXTBUF[TXTBUFSZ]; +#define MAXARGS 256 +char *args[MAXARGS]; + +enum {DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE}; + +struct skit_t { + int nfiles; + int scroll; + int fsel; + char **fnames; + WINDOW *window; + char cwd[FILENAME_MAX]; +} skit; + +static int +spcmp(const void *a, const void *b) +{ + int isdir1, isdir2, cmpdir; + const char *s1 = *(const char **) a; + const char *s2 = *(const char **) b; + isdir1 = strchr(s1, '/') != NULL; + isdir2 = strchr(s2, '/') != NULL; + cmpdir = isdir2 - isdir1; + /* FIXME: why doesn't `return cmpdir || strcoll(s1, s2)` work here? */ + return cmpdir ? cmpdir : strcoll(s1, s2); +} + +int +ls(char *path, char ***namesp) +{ + DIR *dp; + struct dirent *ep; + struct stat statbuf; + char **names; + int i, n; + + if((dp = opendir(path)) == NULL) + return -1; + n = -2; /* We don't want the entries "." and "..". */ + while (readdir(dp)) n++; + rewinddir(dp); + names = (char **) malloc(n * sizeof(char *)); + i = 0; + while ((ep = readdir(dp))) { + if (strcmp(ep->d_name, ".") == 0 || strcmp(ep->d_name, "..") == 0) + continue; + /* FIXME: ANSI C doesn't have lstat(). How do we handle symlinks? */ + (void) stat(ep->d_name, &statbuf); + names[i] = (char *) malloc(strlen(ep->d_name) + 2); + strcpy(names[i], ep->d_name); + if (S_ISDIR(statbuf.st_mode)) + strcat(names[i], "/"); + i++; + } + qsort(names, n, sizeof(char *), spcmp); + (void) closedir(dp); + *namesp = names; + return n; +} + +static void +sk_clean() +{ + endwin(); +} + +static void +sk_init() +{ + setlocale(LC_ALL, ""); + initscr(); + cbreak(); /* Get one character at a time. */ + noecho(); + nonl(); /* No NL->CR/NL on output. */ + intrflush(stdscr, FALSE); + keypad(stdscr, TRUE); + curs_set(FALSE); /* Hide blinking cursor. */ + if (has_colors()) { + start_color(); + init_pair(RED, COLOR_RED, COLOR_BLACK); + init_pair(GREEN, COLOR_GREEN, COLOR_BLACK); + init_pair(YELLOW, COLOR_YELLOW,COLOR_BLACK); + init_pair(BLUE, COLOR_BLUE, COLOR_BLACK); + init_pair(CYAN, COLOR_CYAN, COLOR_BLACK); + init_pair(MAGENTA, COLOR_MAGENTA, COLOR_BLACK); + init_pair(WHITE, COLOR_WHITE, COLOR_BLACK); + } + atexit(sk_clean); +} + +static void +update_browser() +{ + int i, j; + + for (i = 0, j = skit.scroll; i < LINES - 4 && j < skit.nfiles; i++, j++) { + if (j == skit.fsel) + wattr_on(skit.window, A_REVERSE, NULL); + (void) mvwhline(skit.window, i + 1, 1, + ' ', COLS - 2); + (void) mvwaddnstr(skit.window, i + 1, 1, + skit.fnames[j], COLS - 2); + if (j == skit.fsel) + wattr_off(skit.window, A_REVERSE, NULL); + } + wrefresh(skit.window); + /* C89 doesn't have snprintf(), but a buffer overrun will only occur here + * if the number of files reach 10 ^ (TXTBUFSZ / 2), which is unlikely. */ + sprintf(TXTBUF, "% 10d/%d", skit.fsel + 1, skit.nfiles); + mvaddstr(LINES - 1, COLS - strlen(TXTBUF), TXTBUF); + refresh(); +} + +/* NOTE: The caller needs to write the new path to skit.cwd + * *before* calling this function. */ +static void +cd() +{ + int i; + + skit.fsel = 0; + skit.scroll = 0; + (void) chdir(skit.cwd); + (void) mvhline(0, 0, ' ', COLS); + (void) mvaddnstr(0, 0, skit.cwd, COLS); + for (i = 0; i < skit.nfiles; i++) + free(skit.fnames[i]); + if (skit.nfiles) + free(skit.fnames); + skit.nfiles = ls(skit.cwd, &skit.fnames); + (void) wclear(skit.window); + wborder(skit.window, 0, 0, 0, 0, 0, 0, 0, 0); + update_browser(); + refresh(); +} + +static void +spawn() +{ + pid_t pid; + int status; + + pid = fork(); + if (pid > 0) { + /* fork() succeeded. */ + sk_clean(); + (void) waitpid(pid, &status, 0); + sk_init(); + doupdate(); + } + else if (pid == 0) { + /* Child process. */ + execvp(args[0], args); + } +} + +int +main() +{ + int ch; + char *program; + + sk_init(); + /* Avoid invalid free() calls in cd() by zeroing the tally. */ + skit.nfiles = 0; + (void) getcwd(skit.cwd, FILENAME_MAX); + strcat(skit.cwd, "/"); + skit.window = subwin(stdscr, LINES - 2, COLS, 1, 0); + cd(); + while ((ch = getch()) != RVK_QUIT) switch (ch) { + case RVK_DOWN: + if (skit.fsel == skit.nfiles - 1) + skit.scroll = skit.fsel = 0; + else { + skit.fsel++; + if ((skit.fsel - skit.scroll) == (LINES - 4)) + skit.scroll++; + } + update_browser(); + break; + case RVK_UP: + if (skit.fsel == 0) { + skit.fsel = skit.nfiles - 1; + skit.scroll = skit.nfiles - LINES + 4; + if (skit.scroll < 0) + skit.scroll = 0; + } + else { + skit.fsel--; + if (skit.fsel < skit.scroll) + skit.scroll--; + } + update_browser(); + break; + case RVK_JUMP_DOWN: + skit.fsel += RV_JUMP; + if (skit.fsel >= skit.nfiles) + skit.fsel = skit.nfiles - 1; + if (skit.nfiles > LINES - 4) { + skit.scroll += RV_JUMP; + if (skit.scroll > skit.nfiles - LINES + 4) + skit.scroll = skit.nfiles - LINES + 4; + } + update_browser(); + break; + case RVK_JUMP_UP: + skit.fsel -= RV_JUMP; + if (skit.fsel < 0) + skit.fsel = 0; + skit.scroll -= RV_JUMP; + if (skit.scroll < 0) + skit.scroll = 0; + update_browser(); + break; + case RVK_CD_DOWN: + if (strchr(skit.fnames[skit.fsel], '/') == NULL) + continue; + strcat(skit.cwd, skit.fnames[skit.fsel]); + cd(); + break; + case RVK_CD_UP: + if (strlen(skit.cwd) == 1) + continue; + skit.cwd[strlen(skit.cwd) - 1] = '\0'; + *(strrchr(skit.cwd, '/') + 1) = '\0'; + cd(); + break; + case RVK_HOME: + strcpy(skit.cwd, getenv("HOME")); + if (skit.cwd[strlen(skit.cwd) - 1] != '/') + strcat(skit.cwd, "/"); + cd(); + break; + case RVK_SHELL: + program = getenv("SHELL"); + if (program) { + args[0] = program; + args[1] = NULL; + spawn(); + } + break; + case RVK_EDIT: + if (strchr(skit.fnames[skit.fsel], '/') != NULL) + continue; + program = getenv("EDITOR"); + if (program) { + args[0] = program; + args[1] = skit.fnames[skit.fsel]; + args[2] = NULL; + spawn(); + } + break; + default: + continue; + } + while (skit.nfiles--) free(skit.fnames[skit.nfiles]); + free(skit.fnames); + delwin(skit.window); + return 0; +}