Blob


1 #include <stdlib.h>
2 #include <stdint.h>
3 #include <ctype.h>
4 #include <string.h>
5 #include <sys/types.h> /* pid_t, ... */
6 #include <stdio.h> /* FILENAME_MAX */
7 #include <locale.h> /* setlocale(), LC_ALL */
8 #include <unistd.h> /* chdir(), getcwd() */
9 #include <dirent.h> /* DIR, struct dirent, opendir(), ... */
10 #include <sys/stat.h>
11 #include <sys/wait.h> /* waitpid() */
12 #include <curses.h>
14 #include "config.h"
16 #define ROWSZ 256
17 char ROW[ROWSZ];
18 #define STATUSSZ 256
19 char STATUS[STATUSSZ];
20 #define SEARCHSZ 256
21 char SEARCH[SEARCHSZ];
22 #define MAXARGS 256
23 char *args[MAXARGS];
25 typedef enum {DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE} color_t;
27 #define HEIGHT (LINES-4)
29 #define SHOW_FILES 0x01u
30 #define SHOW_DIRS 0x02u
31 #define SHOW_HIDDEN 0x04u
33 typedef struct {
34 char *name;
35 off_t size;
36 } row_t;
38 struct rover_t {
39 int tab;
40 int nfiles;
41 int scroll[10];
42 int fsel[10];
43 uint8_t flags[10];
44 row_t *rows;
45 WINDOW *window;
46 char cwd[10][FILENAME_MAX];
47 } rover;
49 #define FNAME(I) rover.rows[I].name
50 #define FSIZE(I) rover.rows[I].size
51 #define SCROLL rover.scroll[rover.tab]
52 #define FSEL rover.fsel[rover.tab]
53 #define FLAGS rover.flags[rover.tab]
54 #define CWD rover.cwd[rover.tab]
56 static int
57 rowcmp(const void *a, const void *b)
58 {
59 int isdir1, isdir2, cmpdir;
60 const row_t *r1 = a;
61 const row_t *r2 = b;
62 isdir1 = strchr(r1->name, '/') != NULL;
63 isdir2 = strchr(r2->name, '/') != NULL;
64 cmpdir = isdir2 - isdir1;
65 return cmpdir ? cmpdir : strcoll(r1->name, r2->name);
66 }
68 void
69 free_rows(row_t **rowsp, int nfiles)
70 {
71 int i;
73 for (i = 0; i < nfiles; i++)
74 free((*rowsp)[i].name);
75 free(*rowsp);
76 *rowsp = NULL;
77 }
79 int
80 ls(char *path, row_t **rowsp, uint8_t flags)
81 {
82 DIR *dp;
83 struct dirent *ep;
84 struct stat statbuf;
85 row_t *rows;
86 int i, n;
88 if((dp = opendir(path)) == NULL)
89 return -1;
90 n = -2; /* We don't want the entries "." and "..". */
91 while (readdir(dp)) n++;
92 rewinddir(dp);
93 rows = (row_t *) malloc(n * sizeof(row_t));
94 i = 0;
95 while ((ep = readdir(dp))) {
96 if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
97 continue;
98 if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.')
99 continue;
100 /* FIXME: ANSI C doesn't have lstat(). How do we handle symlinks? */
101 stat(ep->d_name, &statbuf);
102 if (S_ISDIR(statbuf.st_mode)) {
103 if (flags & SHOW_DIRS) {
104 rows[i].name = (char *) malloc(strlen(ep->d_name) + 2);
105 strcpy(rows[i].name, ep->d_name);
106 strcat(rows[i].name, "/");
107 i++;
110 else if (flags & SHOW_FILES) {
111 rows[i].name = (char *) malloc(strlen(ep->d_name) + 1);
112 strcpy(rows[i].name, ep->d_name);
113 rows[i].size = statbuf.st_size;
114 i++;
117 n = i; /* Ignore unused space in array caused by filters. */
118 qsort(rows, n, sizeof(row_t), rowcmp);
119 closedir(dp);
120 *rowsp = rows;
121 return n;
124 static void
125 clean_term()
127 endwin();
130 void handle_winch(int sig);
132 static void
133 init_term()
135 struct sigaction sa;
137 setlocale(LC_ALL, "");
138 initscr();
139 cbreak(); /* Get one character at a time. */
140 noecho();
141 nonl(); /* No NL->CR/NL on output. */
142 intrflush(stdscr, FALSE);
143 keypad(stdscr, TRUE);
144 curs_set(FALSE); /* Hide blinking cursor. */
145 memset(&sa, 0, sizeof(struct sigaction));
146 sa.sa_handler = handle_winch;
147 sigaction(SIGWINCH, &sa, NULL);
148 if (has_colors()) {
149 start_color();
150 init_pair(RED, COLOR_RED, COLOR_BLACK);
151 init_pair(GREEN, COLOR_GREEN, COLOR_BLACK);
152 init_pair(YELLOW, COLOR_YELLOW,COLOR_BLACK);
153 init_pair(BLUE, COLOR_BLUE, COLOR_BLACK);
154 init_pair(CYAN, COLOR_CYAN, COLOR_BLACK);
155 init_pair(MAGENTA, COLOR_MAGENTA, COLOR_BLACK);
156 init_pair(WHITE, COLOR_WHITE, COLOR_BLACK);
158 atexit(clean_term);
161 static void
162 update_browser()
164 int i, j;
165 int ishidden, isdir;
167 for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) {
168 ishidden = FNAME(j)[0] == '.';
169 isdir = strchr(FNAME(j), '/') != NULL;
170 if (j == FSEL)
171 wattr_on(rover.window, A_REVERSE, NULL);
172 if (ishidden)
173 wcolor_set(rover.window, RVC_HIDDEN, NULL);
174 else if (isdir)
175 wcolor_set(rover.window, RVC_DIR, NULL);
176 else
177 wcolor_set(rover.window, RVC_FILE, NULL);
178 if (!isdir)
179 sprintf(ROW, "%s%*d", FNAME(j),
180 COLS - strlen(FNAME(j)) - 2, (int) FSIZE(j));
181 else
182 strcpy(ROW, FNAME(j));
183 mvwhline(rover.window, i + 1, 1, ' ', COLS - 2);
184 mvwaddnstr(rover.window, i + 1, 1, ROW, COLS - 2);
185 wcolor_set(rover.window, DEFAULT, NULL);
186 if (j == FSEL)
187 wattr_off(rover.window, A_REVERSE, NULL);
189 if (rover.nfiles > HEIGHT) {
190 int center, height;
191 center = (SCROLL + (HEIGHT >> 1)) * HEIGHT / rover.nfiles;
192 height = (HEIGHT-1) * HEIGHT / rover.nfiles;
193 if (!height) height = 1;
194 wcolor_set(rover.window, RVC_BORDER, NULL);
195 wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0);
196 wcolor_set(rover.window, RVC_SCROLLBAR, NULL);
197 mvwvline(rover.window, center-(height>>1)+1, COLS-1, ACS_CKBOARD, height);
198 wcolor_set(rover.window, DEFAULT, NULL);
200 wrefresh(rover.window);
201 STATUS[0] = FLAGS & SHOW_FILES ? 'F' : ' ';
202 STATUS[1] = FLAGS & SHOW_DIRS ? 'D' : ' ';
203 STATUS[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' ';
204 if (!rover.nfiles)
205 strcpy(ROW, "0/0");
206 else
207 sprintf(ROW, "%d/%d", FSEL + 1, rover.nfiles);
208 sprintf(STATUS+3, "%*s", 12, ROW);
209 color_set(RVC_STATUS, NULL);
210 mvaddstr(LINES - 1, COLS - 15, STATUS);
211 color_set(DEFAULT, NULL);
212 refresh();
215 /* NOTE: The caller needs to write the new path to CWD
216 * *before* calling this function. */
217 static void
218 cd(int reset)
220 if (reset)
221 FSEL = SCROLL = 0;
222 chdir(CWD);
223 mvhline(0, 0, ' ', COLS);
224 color_set(RVC_CWD, NULL);
225 mvaddnstr(0, 0, CWD, COLS);
226 color_set(DEFAULT, NULL);
227 move(0, COLS-2);
228 attr_on(A_BOLD, NULL);
229 color_set(RVC_TABNUM, NULL);
230 echochar(rover.tab + '0');
231 color_set(DEFAULT, NULL);
232 attr_off(A_BOLD, NULL);
233 if (rover.nfiles)
234 free_rows(&rover.rows, rover.nfiles);
235 rover.nfiles = ls(CWD, &rover.rows, FLAGS);
236 wclear(rover.window);
237 wcolor_set(rover.window, RVC_BORDER, NULL);
238 wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0);
239 wcolor_set(rover.window, DEFAULT, NULL);
240 update_browser();
243 static void
244 spawn()
246 pid_t pid;
247 int status;
249 pid = fork();
250 if (pid > 0) {
251 /* fork() succeeded. */
252 clean_term();
253 waitpid(pid, &status, 0);
254 init_term();
255 doupdate();
257 else if (pid == 0) {
258 /* Child process. */
259 execvp(args[0], args);
263 /* Interactive getstr(). */
264 int
265 igetstr(char *buffer, int maxlen)
267 int ch, length;
269 length = strlen(buffer);
270 ch = getch();
271 if (ch == '\r' || ch == '\n' || ch == KEY_DOWN || ch == KEY_ENTER)
272 return 0;
273 else if (ch == erasechar() || ch == KEY_LEFT || ch == KEY_BACKSPACE) {
274 if (length)
275 buffer[--length] = '\0';
277 else if (ch == killchar()) {
278 length = 0;
279 buffer[0] = '\0';
281 else if (length < maxlen - 1 && isprint(ch)) {
282 buffer[length++] = ch;
283 buffer[length] = '\0';
285 return 1;
288 void
289 handle_winch(int sig)
291 (void) sig;
292 delwin(rover.window);
293 endwin();
294 refresh();
295 clear();
296 rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0);
297 cd(0);
300 int
301 main(int argc, char *argv[])
303 int i, ch;
304 char *program, *key;
305 DIR *d;
307 init_term();
308 /* Avoid invalid free() calls in cd() by zeroing the tally. */
309 rover.nfiles = 0;
310 for (i = 0; i < 10; i++) {
311 rover.fsel[i] = rover.scroll[i] = 0;
312 rover.flags[i] = SHOW_FILES | SHOW_DIRS;
314 strcpy(rover.cwd[0], getenv("HOME"));
315 for (i = 1; i < argc && i < 10; i++) {
316 d = opendir(argv[i]);
317 if (d) {
318 strcpy(rover.cwd[i], argv[i]);
319 closedir(d);
321 else strcpy(rover.cwd[i], rover.cwd[0]);
323 getcwd(rover.cwd[i], FILENAME_MAX);
324 for (i++; i < 10; i++)
325 strcpy(rover.cwd[i], rover.cwd[i-1]);
326 for (i = 0; i < 10; i++)
327 if (rover.cwd[i][strlen(rover.cwd[i]) - 1] != '/')
328 strcat(rover.cwd[i], "/");
329 rover.tab = 1;
330 rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0);
331 cd(1);
332 while (1) {
333 ch = getch();
334 key = keyname(ch);
335 if (!strcmp(key, RVK_QUIT))
336 break;
337 else if (ch >= '0' && ch <= '9') {
338 rover.tab = ch - '0';
339 cd(0);
341 else if (!strcmp(key, RVK_DOWN)) {
342 if (!rover.nfiles) continue;
343 if (FSEL == rover.nfiles - 1)
344 SCROLL = FSEL = 0;
345 else {
346 FSEL++;
347 if ((FSEL - SCROLL) == HEIGHT)
348 SCROLL++;
350 update_browser();
352 else if (!strcmp(key, RVK_UP)) {
353 if (!rover.nfiles) continue;
354 if (FSEL == 0) {
355 FSEL = rover.nfiles - 1;
356 SCROLL = rover.nfiles - HEIGHT;
357 if (SCROLL < 0)
358 SCROLL = 0;
360 else {
361 FSEL--;
362 if (FSEL < SCROLL)
363 SCROLL--;
365 update_browser();
367 else if (!strcmp(key, RVK_JUMP_DOWN)) {
368 if (!rover.nfiles) continue;
369 FSEL += RV_JUMP;
370 if (FSEL >= rover.nfiles)
371 FSEL = rover.nfiles - 1;
372 if (rover.nfiles > HEIGHT) {
373 SCROLL += RV_JUMP;
374 if (SCROLL > rover.nfiles - HEIGHT)
375 SCROLL = rover.nfiles - HEIGHT;
377 update_browser();
379 else if (!strcmp(key, RVK_JUMP_UP)) {
380 if (!rover.nfiles) continue;
381 FSEL -= RV_JUMP;
382 if (FSEL < 0)
383 FSEL = 0;
384 SCROLL -= RV_JUMP;
385 if (SCROLL < 0)
386 SCROLL = 0;
387 update_browser();
389 else if (!strcmp(key, RVK_CD_DOWN)) {
390 if (!rover.nfiles) continue;
391 if (strchr(FNAME(FSEL), '/') == NULL)
392 continue;
393 strcat(CWD, FNAME(FSEL));
394 cd(1);
396 else if (!strcmp(key, RVK_CD_UP)) {
397 char *dirname, first;
398 if (strlen(CWD) == 1)
399 continue;
400 CWD[strlen(CWD) - 1] = '\0';
401 dirname = strrchr(CWD, '/') + 1;
402 first = dirname[0];
403 dirname[0] = '\0';
404 cd(1);
405 if ((FLAGS & SHOW_DIRS) &&
406 ((FLAGS & SHOW_HIDDEN) || (first != '.'))
407 ) {
408 dirname[0] = first;
409 dirname[strlen(dirname)] = '/';
410 while (strcmp(FNAME(FSEL), dirname))
411 FSEL++;
412 if (rover.nfiles > HEIGHT) {
413 SCROLL = FSEL - (HEIGHT >> 1);
414 if (SCROLL < 0)
415 SCROLL = 0;
416 if (SCROLL > rover.nfiles - HEIGHT)
417 SCROLL = rover.nfiles - HEIGHT;
419 dirname[0] = '\0';
420 update_browser();
423 else if (!strcmp(key, RVK_HOME)) {
424 strcpy(CWD, getenv("HOME"));
425 if (CWD[strlen(CWD) - 1] != '/')
426 strcat(CWD, "/");
427 cd(1);
429 else if (!strcmp(key, RVK_SHELL)) {
430 program = getenv("SHELL");
431 if (program) {
432 args[0] = program;
433 args[1] = NULL;
434 spawn();
437 else if (!strcmp(key, RVK_VIEW)) {
438 if (!rover.nfiles) continue;
439 if (strchr(FNAME(FSEL), '/') != NULL)
440 continue;
441 program = getenv("PAGER");
442 if (program) {
443 args[0] = program;
444 args[1] = FNAME(FSEL);
445 args[2] = NULL;
446 spawn();
449 else if (!strcmp(key, RVK_EDIT)) {
450 if (!rover.nfiles) continue;
451 if (strchr(FNAME(FSEL), '/') != NULL)
452 continue;
453 program = getenv("EDITOR");
454 if (program) {
455 args[0] = program;
456 args[1] = FNAME(FSEL);
457 args[2] = NULL;
458 spawn();
461 else if (!strcmp(key, RVK_SEARCH)) {
462 int oldsel, oldscroll;
463 if (!rover.nfiles) continue;
464 oldsel = FSEL;
465 oldscroll = SCROLL;
466 *SEARCH = '\0';
467 color_set(RVC_PROMPT, NULL);
468 mvaddstr(LINES - 1, 0, "search: ");
469 color_set(DEFAULT, NULL);
470 while (igetstr(SEARCH, SEARCHSZ)) {
471 int length, sel;
472 color_t color;
473 length = strlen(SEARCH);
474 if (length) {
475 for (sel = 0; sel < rover.nfiles; sel++)
476 if (!strncmp(FNAME(sel), SEARCH, length))
477 break;
478 if (sel < rover.nfiles) {
479 color = GREEN;
480 FSEL = sel;
481 if (rover.nfiles > HEIGHT) {
482 if (sel < 3)
483 SCROLL = 0;
484 else if (sel - 3 > rover.nfiles - HEIGHT)
485 SCROLL = rover.nfiles - HEIGHT;
486 else
487 SCROLL = sel - 3;
490 else
491 color = RED;
493 else {
494 FSEL = oldsel;
495 SCROLL = oldscroll;
497 update_browser();
498 strcat(SEARCH, " ");
499 color_set(color, NULL);
500 mvaddstr(LINES - 1, 8, SEARCH);
501 color_set(DEFAULT, NULL);
502 SEARCH[length] = '\0';
504 move(LINES - 1, 0);
505 clrtoeol();
506 update_browser();
508 else if (!strcmp(key, RVK_TG_FILES)) {
509 FLAGS ^= SHOW_FILES;
510 cd(1);
512 else if (!strcmp(key, RVK_TG_DIRS)) {
513 FLAGS ^= SHOW_DIRS;
514 cd(1);
516 else if (!strcmp(key, RVK_TG_HIDDEN)) {
517 FLAGS ^= SHOW_HIDDEN;
518 cd(1);
521 if (rover.nfiles) {
522 free_rows(&rover.rows, rover.nfiles);
524 delwin(rover.window);
525 return 0;