build: Use pkgconfig to fix undefined references
[rover.git] / rover.c
blob342aa4833670a912953be21fe2688d1ef5c5ef6a
1 #ifndef _XOPEN_SOURCE
2 #define _XOPEN_SOURCE 700
3 #endif
4 #define _XOPEN_SOURCE_EXTENDED
5 #define _FILE_OFFSET_BITS 64
7 #include <stdlib.h>
8 #include <stdint.h>
9 #include <ctype.h>
10 #include <wchar.h>
11 #include <wctype.h>
12 #include <string.h>
13 #include <sys/types.h> /* pid_t, ... */
14 #include <stdio.h>
15 #include <limits.h> /* PATH_MAX */
16 #include <locale.h> /* setlocale(), LC_ALL */
17 #include <unistd.h> /* chdir(), getcwd(), read(), close(), ... */
18 #include <dirent.h> /* DIR, struct dirent, opendir(), ... */
19 #include <libgen.h>
20 #include <sys/stat.h>
21 #include <fcntl.h> /* open() */
22 #include <sys/wait.h> /* waitpid() */
23 #include <signal.h> /* struct sigaction, sigaction() */
24 #include <errno.h>
25 #include <stdarg.h>
26 #include <curses.h>
28 #include "config.h"
30 /* This signal is not defined by POSIX, but should be
31 present on all systems that have resizable terminals. */
32 #ifndef SIGWINCH
33 #define SIGWINCH 28
34 #endif
36 /* String buffers. */
37 #define BUFLEN PATH_MAX
38 static char BUF1[BUFLEN];
39 static char BUF2[BUFLEN];
40 static char INPUT[BUFLEN];
41 static char CLIPBOARD[BUFLEN];
42 static wchar_t WBUF[BUFLEN];
44 /* Paths to external programs. */
45 static char *user_shell;
46 static char *user_pager;
47 static char *user_editor;
48 static char *user_open;
50 /* Listing view parameters. */
51 #define HEIGHT (LINES-4)
52 #define STATUSPOS (COLS-16)
54 /* Listing view flags. */
55 #define SHOW_FILES 0x01u
56 #define SHOW_DIRS 0x02u
57 #define SHOW_HIDDEN 0x04u
59 /* Marks parameters. */
60 #define BULK_INIT 5
61 #define BULK_THRESH 256
63 /* Information associated to each entry in listing. */
64 typedef struct Row {
65 char *name;
66 off_t size;
67 mode_t mode;
68 int islink;
69 int marked;
70 } Row;
72 /* Dynamic array of marked entries. */
73 typedef struct Marks {
74 char dirpath[PATH_MAX];
75 int bulk;
76 int nentries;
77 char **entries;
78 } Marks;
80 /* Line editing state. */
81 typedef struct Edit {
82 wchar_t buffer[BUFLEN+1];
83 int left, right;
84 } Edit;
86 /* Each tab only stores the following information. */
87 typedef struct Tab {
88 int scroll;
89 int esel;
90 uint8_t flags;
91 char cwd[PATH_MAX];
92 } Tab;
94 typedef struct Prog {
95 off_t partial;
96 off_t total;
97 const char *msg;
98 } Prog;
100 /* Global state. */
101 static struct Rover {
102 int tab;
103 int nfiles;
104 Row *rows;
105 WINDOW *window;
106 Marks marks;
107 Edit edit;
108 int edit_scroll;
109 volatile sig_atomic_t pending_usr1;
110 volatile sig_atomic_t pending_winch;
111 Prog prog;
112 Tab tabs[10];
113 } rover;
115 /* Macros for accessing global state. */
116 #define ENAME(I) rover.rows[I].name
117 #define ESIZE(I) rover.rows[I].size
118 #define EMODE(I) rover.rows[I].mode
119 #define ISLINK(I) rover.rows[I].islink
120 #define MARKED(I) rover.rows[I].marked
121 #define SCROLL rover.tabs[rover.tab].scroll
122 #define ESEL rover.tabs[rover.tab].esel
123 #define FLAGS rover.tabs[rover.tab].flags
124 #define CWD rover.tabs[rover.tab].cwd
126 /* Helpers. */
127 #define MIN(A, B) ((A) < (B) ? (A) : (B))
128 #define MAX(A, B) ((A) > (B) ? (A) : (B))
129 #define ISDIR(E) (strchr((E), '/') != NULL)
131 /* Line Editing Macros. */
132 #define EDIT_FULL(E) ((E).left == (E).right)
133 #define EDIT_CAN_LEFT(E) ((E).left)
134 #define EDIT_CAN_RIGHT(E) ((E).right < BUFLEN-1)
135 #define EDIT_LEFT(E) (E).buffer[(E).right--] = (E).buffer[--(E).left]
136 #define EDIT_RIGHT(E) (E).buffer[(E).left++] = (E).buffer[++(E).right]
137 #define EDIT_INSERT(E, C) (E).buffer[(E).left++] = (C)
138 #define EDIT_BACKSPACE(E) (E).left--
139 #define EDIT_DELETE(E) (E).right++
140 #define EDIT_CLEAR(E) do { (E).left = 0; (E).right = BUFLEN-1; } while(0)
142 typedef enum EditStat {CONTINUE, CONFIRM, CANCEL} EditStat;
143 typedef enum Color {DEFAULT, RED, GREEN, YELLOW, BLUE, CYAN, MAGENTA, WHITE, BLACK} Color;
144 typedef int (*PROCESS)(const char *path);
146 static void
147 init_marks(Marks *marks)
149 strcpy(marks->dirpath, "");
150 marks->bulk = BULK_INIT;
151 marks->nentries = 0;
152 marks->entries = calloc(marks->bulk, sizeof *marks->entries);
155 /* Unmark all entries. */
156 static void
157 mark_none(Marks *marks)
159 int i;
161 strcpy(marks->dirpath, "");
162 for (i = 0; i < marks->bulk && marks->nentries; i++)
163 if (marks->entries[i]) {
164 free(marks->entries[i]);
165 marks->entries[i] = NULL;
166 marks->nentries--;
168 if (marks->bulk > BULK_THRESH) {
169 /* Reset bulk to free some memory. */
170 free(marks->entries);
171 marks->bulk = BULK_INIT;
172 marks->entries = calloc(marks->bulk, sizeof *marks->entries);
176 static void
177 add_mark(Marks *marks, char *dirpath, char *entry)
179 int i;
181 if (!strcmp(marks->dirpath, dirpath)) {
182 /* Append mark to directory. */
183 if (marks->nentries == marks->bulk) {
184 /* Expand bulk to accomodate new entry. */
185 int extra = marks->bulk / 2;
186 marks->bulk += extra; /* bulk *= 1.5; */
187 marks->entries = realloc(marks->entries,
188 marks->bulk * sizeof *marks->entries);
189 memset(&marks->entries[marks->nentries], 0,
190 extra * sizeof *marks->entries);
191 i = marks->nentries;
192 } else {
193 /* Search for empty slot (there must be one). */
194 for (i = 0; i < marks->bulk; i++)
195 if (!marks->entries[i])
196 break;
198 } else {
199 /* Directory changed. Discard old marks. */
200 mark_none(marks);
201 strcpy(marks->dirpath, dirpath);
202 i = 0;
204 marks->entries[i] = malloc(strlen(entry) + 1);
205 strcpy(marks->entries[i], entry);
206 marks->nentries++;
209 static void
210 del_mark(Marks *marks, char *entry)
212 int i;
214 if (marks->nentries > 1) {
215 for (i = 0; i < marks->bulk; i++)
216 if (marks->entries[i] && !strcmp(marks->entries[i], entry))
217 break;
218 free(marks->entries[i]);
219 marks->entries[i] = NULL;
220 marks->nentries--;
221 } else
222 mark_none(marks);
225 static void
226 free_marks(Marks *marks)
228 int i;
230 for (i = 0; i < marks->bulk && marks->nentries; i++)
231 if (marks->entries[i]) {
232 free(marks->entries[i]);
233 marks->nentries--;
235 free(marks->entries);
238 static void
239 handle_usr1(int sig)
241 rover.pending_usr1 = 1;
244 static void
245 handle_winch(int sig)
247 rover.pending_winch = 1;
250 static void
251 enable_handlers()
253 struct sigaction sa;
255 memset(&sa, 0, sizeof (struct sigaction));
256 sa.sa_handler = handle_usr1;
257 sigaction(SIGUSR1, &sa, NULL);
258 sa.sa_handler = handle_winch;
259 sigaction(SIGWINCH, &sa, NULL);
262 static void
263 disable_handlers()
265 struct sigaction sa;
267 memset(&sa, 0, sizeof (struct sigaction));
268 sa.sa_handler = SIG_DFL;
269 sigaction(SIGUSR1, &sa, NULL);
270 sigaction(SIGWINCH, &sa, NULL);
273 static void reload();
274 static void update_view();
276 /* Handle any signals received since last call. */
277 static void
278 sync_signals()
280 if (rover.pending_usr1) {
281 /* SIGUSR1 received: refresh directory listing. */
282 reload();
283 rover.pending_usr1 = 0;
285 if (rover.pending_winch) {
286 /* SIGWINCH received: resize application accordingly. */
287 delwin(rover.window);
288 endwin();
289 refresh();
290 clear();
291 rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0);
292 if (HEIGHT < rover.nfiles && SCROLL + HEIGHT > rover.nfiles)
293 SCROLL = ESEL - HEIGHT;
294 update_view();
295 rover.pending_winch = 0;
299 /* This function must be used in place of getch().
300 It handles signals while waiting for user input. */
301 static int
302 rover_getch()
304 int ch;
306 while ((ch = getch()) == ERR)
307 sync_signals();
308 return ch;
311 /* This function must be used in place of get_wch().
312 It handles signals while waiting for user input. */
313 static int
314 rover_get_wch(wint_t *wch)
316 wint_t ret;
318 while ((ret = get_wch(wch)) == (wint_t) ERR)
319 sync_signals();
320 return ret;
323 /* Get user programs from the environment. */
325 #define ROVER_ENV(dst, src) if ((dst = getenv("ROVER_" #src)) == NULL) \
326 dst = getenv(#src);
328 static void
329 get_user_programs()
331 ROVER_ENV(user_shell, SHELL)
332 ROVER_ENV(user_pager, PAGER)
333 ROVER_ENV(user_editor, VISUAL)
334 if (!user_editor)
335 ROVER_ENV(user_editor, EDITOR)
336 ROVER_ENV(user_open, OPEN)
339 /* Do a fork-exec to external program (e.g. $EDITOR). */
340 static void
341 spawn(char **args)
343 pid_t pid;
344 int status;
346 setenv("RVSEL", rover.nfiles ? ENAME(ESEL) : "", 1);
347 pid = fork();
348 if (pid > 0) {
349 /* fork() succeeded. */
350 disable_handlers();
351 endwin();
352 waitpid(pid, &status, 0);
353 enable_handlers();
354 kill(getpid(), SIGWINCH);
355 } else if (pid == 0) {
356 /* Child process. */
357 execvp(args[0], args);
361 static void
362 shell_escaped_cat(char *buf, char *str, size_t n)
364 char *p = buf + strlen(buf);
365 *p++ = '\'';
366 for (n--; n; n--, str++) {
367 switch (*str) {
368 case '\'':
369 if (n < 4)
370 goto done;
371 strcpy(p, "'\\''");
372 n -= 4;
373 p += 4;
374 break;
375 case '\0':
376 goto done;
377 default:
378 *p = *str;
379 p++;
382 done:
383 strncat(p, "'", n);
386 static int
387 open_with_env(char *program, char *path)
389 if (program) {
390 #ifdef RV_SHELL
391 strncpy(BUF1, program, BUFLEN - 1);
392 strncat(BUF1, " ", BUFLEN - strlen(program) - 1);
393 shell_escaped_cat(BUF1, path, BUFLEN - strlen(program) - 2);
394 spawn((char *[]) {RV_SHELL, "-c", BUF1, NULL});
395 #else
396 spawn((char *[]) {program, path, NULL});
397 #endif
398 return 1;
400 return 0;
403 /* Curses setup. */
404 static void
405 init_term()
407 setlocale(LC_ALL, "");
408 initscr();
409 cbreak(); /* Get one character at a time. */
410 timeout(100); /* For getch(). */
411 noecho();
412 nonl(); /* No NL->CR/NL on output. */
413 intrflush(stdscr, FALSE);
414 keypad(stdscr, TRUE);
415 curs_set(FALSE); /* Hide blinking cursor. */
416 if (has_colors()) {
417 short bg;
418 start_color();
419 #ifdef NCURSES_EXT_FUNCS
420 use_default_colors();
421 bg = -1;
422 #else
423 bg = COLOR_BLACK;
424 #endif
425 init_pair(RED, COLOR_RED, bg);
426 init_pair(GREEN, COLOR_GREEN, bg);
427 init_pair(YELLOW, COLOR_YELLOW, bg);
428 init_pair(BLUE, COLOR_BLUE, bg);
429 init_pair(CYAN, COLOR_CYAN, bg);
430 init_pair(MAGENTA, COLOR_MAGENTA, bg);
431 init_pair(WHITE, COLOR_WHITE, bg);
432 init_pair(BLACK, COLOR_BLACK, bg);
434 atexit((void (*)(void)) endwin);
435 enable_handlers();
438 /* Update the listing view. */
439 static void
440 update_view()
442 int i, j;
443 int numsize;
444 int ishidden;
445 int marking;
447 mvhline(0, 0, ' ', COLS);
448 attr_on(A_BOLD, NULL);
449 color_set(RVC_TABNUM, NULL);
450 mvaddch(0, COLS - 2, rover.tab + '0');
451 attr_off(A_BOLD, NULL);
452 if (rover.marks.nentries) {
453 numsize = snprintf(BUF1, BUFLEN, "%d", rover.marks.nentries);
454 color_set(RVC_MARKS, NULL);
455 mvaddstr(0, COLS - 3 - numsize, BUF1);
456 } else
457 numsize = -1;
458 color_set(RVC_CWD, NULL);
459 mbstowcs(WBUF, CWD, PATH_MAX);
460 mvaddnwstr(0, 0, WBUF, COLS - 4 - numsize);
461 wcolor_set(rover.window, RVC_BORDER, NULL);
462 wborder(rover.window, 0, 0, 0, 0, 0, 0, 0, 0);
463 ESEL = MAX(MIN(ESEL, rover.nfiles - 1), 0);
464 /* Selection might not be visible, due to cursor wrapping or window
465 shrinking. In that case, the scroll must be moved to make it visible. */
466 if (rover.nfiles > HEIGHT) {
467 SCROLL = MAX(MIN(SCROLL, ESEL), ESEL - HEIGHT + 1);
468 SCROLL = MIN(MAX(SCROLL, 0), rover.nfiles - HEIGHT);
469 } else
470 SCROLL = 0;
471 marking = !strcmp(CWD, rover.marks.dirpath);
472 for (i = 0, j = SCROLL; i < HEIGHT && j < rover.nfiles; i++, j++) {
473 ishidden = ENAME(j)[0] == '.';
474 if (j == ESEL)
475 wattr_on(rover.window, A_REVERSE, NULL);
476 if (ISLINK(j))
477 wcolor_set(rover.window, RVC_LINK, NULL);
478 else if (ishidden)
479 wcolor_set(rover.window, RVC_HIDDEN, NULL);
480 else if (S_ISREG(EMODE(j))) {
481 if (EMODE(j) & (S_IXUSR | S_IXGRP | S_IXOTH))
482 wcolor_set(rover.window, RVC_EXEC, NULL);
483 else
484 wcolor_set(rover.window, RVC_REG, NULL);
485 } else if (S_ISDIR(EMODE(j)))
486 wcolor_set(rover.window, RVC_DIR, NULL);
487 else if (S_ISCHR(EMODE(j)))
488 wcolor_set(rover.window, RVC_CHR, NULL);
489 else if (S_ISBLK(EMODE(j)))
490 wcolor_set(rover.window, RVC_BLK, NULL);
491 else if (S_ISFIFO(EMODE(j)))
492 wcolor_set(rover.window, RVC_FIFO, NULL);
493 else if (S_ISSOCK(EMODE(j)))
494 wcolor_set(rover.window, RVC_SOCK, NULL);
495 if (S_ISDIR(EMODE(j))) {
496 mbstowcs(WBUF, ENAME(j), PATH_MAX);
497 if (ISLINK(j))
498 wcscat(WBUF, L"/");
499 } else {
500 char *suffix, *suffixes = "BKMGTPEZY";
501 off_t human_size = ESIZE(j) * 10;
502 int length = mbstowcs(WBUF, ENAME(j), PATH_MAX);
503 int namecols = wcswidth(WBUF, length);
504 for (suffix = suffixes; human_size >= 10240; suffix++)
505 human_size = (human_size + 512) / 1024;
506 if (*suffix == 'B')
507 swprintf(WBUF + length, PATH_MAX - length, L"%*d %c",
508 (int) (COLS - namecols - 6),
509 (int) human_size / 10, *suffix);
510 else
511 swprintf(WBUF + length, PATH_MAX - length, L"%*d.%d %c",
512 (int) (COLS - namecols - 8),
513 (int) human_size / 10, (int) human_size % 10, *suffix);
515 mvwhline(rover.window, i + 1, 1, ' ', COLS - 2);
516 mvwaddnwstr(rover.window, i + 1, 2, WBUF, COLS - 4);
517 if (marking && MARKED(j)) {
518 wcolor_set(rover.window, RVC_MARKS, NULL);
519 mvwaddch(rover.window, i + 1, 1, RVS_MARK);
520 } else
521 mvwaddch(rover.window, i + 1, 1, ' ');
522 if (j == ESEL)
523 wattr_off(rover.window, A_REVERSE, NULL);
525 for (; i < HEIGHT; i++)
526 mvwhline(rover.window, i + 1, 1, ' ', COLS - 2);
527 if (rover.nfiles > HEIGHT) {
528 int center, height;
529 center = (SCROLL + HEIGHT / 2) * HEIGHT / rover.nfiles;
530 height = (HEIGHT-1) * HEIGHT / rover.nfiles;
531 if (!height) height = 1;
532 wcolor_set(rover.window, RVC_SCROLLBAR, NULL);
533 mvwvline(rover.window, center-height/2+1, COLS-1, RVS_SCROLLBAR, height);
535 BUF1[0] = FLAGS & SHOW_FILES ? 'F' : ' ';
536 BUF1[1] = FLAGS & SHOW_DIRS ? 'D' : ' ';
537 BUF1[2] = FLAGS & SHOW_HIDDEN ? 'H' : ' ';
538 if (!rover.nfiles)
539 strcpy(BUF2, "0/0");
540 else
541 snprintf(BUF2, BUFLEN, "%d/%d", ESEL + 1, rover.nfiles);
542 snprintf(BUF1+3, BUFLEN-3, "%12s", BUF2);
543 color_set(RVC_STATUS, NULL);
544 mvaddstr(LINES - 1, STATUSPOS, BUF1);
545 wrefresh(rover.window);
548 /* Show a message on the status bar. */
549 static void
550 message(Color color, char *fmt, ...)
552 int len, pos;
553 va_list args;
555 va_start(args, fmt);
556 vsnprintf(BUF1, MIN(BUFLEN, STATUSPOS), fmt, args);
557 va_end(args);
558 len = strlen(BUF1);
559 pos = (STATUSPOS - len) / 2;
560 attr_on(A_BOLD, NULL);
561 color_set(color, NULL);
562 mvaddstr(LINES - 1, pos, BUF1);
563 color_set(DEFAULT, NULL);
564 attr_off(A_BOLD, NULL);
567 /* Clear message area, leaving only status info. */
568 static void
569 clear_message()
571 mvhline(LINES - 1, 0, ' ', STATUSPOS);
574 /* Comparison used to sort listing entries. */
575 static int
576 rowcmp(const void *a, const void *b)
578 int isdir1, isdir2, cmpdir;
579 const Row *r1 = a;
580 const Row *r2 = b;
581 isdir1 = S_ISDIR(r1->mode);
582 isdir2 = S_ISDIR(r2->mode);
583 cmpdir = isdir2 - isdir1;
584 return cmpdir ? cmpdir : strcoll(r1->name, r2->name);
587 /* Get all entries in current working directory. */
588 static int
589 ls(Row **rowsp, uint8_t flags)
591 DIR *dp;
592 struct dirent *ep;
593 struct stat statbuf;
594 Row *rows;
595 int i, n;
597 if(!(dp = opendir("."))) return -1;
598 n = -2; /* We don't want the entries "." and "..". */
599 while (readdir(dp)) n++;
600 if (n == 0) {
601 closedir(dp);
602 return 0;
604 rewinddir(dp);
605 rows = malloc(n * sizeof *rows);
606 i = 0;
607 while ((ep = readdir(dp))) {
608 if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
609 continue;
610 if (!(flags & SHOW_HIDDEN) && ep->d_name[0] == '.')
611 continue;
612 lstat(ep->d_name, &statbuf);
613 rows[i].islink = S_ISLNK(statbuf.st_mode);
614 stat(ep->d_name, &statbuf);
615 if (S_ISDIR(statbuf.st_mode)) {
616 if (flags & SHOW_DIRS) {
617 rows[i].name = malloc(strlen(ep->d_name) + 2);
618 strcpy(rows[i].name, ep->d_name);
619 if (!rows[i].islink)
620 strcat(rows[i].name, "/");
621 rows[i].mode = statbuf.st_mode;
622 i++;
624 } else if (flags & SHOW_FILES) {
625 rows[i].name = malloc(strlen(ep->d_name) + 1);
626 strcpy(rows[i].name, ep->d_name);
627 rows[i].size = statbuf.st_size;
628 rows[i].mode = statbuf.st_mode;
629 i++;
632 n = i; /* Ignore unused space in array caused by filters. */
633 qsort(rows, n, sizeof (*rows), rowcmp);
634 closedir(dp);
635 *rowsp = rows;
636 return n;
639 static void
640 free_rows(Row **rowsp, int nfiles)
642 int i;
644 for (i = 0; i < nfiles; i++)
645 free((*rowsp)[i].name);
646 free(*rowsp);
647 *rowsp = NULL;
650 /* Change working directory to the path in CWD. */
651 static void
652 cd(int reset)
654 int i, j;
656 message(CYAN, "Loading \"%s\"...", CWD);
657 refresh();
658 if (chdir(CWD) == -1) {
659 getcwd(CWD, PATH_MAX-1);
660 if (CWD[strlen(CWD)-1] != '/')
661 strcat(CWD, "/");
662 goto done;
664 if (reset) ESEL = SCROLL = 0;
665 if (rover.nfiles)
666 free_rows(&rover.rows, rover.nfiles);
667 rover.nfiles = ls(&rover.rows, FLAGS);
668 if (!strcmp(CWD, rover.marks.dirpath)) {
669 for (i = 0; i < rover.nfiles; i++) {
670 for (j = 0; j < rover.marks.bulk; j++)
671 if (
672 rover.marks.entries[j] &&
673 !strcmp(rover.marks.entries[j], ENAME(i))
675 break;
676 MARKED(i) = j < rover.marks.bulk;
678 } else
679 for (i = 0; i < rover.nfiles; i++)
680 MARKED(i) = 0;
681 done:
682 clear_message();
683 update_view();
686 /* Select a target entry, if it is present. */
687 static void
688 try_to_sel(const char *target)
690 ESEL = 0;
691 if (!ISDIR(target))
692 while ((ESEL+1) < rover.nfiles && S_ISDIR(EMODE(ESEL)))
693 ESEL++;
694 while ((ESEL+1) < rover.nfiles && strcoll(ENAME(ESEL), target) < 0)
695 ESEL++;
698 /* Reload CWD, but try to keep selection. */
699 static void
700 reload()
702 if (rover.nfiles) {
703 strcpy(INPUT, ENAME(ESEL));
704 cd(0);
705 try_to_sel(INPUT);
706 update_view();
707 } else
708 cd(1);
711 static off_t
712 count_dir(const char *path)
714 DIR *dp;
715 struct dirent *ep;
716 struct stat statbuf;
717 char subpath[PATH_MAX];
718 off_t total;
720 if(!(dp = opendir(path))) return 0;
721 total = 0;
722 while ((ep = readdir(dp))) {
723 if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
724 continue;
725 snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name);
726 lstat(subpath, &statbuf);
727 if (S_ISDIR(statbuf.st_mode)) {
728 strcat(subpath, "/");
729 total += count_dir(subpath);
730 } else
731 total += statbuf.st_size;
733 closedir(dp);
734 return total;
737 static off_t
738 count_marked()
740 int i;
741 char *entry;
742 off_t total;
743 struct stat statbuf;
745 total = 0;
746 chdir(rover.marks.dirpath);
747 for (i = 0; i < rover.marks.bulk; i++) {
748 entry = rover.marks.entries[i];
749 if (entry) {
750 if (ISDIR(entry)) {
751 total += count_dir(entry);
752 } else {
753 lstat(entry, &statbuf);
754 total += statbuf.st_size;
758 chdir(CWD);
759 return total;
762 /* Recursively process a source directory using CWD as destination root.
763 For each node (i.e. directory), do the following:
764 1. call pre(destination);
765 2. call proc() on every child leaf (i.e. files);
766 3. recurse into every child node;
767 4. call pos(source).
768 E.g. to move directory /src/ (and all its contents) inside /dst/:
769 strcpy(CWD, "/dst/");
770 process_dir(adddir, movfile, deldir, "/src/"); */
771 static int
772 process_dir(PROCESS pre, PROCESS proc, PROCESS pos, const char *path)
774 int ret;
775 DIR *dp;
776 struct dirent *ep;
777 struct stat statbuf;
778 char subpath[PATH_MAX];
780 ret = 0;
781 if (pre) {
782 char dstpath[PATH_MAX];
783 strcpy(dstpath, CWD);
784 strcat(dstpath, path + strlen(rover.marks.dirpath));
785 ret |= pre(dstpath);
787 if(!(dp = opendir(path))) return -1;
788 while ((ep = readdir(dp))) {
789 if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
790 continue;
791 snprintf(subpath, PATH_MAX, "%s%s", path, ep->d_name);
792 lstat(subpath, &statbuf);
793 if (S_ISDIR(statbuf.st_mode)) {
794 strcat(subpath, "/");
795 ret |= process_dir(pre, proc, pos, subpath);
796 } else
797 ret |= proc(subpath);
799 closedir(dp);
800 if (pos) ret |= pos(path);
801 return ret;
804 /* Process all marked entries using CWD as destination root.
805 All marked entries that are directories will be recursively processed.
806 See process_dir() for details on the parameters. */
807 static void
808 process_marked(PROCESS pre, PROCESS proc, PROCESS pos,
809 const char *msg_doing, const char *msg_done)
811 int i, ret;
812 char *entry;
813 char path[PATH_MAX];
815 clear_message();
816 message(CYAN, "%s...", msg_doing);
817 refresh();
818 rover.prog = (Prog) {0, count_marked(), msg_doing};
819 for (i = 0; i < rover.marks.bulk; i++) {
820 entry = rover.marks.entries[i];
821 if (entry) {
822 ret = 0;
823 snprintf(path, PATH_MAX, "%s%s", rover.marks.dirpath, entry);
824 if (ISDIR(entry)) {
825 if (!strncmp(path, CWD, strlen(path)))
826 ret = -1;
827 else
828 ret = process_dir(pre, proc, pos, path);
829 } else
830 ret = proc(path);
831 if (!ret) {
832 del_mark(&rover.marks, entry);
833 reload();
837 rover.prog.total = 0;
838 reload();
839 if (!rover.marks.nentries)
840 message(GREEN, "%s all marked entries.", msg_done);
841 else
842 message(RED, "Some errors occured while %s.", msg_doing);
843 RV_ALERT();
846 static void
847 update_progress(off_t delta)
849 int percent;
851 if (!rover.prog.total) return;
852 rover.prog.partial += delta;
853 percent = (int) (rover.prog.partial * 100 / rover.prog.total);
854 message(CYAN, "%s...%d%%", rover.prog.msg, percent);
855 refresh();
858 /* Wrappers for file operations. */
859 static int delfile(const char *path) {
860 int ret;
861 struct stat st;
863 ret = lstat(path, &st);
864 if (ret < 0) return ret;
865 update_progress(st.st_size);
866 return unlink(path);
868 static PROCESS deldir = rmdir;
869 static int addfile(const char *path) {
870 /* Using creat(2) because mknod(2) doesn't seem to be portable. */
871 int ret;
873 ret = creat(path, 0644);
874 if (ret < 0) return ret;
875 return close(ret);
877 static int cpyfile(const char *srcpath) {
878 int src, dst, ret;
879 size_t size;
880 struct stat st;
881 char buf[BUFSIZ];
882 char dstpath[PATH_MAX];
884 strcpy(dstpath, CWD);
885 strcat(dstpath, srcpath + strlen(rover.marks.dirpath));
886 ret = lstat(srcpath, &st);
887 if (ret < 0) return ret;
888 if (S_ISLNK(st.st_mode)) {
889 ret = readlink(srcpath, BUF1, BUFLEN-1);
890 if (ret < 0) return ret;
891 BUF1[ret] = '\0';
892 ret = symlink(BUF1, dstpath);
893 } else {
894 ret = src = open(srcpath, O_RDONLY);
895 if (ret < 0) return ret;
896 ret = dst = creat(dstpath, st.st_mode);
897 if (ret < 0) return ret;
898 while ((size = read(src, buf, BUFSIZ)) > 0) {
899 write(dst, buf, size);
900 update_progress(size);
901 sync_signals();
903 close(src);
904 close(dst);
905 ret = 0;
907 return ret;
909 static int adddir(const char *path) {
910 int ret;
911 struct stat st;
913 ret = stat(CWD, &st);
914 if (ret < 0) return ret;
915 return mkdir(path, st.st_mode);
917 static int movfile(const char *srcpath) {
918 int ret;
919 struct stat st;
920 char dstpath[PATH_MAX];
922 strcpy(dstpath, CWD);
923 strcat(dstpath, srcpath + strlen(rover.marks.dirpath));
924 ret = rename(srcpath, dstpath);
925 if (ret == 0) {
926 ret = lstat(dstpath, &st);
927 if (ret < 0) return ret;
928 update_progress(st.st_size);
929 } else if (errno == EXDEV) {
930 ret = cpyfile(srcpath);
931 if (ret < 0) return ret;
932 ret = unlink(srcpath);
934 return ret;
937 static void
938 start_line_edit(const char *init_input)
940 curs_set(TRUE);
941 strncpy(INPUT, init_input, BUFLEN);
942 rover.edit.left = mbstowcs(rover.edit.buffer, init_input, BUFLEN);
943 rover.edit.right = BUFLEN - 1;
944 rover.edit.buffer[BUFLEN] = L'\0';
945 rover.edit_scroll = 0;
948 /* Read input and change editing state accordingly. */
949 static EditStat
950 get_line_edit()
952 wchar_t eraser, killer, wch;
953 int ret, length;
955 ret = rover_get_wch((wint_t *) &wch);
956 erasewchar(&eraser);
957 killwchar(&killer);
958 if (ret == KEY_CODE_YES) {
959 if (wch == KEY_ENTER) {
960 curs_set(FALSE);
961 return CONFIRM;
962 } else if (wch == KEY_LEFT) {
963 if (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit);
964 } else if (wch == KEY_RIGHT) {
965 if (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit);
966 } else if (wch == KEY_UP) {
967 while (EDIT_CAN_LEFT(rover.edit)) EDIT_LEFT(rover.edit);
968 } else if (wch == KEY_DOWN) {
969 while (EDIT_CAN_RIGHT(rover.edit)) EDIT_RIGHT(rover.edit);
970 } else if (wch == KEY_BACKSPACE) {
971 if (EDIT_CAN_LEFT(rover.edit)) EDIT_BACKSPACE(rover.edit);
972 } else if (wch == KEY_DC) {
973 if (EDIT_CAN_RIGHT(rover.edit)) EDIT_DELETE(rover.edit);
975 } else {
976 if (wch == L'\r' || wch == L'\n') {
977 curs_set(FALSE);
978 return CONFIRM;
979 } else if (wch == L'\t') {
980 curs_set(FALSE);
981 return CANCEL;
982 } else if (wch == eraser) {
983 if (EDIT_CAN_LEFT(rover.edit)) EDIT_BACKSPACE(rover.edit);
984 } else if (wch == killer) {
985 EDIT_CLEAR(rover.edit);
986 clear_message();
987 } else if (iswprint(wch)) {
988 if (!EDIT_FULL(rover.edit)) EDIT_INSERT(rover.edit, wch);
991 /* Encode edit contents in INPUT. */
992 rover.edit.buffer[rover.edit.left] = L'\0';
993 length = wcstombs(INPUT, rover.edit.buffer, BUFLEN);
994 wcstombs(&INPUT[length], &rover.edit.buffer[rover.edit.right+1],
995 BUFLEN-length);
996 return CONTINUE;
999 /* Update line input on the screen. */
1000 static void
1001 update_input(const char *prompt, Color color)
1003 int plen, ilen, maxlen;
1005 plen = strlen(prompt);
1006 ilen = mbstowcs(NULL, INPUT, 0);
1007 maxlen = STATUSPOS - plen - 2;
1008 if (ilen - rover.edit_scroll < maxlen)
1009 rover.edit_scroll = MAX(ilen - maxlen, 0);
1010 else if (rover.edit.left > rover.edit_scroll + maxlen - 1)
1011 rover.edit_scroll = rover.edit.left - maxlen;
1012 else if (rover.edit.left < rover.edit_scroll)
1013 rover.edit_scroll = MAX(rover.edit.left - maxlen, 0);
1014 color_set(RVC_PROMPT, NULL);
1015 mvaddstr(LINES - 1, 0, prompt);
1016 color_set(color, NULL);
1017 mbstowcs(WBUF, INPUT, COLS);
1018 mvaddnwstr(LINES - 1, plen, &WBUF[rover.edit_scroll], maxlen);
1019 mvaddch(LINES - 1, plen + MIN(ilen - rover.edit_scroll, maxlen + 1), ' ');
1020 color_set(DEFAULT, NULL);
1021 if (rover.edit_scroll)
1022 mvaddch(LINES - 1, plen - 1, '<');
1023 if (ilen > rover.edit_scroll + maxlen)
1024 mvaddch(LINES - 1, plen + maxlen, '>');
1025 move(LINES - 1, plen + rover.edit.left - rover.edit_scroll);
1029 main(int argc, char *argv[])
1031 int i, ch;
1032 char *program;
1033 char *entry;
1034 const char *key;
1035 const char *clip_path;
1036 DIR *d;
1037 EditStat edit_stat;
1038 FILE *save_cwd_file = NULL;
1039 FILE *save_marks_file = NULL;
1040 FILE *clip_file;
1042 if (argc >= 2) {
1043 if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) {
1044 printf("rover %s\n", RV_VERSION);
1045 return 0;
1046 } else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
1047 printf(
1048 "Usage: rover [OPTIONS] [DIR [DIR [...]]]\n"
1049 " Browse current directory or the ones specified.\n\n"
1050 " or: rover -h|--help\n"
1051 " Print this help message and exit.\n\n"
1052 " or: rover -v|--version\n"
1053 " Print program version and exit.\n\n"
1054 "See rover(1) for more information.\n"
1055 "Rover homepage: <https://github.com/lecram/rover>.\n"
1057 return 0;
1058 } else if (!strcmp(argv[1], "-d") || !strcmp(argv[1], "--save-cwd")) {
1059 if (argc > 2) {
1060 save_cwd_file = fopen(argv[2], "w");
1061 argc -= 2; argv += 2;
1062 } else {
1063 fprintf(stderr, "error: missing argument to %s\n", argv[1]);
1064 return 1;
1066 } else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--save-marks")) {
1067 if (argc > 2) {
1068 save_marks_file = fopen(argv[2], "a");
1069 argc -= 2; argv += 2;
1070 } else {
1071 fprintf(stderr, "error: missing argument to %s\n", argv[1]);
1072 return 1;
1076 get_user_programs();
1077 init_term();
1078 rover.nfiles = 0;
1079 for (i = 0; i < 10; i++) {
1080 rover.tabs[i].esel = rover.tabs[i].scroll = 0;
1081 rover.tabs[i].flags = RV_FLAGS;
1083 strcpy(rover.tabs[0].cwd, getenv("HOME"));
1084 for (i = 1; i < argc && i < 10; i++) {
1085 if ((d = opendir(argv[i]))) {
1086 realpath(argv[i], rover.tabs[i].cwd);
1087 closedir(d);
1088 } else
1089 strcpy(rover.tabs[i].cwd, rover.tabs[0].cwd);
1091 getcwd(rover.tabs[i].cwd, PATH_MAX);
1092 for (i++; i < 10; i++)
1093 strcpy(rover.tabs[i].cwd, rover.tabs[i-1].cwd);
1094 for (i = 0; i < 10; i++)
1095 if (rover.tabs[i].cwd[strlen(rover.tabs[i].cwd) - 1] != '/')
1096 strcat(rover.tabs[i].cwd, "/");
1097 rover.tab = 1;
1098 rover.window = subwin(stdscr, LINES - 2, COLS, 1, 0);
1099 init_marks(&rover.marks);
1100 cd(1);
1101 strcpy(CLIPBOARD, CWD);
1102 if (rover.nfiles > 0)
1103 strcat(CLIPBOARD, ENAME(ESEL));
1104 while (1) {
1105 ch = rover_getch();
1106 key = keyname(ch);
1107 clear_message();
1108 if (!strcmp(key, RVK_QUIT)) break;
1109 else if (ch >= '0' && ch <= '9') {
1110 rover.tab = ch - '0';
1111 cd(0);
1112 } else if (!strcmp(key, RVK_HELP)) {
1113 spawn((char *[]) {"man", "rover", NULL});
1114 } else if (!strcmp(key, RVK_DOWN)) {
1115 if (!rover.nfiles) continue;
1116 ESEL = MIN(ESEL + 1, rover.nfiles - 1);
1117 update_view();
1118 } else if (!strcmp(key, RVK_UP)) {
1119 if (!rover.nfiles) continue;
1120 ESEL = MAX(ESEL - 1, 0);
1121 update_view();
1122 } else if (!strcmp(key, RVK_JUMP_DOWN)) {
1123 if (!rover.nfiles) continue;
1124 ESEL = MIN(ESEL + RV_JUMP, rover.nfiles - 1);
1125 if (rover.nfiles > HEIGHT)
1126 SCROLL = MIN(SCROLL + RV_JUMP, rover.nfiles - HEIGHT);
1127 update_view();
1128 } else if (!strcmp(key, RVK_JUMP_UP)) {
1129 if (!rover.nfiles) continue;
1130 ESEL = MAX(ESEL - RV_JUMP, 0);
1131 SCROLL = MAX(SCROLL - RV_JUMP, 0);
1132 update_view();
1133 } else if (!strcmp(key, RVK_JUMP_TOP)) {
1134 if (!rover.nfiles) continue;
1135 ESEL = 0;
1136 update_view();
1137 } else if (!strcmp(key, RVK_JUMP_BOTTOM)) {
1138 if (!rover.nfiles) continue;
1139 ESEL = rover.nfiles - 1;
1140 update_view();
1141 } else if (!strcmp(key, RVK_CD_DOWN)) {
1142 if (!rover.nfiles || !S_ISDIR(EMODE(ESEL))) continue;
1143 if (chdir(ENAME(ESEL)) == -1) {
1144 message(RED, "Cannot access \"%s\".", ENAME(ESEL));
1145 continue;
1147 strcat(CWD, ENAME(ESEL));
1148 cd(1);
1149 } else if (!strcmp(key, RVK_CD_UP)) {
1150 char *dirname, first;
1151 if (!strcmp(CWD, "/")) continue;
1152 CWD[strlen(CWD) - 1] = '\0';
1153 dirname = strrchr(CWD, '/') + 1;
1154 first = dirname[0];
1155 dirname[0] = '\0';
1156 cd(1);
1157 dirname[0] = first;
1158 dirname[strlen(dirname)] = '/';
1159 try_to_sel(dirname);
1160 dirname[0] = '\0';
1161 if (rover.nfiles > HEIGHT)
1162 SCROLL = ESEL - HEIGHT / 2;
1163 update_view();
1164 } else if (!strcmp(key, RVK_HOME)) {
1165 strcpy(CWD, getenv("HOME"));
1166 if (CWD[strlen(CWD) - 1] != '/')
1167 strcat(CWD, "/");
1168 cd(1);
1169 } else if (!strcmp(key, RVK_TARGET)) {
1170 char *bname, first;
1171 int is_dir = S_ISDIR(EMODE(ESEL));
1172 ssize_t len = readlink(ENAME(ESEL), BUF1, BUFLEN-1);
1173 if (len == -1) continue;
1174 BUF1[len] = '\0';
1175 if (access(BUF1, F_OK) == -1) {
1176 char *msg;
1177 switch (errno) {
1178 case EACCES:
1179 msg = "Cannot access \"%s\".";
1180 break;
1181 case ENOENT:
1182 msg = "\"%s\" does not exist.";
1183 break;
1184 default:
1185 msg = "Cannot navigate to \"%s\".";
1187 strcpy(BUF2, BUF1); /* message() uses BUF1. */
1188 message(RED, msg, BUF2);
1189 continue;
1191 realpath(BUF1, CWD);
1192 len = strlen(CWD);
1193 if (CWD[len - 1] == '/')
1194 CWD[len - 1] = '\0';
1195 bname = strrchr(CWD, '/') + 1;
1196 first = *bname;
1197 *bname = '\0';
1198 cd(1);
1199 *bname = first;
1200 if (is_dir)
1201 strcat(CWD, "/");
1202 try_to_sel(bname);
1203 *bname = '\0';
1204 update_view();
1205 } else if (!strcmp(key, RVK_COPY_PATH)) {
1206 clip_path = getenv("CLIP");
1207 if (!clip_path) goto copy_path_fail;
1208 clip_file = fopen(clip_path, "w");
1209 if (!clip_file) goto copy_path_fail;
1210 fprintf(clip_file, "%s%s\n", CWD, ENAME(ESEL));
1211 fclose(clip_file);
1212 goto copy_path_done;
1213 copy_path_fail:
1214 strcpy(CLIPBOARD, CWD);
1215 strcat(CLIPBOARD, ENAME(ESEL));
1216 copy_path_done:
1218 } else if (!strcmp(key, RVK_PASTE_PATH)) {
1219 clip_path = getenv("CLIP");
1220 if (!clip_path) goto paste_path_fail;
1221 clip_file = fopen(clip_path, "r");
1222 if (!clip_file) goto paste_path_fail;
1223 fscanf(clip_file, "%s\n", CLIPBOARD);
1224 fclose(clip_file);
1225 paste_path_fail:
1226 strcpy(BUF1, CLIPBOARD);
1227 strcpy(CWD, dirname(BUF1));
1228 if (strcmp(CWD, "/"))
1229 strcat(CWD, "/");
1230 cd(1);
1231 strcpy(BUF1, CLIPBOARD);
1232 try_to_sel(strstr(CLIPBOARD, basename(BUF1)));
1233 update_view();
1234 } else if (!strcmp(key, RVK_REFRESH)) {
1235 reload();
1236 } else if (!strcmp(key, RVK_SHELL)) {
1237 program = user_shell;
1238 if (program) {
1239 #ifdef RV_SHELL
1240 spawn((char *[]) {RV_SHELL, "-c", program, NULL});
1241 #else
1242 spawn((char *[]) {program, NULL});
1243 #endif
1244 reload();
1246 } else if (!strcmp(key, RVK_VIEW)) {
1247 if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
1248 if (open_with_env(user_pager, ENAME(ESEL)))
1249 cd(0);
1250 } else if (!strcmp(key, RVK_EDIT)) {
1251 if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
1252 if (open_with_env(user_editor, ENAME(ESEL)))
1253 cd(0);
1254 } else if (!strcmp(key, RVK_OPEN)) {
1255 if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
1256 if (open_with_env(user_open, ENAME(ESEL)))
1257 cd(0);
1258 } else if (!strcmp(key, RVK_SEARCH)) {
1259 int oldsel, oldscroll, length;
1260 if (!rover.nfiles) continue;
1261 oldsel = ESEL;
1262 oldscroll = SCROLL;
1263 start_line_edit("");
1264 update_input(RVP_SEARCH, RED);
1265 while ((edit_stat = get_line_edit()) == CONTINUE) {
1266 int sel;
1267 Color color = RED;
1268 length = strlen(INPUT);
1269 if (length) {
1270 for (sel = 0; sel < rover.nfiles; sel++)
1271 if (!strncmp(ENAME(sel), INPUT, length))
1272 break;
1273 if (sel < rover.nfiles) {
1274 color = GREEN;
1275 ESEL = sel;
1276 if (rover.nfiles > HEIGHT) {
1277 if (sel < 3)
1278 SCROLL = 0;
1279 else if (sel - 3 > rover.nfiles - HEIGHT)
1280 SCROLL = rover.nfiles - HEIGHT;
1281 else
1282 SCROLL = sel - 3;
1285 } else {
1286 ESEL = oldsel;
1287 SCROLL = oldscroll;
1289 update_view();
1290 update_input(RVP_SEARCH, color);
1292 if (edit_stat == CANCEL) {
1293 ESEL = oldsel;
1294 SCROLL = oldscroll;
1296 clear_message();
1297 update_view();
1298 } else if (!strcmp(key, RVK_TG_FILES)) {
1299 FLAGS ^= SHOW_FILES;
1300 reload();
1301 } else if (!strcmp(key, RVK_TG_DIRS)) {
1302 FLAGS ^= SHOW_DIRS;
1303 reload();
1304 } else if (!strcmp(key, RVK_TG_HIDDEN)) {
1305 FLAGS ^= SHOW_HIDDEN;
1306 reload();
1307 } else if (!strcmp(key, RVK_NEW_FILE)) {
1308 int ok = 0;
1309 start_line_edit("");
1310 update_input(RVP_NEW_FILE, RED);
1311 while ((edit_stat = get_line_edit()) == CONTINUE) {
1312 int length = strlen(INPUT);
1313 ok = length;
1314 for (i = 0; i < rover.nfiles; i++) {
1315 if (
1316 !strncmp(ENAME(i), INPUT, length) &&
1317 (!strcmp(ENAME(i) + length, "") ||
1318 !strcmp(ENAME(i) + length, "/"))
1320 ok = 0;
1321 break;
1324 update_input(RVP_NEW_FILE, ok ? GREEN : RED);
1326 clear_message();
1327 if (edit_stat == CONFIRM) {
1328 if (ok) {
1329 if (addfile(INPUT) == 0) {
1330 cd(1);
1331 try_to_sel(INPUT);
1332 update_view();
1333 } else
1334 message(RED, "Could not create \"%s\".", INPUT);
1335 } else
1336 message(RED, "\"%s\" already exists.", INPUT);
1338 } else if (!strcmp(key, RVK_NEW_DIR)) {
1339 int ok = 0;
1340 start_line_edit("");
1341 update_input(RVP_NEW_DIR, RED);
1342 while ((edit_stat = get_line_edit()) == CONTINUE) {
1343 int length = strlen(INPUT);
1344 ok = length;
1345 for (i = 0; i < rover.nfiles; i++) {
1346 if (
1347 !strncmp(ENAME(i), INPUT, length) &&
1348 (!strcmp(ENAME(i) + length, "") ||
1349 !strcmp(ENAME(i) + length, "/"))
1351 ok = 0;
1352 break;
1355 update_input(RVP_NEW_DIR, ok ? GREEN : RED);
1357 clear_message();
1358 if (edit_stat == CONFIRM) {
1359 if (ok) {
1360 if (adddir(INPUT) == 0) {
1361 cd(1);
1362 strcat(INPUT, "/");
1363 try_to_sel(INPUT);
1364 update_view();
1365 } else
1366 message(RED, "Could not create \"%s/\".", INPUT);
1367 } else
1368 message(RED, "\"%s\" already exists.", INPUT);
1370 } else if (!strcmp(key, RVK_RENAME)) {
1371 int ok = 0;
1372 char *last;
1373 int isdir;
1374 strcpy(INPUT, ENAME(ESEL));
1375 last = INPUT + strlen(INPUT) - 1;
1376 if ((isdir = *last == '/'))
1377 *last = '\0';
1378 start_line_edit(INPUT);
1379 update_input(RVP_RENAME, RED);
1380 while ((edit_stat = get_line_edit()) == CONTINUE) {
1381 int length = strlen(INPUT);
1382 ok = length;
1383 for (i = 0; i < rover.nfiles; i++)
1384 if (
1385 !strncmp(ENAME(i), INPUT, length) &&
1386 (!strcmp(ENAME(i) + length, "") ||
1387 !strcmp(ENAME(i) + length, "/"))
1389 ok = 0;
1390 break;
1392 update_input(RVP_RENAME, ok ? GREEN : RED);
1394 clear_message();
1395 if (edit_stat == CONFIRM) {
1396 if (isdir)
1397 strcat(INPUT, "/");
1398 if (ok) {
1399 if (!rename(ENAME(ESEL), INPUT) && MARKED(ESEL)) {
1400 del_mark(&rover.marks, ENAME(ESEL));
1401 add_mark(&rover.marks, CWD, INPUT);
1403 cd(1);
1404 try_to_sel(INPUT);
1405 update_view();
1406 } else
1407 message(RED, "\"%s\" already exists.", INPUT);
1409 } else if (!strcmp(key, RVK_TG_EXEC)) {
1410 if (!rover.nfiles || S_ISDIR(EMODE(ESEL))) continue;
1411 if (S_IXUSR & EMODE(ESEL))
1412 EMODE(ESEL) &= ~(S_IXUSR | S_IXGRP | S_IXOTH);
1413 else
1414 EMODE(ESEL) |= S_IXUSR | S_IXGRP | S_IXOTH ;
1415 if (chmod(ENAME(ESEL), EMODE(ESEL))) {
1416 message(RED, "Failed to change mode of \"%s\".", ENAME(ESEL));
1417 } else {
1418 message(GREEN, "Changed mode of \"%s\".", ENAME(ESEL));
1419 update_view();
1421 } else if (!strcmp(key, RVK_DELETE)) {
1422 if (rover.nfiles) {
1423 message(YELLOW, "Delete \"%s\"? (Y/n)", ENAME(ESEL));
1424 if (rover_getch() == 'Y') {
1425 const char *name = ENAME(ESEL);
1426 int ret = ISDIR(ENAME(ESEL)) ? deldir(name) : delfile(name);
1427 reload();
1428 if (ret)
1429 message(RED, "Could not delete \"%s\".", ENAME(ESEL));
1430 } else
1431 clear_message();
1432 } else
1433 message(RED, "No entry selected for deletion.");
1434 } else if (!strcmp(key, RVK_TG_MARK)) {
1435 if (MARKED(ESEL))
1436 del_mark(&rover.marks, ENAME(ESEL));
1437 else
1438 add_mark(&rover.marks, CWD, ENAME(ESEL));
1439 MARKED(ESEL) = !MARKED(ESEL);
1440 ESEL = (ESEL + 1) % rover.nfiles;
1441 update_view();
1442 } else if (!strcmp(key, RVK_INVMARK)) {
1443 for (i = 0; i < rover.nfiles; i++) {
1444 if (MARKED(i))
1445 del_mark(&rover.marks, ENAME(i));
1446 else
1447 add_mark(&rover.marks, CWD, ENAME(i));
1448 MARKED(i) = !MARKED(i);
1450 update_view();
1451 } else if (!strcmp(key, RVK_MARKALL)) {
1452 for (i = 0; i < rover.nfiles; i++)
1453 if (!MARKED(i)) {
1454 add_mark(&rover.marks, CWD, ENAME(i));
1455 MARKED(i) = 1;
1457 update_view();
1458 } else if (!strcmp(key, RVK_MARK_DELETE)) {
1459 if (rover.marks.nentries) {
1460 message(YELLOW, "Delete all marked entries? (Y/n)");
1461 if (rover_getch() == 'Y')
1462 process_marked(NULL, delfile, deldir, "Deleting", "Deleted");
1463 else
1464 clear_message();
1465 } else
1466 message(RED, "No entries marked for deletion.");
1467 } else if (!strcmp(key, RVK_MARK_COPY)) {
1468 if (rover.marks.nentries) {
1469 if (strcmp(CWD, rover.marks.dirpath))
1470 process_marked(adddir, cpyfile, NULL, "Copying", "Copied");
1471 else
1472 message(RED, "Cannot copy to the same path.");
1473 } else
1474 message(RED, "No entries marked for copying.");
1475 } else if (!strcmp(key, RVK_MARK_MOVE)) {
1476 if (rover.marks.nentries) {
1477 if (strcmp(CWD, rover.marks.dirpath))
1478 process_marked(adddir, movfile, deldir, "Moving", "Moved");
1479 else
1480 message(RED, "Cannot move to the same path.");
1481 } else
1482 message(RED, "No entries marked for moving.");
1485 if (rover.nfiles)
1486 free_rows(&rover.rows, rover.nfiles);
1487 delwin(rover.window);
1488 if (save_cwd_file != NULL) {
1489 fputs(CWD, save_cwd_file);
1490 fclose(save_cwd_file);
1492 if (save_marks_file != NULL) {
1493 for (i = 0; i < rover.marks.bulk; i++) {
1494 entry = rover.marks.entries[i];
1495 if (entry)
1496 fprintf(save_marks_file, "%s%s\n", rover.marks.dirpath, entry);
1498 fclose(save_marks_file);
1500 free_marks(&rover.marks);
1501 return 0;