ncurses 6.0 - patch 20170107
[ncurses.git] / test / view.c
1 /****************************************************************************
2  * Copyright (c) 1998-2015,2016 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.97 2016/09/10 21:05:46 tom Exp $
54  */
55
56 #include <test.priv.h>
57 #include <widechars.h>
58
59 #include <time.h>
60
61 #undef CTRL                     /* conflict on AIX 5.2 with <sys/ioctl.h> */
62
63 #if HAVE_TERMIOS_H
64 # include <termios.h>
65 #else
66 #if !defined(__MINGW32__)
67 # include <sgtty.h>
68 #endif
69 #endif
70
71 #if !defined(sun) || !HAVE_TERMIOS_H
72 # if HAVE_SYS_IOCTL_H
73 #  include <sys/ioctl.h>
74 # endif
75 #endif
76
77 #define my_pair 1
78
79 /* This is needed to compile 'struct winsize' */
80 #if NEED_PTEM_H
81 #include <sys/stream.h>
82 #include <sys/ptem.h>
83 #endif
84
85 #undef CTRL
86 #define CTRL(x) ((x) & 0x1f)
87
88 static void finish(int sig) GCC_NORETURN;
89 static void show_all(const char *tag);
90
91 #if defined(SIGWINCH) && defined(TIOCGWINSZ) && HAVE_RESIZE_TERM
92 #define CAN_RESIZE 1
93 #else
94 #define CAN_RESIZE 0
95 #endif
96
97 #if CAN_RESIZE
98 static void adjust(int sig);
99 static int interrupted;
100 static bool waiting = FALSE;
101 #endif
102
103 static int shift = 0;
104 static bool try_color = FALSE;
105
106 static char *fname;
107 static NCURSES_CH_T **vec_lines;
108 static NCURSES_CH_T **lptr;
109 static int num_lines;
110
111 static void usage(void) GCC_NORETURN;
112
113 static void
114 usage(void)
115 {
116     static const char *msg[] =
117     {
118         "Usage: view [options] file"
119         ,""
120         ,"Options:"
121         ," -c       use color if terminal supports it"
122         ," -i       ignore INT, QUIT, TERM signals"
123         ," -n NUM   specify maximum number of lines (default 1000)"
124 #if defined(KEY_RESIZE)
125         ," -r       use old-style sigwinch handler rather than KEY_RESIZE"
126 #endif
127         ," -s       start in single-step mode, waiting for input"
128 #ifdef TRACE
129         ," -t       trace screen updates"
130         ," -T NUM   specify trace mask"
131 #endif
132     };
133     size_t n;
134     for (n = 0; n < SIZEOF(msg); n++)
135         fprintf(stderr, "%s\n", msg[n]);
136     ExitProgram(EXIT_FAILURE);
137 }
138
139 static int
140 ch_len(NCURSES_CH_T * src)
141 {
142     int result = 0;
143 #if USE_WIDEC_SUPPORT
144     int count;
145 #endif
146
147 #if USE_WIDEC_SUPPORT
148     for (;;) {
149         TEST_CCHAR(src, count, {
150             ++result;
151             ++src;
152         }
153         , {
154             break;
155         })
156     }
157 #else
158     while (*src++)
159         result++;
160 #endif
161     return result;
162 }
163
164 /*
165  * Allocate a string into an array of chtype's.  If UTF-8 mode is
166  * active, translate the string accordingly.
167  */
168 static NCURSES_CH_T *
169 ch_dup(char *src)
170 {
171     unsigned len = (unsigned) strlen(src);
172     NCURSES_CH_T *dst = typeMalloc(NCURSES_CH_T, len + 1);
173     size_t j, k;
174 #if USE_WIDEC_SUPPORT
175     wchar_t wstr[CCHARW_MAX + 1];
176     wchar_t wch;
177     int l = 0;
178     size_t rc;
179     int width;
180 #ifndef state_unused
181     mbstate_t state;
182 #endif
183 #endif /* USE_WIDEC_SUPPORT */
184
185 #if USE_WIDEC_SUPPORT
186     reset_mbytes(state);
187 #endif
188     for (j = k = 0; j < len; j++) {
189 #if USE_WIDEC_SUPPORT
190         rc = (size_t) check_mbytes(wch, src + j, len - j, state);
191         if (rc == (size_t) -1 || rc == (size_t) -2) {
192             break;
193         }
194         j += rc - 1;
195         width = wcwidth(wch);
196         if (width == 0) {
197             if (l == 0) {
198                 wstr[l++] = L' ';
199             }
200         } else if ((l > 0) || (l == CCHARW_MAX)) {
201             wstr[l] = L'\0';
202             l = 0;
203             if (setcchar(dst + k, wstr, 0, 0, NULL) != OK) {
204                 break;
205             }
206             ++k;
207         }
208         wstr[l++] = wch;
209 #else
210         dst[k++] = (chtype) UChar(src[j]);
211 #endif
212     }
213 #if USE_WIDEC_SUPPORT
214     if (l > 0) {
215         wstr[l] = L'\0';
216         if (setcchar(dst + k, wstr, 0, 0, NULL) == OK)
217             ++k;
218     }
219     wstr[0] = L'\0';
220     setcchar(dst + k, wstr, 0, 0, NULL);
221 #else
222     dst[k] = 0;
223 #endif
224     return dst;
225 }
226
227 int
228 main(int argc, char *argv[])
229 {
230     int MAXLINES = 1000;
231     FILE *fp;
232     char buf[BUFSIZ];
233     int i;
234     int my_delay = 0;
235     NCURSES_CH_T **olptr;
236     int value = 0;
237     bool done = FALSE;
238     bool got_number = FALSE;
239     bool single_step = FALSE;
240 #if CAN_RESIZE
241     bool nonposix_resize = FALSE;
242 #endif
243     const char *my_label = "Input";
244
245     setlocale(LC_ALL, "");
246
247 #ifndef NCURSES_VERSION
248     /*
249      * We know ncurses will catch SIGINT if we don't establish our own handler.
250      * Other versions of curses may/may not catch it.
251      */
252     (void) signal(SIGINT, finish);      /* arrange interrupts to terminate */
253 #endif
254
255     while ((i = getopt(argc, argv, "cin:rstT:")) != -1) {
256         switch (i) {
257         case 'c':
258             try_color = TRUE;
259             break;
260         case 'i':
261             CATCHALL(SIG_IGN);
262             break;
263         case 'n':
264             if ((MAXLINES = atoi(optarg)) < 1 ||
265                 (MAXLINES + 2) <= 1)
266                 usage();
267             break;
268 #if CAN_RESIZE
269         case 'r':
270             nonposix_resize = TRUE;
271             break;
272 #endif
273         case 's':
274             single_step = TRUE;
275             break;
276 #ifdef TRACE
277         case 'T':
278             {
279                 char *next = 0;
280                 int tvalue = (int) strtol(optarg, &next, 0);
281                 if (tvalue < 0 || (next != 0 && *next != 0))
282                     usage();
283                 trace((unsigned) tvalue);
284             }
285             break;
286         case 't':
287             trace(TRACE_CALLS);
288             break;
289 #endif
290         default:
291             usage();
292         }
293     }
294     if (optind + 1 != argc)
295         usage();
296
297     if ((vec_lines = typeCalloc(NCURSES_CH_T *, (size_t) MAXLINES + 2)) == 0)
298         usage();
299
300     assert(vec_lines != 0);
301
302     fname = argv[optind];
303     if ((fp = fopen(fname, "r")) == 0) {
304         perror(fname);
305         ExitProgram(EXIT_FAILURE);
306     }
307 #if CAN_RESIZE
308     if (nonposix_resize)
309         (void) signal(SIGWINCH, adjust);        /* arrange interrupts to resize */
310 #endif
311
312     Trace(("slurp the file"));
313     for (lptr = &vec_lines[0]; (lptr - vec_lines) < MAXLINES; lptr++) {
314         char temp[BUFSIZ], *s, *d;
315         int col;
316
317         if (fgets(buf, sizeof(buf), fp) == 0)
318             break;
319
320 #if USE_WIDEC_SUPPORT
321         if (lptr == vec_lines) {
322             if (!memcmp("", buf, 3)) {
323                 Trace(("trim BOM"));
324                 s = buf + 3;
325                 d = buf;
326                 do {
327                 } while ((*d++ = *s++) != '\0');
328             }
329         }
330 #endif
331
332         /* convert tabs and nonprinting chars so that shift will work properly */
333         for (s = buf, d = temp, col = 0; (*d = *s) != '\0'; s++) {
334             if (*d == '\r') {
335                 if (s[1] == '\n') {
336                     continue;
337                 } else {
338                     break;
339                 }
340             }
341             if (*d == '\n') {
342                 *d = '\0';
343                 break;
344             } else if (*d == '\t') {
345                 col = (col | 7) + 1;
346                 while ((d - temp) != col)
347                     *d++ = ' ';
348             } else
349 #if USE_WIDEC_SUPPORT
350                 col++, d++;
351 #else
352             if (isprint(UChar(*d))) {
353                 col++;
354                 d++;
355             } else {
356                 _nc_SPRINTF(d, _nc_SLIMIT(sizeof(temp) - (d - buf))
357                             "\\%03o", UChar(*s));
358                 d += strlen(d);
359                 col = (int) (d - temp);
360             }
361 #endif
362         }
363         *lptr = ch_dup(temp);
364     }
365     (void) fclose(fp);
366     num_lines = (int) (lptr - vec_lines);
367
368     (void) initscr();           /* initialize the curses library */
369     keypad(stdscr, TRUE);       /* enable keyboard mapping */
370     (void) nonl();              /* tell curses not to do NL->CR/NL on output */
371     (void) cbreak();            /* take input chars one at a time, no wait for \n */
372     (void) noecho();            /* don't echo input */
373     if (!single_step)
374         nodelay(stdscr, TRUE);
375     idlok(stdscr, TRUE);        /* allow use of insert/delete line */
376
377     if (try_color) {
378         if (has_colors()) {
379             start_color();
380             init_pair(my_pair, COLOR_WHITE, COLOR_BLUE);
381             bkgd((chtype) COLOR_PAIR(my_pair));
382         } else {
383             try_color = FALSE;
384         }
385     }
386
387     lptr = vec_lines;
388     while (!done) {
389         int n, c;
390
391         if (!got_number)
392             show_all(my_label);
393
394         for (;;) {
395 #if CAN_RESIZE
396             if (interrupted) {
397                 adjust(0);
398                 my_label = "interrupt";
399             }
400             waiting = TRUE;
401             c = getch();
402             waiting = FALSE;
403 #else
404             c = getch();
405 #endif
406             if ((c < 127) && isdigit(c)) {
407                 if (!got_number) {
408                     MvPrintw(0, 0, "Count: ");
409                     clrtoeol();
410                 }
411                 addch(UChar(c));
412                 value = 10 * value + (c - '0');
413                 got_number = TRUE;
414             } else
415                 break;
416         }
417         if (got_number && value) {
418             n = value;
419         } else {
420             n = 1;
421         }
422
423         if (c != ERR)
424             my_label = keyname(c);
425         switch (c) {
426         case KEY_DOWN:
427         case 'n':
428             olptr = lptr;
429             for (i = 0; i < n; i++)
430                 if ((lptr - vec_lines) < (num_lines - LINES + 1))
431                     lptr++;
432                 else
433                     break;
434             scrl((int) (lptr - olptr));
435             break;
436
437         case KEY_UP:
438         case 'p':
439             olptr = lptr;
440             for (i = 0; i < n; i++)
441                 if (lptr > vec_lines)
442                     lptr--;
443                 else
444                     break;
445             scrl((int) (lptr - olptr));
446             break;
447
448         case 'h':
449         case KEY_HOME:
450             lptr = vec_lines;
451             break;
452
453         case 'e':
454         case KEY_END:
455             if (num_lines > LINES)
456                 lptr = vec_lines + num_lines - LINES + 1;
457             else
458                 lptr = vec_lines;
459             break;
460
461         case 'r':
462         case KEY_RIGHT:
463             shift += n;
464             break;
465
466         case 'l':
467         case KEY_LEFT:
468             shift -= n;
469             if (shift < 0) {
470                 shift = 0;
471                 beep();
472             }
473             break;
474
475         case 'q':
476             done = TRUE;
477             break;
478
479 #ifdef KEY_RESIZE
480         case KEY_RESIZE:        /* ignore this; ncurses will repaint */
481             break;
482 #endif
483         case 's':
484             if (got_number) {
485                 halfdelay(my_delay = n);
486             } else {
487                 nodelay(stdscr, FALSE);
488                 my_delay = -1;
489             }
490             break;
491         case ' ':
492             nodelay(stdscr, TRUE);
493             my_delay = 0;
494             break;
495         case CTRL('L'):
496             redrawwin(stdscr);
497             break;
498         case ERR:
499             if (!my_delay)
500                 napms(50);
501             break;
502         default:
503             beep();
504             break;
505         }
506         if (c >= KEY_MIN || (c > 0 && !isdigit(c))) {
507             got_number = FALSE;
508             value = 0;
509         }
510     }
511
512     finish(0);                  /* we're done */
513 }
514
515 static void
516 finish(int sig)
517 {
518     endwin();
519 #if NO_LEAKS
520     if (vec_lines != 0) {
521         int n;
522         for (n = 0; n < num_lines; ++n) {
523             free(vec_lines[n]);
524         }
525         free(vec_lines);
526     }
527 #endif
528     ExitProgram(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
529 }
530
531 #if CAN_RESIZE
532 /*
533  * This uses functions that are "unsafe", but it seems to work on SunOS. 
534  * Usually: the "unsafe" refers to the functions that POSIX lists which may be
535  * called from a signal handler.  Those do not include buffered I/O, which is
536  * used for instance in wrefresh().  To be really portable, you should use the
537  * KEY_RESIZE return (which relies on ncurses' sigwinch handler).
538  *
539  * The 'wrefresh(curscr)' is needed to force the refresh to start from the top
540  * of the screen -- some xterms mangle the bitmap while resizing.
541  */
542 static void
543 adjust(int sig)
544 {
545     if (waiting || sig == 0) {
546         struct winsize size;
547
548         if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) {
549             resize_term(size.ws_row, size.ws_col);
550             wrefresh(curscr);
551             show_all(sig ? "SIGWINCH" : "interrupt");
552         }
553         interrupted = FALSE;
554     } else {
555         interrupted = TRUE;
556     }
557     (void) signal(SIGWINCH, adjust);    /* some systems need this */
558 }
559 #endif /* CAN_RESIZE */
560
561 static void
562 show_all(const char *tag)
563 {
564     int i;
565     char temp[BUFSIZ];
566     NCURSES_CH_T *s;
567     time_t this_time;
568
569 #if CAN_RESIZE
570     _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp))
571                 "%.20s (%3dx%3d) col %d ", tag, LINES, COLS, shift);
572     i = (int) strlen(temp);
573     if ((i + 7) < (int) sizeof(temp)) {
574         _nc_SPRINTF(temp + i, _nc_SLIMIT(sizeof(temp) - i)
575                     "view %.*s",
576                     (int) (sizeof(temp) - 7 - (size_t) i),
577                     fname);
578     }
579 #else
580     (void) tag;
581     _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp))
582                 "view %.*s", (int) sizeof(temp) - 7, fname);
583 #endif
584     move(0, 0);
585     printw("%.*s", COLS, temp);
586     clrtoeol();
587     this_time = time((time_t *) 0);
588     _nc_STRNCPY(temp, ctime(&this_time), (size_t) 30);
589     if ((i = (int) strlen(temp)) != 0) {
590         temp[--i] = 0;
591         if (move(0, COLS - i - 2) != ERR)
592             printw("  %s", temp);
593     }
594
595     scrollok(stdscr, FALSE);    /* prevent screen from moving */
596     for (i = 1; i < LINES; i++) {
597         move(i, 0);
598         printw("%3ld:", (long) (lptr + i - vec_lines));
599         clrtoeol();
600         if ((s = lptr[i - 1]) != 0) {
601             int len = ch_len(s);
602             if (len > shift) {
603 #if USE_WIDEC_SUPPORT
604                 add_wchstr(s + shift);
605 #else
606                 addchstr(s + shift);
607 #endif
608             }
609 #if defined(NCURSES_VERSION) || defined(HAVE_WCHGAT)
610             if (try_color)
611                 wchgat(stdscr, -1, A_NORMAL, my_pair, NULL);
612 #endif
613         }
614     }
615     setscrreg(1, LINES - 1);
616     scrollok(stdscr, TRUE);
617     refresh();
618 }