My build of nnn with minor changes
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

850 wiersze
15 KiB

  1. /* See LICENSE file for copyright and license details. */
  2. #include <sys/stat.h>
  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. #include <curses.h>
  6. #include <dirent.h>
  7. #include <errno.h>
  8. #include <fcntl.h>
  9. #include <libgen.h>
  10. #include <limits.h>
  11. #include <locale.h>
  12. #include <regex.h>
  13. #include <signal.h>
  14. #include <stdarg.h>
  15. #include <stdio.h>
  16. #include <stdlib.h>
  17. #include <string.h>
  18. #include <unistd.h>
  19. #include "util.h"
  20. #ifdef DEBUG
  21. #define DEBUG_FD 8
  22. #define DPRINTF_D(x) dprintf(DEBUG_FD, #x "=%d\n", x)
  23. #define DPRINTF_U(x) dprintf(DEBUG_FD, #x "=%u\n", x)
  24. #define DPRINTF_S(x) dprintf(DEBUG_FD, #x "=%s\n", x)
  25. #define DPRINTF_P(x) dprintf(DEBUG_FD, #x "=0x%p\n", x)
  26. #else
  27. #define DPRINTF_D(x)
  28. #define DPRINTF_U(x)
  29. #define DPRINTF_S(x)
  30. #define DPRINTF_P(x)
  31. #endif /* DEBUG */
  32. #define LEN(x) (sizeof(x) / sizeof(*(x)))
  33. #undef MIN
  34. #define MIN(x, y) ((x) < (y) ? (x) : (y))
  35. #define ISODD(x) ((x) & 1)
  36. #define CONTROL(c) ((c) ^ 0x40)
  37. #define MAX_PATH_LEN 1024
  38. struct assoc {
  39. char *regex; /* Regex to match on filename */
  40. char *bin; /* Program */
  41. };
  42. /* Supported actions */
  43. enum action {
  44. SEL_QUIT = 1,
  45. SEL_BACK,
  46. SEL_GOIN,
  47. SEL_FLTR,
  48. SEL_NEXT,
  49. SEL_PREV,
  50. SEL_PGDN,
  51. SEL_PGUP,
  52. SEL_HOME,
  53. SEL_END,
  54. SEL_CD,
  55. SEL_CDHOME,
  56. SEL_TOGGLEDOT,
  57. SEL_MTIME,
  58. SEL_REDRAW,
  59. SEL_RUN,
  60. SEL_RUNARG,
  61. };
  62. struct key {
  63. int sym; /* Key pressed */
  64. enum action act; /* Action */
  65. char *run; /* Program to run */
  66. char *env; /* Environment variable to run */
  67. };
  68. #include "config.h"
  69. struct entry {
  70. char name[PATH_MAX];
  71. mode_t mode;
  72. time_t t;
  73. };
  74. /* Global context */
  75. struct entry *dents;
  76. int ndents, cur;
  77. int idle;
  78. /*
  79. * Layout:
  80. * .---------
  81. * | cwd: /mnt/path
  82. * |
  83. * | file0
  84. * | file1
  85. * | > file2
  86. * | file3
  87. * | file4
  88. * ...
  89. * | filen
  90. * |
  91. * | Permission denied
  92. * '------
  93. */
  94. void printmsg(char *);
  95. void printwarn(void);
  96. void printerr(int, char *);
  97. #undef dprintf
  98. int
  99. dprintf(int fd, const char *fmt, ...)
  100. {
  101. char buf[BUFSIZ];
  102. int r;
  103. va_list ap;
  104. va_start(ap, fmt);
  105. r = vsnprintf(buf, sizeof(buf), fmt, ap);
  106. if (r > 0)
  107. r = write(fd, buf, r);
  108. va_end(ap);
  109. return r;
  110. }
  111. void *
  112. xmalloc(size_t size)
  113. {
  114. void *p;
  115. p = malloc(size);
  116. if (p == NULL)
  117. printerr(1, "malloc");
  118. return p;
  119. }
  120. void *
  121. xrealloc(void *p, size_t size)
  122. {
  123. p = realloc(p, size);
  124. if (p == NULL)
  125. printerr(1, "realloc");
  126. return p;
  127. }
  128. char *
  129. xstrdup(const char *s)
  130. {
  131. char *p;
  132. p = strdup(s);
  133. if (p == NULL)
  134. printerr(1, "strdup");
  135. return p;
  136. }
  137. /* Some implementations of dirname(3) may modify `path' and some
  138. * return a pointer inside `path'. */
  139. char *
  140. xdirname(const char *path)
  141. {
  142. static char out[PATH_MAX];
  143. char tmp[PATH_MAX], *p;
  144. strlcpy(tmp, path, sizeof(tmp));
  145. p = dirname(tmp);
  146. if (p == NULL)
  147. printerr(1, "dirname");
  148. strlcpy(out, p, sizeof(out));
  149. return out;
  150. }
  151. void
  152. spawn(char *file, char *arg, char *dir)
  153. {
  154. pid_t pid;
  155. int status;
  156. pid = fork();
  157. if (pid == 0) {
  158. if (dir != NULL)
  159. status = chdir(dir);
  160. execlp(file, file, arg, NULL);
  161. _exit(1);
  162. } else {
  163. /* Ignore interruptions */
  164. while (waitpid(pid, &status, 0) == -1)
  165. DPRINTF_D(status);
  166. DPRINTF_D(pid);
  167. }
  168. }
  169. char *
  170. xgetenv(char *name, char *fallback)
  171. {
  172. char *value;
  173. if (name == NULL)
  174. return fallback;
  175. value = getenv(name);
  176. return value && value[0] ? value : fallback;
  177. }
  178. char *
  179. openwith(char *file)
  180. {
  181. regex_t regex;
  182. char *bin = NULL;
  183. int i;
  184. for (i = 0; i < LEN(assocs); i++) {
  185. if (regcomp(&regex, assocs[i].regex,
  186. REG_NOSUB | REG_EXTENDED | REG_ICASE) != 0)
  187. continue;
  188. if (regexec(&regex, file, 0, NULL, 0) == 0) {
  189. bin = assocs[i].bin;
  190. break;
  191. }
  192. }
  193. DPRINTF_S(bin);
  194. return bin;
  195. }
  196. int
  197. setfilter(regex_t *regex, char *filter)
  198. {
  199. char errbuf[LINE_MAX];
  200. size_t len;
  201. int r;
  202. r = regcomp(regex, filter, REG_NOSUB | REG_EXTENDED | REG_ICASE);
  203. if (r != 0) {
  204. len = COLS;
  205. if (len > sizeof(errbuf))
  206. len = sizeof(errbuf);
  207. regerror(r, regex, errbuf, len);
  208. printmsg(errbuf);
  209. }
  210. return r;
  211. }
  212. int
  213. visible(regex_t *regex, char *file)
  214. {
  215. return regexec(regex, file, 0, NULL, 0) == 0;
  216. }
  217. int
  218. entrycmp(const void *va, const void *vb)
  219. {
  220. const struct entry *a = va, *b = vb;
  221. if (mtimeorder)
  222. return b->t - a->t;
  223. return strcmp(a->name, b->name);
  224. }
  225. void
  226. initcurses(void)
  227. {
  228. char *term;
  229. if (initscr() == NULL) {
  230. term = getenv("TERM");
  231. if (term != NULL)
  232. fprintf(stderr, "error opening terminal: %s\n", term);
  233. else
  234. fprintf(stderr, "failed to initialize curses\n");
  235. exit(1);
  236. }
  237. cbreak();
  238. noecho();
  239. nonl();
  240. intrflush(stdscr, FALSE);
  241. keypad(stdscr, TRUE);
  242. curs_set(FALSE); /* Hide cursor */
  243. timeout(1000); /* One second */
  244. }
  245. void
  246. exitcurses(void)
  247. {
  248. endwin(); /* Restore terminal */
  249. }
  250. /* Messages show up at the bottom */
  251. void
  252. printmsg(char *msg)
  253. {
  254. move(LINES - 1, 0);
  255. printw("%s\n", msg);
  256. }
  257. /* Display warning as a message */
  258. void
  259. printwarn(void)
  260. {
  261. printmsg(strerror(errno));
  262. }
  263. /* Kill curses and display error before exiting */
  264. void
  265. printerr(int ret, char *prefix)
  266. {
  267. exitcurses();
  268. fprintf(stderr, "%s: %s\n", prefix, strerror(errno));
  269. exit(ret);
  270. }
  271. /* Clear the last line */
  272. void
  273. clearprompt(void)
  274. {
  275. printmsg("");
  276. }
  277. /* Print prompt on the last line */
  278. void
  279. printprompt(char *str)
  280. {
  281. clearprompt();
  282. printw(str);
  283. }
  284. /* Returns SEL_* if key is bound and 0 otherwise.
  285. * Also modifies the run and env pointers (used on SEL_{RUN,RUNARG}) */
  286. int
  287. nextsel(char **run, char **env)
  288. {
  289. int c, i;
  290. c = getch();
  291. if (c == -1)
  292. idle++;
  293. else
  294. idle = 0;
  295. for (i = 0; i < LEN(bindings); i++)
  296. if (c == bindings[i].sym) {
  297. *run = bindings[i].run;
  298. *env = bindings[i].env;
  299. return bindings[i].act;
  300. }
  301. return 0;
  302. }
  303. char *
  304. readln(void)
  305. {
  306. static char ln[LINE_MAX];
  307. timeout(-1);
  308. echo();
  309. curs_set(TRUE);
  310. memset(ln, 0, sizeof(ln));
  311. wgetnstr(stdscr, ln, sizeof(ln) - 1);
  312. noecho();
  313. curs_set(FALSE);
  314. timeout(1000);
  315. return ln[0] ? ln : NULL;
  316. }
  317. int
  318. canopendir(char *path)
  319. {
  320. DIR *dirp;
  321. dirp = opendir(path);
  322. if (dirp == NULL)
  323. return 0;
  324. closedir(dirp);
  325. return 1;
  326. }
  327. char *
  328. mkpath(char *dir, char *name, char *out, size_t n)
  329. {
  330. /* Handle absolute path */
  331. if (name[0] == '/') {
  332. strlcpy(out, name, n);
  333. } else {
  334. /* Handle root case */
  335. if (strcmp(dir, "/") == 0) {
  336. strlcpy(out, "/", n);
  337. strlcat(out, name, n);
  338. } else {
  339. strlcpy(out, dir, n);
  340. strlcat(out, "/", n);
  341. strlcat(out, name, n);
  342. }
  343. }
  344. return out;
  345. }
  346. void
  347. printent(struct entry *ent, int active)
  348. {
  349. char name[PATH_MAX];
  350. unsigned int maxlen = COLS - strlen(CURSR) - 1;
  351. char cm = 0;
  352. /* Copy name locally */
  353. strlcpy(name, ent->name, sizeof(name));
  354. if (S_ISDIR(ent->mode)) {
  355. cm = '/';
  356. maxlen--;
  357. } else if (S_ISLNK(ent->mode)) {
  358. cm = '@';
  359. maxlen--;
  360. } else if (S_ISSOCK(ent->mode)) {
  361. cm = '=';
  362. maxlen--;
  363. } else if (S_ISFIFO(ent->mode)) {
  364. cm = '|';
  365. maxlen--;
  366. } else if (ent->mode & S_IXUSR) {
  367. cm = '*';
  368. maxlen--;
  369. }
  370. /* No text wrapping in entries */
  371. if (strlen(name) > maxlen)
  372. name[maxlen] = '\0';
  373. if (cm == 0)
  374. printw("%s%s\n", active ? CURSR : EMPTY, name);
  375. else
  376. printw("%s%s%c\n", active ? CURSR : EMPTY, name, cm);
  377. }
  378. int
  379. dentfill(char *path, struct entry **dents,
  380. int (*filter)(regex_t *, char *), regex_t *re)
  381. {
  382. char newpath[PATH_MAX];
  383. DIR *dirp;
  384. struct dirent *dp;
  385. struct stat sb;
  386. int r, n = 0;
  387. dirp = opendir(path);
  388. if (dirp == NULL)
  389. return 0;
  390. while ((dp = readdir(dirp)) != NULL) {
  391. /* Skip self and parent */
  392. if (strcmp(dp->d_name, ".") == 0 ||
  393. strcmp(dp->d_name, "..") == 0)
  394. continue;
  395. if (filter(re, dp->d_name) == 0)
  396. continue;
  397. *dents = xrealloc(*dents, (n + 1) * sizeof(**dents));
  398. strlcpy((*dents)[n].name, dp->d_name, sizeof((*dents)[n].name));
  399. /* Get mode flags */
  400. mkpath(path, dp->d_name, newpath, sizeof(newpath));
  401. r = lstat(newpath, &sb);
  402. if (r == -1)
  403. printerr(1, "lstat");
  404. (*dents)[n].mode = sb.st_mode;
  405. (*dents)[n].t = sb.st_mtime;
  406. n++;
  407. }
  408. /* Should never be null */
  409. r = closedir(dirp);
  410. if (r == -1)
  411. printerr(1, "closedir");
  412. return n;
  413. }
  414. void
  415. dentfree(struct entry *dents)
  416. {
  417. free(dents);
  418. }
  419. /* Return the position of the matching entry or 0 otherwise */
  420. int
  421. dentfind(struct entry *dents, int n, char *cwd, char *path)
  422. {
  423. char tmp[PATH_MAX];
  424. int i;
  425. if (path == NULL)
  426. return 0;
  427. for (i = 0; i < n; i++) {
  428. mkpath(cwd, dents[i].name, tmp, sizeof(tmp));
  429. DPRINTF_S(path);
  430. DPRINTF_S(tmp);
  431. if (strcmp(tmp, path) == 0)
  432. return i;
  433. }
  434. return 0;
  435. }
  436. int
  437. populate(char *path, char *oldpath, char *fltr)
  438. {
  439. regex_t re;
  440. int r;
  441. /* Can fail when permissions change while browsing */
  442. if (canopendir(path) == 0)
  443. return -1;
  444. /* Search filter */
  445. r = setfilter(&re, fltr);
  446. if (r != 0)
  447. return -1;
  448. dentfree(dents);
  449. ndents = 0;
  450. dents = NULL;
  451. ndents = dentfill(path, &dents, visible, &re);
  452. qsort(dents, ndents, sizeof(*dents), entrycmp);
  453. /* Find cur from history */
  454. cur = dentfind(dents, ndents, path, oldpath);
  455. return 0;
  456. }
  457. void
  458. redraw(char *path)
  459. {
  460. char cwd[PATH_MAX], cwdresolved[PATH_MAX];
  461. size_t ncols;
  462. int nlines, odd;
  463. int i;
  464. nlines = MIN(LINES - 4, ndents);
  465. /* Clean screen */
  466. erase();
  467. /* Strip trailing slashes */
  468. for (i = strlen(path) - 1; i > 0; i--)
  469. if (path[i] == '/')
  470. path[i] = '\0';
  471. else
  472. break;
  473. DPRINTF_D(cur);
  474. DPRINTF_S(path);
  475. /* No text wrapping in cwd line */
  476. ncols = COLS;
  477. if (ncols > PATH_MAX)
  478. ncols = PATH_MAX;
  479. strlcpy(cwd, path, ncols);
  480. cwd[ncols - strlen(CWD) - 1] = '\0';
  481. if (!realpath(cwd, cwdresolved)) {
  482. printmsg("Cannot resolve path");
  483. return;
  484. }
  485. printw(CWD "%s\n\n", cwdresolved);
  486. /* Print listing */
  487. odd = ISODD(nlines);
  488. if (cur < nlines / 2) {
  489. for (i = 0; i < nlines; i++)
  490. printent(&dents[i], i == cur);
  491. } else if (cur >= ndents - nlines / 2) {
  492. for (i = ndents - nlines; i < ndents; i++)
  493. printent(&dents[i], i == cur);
  494. } else {
  495. for (i = cur - nlines / 2;
  496. i < cur + nlines / 2 + odd; i++)
  497. printent(&dents[i], i == cur);
  498. }
  499. }
  500. void
  501. browse(char *ipath, char *ifilter)
  502. {
  503. char path[PATH_MAX], oldpath[PATH_MAX], newpath[PATH_MAX];
  504. char fltr[LINE_MAX];
  505. char *bin, *dir, *tmp, *run, *env;
  506. struct stat sb;
  507. regex_t re;
  508. int r, fd;
  509. strlcpy(path, ipath, sizeof(path));
  510. strlcpy(fltr, ifilter, sizeof(fltr));
  511. oldpath[0] = '\0';
  512. begin:
  513. r = populate(path, oldpath, fltr);
  514. if (r == -1) {
  515. printwarn();
  516. goto nochange;
  517. }
  518. for (;;) {
  519. redraw(path);
  520. nochange:
  521. switch (nextsel(&run, &env)) {
  522. case SEL_QUIT:
  523. dentfree(dents);
  524. return;
  525. case SEL_BACK:
  526. /* There is no going back */
  527. if (strcmp(path, "/") == 0 ||
  528. strcmp(path, ".") == 0 ||
  529. strchr(path, '/') == NULL)
  530. goto nochange;
  531. dir = xdirname(path);
  532. if (canopendir(dir) == 0) {
  533. printwarn();
  534. goto nochange;
  535. }
  536. /* Save history */
  537. strlcpy(oldpath, path, sizeof(oldpath));
  538. strlcpy(path, dir, sizeof(path));
  539. /* Reset filter */
  540. strlcpy(fltr, ifilter, sizeof(fltr));
  541. goto begin;
  542. case SEL_GOIN:
  543. /* Cannot descend in empty directories */
  544. if (ndents == 0)
  545. goto nochange;
  546. mkpath(path, dents[cur].name, newpath, sizeof(newpath));
  547. DPRINTF_S(newpath);
  548. /* Get path info */
  549. fd = open(newpath, O_RDONLY | O_NONBLOCK);
  550. if (fd == -1) {
  551. printwarn();
  552. goto nochange;
  553. }
  554. r = fstat(fd, &sb);
  555. if (r == -1) {
  556. printwarn();
  557. close(fd);
  558. goto nochange;
  559. }
  560. close(fd);
  561. DPRINTF_U(sb.st_mode);
  562. switch (sb.st_mode & S_IFMT) {
  563. case S_IFDIR:
  564. if (canopendir(newpath) == 0) {
  565. printwarn();
  566. goto nochange;
  567. }
  568. strlcpy(path, newpath, sizeof(path));
  569. /* Reset filter */
  570. strlcpy(fltr, ifilter, sizeof(fltr));
  571. goto begin;
  572. case S_IFREG:
  573. bin = openwith(newpath);
  574. char *execvim = "vim";
  575. if (bin == NULL) {
  576. FILE *fp;
  577. char cmd[MAX_PATH_LEN];
  578. int status;
  579. snprintf(cmd, MAX_PATH_LEN, "file \"%s\"", newpath);
  580. fp = popen(cmd, "r");
  581. if (fp == NULL)
  582. goto nochange;
  583. if (fgets(cmd, MAX_PATH_LEN, fp) == NULL) {
  584. pclose(fp);
  585. goto nochange;
  586. }
  587. pclose(fp);
  588. if (strstr(cmd, "ASCII text") != NULL)
  589. bin = execvim;
  590. else {
  591. snprintf(cmd, MAX_PATH_LEN, "xdg-open \"%s\" > /dev/null 2>&1", newpath);
  592. status = system(cmd);
  593. continue;
  594. }
  595. }
  596. exitcurses();
  597. spawn(bin, newpath, NULL);
  598. initcurses();
  599. continue;
  600. default:
  601. printmsg("Unsupported file");
  602. goto nochange;
  603. }
  604. case SEL_FLTR:
  605. /* Read filter */
  606. printprompt("filter: ");
  607. tmp = readln();
  608. if (tmp == NULL)
  609. tmp = ifilter;
  610. /* Check and report regex errors */
  611. r = setfilter(&re, tmp);
  612. if (r != 0)
  613. goto nochange;
  614. strlcpy(fltr, tmp, sizeof(fltr));
  615. DPRINTF_S(fltr);
  616. /* Save current */
  617. if (ndents > 0)
  618. mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
  619. goto begin;
  620. case SEL_NEXT:
  621. if (cur < ndents - 1)
  622. cur++;
  623. break;
  624. case SEL_PREV:
  625. if (cur > 0)
  626. cur--;
  627. break;
  628. case SEL_PGDN:
  629. if (cur < ndents - 1)
  630. cur += MIN((LINES - 4) / 2, ndents - 1 - cur);
  631. break;
  632. case SEL_PGUP:
  633. if (cur > 0)
  634. cur -= MIN((LINES - 4) / 2, cur);
  635. break;
  636. case SEL_HOME:
  637. cur = 0;
  638. break;
  639. case SEL_END:
  640. cur = ndents - 1;
  641. break;
  642. case SEL_CD:
  643. /* Read target dir */
  644. printprompt("chdir: ");
  645. tmp = readln();
  646. if (tmp == NULL) {
  647. clearprompt();
  648. goto nochange;
  649. }
  650. mkpath(path, tmp, newpath, sizeof(newpath));
  651. if (canopendir(newpath) == 0) {
  652. printwarn();
  653. goto nochange;
  654. }
  655. strlcpy(path, newpath, sizeof(path));
  656. /* Reset filter */
  657. strlcpy(fltr, ifilter, sizeof(fltr))
  658. DPRINTF_S(path);
  659. goto begin;
  660. case SEL_CDHOME:
  661. tmp = getenv("HOME");
  662. if (tmp == NULL) {
  663. clearprompt();
  664. goto nochange;
  665. }
  666. if (canopendir(tmp) == 0) {
  667. printwarn();
  668. goto nochange;
  669. }
  670. strlcpy(path, tmp, sizeof(path));
  671. /* Reset filter */
  672. strlcpy(fltr, ifilter, sizeof(fltr));
  673. DPRINTF_S(path);
  674. goto begin;
  675. case SEL_TOGGLEDOT:
  676. if (strcmp(fltr, ifilter) != 0)
  677. strlcpy(fltr, ifilter, sizeof(fltr));
  678. else
  679. strlcpy(fltr, ".", sizeof(fltr));
  680. goto begin;
  681. case SEL_MTIME:
  682. mtimeorder = !mtimeorder;
  683. /* Save current */
  684. if (ndents > 0)
  685. mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
  686. goto begin;
  687. case SEL_REDRAW:
  688. /* Save current */
  689. if (ndents > 0)
  690. mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
  691. goto begin;
  692. case SEL_RUN:
  693. run = xgetenv(env, run);
  694. exitcurses();
  695. spawn(run, NULL, path);
  696. initcurses();
  697. break;
  698. case SEL_RUNARG:
  699. run = xgetenv(env, run);
  700. exitcurses();
  701. spawn(run, dents[cur].name, path);
  702. initcurses();
  703. break;
  704. }
  705. /* Screensaver */
  706. if (idletimeout != 0 && idle == idletimeout) {
  707. idle = 0;
  708. exitcurses();
  709. spawn(idlecmd, NULL, NULL);
  710. initcurses();
  711. }
  712. }
  713. }
  714. void
  715. usage(char *argv0)
  716. {
  717. fprintf(stderr, "usage: %s [dir]\n", argv0);
  718. exit(1);
  719. }
  720. int
  721. main(int argc, char *argv[])
  722. {
  723. char cwd[PATH_MAX], *ipath;
  724. char *ifilter;
  725. if (argc > 2)
  726. usage(argv[0]);
  727. /* Confirm we are in a terminal */
  728. if (!isatty(0) || !isatty(1)) {
  729. fprintf(stderr, "stdin or stdout is not a tty\n");
  730. exit(1);
  731. }
  732. if (getuid() == 0)
  733. ifilter = ".";
  734. else
  735. ifilter = "^[^.]"; /* Hide dotfiles */
  736. if (argv[1] != NULL) {
  737. ipath = argv[1];
  738. } else {
  739. ipath = getcwd(cwd, sizeof(cwd));
  740. if (ipath == NULL)
  741. ipath = "/";
  742. }
  743. signal(SIGINT, SIG_IGN);
  744. /* Test initial path */
  745. if (canopendir(ipath) == 0) {
  746. fprintf(stderr, "%s: %s\n", ipath, strerror(errno));
  747. exit(1);
  748. }
  749. /* Set locale before curses setup */
  750. setlocale(LC_ALL, "");
  751. initcurses();
  752. browse(ipath, ifilter);
  753. exitcurses();
  754. exit(0);
  755. }