#include <stdlib.h>
#include <string.h>
#include <stdint.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 STATUSSZ 256
char STATUS[STATUSSZ];
#define SEARCHSZ 256
char SEARCH[SEARCHSZ];
#define MAXARGS 256
char *args[MAXARGS];
typedef enum {DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE} color_t;
#define HEIGHT (LINES-4)
#define SHOW_FILES 0x01u
#define SHOW_DIRS 0x02u
#define SHOW_HIDDEN 0x04u
struct rover_t {
int nfiles;
int scroll;
int fsel;
uint8_t flags;
char **fnames;
WINDOW *window;
char cwd[FILENAME_MAX];
} rover;
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, uint8_t flags)
{
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, ".") || !strcmp(ep->d_name, ".."))
continue;
if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.')
continue;
/* FIXME: ANSI C doesn't have lstat(). How do we handle symlinks? */
(void) stat(ep->d_name, &statbuf);
if (S_ISDIR(statbuf.st_mode)) {
if (flags & SHOW_DIRS) {
names[i] = (char *) malloc(strlen(ep->d_name) + 2);
strcpy(names[i], ep->d_name);
strcat(names[i], "/");
i++;
}
}
else if (flags & SHOW_FILES) {
names[i] = (char *) malloc(strlen(ep->d_name) + 1);
strcpy(names[i], ep->d_name);
i++;
}
}
n = i; /* Ignore unused space in array caused by filters. */
qsort(names, n, sizeof(char *), spcmp);
(void) closedir(dp);
*namesp = names;
return n;
}
static void
clean_term()
{
endwin();
}
static void
init_term()
{
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(clean_term);
}
static void
update_browser()
{
int i, j, n;
char fmt[32];
for (i = 0, j = rover.scroll; i < HEIGHT && j < rover.nfiles; i++, j++) {
if (j == rover.fsel)
wattr_on(rover.window, A_REVERSE, NULL);
if (rover.fnames[j][0] == '.')
wcolor_set(rover.window, RVC_HIDDEN, NULL);
else if (strchr(rover.fnames[j], '/') != NULL)
wcolor_set(rover.window, RVC_DIR, NULL);
else
wcolor_set(rover.window, RVC_FILE, NULL);
(void) mvwhline(rover.window, i + 1, 1, ' ', COLS - 2);
(void) mvwaddnstr(rover.window, i + 1, 1, rover.fnames[j], COLS - 2);
wcolor_set(rover.window, DEFAULT, NULL);
if (j == rover.fsel)
wattr_off(rover.window, A_REVERSE, NULL);
}
wrefresh(rover.window);
sprintf(STATUS, "%d/%d%n", rover.fsel + 1, rover.nfiles, &n);
sprintf(fmt, "%% %dd/%%d", 10-n);
STATUS[0] = rover.flags & SHOW_FILES ? 'F' : ' ';
STATUS[1] = rover.flags & SHOW_DIRS ? 'D' : ' ';
STATUS[2] = rover.flags & SHOW_HIDDEN ? 'H' : ' ';
sprintf(STATUS+3, fmt, rover.fsel + 1, rover.nfiles);
color_set(RVC_STATUS, NULL);
mvaddstr(LINES - 1, COLS - strlen(STATUS), STATUS);
color_set(DEFAULT, NULL);
refresh();
}
/* NOTE: The caller needs to write the new path to rover.cwd
* *before* calling this function. */
static void
cd()
{
int i;
rover.fsel = 0;
rover.scroll = 0;
(void) chdir(rover.cwd);
(void) mvhline(0, 0, ' ', COLS);
color_set(RVC_CWD, NULL);
(void) mvaddnstr(0, 0, rover.cwd, COLS);
color_set(DEFAULT, NULL);
for (i = 0; i < rover.nfiles; i++)
free(rover.fnames[i]);
if (rover.nfiles)
free(rover.fnames);
rover.nfiles = ls(rover.cwd, &rover.fnames, rover.flags);
(void) wclear(rover.window);
wcolor_set(rover.window, RVC_BORDER, NULL);
wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0);
wcolor_set(rover.window, DEFAULT, NULL);
update_browser();
refresh();
}
static void
spawn()
{
pid_t pid;
int status;
pid = fork();
if (pid > 0) {
/* fork() succeeded. */
clean_term();
(void) waitpid(pid, &status, 0);
init_term();
doupdate();
}
else if (pid == 0) {
/* Child process. */
execvp(args[0], args);
}
}
int
main()
{
char *program;
char *key;
init_term();
/* Avoid invalid free() calls in cd() by zeroing the tally. */
rover.nfiles = 0;
rover.flags = SHOW_FILES | SHOW_DIRS;
(void) getcwd(rover.cwd, FILENAME_MAX);
strcat(rover.cwd, "/");
rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0);
cd();
while (1) {
key = keyname(getch());
if (!strcmp(key, RVK_QUIT))
break;
else if (!strcmp(key, RVK_DOWN)) {
if (rover.fsel == rover.nfiles - 1)
rover.scroll = rover.fsel = 0;
else {
rover.fsel++;
if ((rover.fsel - rover.scroll) == HEIGHT)
rover.scroll++;
}
update_browser();
}
else if (!strcmp(key, RVK_UP)) {
if (rover.fsel == 0) {
rover.fsel = rover.nfiles - 1;
rover.scroll = rover.nfiles - HEIGHT;
if (rover.scroll < 0)
rover.scroll = 0;
}
else {
rover.fsel--;
if (rover.fsel < rover.scroll)
rover.scroll--;
}
update_browser();
}
else if (!strcmp(key, RVK_JUMP_DOWN)) {
rover.fsel += RV_JUMP;
if (rover.fsel >= rover.nfiles)
rover.fsel = rover.nfiles - 1;
if (rover.nfiles > HEIGHT) {
rover.scroll += RV_JUMP;
if (rover.scroll > rover.nfiles - HEIGHT)
rover.scroll = rover.nfiles - HEIGHT;
}
update_browser();
}
else if (!strcmp(key, RVK_JUMP_UP)) {
rover.fsel -= RV_JUMP;
if (rover.fsel < 0)
rover.fsel = 0;
rover.scroll -= RV_JUMP;
if (rover.scroll < 0)
rover.scroll = 0;
update_browser();
}
else if (!strcmp(key, RVK_CD_DOWN)) {
if (strchr(rover.fnames[rover.fsel], '/') == NULL)
continue;
strcat(rover.cwd, rover.fnames[rover.fsel]);
cd();
}
else if (!strcmp(key, RVK_CD_UP)) {
if (strlen(rover.cwd) == 1)
continue;
rover.cwd[strlen(rover.cwd) - 1] = '\0';
*(strrchr(rover.cwd, '/') + 1) = '\0';
cd();
}
else if (!strcmp(key, RVK_HOME)) {
strcpy(rover.cwd, getenv("HOME"));
if (rover.cwd[strlen(rover.cwd) - 1] != '/')
strcat(rover.cwd, "/");
cd();
}
else if (!strcmp(key, RVK_SHELL)) {
program = getenv("SHELL");
if (program) {
args[0] = program;
args[1] = NULL;
spawn();
}
}
else if (!strcmp(key, RVK_VIEW)) {
if (strchr(rover.fnames[rover.fsel], '/') != NULL)
continue;
program = getenv("PAGER");
if (program) {
args[0] = program;
args[1] = rover.fnames[rover.fsel];
args[2] = NULL;
spawn();
}
}
else if (!strcmp(key, RVK_EDIT)) {
if (strchr(rover.fnames[rover.fsel], '/') != NULL)
continue;
program = getenv("EDITOR");
if (program) {
args[0] = program;
args[1] = rover.fnames[rover.fsel];
args[2] = NULL;
spawn();
}
}
else if (!strcmp(key, RVK_SEARCH)) {
int ch, length, sel, oldsel, oldscroll;
color_t color;
oldsel = rover.fsel;
oldscroll = rover.scroll;
*SEARCH = '\0';
length = 0;
while ((ch = getch()) != '\r') {
switch (ch) {
case 8:
case 127:
if (length)
SEARCH[--length] = '\0';
if (!length) {
rover.fsel = oldsel;
rover.scroll = oldscroll;
}
break;
default:
if (length < SEARCHSZ - 2)
SEARCH[length++] = ch;
}
if (length) {
for (sel = 0; sel < rover.nfiles; sel++)
if (!strncmp(rover.fnames[sel], SEARCH, length))
break;
if (sel < rover.nfiles) {
color = GREEN;
rover.fsel = sel;
if (rover.nfiles > HEIGHT) {
if (sel > rover.nfiles - HEIGHT)
rover.scroll = rover.nfiles - HEIGHT;
else
rover.scroll = sel;
}
}
else
color = RED;
}
update_browser();
SEARCH[length] = ' ';
color_set(color, NULL);
mvaddstr(LINES - 1, 0, SEARCH);
color_set(DEFAULT, NULL);
}
move(LINES - 1, 0);
clrtoeol();
update_browser();
}
else if (!strcmp(key, RVK_TG_FILES)) {
rover.flags ^= SHOW_FILES;
cd();
}
else if (!strcmp(key, RVK_TG_DIRS)) {
rover.flags ^= SHOW_DIRS;
cd();
}
else if (!strcmp(key, RVK_TG_HIDDEN)) {
rover.flags ^= SHOW_HIDDEN;
cd();
}
}
while (rover.nfiles--) free(rover.fnames[rover.nfiles]);
free(rover.fnames);
delwin(rover.window);
return 0;
}