Commit Diff


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 <stdlib.h>
+#include <string.h>
+#include <sys/types.h>  /* ? */
+#include <stdio.h>      /* FILENAME_MAX */
+#include <locale.h>     /* setlocale(), LC_ALL */
+#include <unistd.h>     /* chdir(), getcwd() */
+#include <dirent.h>     /* DIR, struct dirent, opendir(), ... */
+#include <sys/stat.h>
+#include <sys/wait.h>   /* waitpid() */
+#include <curses.h>
+
+#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;
+}