view.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. /****************************************************************************
  2. * Copyright 2019,2020 Thomas E. Dickey *
  3. * Copyright 1998-2016,2017 Free Software Foundation, Inc. *
  4. * *
  5. * Permission is hereby granted, free of charge, to any person obtaining a *
  6. * copy of this software and associated documentation files (the *
  7. * "Software"), to deal in the Software without restriction, including *
  8. * without limitation the rights to use, copy, modify, merge, publish, *
  9. * distribute, distribute with modifications, sublicense, and/or sell *
  10. * copies of the Software, and to permit persons to whom the Software is *
  11. * furnished to do so, subject to the following conditions: *
  12. * *
  13. * The above copyright notice and this permission notice shall be included *
  14. * in all copies or substantial portions of the Software. *
  15. * *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
  17. * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
  18. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
  19. * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
  20. * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
  21. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
  22. * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
  23. * *
  24. * Except as contained in this notice, the name(s) of the above copyright *
  25. * holders shall not be used in advertising or otherwise to promote the *
  26. * sale, use or other dealings in this Software without prior written *
  27. * authorization. *
  28. ****************************************************************************/
  29. /*
  30. * view.c -- a silly little viewer program
  31. *
  32. * written by Eric S. Raymond <esr@snark.thyrsus.com> December 1994
  33. * to test the scrolling code in ncurses.
  34. *
  35. * modified by Thomas Dickey <dickey@clark.net> July 1995 to demonstrate
  36. * the use of 'resizeterm()', and May 2000 to illustrate wide-character
  37. * handling. This program intentionally does not use pads, to allow testing
  38. * with less-capable implementations of curses.
  39. *
  40. * Takes a filename argument. It's a simple file-viewer with various
  41. * scroll-up and scroll-down commands.
  42. *
  43. * n -- scroll one line forward
  44. * p -- scroll one line back
  45. *
  46. * Either command accepts a numeric prefix interpreted as a repeat count.
  47. * Thus, typing `5n' should scroll forward 5 lines in the file.
  48. *
  49. * The way you can tell this is working OK is that, in the trace file,
  50. * there should be one scroll operation plus a small number of line
  51. * updates, as opposed to a whole-page update. This means the physical
  52. * scroll operation worked, and the refresh() code only had to do a
  53. * partial repaint.
  54. *
  55. * $Id: view.c,v 1.138 2020/02/02 23:34:34 tom Exp $
  56. */
  57. #include <test.priv.h>
  58. #include <widechars.h>
  59. #include <popup_msg.h>
  60. #include <sys/stat.h>
  61. #include <time.h>
  62. static void finish(int sig) GCC_NORETURN;
  63. #define my_pair 1
  64. static int shift = 0;
  65. static bool try_color = FALSE;
  66. static char *fname;
  67. static NCURSES_CH_T **vec_lines;
  68. static NCURSES_CH_T **lptr;
  69. static int num_lines;
  70. #if USE_WIDEC_SUPPORT
  71. static bool n_option = FALSE;
  72. #endif
  73. static void usage(void) GCC_NORETURN;
  74. static void
  75. failed(const char *msg)
  76. {
  77. endwin();
  78. fprintf(stderr, "%s\n", msg);
  79. ExitProgram(EXIT_FAILURE);
  80. }
  81. static int
  82. ch_len(NCURSES_CH_T *src)
  83. {
  84. int result = 0;
  85. #if USE_WIDEC_SUPPORT
  86. int count;
  87. #endif
  88. #if USE_WIDEC_SUPPORT
  89. for (;;) {
  90. TEST_CCHAR(src, count, {
  91. int len = wcwidth(test_wch[0]);
  92. result += (len > 0) ? len : 1;
  93. ++src;
  94. }
  95. , {
  96. break;
  97. })
  98. }
  99. #else
  100. while (*src++)
  101. result++;
  102. #endif
  103. return result;
  104. }
  105. static void
  106. finish(int sig)
  107. {
  108. endwin();
  109. #if NO_LEAKS
  110. if (vec_lines != 0) {
  111. int n;
  112. for (n = 0; n < num_lines; ++n) {
  113. free(vec_lines[n]);
  114. }
  115. free(vec_lines);
  116. }
  117. #endif
  118. ExitProgram(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
  119. }
  120. static void
  121. show_all(const char *tag)
  122. {
  123. int i;
  124. int digits;
  125. char temp[BUFSIZ];
  126. NCURSES_CH_T *s;
  127. time_t this_time;
  128. for (digits = 1, i = num_lines; i > 0; i /= 10) {
  129. ++digits;
  130. }
  131. _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp))
  132. "view %.*s", (int) strlen(tag), tag);
  133. i = (int) strlen(temp);
  134. _nc_SPRINTF(temp + i, _nc_SLIMIT(sizeof(temp) - (size_t) i)
  135. " %.*s", (int) sizeof(temp) - i - 2, fname);
  136. move(0, 0);
  137. printw("%.*s", COLS, temp);
  138. clrtoeol();
  139. this_time = time((time_t *) 0);
  140. _nc_STRNCPY(temp, ctime(&this_time), (size_t) 30);
  141. if ((i = (int) strlen(temp)) != 0) {
  142. temp[--i] = 0;
  143. if (move(0, COLS - i - 2) != ERR)
  144. printw(" %s", temp);
  145. }
  146. scrollok(stdscr, FALSE); /* prevent screen from moving */
  147. for (i = 1; i < LINES; i++) {
  148. int len;
  149. int actual = (int) (lptr + i - vec_lines);
  150. if (actual > num_lines) {
  151. if (i < LINES - 1) {
  152. int y, x;
  153. getyx(stdscr, y, x);
  154. move(i, 0);
  155. clrtobot();
  156. move(y, x);
  157. }
  158. break;
  159. }
  160. move(i, 0);
  161. printw("%*d:", digits, actual);
  162. clrtoeol();
  163. if ((s = lptr[i - 1]) == 0) {
  164. continue;
  165. }
  166. len = ch_len(s);
  167. if (len > shift) {
  168. #if USE_WIDEC_SUPPORT
  169. /*
  170. * An index into an array of cchar_t's is not necessarily the same
  171. * as the column-offset. A pad would do this directly. Here we
  172. * must translate (or compute a table of offsets).
  173. */
  174. {
  175. int j;
  176. int width = 1, count;
  177. for (j = actual = 0; j < shift; ++j) {
  178. TEST_CCHAR(s + j, count, {
  179. width = wcwidth(test_wch[0]);
  180. }
  181. , {
  182. width = 1;
  183. });
  184. actual += width;
  185. if (actual > shift) {
  186. break;
  187. } else if (actual == shift) {
  188. ++j;
  189. break;
  190. }
  191. }
  192. if (actual < len) {
  193. if (actual > shift)
  194. addch('<');
  195. add_wchstr(s + j + (actual > shift));
  196. }
  197. }
  198. #else
  199. addchstr(s + shift);
  200. #endif
  201. }
  202. #if defined(NCURSES_VERSION) || defined(HAVE_WCHGAT)
  203. if (try_color)
  204. wchgat(stdscr, -1, WA_NORMAL, my_pair, NULL);
  205. #endif
  206. }
  207. setscrreg(1, LINES - 1);
  208. scrollok(stdscr, TRUE);
  209. refresh();
  210. }
  211. static void
  212. read_file(const char *filename)
  213. {
  214. FILE *fp;
  215. int pass;
  216. int k;
  217. int width;
  218. size_t j;
  219. size_t len;
  220. struct stat sb;
  221. char *my_blob;
  222. char **my_vec = 0;
  223. WINDOW *my_win;
  224. if (stat(filename, &sb) != 0
  225. || (sb.st_mode & S_IFMT) != S_IFREG) {
  226. failed("input is not a file");
  227. }
  228. if (sb.st_size == 0) {
  229. failed("input is empty");
  230. }
  231. if ((fp = fopen(filename, "r")) == 0) {
  232. failed("cannot open input-file");
  233. }
  234. if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0) {
  235. failed("cannot allocate memory for input-file");
  236. }
  237. len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp);
  238. my_blob[sb.st_size] = '\0';
  239. fclose(fp);
  240. for (pass = 0; pass < 2; ++pass) {
  241. char *base = my_blob;
  242. k = 0;
  243. for (j = 0; j < len; ++j) {
  244. if (my_blob[j] == '\n') {
  245. if (pass) {
  246. my_vec[k] = base;
  247. my_blob[j] = '\0';
  248. }
  249. base = my_blob + j + 1;
  250. ++k;
  251. }
  252. }
  253. num_lines = k;
  254. if (base != (my_blob + j))
  255. ++num_lines;
  256. if (!pass &&
  257. ((my_vec = typeCalloc(char *, (size_t) k + 2)) == 0)) {
  258. failed("cannot allocate line-vector #1");
  259. }
  260. }
  261. #if USE_WIDEC_SUPPORT
  262. if (!memcmp("\357\273\277", my_blob, 3)) {
  263. char *s = my_blob + 3;
  264. char *d = my_blob;
  265. Trace(("trim BOM"));
  266. do {
  267. } while ((*d++ = *s++) != '\0');
  268. }
  269. #endif
  270. width = (int) strlen(my_vec[0]);
  271. for (k = 1; my_vec[k]; ++k) {
  272. int check = (int) (my_vec[k] - my_vec[k - 1]);
  273. if (width < check)
  274. width = check;
  275. }
  276. width = (width + 1) * 5;
  277. my_win = newwin(2, width, 0, 0);
  278. if (my_win == 0) {
  279. failed("cannot allocate temporary window");
  280. }
  281. if ((vec_lines = typeCalloc(NCURSES_CH_T *, (size_t) num_lines + 2)) == 0) {
  282. failed("cannot allocate line-vector #2");
  283. }
  284. /*
  285. * Use the curses library for rendering, including tab-conversion. This
  286. * will not make the resulting array's indices correspond to column for
  287. * lines containing double-width cells because the "in_wch" functions will
  288. * ignore the skipped cells. Use pads for that sort of thing.
  289. */
  290. Trace(("slurp the file"));
  291. for (k = 0; my_vec[k]; ++k) {
  292. char *s;
  293. int y, x;
  294. #if USE_WIDEC_SUPPORT
  295. char *last = my_vec[k] + (int) strlen(my_vec[k]);
  296. wchar_t wch[2];
  297. size_t rc;
  298. #ifndef state_unused
  299. mbstate_t state;
  300. #endif
  301. #endif /* USE_WIDEC_SUPPORT */
  302. werase(my_win);
  303. wmove(my_win, 0, 0);
  304. #if USE_WIDEC_SUPPORT
  305. wch[1] = 0;
  306. reset_mbytes(state);
  307. #endif
  308. for (s = my_vec[k]; *s != '\0'; ++s) {
  309. #if USE_WIDEC_SUPPORT
  310. if (!n_option) {
  311. rc = (size_t) check_mbytes(wch[0], s, (size_t) (last - s), state);
  312. if ((long) rc == -1 || (long) rc == -2) {
  313. break;
  314. }
  315. s += rc - 1;
  316. waddwstr(my_win, wch);
  317. } else
  318. #endif
  319. waddch(my_win, *s & 0xff);
  320. }
  321. getyx(my_win, y, x);
  322. if (y)
  323. x = width - 1;
  324. wmove(my_win, 0, 0);
  325. /* "x + 1" works with standard curses; some implementations are buggy */
  326. if ((vec_lines[k] = typeCalloc(NCURSES_CH_T, x + width + 1)) == 0) {
  327. failed("cannot allocate line-vector #3");
  328. }
  329. #if USE_WIDEC_SUPPORT
  330. win_wchnstr(my_win, vec_lines[k], x);
  331. #else
  332. winchnstr(my_win, vec_lines[k], x);
  333. #endif
  334. }
  335. delwin(my_win);
  336. free(my_vec);
  337. free(my_blob);
  338. }
  339. static void
  340. usage(void)
  341. {
  342. static const char *msg[] =
  343. {
  344. "Usage: view [options] file"
  345. ,""
  346. ,"Options:"
  347. ," -c use color if terminal supports it"
  348. ," -i ignore INT, QUIT, TERM signals"
  349. #if USE_WIDEC_SUPPORT
  350. ," -n use waddch (bytes) rather then wadd_wch (wide-chars)"
  351. #endif
  352. ," -s start in single-step mode, waiting for input"
  353. #ifdef TRACE
  354. ," -t trace screen updates"
  355. ," -T NUM specify trace mask"
  356. #endif
  357. };
  358. size_t n;
  359. for (n = 0; n < SIZEOF(msg); n++)
  360. fprintf(stderr, "%s\n", msg[n]);
  361. ExitProgram(EXIT_FAILURE);
  362. }
  363. int
  364. main(int argc, char *argv[])
  365. {
  366. static const char *help[] =
  367. {
  368. "Commands:",
  369. " q,^Q,ESC - quit this program",
  370. "",
  371. " p,<Up> - scroll the viewport up by one row",
  372. " n,<Down> - scroll the viewport down by one row",
  373. " l,<Left> - scroll the viewport left by one column",
  374. " r,<Right> - scroll the viewport right by one column",
  375. " <,> - scroll the viewport left/right by 8 columns",
  376. "",
  377. " h,<Home> - scroll the viewport to top of file",
  378. " ^F,<PageDn> - scroll to the next page",
  379. " ^B,<PageUp> - scroll to the previous page",
  380. " e,<End> - scroll the viewport to end of file",
  381. "",
  382. " ^L - repaint using redrawwin()",
  383. "",
  384. " 0 through 9 - enter digits for count",
  385. " s - use entered count for halfdelay() parameter",
  386. " - if no entered count, stop nodelay()",
  387. " <space> - begin nodelay()",
  388. 0
  389. };
  390. int i;
  391. int my_delay = 0;
  392. NCURSES_CH_T **olptr;
  393. int value = 0;
  394. bool done = FALSE;
  395. bool got_number = FALSE;
  396. bool ignore_sigs = FALSE;
  397. bool single_step = FALSE;
  398. const char *my_label = "Input";
  399. setlocale(LC_ALL, "");
  400. while ((i = getopt(argc, argv, "cinstT:")) != -1) {
  401. switch (i) {
  402. case 'c':
  403. try_color = TRUE;
  404. break;
  405. case 'i':
  406. ignore_sigs = TRUE;
  407. break;
  408. #if USE_WIDEC_SUPPORT
  409. case 'n':
  410. n_option = TRUE;
  411. break;
  412. #endif
  413. case 's':
  414. single_step = TRUE;
  415. break;
  416. #ifdef TRACE
  417. case 'T':
  418. {
  419. char *next = 0;
  420. int tvalue = (int) strtol(optarg, &next, 0);
  421. if (tvalue < 0 || (next != 0 && *next != 0))
  422. usage();
  423. curses_trace((unsigned) tvalue);
  424. }
  425. break;
  426. case 't':
  427. curses_trace(TRACE_CALLS);
  428. break;
  429. #endif
  430. default:
  431. usage();
  432. }
  433. }
  434. if (optind + 1 != argc)
  435. usage();
  436. InitAndCatch(initscr(), ignore_sigs ? SIG_IGN : finish);
  437. keypad(stdscr, TRUE); /* enable keyboard mapping */
  438. (void) nonl(); /* tell curses not to do NL->CR/NL on output */
  439. (void) cbreak(); /* take input chars one at a time, no wait for \n */
  440. (void) noecho(); /* don't echo input */
  441. if (!single_step)
  442. nodelay(stdscr, TRUE);
  443. idlok(stdscr, TRUE); /* allow use of insert/delete line */
  444. read_file(fname = argv[optind]);
  445. if (try_color) {
  446. if (has_colors()) {
  447. start_color();
  448. init_pair(my_pair, COLOR_WHITE, COLOR_BLUE);
  449. bkgd((chtype) COLOR_PAIR(my_pair));
  450. } else {
  451. try_color = FALSE;
  452. }
  453. }
  454. lptr = vec_lines;
  455. while (!done) {
  456. int n, c;
  457. if (!got_number)
  458. show_all(my_label);
  459. for (;;) {
  460. c = getch();
  461. if ((c < 127) && isdigit(c)) {
  462. if (!got_number) {
  463. MvPrintw(0, 0, "Count: ");
  464. clrtoeol();
  465. }
  466. addch(UChar(c));
  467. value = 10 * value + (c - '0');
  468. got_number = TRUE;
  469. } else
  470. break;
  471. }
  472. if (got_number && value) {
  473. n = value;
  474. } else {
  475. n = 1;
  476. }
  477. if (c != ERR)
  478. my_label = keyname(c);
  479. switch (c) {
  480. case KEY_DOWN:
  481. case 'n':
  482. olptr = lptr;
  483. for (i = 0; i < n; i++)
  484. if ((lptr - vec_lines) < (num_lines - LINES + 1))
  485. lptr++;
  486. else
  487. break;
  488. scrl((int) (lptr - olptr));
  489. break;
  490. case KEY_UP:
  491. case 'p':
  492. olptr = lptr;
  493. for (i = 0; i < n; i++)
  494. if (lptr > vec_lines)
  495. lptr--;
  496. else
  497. break;
  498. scrl((int) (lptr - olptr));
  499. break;
  500. case 'h':
  501. /* FALLTHRU */
  502. case KEY_HOME:
  503. lptr = vec_lines;
  504. break;
  505. case '<':
  506. if ((shift -= 8) < 0)
  507. shift = 0;
  508. break;
  509. case '>':
  510. shift += 8;
  511. break;
  512. case 'e':
  513. /* FALLTHRU */
  514. case KEY_END:
  515. if (num_lines > LINES)
  516. lptr = (vec_lines + num_lines - LINES + 1);
  517. else
  518. lptr = (vec_lines + (num_lines - 2));
  519. break;
  520. case CTRL('F'):
  521. /* FALLTHRU */
  522. case KEY_NPAGE:
  523. for (i = 0; i < n; i++) {
  524. if ((lptr - vec_lines) < (num_lines - 5))
  525. lptr += (LINES - 1);
  526. else
  527. lptr = (vec_lines + num_lines - 2);
  528. }
  529. break;
  530. case CTRL('B'):
  531. /* FALLTHRU */
  532. case KEY_PPAGE:
  533. for (i = 0; i < n; i++) {
  534. if ((lptr - vec_lines) >= LINES)
  535. lptr -= (LINES - 1);
  536. else
  537. lptr = vec_lines;
  538. }
  539. break;
  540. case 'r':
  541. case KEY_RIGHT:
  542. shift += n;
  543. break;
  544. case 'l':
  545. case KEY_LEFT:
  546. shift -= n;
  547. if (shift < 0) {
  548. shift = 0;
  549. beep();
  550. }
  551. break;
  552. case 'q':
  553. case QUIT:
  554. case ESCAPE:
  555. done = TRUE;
  556. break;
  557. #ifdef KEY_RESIZE
  558. case KEY_RESIZE: /* ignore this; ncurses will repaint */
  559. break;
  560. #endif
  561. case 's':
  562. #if HAVE_HALFDELAY
  563. if (got_number) {
  564. halfdelay(my_delay = n);
  565. } else {
  566. nodelay(stdscr, FALSE);
  567. my_delay = -1;
  568. }
  569. #else
  570. nodelay(stdscr, FALSE);
  571. my_delay = -1;
  572. #endif
  573. break;
  574. case ' ':
  575. nodelay(stdscr, TRUE);
  576. my_delay = 0;
  577. break;
  578. case CTRL('L'):
  579. redrawwin(stdscr);
  580. break;
  581. case ERR:
  582. if (!my_delay)
  583. napms(50);
  584. break;
  585. case HELP_KEY_1:
  586. popup_msg(stdscr, help);
  587. break;
  588. default:
  589. beep();
  590. break;
  591. }
  592. if (c >= KEY_MIN || (c > 0 && !isdigit(c))) {
  593. got_number = FALSE;
  594. value = 0;
  595. }
  596. }
  597. finish(0); /* we're done */
  598. }