My build of nnn with minor changes
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 
 

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