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