/* ToDo
* - 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 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;
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 ^ (STATUSSZ / 2), which is unlikely. */
sprintf(STATUS, "% 10d/%d", skit.fsel + 1, skit.nfiles);
mvaddstr(LINES - 1, COLS - strlen(STATUS), STATUS);
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()
{
char *program;
char *key;
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 (1) {
key = keyname(getch());
if (!strcmp(key, RVK_QUIT))
break;
else if (!strcmp(key, 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();
}
else if (!strcmp(key, 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();
}
else if (!strcmp(key, 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();
}
else if (!strcmp(key, 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();
}
else if (!strcmp(key, RVK_CD_DOWN)) {
if (strchr(skit.fnames[skit.fsel], '/') == NULL)
continue;
strcat(skit.cwd, skit.fnames[skit.fsel]);
cd();
}
else if (!strcmp(key, RVK_CD_UP)) {
if (strlen(skit.cwd) == 1)
continue;
skit.cwd[strlen(skit.cwd) - 1] = '\0';
*(strrchr(skit.cwd, '/') + 1) = '\0';
cd();
}
else if (!strcmp(key, RVK_HOME)) {
strcpy(skit.cwd, getenv("HOME"));
if (skit.cwd[strlen(skit.cwd) - 1] != '/')
strcat(skit.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_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();
}
}
else if (!strcmp(key, RVK_SEARCH)) {
int ch, length, sel, oldsel, oldscroll;
color_t color;
oldsel = skit.fsel;
oldscroll = skit.scroll;
*SEARCH = '\0';
length = 0;
while ((ch = getch()) != '\r') {
switch (ch) {
case 8:
case 127:
if (length)
SEARCH[--length] = '\0';
if (!length) {
skit.fsel = oldsel;
skit.scroll = oldscroll;
}
break;
default:
if (length < SEARCHSZ - 2)
SEARCH[length++] = ch;
}
if (length) {
for (sel = 0; sel < skit.nfiles; sel++)
if (!strncmp(skit.fnames[sel], SEARCH, length))
break;
if (sel < skit.nfiles) {
color = GREEN;
skit.fsel = sel;
if (skit.nfiles > LINES - 4) {
if (sel > skit.nfiles - LINES + 4)
skit.scroll = skit.nfiles - LINES + 4;
else
skit.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();
}
}
while (skit.nfiles--) free(skit.fnames[skit.nfiles]);
free(skit.fnames);
delwin(skit.window);
return 0;
}