My build of nnn with minor changes
 
 
 
 
 
 

739 lines
12 KiB

  1. #include <sys/stat.h>
  2. #include <sys/types.h>
  3. #include <errno.h>
  4. #include <fcntl.h>
  5. #include <dirent.h>
  6. #include <curses.h>
  7. #include <libgen.h>
  8. #include <limits.h>
  9. #include <locale.h>
  10. #include <regex.h>
  11. #include <stdlib.h>
  12. #include <stdio.h>
  13. #include <signal.h>
  14. #include <string.h>
  15. #include <unistd.h>
  16. #include "queue.h"
  17. #include "util.h"
  18. #ifdef DEBUG
  19. #define DEBUG_FD 8
  20. #define DPRINTF_D(x) dprintf(DEBUG_FD, #x "=%d\n", x)
  21. #define DPRINTF_U(x) dprintf(DEBUG_FD, #x "=%u\n", x)
  22. #define DPRINTF_S(x) dprintf(DEBUG_FD, #x "=%s\n", x)
  23. #define DPRINTF_P(x) dprintf(DEBUG_FD, #x "=0x%p\n", x)
  24. #else
  25. #define DPRINTF_D(x)
  26. #define DPRINTF_U(x)
  27. #define DPRINTF_S(x)
  28. #define DPRINTF_P(x)
  29. #endif /* DEBUG */
  30. #define LEN(x) (sizeof(x) / sizeof(*(x)))
  31. #define MIN(x, y) ((x) < (y) ? (x) : (y))
  32. #define ISODD(x) ((x) & 1)
  33. #define CONTROL(c) ((c) ^ 0x40)
  34. struct assoc {
  35. char *regex; /* Regex to match on filename */
  36. char *bin; /* Program */
  37. };
  38. #include "config.h"
  39. struct entry {
  40. char *name;
  41. mode_t mode;
  42. };
  43. struct history {
  44. int pos;
  45. SLIST_ENTRY(history) entry;
  46. };
  47. SLIST_HEAD(histhead, history) histhead = SLIST_HEAD_INITIALIZER(histhead);
  48. /*
  49. * Layout:
  50. * .---------
  51. * | cwd: /mnt/path
  52. * |
  53. * | file0
  54. * | file1
  55. * | > file2
  56. * | file3
  57. * | file4
  58. * ...
  59. * | filen
  60. * |
  61. * | Permission denied
  62. * '------
  63. */
  64. void printmsg(char *msg);
  65. void printwarn(void);
  66. void printerr(int ret, char *prefix);
  67. void *
  68. xmalloc(size_t size)
  69. {
  70. void *p;
  71. p = malloc(size);
  72. if (p == NULL)
  73. printerr(1, "malloc");
  74. return p;
  75. }
  76. void *
  77. xrealloc(void *p, size_t size)
  78. {
  79. p = realloc(p, size);
  80. if (p == NULL)
  81. printerr(1, "realloc");
  82. return p;
  83. }
  84. char *
  85. xstrdup(const char *s)
  86. {
  87. char *p;
  88. p = strdup(s);
  89. if (p == NULL)
  90. printerr(1, "strdup");
  91. return p;
  92. }
  93. char *
  94. xrealpath(const char *path)
  95. {
  96. char *p;
  97. p = realpath(path, NULL);
  98. if (p == NULL)
  99. printerr(1, "realpath");
  100. return p;
  101. }
  102. char *
  103. xdirname(const char *path)
  104. {
  105. char *p, *tmp;
  106. /* Some implementations of dirname(3) may modify `path' */
  107. tmp = xstrdup(path);
  108. p = dirname(tmp);
  109. free(tmp);
  110. if (p == NULL)
  111. printerr(1, "dirname");
  112. return p;
  113. }
  114. void
  115. spawn(const char *file, const char *arg)
  116. {
  117. pid_t pid;
  118. int status;
  119. pid = fork();
  120. if (pid == 0) {
  121. execlp(file, file, arg, NULL);
  122. _exit(1);
  123. } else {
  124. /* Ignore interruptions */
  125. while (waitpid(pid, &status, 0) == -1)
  126. DPRINTF_D(status);
  127. DPRINTF_D(pid);
  128. }
  129. }
  130. char *
  131. openwith(char *file)
  132. {
  133. regex_t regex;
  134. char *bin = NULL;
  135. int i;
  136. for (i = 0; i < LEN(assocs); i++) {
  137. if (regcomp(&regex, assocs[i].regex,
  138. REG_NOSUB | REG_EXTENDED) != 0)
  139. continue;
  140. if (regexec(&regex, file, 0, NULL, 0) != REG_NOMATCH) {
  141. bin = assocs[i].bin;
  142. break;
  143. }
  144. }
  145. DPRINTF_S(bin);
  146. return bin;
  147. }
  148. int
  149. setfilter(regex_t *regex, char *filter)
  150. {
  151. char *errbuf;
  152. int r;
  153. r = regcomp(regex, filter, REG_NOSUB | REG_EXTENDED);
  154. if (r != 0) {
  155. errbuf = xmalloc(COLS * sizeof(char));
  156. regerror(r, regex, errbuf, COLS * sizeof(char));
  157. printmsg(errbuf);
  158. free(errbuf);
  159. }
  160. return r;
  161. }
  162. int
  163. visible(regex_t *regex, char *file)
  164. {
  165. if (regexec(regex, file, 0, NULL, 0) != REG_NOMATCH)
  166. return 1;
  167. return 0;
  168. }
  169. int
  170. entrycmp(const void *va, const void *vb)
  171. {
  172. const struct entry *a, *b;
  173. a = (struct entry *)va;
  174. b = (struct entry *)vb;
  175. return strcmp(a->name, b->name);
  176. }
  177. void
  178. initcurses(void)
  179. {
  180. initscr();
  181. cbreak();
  182. noecho();
  183. nonl();
  184. intrflush(stdscr, FALSE);
  185. keypad(stdscr, TRUE);
  186. curs_set(FALSE); /* Hide cursor */
  187. }
  188. void
  189. exitcurses(void)
  190. {
  191. endwin(); /* Restore terminal */
  192. }
  193. /* Messages show up at the bottom */
  194. void
  195. printmsg(char *msg)
  196. {
  197. move(LINES - 1, 0);
  198. printw("%s\n", msg);
  199. }
  200. /* Display warning as a message */
  201. void
  202. printwarn(void)
  203. {
  204. printmsg(strerror(errno));
  205. }
  206. /* Kill curses and display error before exiting */
  207. void
  208. printerr(int ret, char *prefix)
  209. {
  210. exitcurses();
  211. fprintf(stderr, "%s: %s\n", prefix, strerror(errno));
  212. exit(ret);
  213. }
  214. /*
  215. * Returns 0 normally
  216. * On movement it updates *cur
  217. * Returns SEL_{QUIT,BACK,GOIN,FLTR,SH,CD} otherwise
  218. */
  219. enum {
  220. SEL_QUIT = 1,
  221. SEL_BACK,
  222. SEL_GOIN,
  223. SEL_FLTR,
  224. SEL_SH,
  225. SEL_CD,
  226. };
  227. int
  228. nextsel(int *cur, int max)
  229. {
  230. int c;
  231. c = getch();
  232. switch (c) {
  233. case 'q':
  234. return SEL_QUIT;
  235. /* Back */
  236. case KEY_BACKSPACE:
  237. case KEY_LEFT:
  238. case 'h':
  239. return SEL_BACK;
  240. /* Inside */
  241. case KEY_ENTER:
  242. case '\r':
  243. case KEY_RIGHT:
  244. case 'l':
  245. return SEL_GOIN;
  246. /* Filter */
  247. case '/':
  248. case '&':
  249. return SEL_FLTR;
  250. /* Next */
  251. case 'j':
  252. case KEY_DOWN:
  253. case CONTROL('N'):
  254. if (*cur < max - 1)
  255. (*cur)++;
  256. break;
  257. /* Previous */
  258. case 'k':
  259. case KEY_UP:
  260. case CONTROL('P'):
  261. if (*cur > 0)
  262. (*cur)--;
  263. break;
  264. /* Page down */
  265. case KEY_NPAGE:
  266. case CONTROL('D'):
  267. if (*cur < max -1)
  268. (*cur) += MIN((LINES - 4) / 2, max - 1 - *cur);
  269. break;
  270. /* Page up */
  271. case KEY_PPAGE:
  272. case CONTROL('U'):
  273. if (*cur > 0)
  274. (*cur) -= MIN((LINES - 4) / 2, *cur);
  275. break;
  276. case '!':
  277. return SEL_SH;
  278. case 'c':
  279. return SEL_CD;
  280. }
  281. return 0;
  282. }
  283. char *
  284. readln(void)
  285. {
  286. int c;
  287. int i = 0;
  288. char *ln = NULL;
  289. int y, x, x0;
  290. echo();
  291. curs_set(TRUE);
  292. /* Starting point */
  293. getyx(stdscr, y, x);
  294. x0 = x;
  295. while (c = getch()) {
  296. if (c == KEY_ENTER || c == '\r')
  297. break;
  298. if (c == KEY_BACKSPACE) {
  299. getyx(stdscr, y, x);
  300. if (x >= x0) {
  301. if (i > 0) {
  302. ln = xrealloc(ln, (i - 1) * sizeof(*ln));
  303. i--;
  304. } else {
  305. free(ln);
  306. ln = NULL;
  307. }
  308. move(y, x);
  309. printw("%c", ' ');
  310. move(y, x);
  311. } else {
  312. move(y, x0);
  313. }
  314. continue;
  315. }
  316. ln = xrealloc(ln, (i + 1) * sizeof(*ln));
  317. ln[i] = c;
  318. i++;
  319. }
  320. if (ln != NULL) {
  321. ln = xrealloc(ln, (i + 1) * sizeof(*ln));
  322. ln[i] = '\0';
  323. }
  324. curs_set(FALSE);
  325. noecho();
  326. return ln;
  327. }
  328. int
  329. testopendir(char *path)
  330. {
  331. DIR *dirp;
  332. dirp = opendir(path);
  333. if (dirp == NULL) {
  334. return 0;
  335. } else {
  336. closedir(dirp);
  337. return 1;
  338. }
  339. }
  340. void
  341. printent(struct entry *ent, int active)
  342. {
  343. char *name;
  344. unsigned int maxlen = COLS - strlen(CURSR) - 1;
  345. char cm = 0;
  346. /* Copy name locally */
  347. name = xstrdup(ent->name);
  348. if (S_ISDIR(ent->mode)) {
  349. cm = '/';
  350. maxlen--;
  351. } else if (S_ISLNK(ent->mode)) {
  352. cm = '@';
  353. maxlen--;
  354. } else if (ent->mode & S_IXUSR) {
  355. cm = '*';
  356. maxlen--;
  357. }
  358. /* No text wrapping in entries */
  359. if (strlen(name) > maxlen)
  360. name[maxlen] = '\0';
  361. if (cm == 0)
  362. printw("%s%s\n", active ? CURSR : EMPTY, name);
  363. else
  364. printw("%s%s%c\n", active ? CURSR : EMPTY, name, cm);
  365. free(name);
  366. }
  367. void
  368. browse(const char *ipath, const char *ifilter)
  369. {
  370. DIR *dirp;
  371. int dfd;
  372. struct dirent *dp;
  373. struct entry *dents;
  374. int i, n, cur;
  375. int r, ret;
  376. char *path = xrealpath(ipath);
  377. char *filter = xstrdup(ifilter);
  378. regex_t filter_re;
  379. char *cwd;
  380. struct stat sb;
  381. cur = 0;
  382. begin:
  383. /* Path and filter should be malloc(3)-ed strings at all times */
  384. n = 0;
  385. dents = NULL;
  386. dirp = opendir(path);
  387. if (dirp == NULL) {
  388. printwarn();
  389. goto nochange;
  390. } else {
  391. if (chdir(path) == -1)
  392. printwarn();
  393. }
  394. /* Search filter */
  395. r = setfilter(&filter_re, filter);
  396. if (r != 0)
  397. goto nochange;
  398. while ((dp = readdir(dirp)) != NULL) {
  399. char *name;
  400. /* Skip self and parent */
  401. if (strcmp(dp->d_name, ".") == 0
  402. || strcmp(dp->d_name, "..") == 0)
  403. continue;
  404. if (!visible(&filter_re, dp->d_name))
  405. continue;
  406. /* Deep copy because readdir(3) reuses the entries */
  407. dents = xrealloc(dents, (n + 1) * sizeof(*dents));
  408. dents[n].name = xstrdup(dp->d_name);
  409. /* Handle root case */
  410. if (strcmp(path, "/") == 0)
  411. asprintf(&name, "/%s", dents[n].name);
  412. else
  413. asprintf(&name, "%s/%s", path, dents[n].name);
  414. /* Get mode flags */
  415. r = lstat(name, &sb);
  416. free(name);
  417. if (r == -1)
  418. printerr(1, "stat");
  419. dents[n].mode = sb.st_mode;
  420. n++;
  421. }
  422. /* Make sure cur is in range */
  423. cur = MIN(cur, n - 1);
  424. qsort(dents, n, sizeof(*dents), entrycmp);
  425. for (;;) {
  426. int nlines;
  427. int maxlen;
  428. int odd;
  429. char *pathnew;
  430. char *name;
  431. char *bin;
  432. int fd;
  433. char *dir;
  434. char *tmp;
  435. regex_t re;
  436. struct history *hist;
  437. redraw:
  438. nlines = MIN(LINES - 4, n);
  439. /* Clean screen */
  440. erase();
  441. DPRINTF_D(cur);
  442. DPRINTF_S(path);
  443. /* No text wrapping in cwd line */
  444. cwd = xmalloc(COLS * sizeof(char));
  445. strlcpy(cwd, path, COLS * sizeof(char));
  446. cwd[COLS - strlen(CWD) - 1] = '\0';
  447. printw(CWD "%s\n\n", cwd);
  448. /* Print listing */
  449. odd = ISODD(nlines);
  450. if (cur < nlines / 2) {
  451. for (i = 0; i < nlines; i++)
  452. printent(&dents[i], i == cur);
  453. } else if (cur >= n - nlines / 2) {
  454. for (i = n - nlines; i < n; i++)
  455. printent(&dents[i], i == cur);
  456. } else {
  457. for (i = cur - nlines / 2;
  458. i < cur + nlines / 2 + odd; i++)
  459. printent(&dents[i], i == cur);
  460. }
  461. nochange:
  462. ret = nextsel(&cur, n);
  463. switch (ret) {
  464. case SEL_QUIT:
  465. free(path);
  466. free(filter);
  467. /* Forget history */
  468. while (!SLIST_EMPTY(&histhead)) {
  469. hist = SLIST_FIRST(&histhead);
  470. SLIST_REMOVE_HEAD(&histhead, entry);
  471. free(hist);
  472. }
  473. return;
  474. case SEL_BACK:
  475. /* There is no going back */
  476. if (strcmp(path, "/") == 0) {
  477. goto nochange;
  478. } else {
  479. dir = xdirname(path);
  480. tmp = xmalloc(strlen(dir) + 1);
  481. strlcpy(tmp, dir, strlen(dir) + 1);
  482. free(path);
  483. path = tmp;
  484. free(filter);
  485. filter = xstrdup(ifilter); /* Reset filter */
  486. /* Recall history */
  487. hist = SLIST_FIRST(&histhead);
  488. if (hist != NULL) {
  489. cur = hist->pos;
  490. DPRINTF_D(hist->pos);
  491. SLIST_REMOVE_HEAD(&histhead, entry);
  492. free(hist);
  493. } else {
  494. cur = 0;
  495. }
  496. goto out;
  497. }
  498. case SEL_GOIN:
  499. /* Cannot descend in empty directories */
  500. if (n == 0)
  501. goto nochange;
  502. name = dents[cur].name;
  503. /* Handle root case */
  504. if (strcmp(path, "/") == 0)
  505. asprintf(&pathnew, "/%s", name);
  506. else
  507. asprintf(&pathnew, "%s/%s", path, name);
  508. DPRINTF_S(name);
  509. DPRINTF_S(pathnew);
  510. /* Get path info */
  511. fd = open(pathnew, O_RDONLY | O_NONBLOCK);
  512. if (fd == -1) {
  513. printwarn();
  514. free(pathnew);
  515. goto nochange;
  516. }
  517. r = fstat(fd, &sb);
  518. close(fd);
  519. if (r == -1) {
  520. printwarn();
  521. free(pathnew);
  522. goto nochange;
  523. }
  524. DPRINTF_U(sb.st_mode);
  525. /* Directory */
  526. if (S_ISDIR(sb.st_mode)) {
  527. free(path);
  528. path = pathnew;
  529. free(filter);
  530. filter = xstrdup(ifilter); /* Reset filter */
  531. /* Save history */
  532. hist = xmalloc(sizeof(struct history));
  533. hist->pos = cur;
  534. SLIST_INSERT_HEAD(&histhead, hist, entry);
  535. cur = 0;
  536. goto out;
  537. }
  538. /* Regular file */
  539. if (S_ISREG(sb.st_mode)) {
  540. /* Open with */
  541. bin = openwith(name);
  542. if (bin == NULL) {
  543. printmsg("No association");
  544. free(pathnew);
  545. goto nochange;
  546. }
  547. exitcurses();
  548. spawn(bin, pathnew);
  549. initcurses();
  550. free(pathnew);
  551. goto redraw;
  552. }
  553. /* All the rest */
  554. printmsg("Unsupported file");
  555. free(pathnew);
  556. goto nochange;
  557. case SEL_FLTR:
  558. /* Read filter */
  559. printmsg("");
  560. move(LINES - 1, 0);
  561. printw("filter: ");
  562. tmp = readln();
  563. if (tmp == NULL) {
  564. printmsg("");
  565. goto nochange;
  566. }
  567. r = setfilter(&re, tmp);
  568. if (r != 0) {
  569. free(tmp);
  570. goto nochange;
  571. }
  572. free(filter);
  573. filter = tmp;
  574. filter_re = re;
  575. DPRINTF_S(filter);
  576. cur = 0;
  577. goto out;
  578. case SEL_SH:
  579. exitcurses();
  580. spawn("/bin/sh", NULL);
  581. initcurses();
  582. break;
  583. case SEL_CD:
  584. /* Read target dir */
  585. printmsg("");
  586. move(LINES - 1, 0);
  587. printw("chdir: ");
  588. tmp = readln();
  589. if (tmp == NULL) {
  590. printmsg("");
  591. goto nochange;
  592. }
  593. if (testopendir(tmp) == 0) {
  594. printwarn();
  595. goto nochange;
  596. } else {
  597. free(path);
  598. path = xrealpath(tmp);
  599. free(tmp);
  600. free(filter);
  601. filter = xstrdup(ifilter); /* Reset filter */
  602. /* Forget history */
  603. while (!SLIST_EMPTY(&histhead)) {
  604. hist = SLIST_FIRST(&histhead);
  605. SLIST_REMOVE_HEAD(&histhead, entry);
  606. free(hist);
  607. }
  608. DPRINTF_S(path);
  609. cur = 0;
  610. goto out;
  611. }
  612. }
  613. }
  614. out:
  615. for (i = 0; i < n; i++)
  616. free(dents[i].name);
  617. free(dents);
  618. /* Should never be null */
  619. r = closedir(dirp);
  620. if (r == -1)
  621. printerr(1, "closedir");
  622. goto begin;
  623. }
  624. int
  625. main(int argc, char *argv[])
  626. {
  627. char cwd[PATH_MAX], *ipath;
  628. char *ifilter;
  629. if (getuid() == 0)
  630. ifilter = ".*";
  631. else
  632. ifilter = "^[^.].*"; /* Hide dotfiles */
  633. if (argv[1] != NULL) {
  634. ipath = argv[1];
  635. } else {
  636. ipath = getcwd(cwd, sizeof(cwd));
  637. if (ipath == NULL)
  638. ipath = "/";
  639. }
  640. /* Test initial path */
  641. if (!testopendir(ipath))
  642. printerr(1, ipath);
  643. /* Set locale before curses setup */
  644. setlocale(LC_ALL, "");
  645. initcurses();
  646. browse(ipath, ifilter);
  647. exitcurses();
  648. return 0;
  649. }