ncurses 6.2 - patch 20210418
[ncurses.git] / test / view.c
1 /****************************************************************************
2  * Copyright 2019-2020,2021 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.140 2021/03/27 22:42:22 tom Exp $
56  */
57
58 #include <test.priv.h>
59 #include <widechars.h>
60 #include <popup_msg.h>
61
62 #include <sys/stat.h>
63 #include <time.h>
64
65 static GCC_NORETURN void finish(int sig);
66
67 #define my_pair 1
68
69 static int shift = 0;
70 static bool try_color = FALSE;
71
72 static char *fname;
73 static NCURSES_CH_T **vec_lines;
74 static NCURSES_CH_T **lptr;
75 static int num_lines;
76
77 #if USE_WIDEC_SUPPORT
78 static bool n_option = FALSE;
79 #endif
80
81 static GCC_NORETURN void usage(void);
82
83 static void
84 failed(const char *msg)
85 {
86     endwin();
87     fprintf(stderr, "%s\n", msg);
88     ExitProgram(EXIT_FAILURE);
89 }
90
91 static int
92 ch_len(NCURSES_CH_T *src)
93 {
94     int result = 0;
95
96 #if USE_WIDEC_SUPPORT
97     for (;;) {
98         int count;
99         TEST_CCHAR(src, count, {
100             int len = wcwidth(test_wch[0]);
101             result += (len > 0) ? len : 1;
102             ++src;
103         }
104         , {
105             break;
106         })
107     }
108 #else
109     while (*src++)
110         result++;
111 #endif
112     return result;
113 }
114
115 static void
116 finish(int sig)
117 {
118     endwin();
119 #if NO_LEAKS
120     if (vec_lines != 0) {
121         int n;
122         for (n = 0; n < num_lines; ++n) {
123             free(vec_lines[n]);
124         }
125         free(vec_lines);
126     }
127 #endif
128     ExitProgram(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
129 }
130
131 static void
132 show_all(const char *tag)
133 {
134     int i;
135     int digits;
136     char temp[BUFSIZ];
137     time_t this_time;
138
139     for (digits = 1, i = num_lines; i > 0; i /= 10) {
140         ++digits;
141     }
142
143     _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp))
144                 "view %.*s", (int) strlen(tag), tag);
145     i = (int) strlen(temp);
146     _nc_SPRINTF(temp + i, _nc_SLIMIT(sizeof(temp) - (size_t) i)
147                 " %.*s", (int) sizeof(temp) - i - 2, fname);
148     move(0, 0);
149     printw("%.*s", COLS, temp);
150     clrtoeol();
151     this_time = time((time_t *) 0);
152     _nc_STRNCPY(temp, ctime(&this_time), (size_t) 30);
153     if ((i = (int) strlen(temp)) != 0) {
154         temp[--i] = 0;
155         if (move(0, COLS - i - 2) != ERR)
156             printw("  %s", temp);
157     }
158
159     scrollok(stdscr, FALSE);    /* prevent screen from moving */
160     for (i = 1; i < LINES; i++) {
161         NCURSES_CH_T *s;
162         int len;
163         int actual = (int) (lptr + i - vec_lines);
164
165         if (actual > num_lines) {
166             if (i < LINES - 1) {
167                 int y, x;
168                 getyx(stdscr, y, x);
169                 move(i, 0);
170                 clrtobot();
171                 move(y, x);
172             }
173             break;
174         }
175         move(i, 0);
176         printw("%*d:", digits, actual);
177         clrtoeol();
178         if ((s = lptr[i - 1]) == 0) {
179             continue;
180         }
181         len = ch_len(s);
182         if (len > shift) {
183 #if USE_WIDEC_SUPPORT
184             /*
185              * An index into an array of cchar_t's is not necessarily the same
186              * as the column-offset.  A pad would do this directly.  Here we
187              * must translate (or compute a table of offsets).
188              */
189             {
190                 int j;
191                 int width = 1;
192
193                 for (j = actual = 0; j < shift; ++j) {
194                     int count;
195
196                     TEST_CCHAR(s + j, count, {
197                         width = wcwidth(test_wch[0]);
198                     }
199                     , {
200                         width = 1;
201                     });
202                     actual += width;
203                     if (actual > shift) {
204                         break;
205                     } else if (actual == shift) {
206                         ++j;
207                         break;
208                     }
209                 }
210                 if (actual < len) {
211                     if (actual > shift)
212                         addch('<');
213                     add_wchstr(s + j + (actual > shift));
214                 }
215             }
216 #else
217             addchstr(s + shift);
218 #endif
219         }
220 #if defined(NCURSES_VERSION) || defined(HAVE_WCHGAT)
221         if (try_color)
222             wchgat(stdscr, -1, WA_NORMAL, my_pair, NULL);
223 #endif
224     }
225     setscrreg(1, LINES - 1);
226     scrollok(stdscr, TRUE);
227     refresh();
228 }
229
230 static void
231 read_file(const char *filename)
232 {
233     FILE *fp;
234     int pass;
235     int k;
236     int width;
237     size_t j;
238     size_t len;
239     struct stat sb;
240     char *my_blob;
241     char **my_vec = 0;
242     WINDOW *my_win;
243
244     if (stat(filename, &sb) != 0
245         || (sb.st_mode & S_IFMT) != S_IFREG) {
246         failed("input is not a file");
247     }
248
249     if (sb.st_size == 0) {
250         failed("input is empty");
251     }
252
253     if ((fp = fopen(filename, "r")) == 0) {
254         failed("cannot open input-file");
255     }
256
257     if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0) {
258         failed("cannot allocate memory for input-file");
259     }
260
261     len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp);
262     my_blob[sb.st_size] = '\0';
263     fclose(fp);
264
265     for (pass = 0; pass < 2; ++pass) {
266         char *base = my_blob;
267         k = 0;
268         for (j = 0; j < len; ++j) {
269             if (my_blob[j] == '\n') {
270                 if (pass) {
271                     my_vec[k] = base;
272                     my_blob[j] = '\0';
273                 }
274                 base = my_blob + j + 1;
275                 ++k;
276             }
277         }
278         num_lines = k;
279         if (base != (my_blob + j))
280             ++num_lines;
281         if (!pass &&
282             ((my_vec = typeCalloc(char *, (size_t) k + 2)) == 0)) {
283             failed("cannot allocate line-vector #1");
284         }
285     }
286
287 #if USE_WIDEC_SUPPORT
288     if (!memcmp("\357\273\277", my_blob, 3)) {
289         char *s = my_blob + 3;
290         char *d = my_blob;
291         Trace(("trim BOM"));
292         do {
293         } while ((*d++ = *s++) != '\0');
294     }
295 #endif
296
297     width = (int) strlen(my_vec[0]);
298     for (k = 1; my_vec[k]; ++k) {
299         int check = (int) (my_vec[k] - my_vec[k - 1]);
300         if (width < check)
301             width = check;
302     }
303     width = (width + 1) * 5;
304     my_win = newwin(2, width, 0, 0);
305     if (my_win == 0) {
306         failed("cannot allocate temporary window");
307     }
308
309     if ((vec_lines = typeCalloc(NCURSES_CH_T *, (size_t) num_lines + 2)) == 0) {
310         failed("cannot allocate line-vector #2");
311     }
312
313     /*
314      * Use the curses library for rendering, including tab-conversion.  This
315      * will not make the resulting array's indices correspond to column for
316      * lines containing double-width cells because the "in_wch" functions will
317      * ignore the skipped cells.  Use pads for that sort of thing.
318      */
319     Trace(("slurp the file"));
320     for (k = 0; my_vec[k]; ++k) {
321         char *s;
322         int y, x;
323 #if USE_WIDEC_SUPPORT
324         char *last = my_vec[k] + (int) strlen(my_vec[k]);
325         wchar_t wch[2];
326         size_t rc;
327 #ifndef state_unused
328         mbstate_t state;
329 #endif
330 #endif /* USE_WIDEC_SUPPORT */
331
332         werase(my_win);
333         wmove(my_win, 0, 0);
334 #if USE_WIDEC_SUPPORT
335         wch[1] = 0;
336         reset_mbytes(state);
337 #endif
338         for (s = my_vec[k]; *s != '\0'; ++s) {
339 #if USE_WIDEC_SUPPORT
340             if (!n_option) {
341                 rc = (size_t) check_mbytes(wch[0], s, (size_t) (last - s), state);
342                 if ((long) rc == -1 || (long) rc == -2) {
343                     break;
344                 }
345                 s += rc - 1;
346                 waddwstr(my_win, wch);
347             } else
348 #endif
349                 waddch(my_win, *s & 0xff);
350         }
351         getyx(my_win, y, x);
352         if (y)
353             x = width - 1;
354         wmove(my_win, 0, 0);
355         /* "x + 1" works with standard curses; some implementations are buggy */
356         if ((vec_lines[k] = typeCalloc(NCURSES_CH_T, x + width + 1)) == 0) {
357             failed("cannot allocate line-vector #3");
358         }
359 #if USE_WIDEC_SUPPORT
360         win_wchnstr(my_win, vec_lines[k], x);
361 #else
362         winchnstr(my_win, vec_lines[k], x);
363 #endif
364     }
365
366     delwin(my_win);
367     free(my_vec);
368     free(my_blob);
369 }
370
371 static void
372 usage(void)
373 {
374     static const char *msg[] =
375     {
376         "Usage: view [options] file"
377         ,""
378         ,"Options:"
379         ," -c       use color if terminal supports it"
380         ," -i       ignore INT, QUIT, TERM signals"
381 #if USE_WIDEC_SUPPORT
382         ," -n       use waddch (bytes) rather then wadd_wch (wide-chars)"
383 #endif
384         ," -s       start in single-step mode, waiting for input"
385 #ifdef TRACE
386         ," -t       trace screen updates"
387         ," -T NUM   specify trace mask"
388 #endif
389     };
390     size_t n;
391     for (n = 0; n < SIZEOF(msg); n++)
392         fprintf(stderr, "%s\n", msg[n]);
393     ExitProgram(EXIT_FAILURE);
394 }
395
396 int
397 main(int argc, char *argv[])
398 {
399     static const char *help[] =
400     {
401         "Commands:",
402         "  q,^Q,ESC       - quit this program",
403         "",
404         "  p,<Up>         - scroll the viewport up by one row",
405         "  n,<Down>       - scroll the viewport down by one row",
406         "  l,<Left>       - scroll the viewport left by one column",
407         "  r,<Right>      - scroll the viewport right by one column",
408         "  <,>            - scroll the viewport left/right by 8 columns",
409         "",
410         "  h,<Home>       - scroll the viewport to top of file",
411         "  ^F,<PageDn>    - scroll to the next page",
412         "  ^B,<PageUp>    - scroll to the previous page",
413         "  e,<End>        - scroll the viewport to end of file",
414         "",
415         "  ^L             - repaint using redrawwin()",
416         "",
417         "  0 through 9    - enter digits for count",
418         "  s              - use entered count for halfdelay() parameter",
419         "                 - if no entered count, stop nodelay()",
420         "  <space>        - begin nodelay()",
421         0
422     };
423
424     int i;
425     int my_delay = 0;
426     NCURSES_CH_T **olptr;
427     int value = 0;
428     bool done = FALSE;
429     bool got_number = FALSE;
430     bool ignore_sigs = FALSE;
431     bool single_step = FALSE;
432     const char *my_label = "Input";
433
434     setlocale(LC_ALL, "");
435
436     while ((i = getopt(argc, argv, "cinstT:")) != -1) {
437         switch (i) {
438         case 'c':
439             try_color = TRUE;
440             break;
441         case 'i':
442             ignore_sigs = TRUE;
443             break;
444 #if USE_WIDEC_SUPPORT
445         case 'n':
446             n_option = TRUE;
447             break;
448 #endif
449         case 's':
450             single_step = TRUE;
451             break;
452 #ifdef TRACE
453         case 'T':
454             {
455                 char *next = 0;
456                 int tvalue = (int) strtol(optarg, &next, 0);
457                 if (tvalue < 0 || (next != 0 && *next != 0))
458                     usage();
459                 curses_trace((unsigned) tvalue);
460             }
461             break;
462         case 't':
463             curses_trace(TRACE_CALLS);
464             break;
465 #endif
466         default:
467             usage();
468         }
469     }
470     if (optind + 1 != argc)
471         usage();
472
473     InitAndCatch(initscr(), ignore_sigs ? SIG_IGN : finish);
474     keypad(stdscr, TRUE);       /* enable keyboard mapping */
475     (void) nonl();              /* tell curses not to do NL->CR/NL on output */
476     (void) cbreak();            /* take input chars one at a time, no wait for \n */
477     (void) noecho();            /* don't echo input */
478     if (!single_step)
479         nodelay(stdscr, TRUE);
480     idlok(stdscr, TRUE);        /* allow use of insert/delete line */
481
482     read_file(fname = argv[optind]);
483
484     if (try_color) {
485         if (has_colors()) {
486             start_color();
487             init_pair(my_pair, COLOR_WHITE, COLOR_BLUE);
488             bkgd((chtype) COLOR_PAIR(my_pair));
489         } else {
490             try_color = FALSE;
491         }
492     }
493
494     lptr = vec_lines;
495     while (!done) {
496         int n, c;
497
498         if (!got_number)
499             show_all(my_label);
500
501         for (;;) {
502             c = getch();
503             if ((c < 127) && isdigit(c)) {
504                 if (!got_number) {
505                     MvPrintw(0, 0, "Count: ");
506                     clrtoeol();
507                 }
508                 addch(UChar(c));
509                 value = 10 * value + (c - '0');
510                 got_number = TRUE;
511             } else
512                 break;
513         }
514         if (got_number && value) {
515             n = value;
516         } else {
517             n = 1;
518         }
519
520         if (c != ERR)
521             my_label = keyname(c);
522         switch (c) {
523         case KEY_DOWN:
524         case 'n':
525             olptr = lptr;
526             for (i = 0; i < n; i++)
527                 if ((lptr - vec_lines) < (num_lines - LINES + 1))
528                     lptr++;
529                 else
530                     break;
531             scrl((int) (lptr - olptr));
532             break;
533
534         case KEY_UP:
535         case 'p':
536             olptr = lptr;
537             for (i = 0; i < n; i++)
538                 if (lptr > vec_lines)
539                     lptr--;
540                 else
541                     break;
542             scrl((int) (lptr - olptr));
543             break;
544
545         case 'h':
546             /* FALLTHRU */
547         case KEY_HOME:
548             lptr = vec_lines;
549             break;
550
551         case '<':
552             if ((shift -= 8) < 0)
553                 shift = 0;
554             break;
555         case '>':
556             shift += 8;
557             break;
558
559         case 'e':
560             /* FALLTHRU */
561         case KEY_END:
562             if (num_lines > LINES)
563                 lptr = (vec_lines + num_lines - LINES + 1);
564             else
565                 lptr = (vec_lines + (num_lines - 2));
566             break;
567
568         case CTRL('F'):
569             /* FALLTHRU */
570         case KEY_NPAGE:
571             for (i = 0; i < n; i++) {
572                 if ((lptr - vec_lines) < (num_lines - 5))
573                     lptr += (LINES - 1);
574                 else
575                     lptr = (vec_lines + num_lines - 2);
576             }
577             break;
578
579         case CTRL('B'):
580             /* FALLTHRU */
581         case KEY_PPAGE:
582             for (i = 0; i < n; i++) {
583                 if ((lptr - vec_lines) >= LINES)
584                     lptr -= (LINES - 1);
585                 else
586                     lptr = vec_lines;
587             }
588             break;
589
590         case 'r':
591         case KEY_RIGHT:
592             shift += n;
593             break;
594
595         case 'l':
596         case KEY_LEFT:
597             shift -= n;
598             if (shift < 0) {
599                 shift = 0;
600                 beep();
601             }
602             break;
603
604         case 'q':
605         case QUIT:
606         case ESCAPE:
607             done = TRUE;
608             break;
609
610 #ifdef KEY_RESIZE
611         case KEY_RESIZE:        /* ignore this; ncurses will repaint */
612             break;
613 #endif
614         case 's':
615 #if HAVE_HALFDELAY
616             if (got_number) {
617                 halfdelay(my_delay = n);
618             } else {
619                 nodelay(stdscr, FALSE);
620                 my_delay = -1;
621             }
622 #else
623             nodelay(stdscr, FALSE);
624             my_delay = -1;
625 #endif
626             break;
627         case ' ':
628             nodelay(stdscr, TRUE);
629             my_delay = 0;
630             break;
631         case CTRL('L'):
632             redrawwin(stdscr);
633             break;
634         case ERR:
635             if (!my_delay)
636                 napms(50);
637             break;
638         case HELP_KEY_1:
639             popup_msg(stdscr, help);
640             break;
641         default:
642             beep();
643             break;
644         }
645         if (c >= KEY_MIN || (c > 0 && !isdigit(c))) {
646             got_number = FALSE;
647             value = 0;
648         }
649     }
650
651     finish(0);                  /* we're done */
652 }