ncurses 6.0 - patch 20171014
[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.111 2017/10/15 00:56:58 tom Exp $
54  */
55
56 #include <test.priv.h>
57 #include <widechars.h>
58 #include <popup_msg.h>
59
60 #include <sys/stat.h>
61 #include <time.h>
62
63 #undef CTRL                     /* conflict on AIX 5.2 with <sys/ioctl.h> */
64
65 static void finish(int sig) GCC_NORETURN;
66
67 #if HAVE_TERMIOS_H
68 # include <termios.h>
69 #else
70 #if !defined(__MINGW32__)
71 # include <sgtty.h>
72 #endif
73 #endif
74
75 #if !defined(sun) || !HAVE_TERMIOS_H
76 # if HAVE_SYS_IOCTL_H
77 #  include <sys/ioctl.h>
78 # endif
79 #endif
80
81 #define my_pair 1
82
83 /* This is needed to compile 'struct winsize' */
84 #if NEED_PTEM_H
85 #include <sys/stream.h>
86 #include <sys/ptem.h>
87 #endif
88
89 #undef CTRL
90 #define CTRL(x) ((x) & 0x1f)
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 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 failed(const char *msg)
115 {
116     fprintf(stderr, "%s\n", msg);
117     ExitProgram(EXIT_FAILURE);
118 }
119
120 static void
121 usage(void)
122 {
123     static const char *msg[] =
124     {
125         "Usage: view [options] file"
126         ,""
127         ,"Options:"
128         ," -c       use color if terminal supports it"
129         ," -i       ignore INT, QUIT, TERM signals"
130 #if defined(KEY_RESIZE)
131         ," -r       use old-style sigwinch handler rather than KEY_RESIZE"
132 #endif
133         ," -s       start in single-step mode, waiting for input"
134 #ifdef TRACE
135         ," -t       trace screen updates"
136         ," -T NUM   specify trace mask"
137 #endif
138     };
139     size_t n;
140     for (n = 0; n < SIZEOF(msg); n++)
141         fprintf(stderr, "%s\n", msg[n]);
142     ExitProgram(EXIT_FAILURE);
143 }
144
145 static int
146 ch_len(NCURSES_CH_T * src)
147 {
148     int result = 0;
149 #if USE_WIDEC_SUPPORT
150     int count;
151 #endif
152
153 #if USE_WIDEC_SUPPORT
154     for (;;) {
155         TEST_CCHAR(src, count, {
156             ++result;
157             ++src;
158         }
159         , {
160             break;
161         })
162     }
163 #else
164     while (*src++)
165         result++;
166 #endif
167     return result;
168 }
169
170 /*
171  * Allocate a string into an array of chtype's.  If UTF-8 mode is
172  * active, translate the string accordingly.
173  */
174 static NCURSES_CH_T *
175 ch_dup(char *src)
176 {
177     unsigned len = (unsigned) strlen(src);
178     NCURSES_CH_T *dst = typeMalloc(NCURSES_CH_T, len + 1);
179     size_t j, k;
180 #if USE_WIDEC_SUPPORT
181     wchar_t wstr[CCHARW_MAX + 1];
182     wchar_t wch;
183     int l = 0;
184     size_t rc;
185     int width;
186 #ifndef state_unused
187     mbstate_t state;
188 #endif
189 #endif /* USE_WIDEC_SUPPORT */
190
191 #if USE_WIDEC_SUPPORT
192     reset_mbytes(state);
193 #endif
194     for (j = k = 0; j < len; j++) {
195 #if USE_WIDEC_SUPPORT
196         rc = (size_t) check_mbytes(wch, src + j, len - j, state);
197         if (rc == (size_t) -1 || rc == (size_t) -2) {
198             break;
199         }
200         j += rc - 1;
201         width = wcwidth(wch);
202         if (width == 0) {
203             if (l == 0) {
204                 wstr[l++] = L' ';
205             }
206         } else if ((l > 0) || (l == CCHARW_MAX)) {
207             wstr[l] = L'\0';
208             l = 0;
209             if (setcchar(dst + k, wstr, 0, 0, NULL) != OK) {
210                 break;
211             }
212             ++k;
213         }
214         wstr[l++] = wch;
215 #else
216         dst[k++] = (chtype) UChar(src[j]);
217 #endif
218     }
219 #if USE_WIDEC_SUPPORT
220     if (l > 0) {
221         wstr[l] = L'\0';
222         if (setcchar(dst + k, wstr, 0, 0, NULL) == OK)
223             ++k;
224     }
225     wstr[0] = L'\0';
226     setcchar(dst + k, wstr, 0, 0, NULL);
227 #else
228     dst[k] = 0;
229 #endif
230     return dst;
231 }
232
233 static void
234 finish(int sig)
235 {
236     endwin();
237 #if NO_LEAKS
238     if (vec_lines != 0) {
239         int n;
240         for (n = 0; n < num_lines; ++n) {
241             free(vec_lines[n]);
242         }
243         free(vec_lines);
244     }
245 #endif
246     ExitProgram(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
247 }
248
249 static void
250 show_all(const char *tag)
251 {
252     int i;
253     char temp[BUFSIZ];
254     NCURSES_CH_T *s;
255     time_t this_time;
256
257 #if CAN_RESIZE
258     _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp))
259                 "%.20s (%3dx%3d) col %d ", tag, LINES, COLS, shift);
260     i = (int) strlen(temp);
261     if ((i + 7) < (int) sizeof(temp)) {
262         _nc_SPRINTF(temp + i, _nc_SLIMIT(sizeof(temp) - (size_t) i)
263                     "view %.*s",
264                     (int) (sizeof(temp) - 7 - (size_t) i),
265                     fname);
266     }
267 #else
268     (void) tag;
269     _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp))
270                 "view %.*s", (int) sizeof(temp) - 7, fname);
271 #endif
272     move(0, 0);
273     printw("%.*s", COLS, temp);
274     clrtoeol();
275     this_time = time((time_t *) 0);
276     _nc_STRNCPY(temp, ctime(&this_time), (size_t) 30);
277     if ((i = (int) strlen(temp)) != 0) {
278         temp[--i] = 0;
279         if (move(0, COLS - i - 2) != ERR)
280             printw("  %s", temp);
281     }
282
283     scrollok(stdscr, FALSE);    /* prevent screen from moving */
284     for (i = 1; i < LINES; i++) {
285         int len;
286         int actual = (int) (lptr + i - vec_lines);
287         if (actual >= num_lines) {
288             clrtobot();
289             break;
290         }
291         move(i, 0);
292         printw("%3d:", actual);
293         clrtoeol();
294         if ((s = lptr[i - 1]) == 0) {
295             continue;
296         }
297         len = ch_len(s);
298         if (len > shift) {
299 #if USE_WIDEC_SUPPORT
300             add_wchstr(s + shift);
301 #else
302             addchstr(s + shift);
303 #endif
304         }
305 #if defined(NCURSES_VERSION) || defined(HAVE_WCHGAT)
306         if (try_color)
307             wchgat(stdscr, -1, WA_NORMAL, my_pair, NULL);
308 #endif
309     }
310     setscrreg(1, LINES - 1);
311     scrollok(stdscr, TRUE);
312     refresh();
313 }
314
315 #if CAN_RESIZE
316 /*
317  * This uses functions that are "unsafe", but it seems to work on SunOS. 
318  * Usually: the "unsafe" refers to the functions that POSIX lists which may be
319  * called from a signal handler.  Those do not include buffered I/O, which is
320  * used for instance in wrefresh().  To be really portable, you should use the
321  * KEY_RESIZE return (which relies on ncurses' sigwinch handler).
322  *
323  * The 'wrefresh(curscr)' is needed to force the refresh to start from the top
324  * of the screen -- some xterms mangle the bitmap while resizing.
325  */
326 static void
327 adjust(int sig)
328 {
329     if (waiting || sig == 0) {
330         struct winsize size;
331
332         if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) {
333             resize_term(size.ws_row, size.ws_col);
334             wrefresh(curscr);
335             show_all(sig ? "SIGWINCH" : "interrupt");
336         }
337         interrupted = FALSE;
338     } else {
339         interrupted = TRUE;
340     }
341     (void) signal(SIGWINCH, adjust);    /* some systems need this */
342 }
343 #endif /* CAN_RESIZE */
344
345 static void
346 read_file(const char *filename)
347 {
348     FILE *fp;
349     int pass;
350     int k;
351     size_t j;
352     size_t len;
353     struct stat sb;
354     char *my_blob;
355     char **my_vec = 0;
356
357     if (stat(filename, &sb) != 0
358         || (sb.st_mode & S_IFMT) != S_IFREG) {
359         failed("input is not a file");
360     }
361
362     if (sb.st_size == 0) {
363         failed("input is empty");
364     }
365
366     if ((fp = fopen(filename, "r")) == 0) {
367         failed("cannot open input-file");
368     }
369
370     if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0) {
371         failed("cannot allocate memory for input-file");
372     }
373
374     len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp);
375     my_blob[sb.st_size] = '\0';
376     fclose(fp);
377
378     for (pass = 0; pass < 2; ++pass) {
379         char *base = my_blob;
380         k = 0;
381         for (j = 0; j < len; ++j) {
382             if (my_blob[j] == '\n') {
383                 if (pass) {
384                     my_vec[k] = base;
385                     my_blob[j] = '\0';
386                 }
387                 base = my_blob + j + 1;
388                 ++k;
389             }
390         }
391         num_lines = k;
392         if (base != (my_blob + j))
393             ++num_lines;
394         if (!pass &&
395             ((my_vec = typeCalloc(char *, (size_t) k + 2)) == 0)) {
396             failed("cannot allocate line-vector #1");
397         }
398     }
399     if ((vec_lines = typeCalloc(NCURSES_CH_T *, (size_t) num_lines + 2)) == 0)
400         failed("cannot allocate line-vector #2");
401
402     Trace(("slurp the file"));
403     for (k = 0; k < num_lines; ++k) {
404         char *buf = my_vec[k];
405         char temp[BUFSIZ], *s, *d;
406         int col;
407
408         lptr = &vec_lines[k];
409
410 #if USE_WIDEC_SUPPORT
411         if (lptr == vec_lines) {
412             if (!memcmp("", buf, 3)) {
413                 Trace(("trim BOM"));
414                 s = buf + 3;
415                 d = buf;
416                 do {
417                 } while ((*d++ = *s++) != '\0');
418             }
419         }
420 #endif
421
422         /* convert tabs and nonprinting chars so that shift will work properly */
423         for (s = buf, d = temp, col = 0; (*d = *s) != '\0'; s++) {
424             if (*d == '\r') {
425                 if (s[1] == '\n') {
426                     continue;
427                 } else {
428                     break;
429                 }
430             }
431             if (*d == '\n') {
432                 *d = '\0';
433                 break;
434             } else if (*d == '\t') {
435                 col = (col | 7) + 1;
436                 while ((d - temp) != col)
437                     *d++ = ' ';
438             } else
439 #if USE_WIDEC_SUPPORT
440                 col++, d++;
441 #else
442             if (isprint(UChar(*d))) {
443                 col++;
444                 d++;
445             } else {
446                 _nc_SPRINTF(d, _nc_SLIMIT(sizeof(temp) - (d - buf))
447                             "\\%03o", UChar(*s));
448                 d += strlen(d);
449                 col = (int) (d - temp);
450             }
451 #endif
452         }
453         *lptr = ch_dup(temp);
454     }
455
456     free(my_vec);
457     free(my_blob);
458 }
459
460 int
461 main(int argc, char *argv[])
462 {
463     static const char *help[] =
464     {
465         "Commands:",
466         "  q,^Q,ESC       - quit this program",
467         "",
468         "  p,<Up>         - scroll the viewport up by one row",
469         "  n,<Down>       - scroll the viewport down by one row",
470         "  l,<Left>       - scroll the viewport left by one column",
471         "  r,<Right>      - scroll the viewport right by one column",
472         "",
473         "  h,<Home>       - scroll the viewport to top of file",
474         "  ^F,<PageDn>    - scroll to the next page",
475         "  ^B,<PageUp>    - scroll to the previous page",
476         "  e,<End>        - scroll the viewport to end of file",
477         "",
478         "  ^L             - repaint using redrawwin()",
479         "",
480         "  0 through 9    - enter digits for count",
481         "  s              - use entered count for halfdelay() parameter",
482         "                 - if no entered count, stop nodelay()",
483         "  <space>        - begin nodelay()",
484         0
485     };
486
487     int i;
488     int my_delay = 0;
489     NCURSES_CH_T **olptr;
490     int value = 0;
491     bool done = FALSE;
492     bool got_number = FALSE;
493     bool ignore_sigs = FALSE;
494     bool single_step = FALSE;
495 #if CAN_RESIZE
496     bool nonposix_resize = FALSE;
497 #endif
498     const char *my_label = "Input";
499
500     setlocale(LC_ALL, "");
501
502     while ((i = getopt(argc, argv, "cirstT:")) != -1) {
503         switch (i) {
504         case 'c':
505             try_color = TRUE;
506             break;
507         case 'i':
508             ignore_sigs = TRUE;
509             break;
510 #if CAN_RESIZE
511         case 'r':
512             nonposix_resize = TRUE;
513             break;
514 #endif
515         case 's':
516             single_step = TRUE;
517             break;
518 #ifdef TRACE
519         case 'T':
520             {
521                 char *next = 0;
522                 int tvalue = (int) strtol(optarg, &next, 0);
523                 if (tvalue < 0 || (next != 0 && *next != 0))
524                     usage();
525                 trace((unsigned) tvalue);
526             }
527             break;
528         case 't':
529             trace(TRACE_CALLS);
530             break;
531 #endif
532         default:
533             usage();
534         }
535     }
536     if (optind + 1 != argc)
537         usage();
538
539     read_file(fname = argv[optind]);
540
541 #if CAN_RESIZE
542     if (nonposix_resize)
543         (void) signal(SIGWINCH, adjust);        /* arrange interrupts to resize */
544 #endif
545
546     InitAndCatch(initscr(), ignore_sigs ? SIG_IGN : finish);
547     keypad(stdscr, TRUE);       /* enable keyboard mapping */
548     (void) nonl();              /* tell curses not to do NL->CR/NL on output */
549     (void) cbreak();            /* take input chars one at a time, no wait for \n */
550     (void) noecho();            /* don't echo input */
551     if (!single_step)
552         nodelay(stdscr, TRUE);
553     idlok(stdscr, TRUE);        /* allow use of insert/delete line */
554
555     if (try_color) {
556         if (has_colors()) {
557             start_color();
558             init_pair(my_pair, COLOR_WHITE, COLOR_BLUE);
559             bkgd((chtype) COLOR_PAIR(my_pair));
560         } else {
561             try_color = FALSE;
562         }
563     }
564
565     lptr = vec_lines;
566     while (!done) {
567         int n, c;
568
569         if (!got_number)
570             show_all(my_label);
571
572         for (;;) {
573 #if CAN_RESIZE
574             if (interrupted) {
575                 adjust(0);
576                 my_label = "interrupt";
577             }
578             waiting = TRUE;
579             c = getch();
580             waiting = FALSE;
581 #else
582             c = getch();
583 #endif
584             if ((c < 127) && isdigit(c)) {
585                 if (!got_number) {
586                     MvPrintw(0, 0, "Count: ");
587                     clrtoeol();
588                 }
589                 addch(UChar(c));
590                 value = 10 * value + (c - '0');
591                 got_number = TRUE;
592             } else
593                 break;
594         }
595         if (got_number && value) {
596             n = value;
597         } else {
598             n = 1;
599         }
600
601         if (c != ERR)
602             my_label = keyname(c);
603         switch (c) {
604         case KEY_DOWN:
605         case 'n':
606             olptr = lptr;
607             for (i = 0; i < n; i++)
608                 if ((lptr - vec_lines) < (num_lines - LINES + 1))
609                     lptr++;
610                 else
611                     break;
612             scrl((int) (lptr - olptr));
613             break;
614
615         case KEY_UP:
616         case 'p':
617             olptr = lptr;
618             for (i = 0; i < n; i++)
619                 if (lptr > vec_lines)
620                     lptr--;
621                 else
622                     break;
623             scrl((int) (lptr - olptr));
624             break;
625
626         case '<':
627             shift = 0;
628             /* FALLTHRU */
629         case 'h':
630             /* FALLTHRU */
631         case KEY_HOME:
632             lptr = vec_lines;
633             break;
634
635         case '>':
636             shift = 0;
637             /* FALLTHRU */
638         case 'e':
639             /* FALLTHRU */
640         case KEY_END:
641             if (num_lines > LINES)
642                 lptr = (vec_lines + num_lines - LINES + 1);
643             else
644                 lptr = (vec_lines + (num_lines - 2));
645             break;
646
647         case CTRL('F'):
648             /* FALLTHRU */
649         case KEY_NPAGE:
650             if ((lptr - vec_lines) < (num_lines - 5))
651                 lptr += (LINES - 1);
652             else
653                 lptr = (vec_lines + num_lines - 2);
654             break;
655
656         case CTRL('B'):
657             /* FALLTHRU */
658         case KEY_PPAGE:
659             if ((lptr - vec_lines) >= LINES)
660                 lptr -= (LINES - 1);
661             else
662                 lptr = vec_lines;
663             break;
664
665         case 'r':
666         case KEY_RIGHT:
667             shift += n;
668             break;
669
670         case 'l':
671         case KEY_LEFT:
672             shift -= n;
673             if (shift < 0) {
674                 shift = 0;
675                 beep();
676             }
677             break;
678
679         case 'q':
680         case QUIT:
681         case ESCAPE:
682             done = TRUE;
683             break;
684
685 #ifdef KEY_RESIZE
686         case KEY_RESIZE:        /* ignore this; ncurses will repaint */
687             break;
688 #endif
689         case 's':
690 #if HAVE_HALFDELAY
691             if (got_number) {
692                 halfdelay(my_delay = n);
693             } else {
694                 nodelay(stdscr, FALSE);
695                 my_delay = -1;
696             }
697 #else
698             nodelay(stdscr, FALSE);
699             my_delay = -1;
700 #endif
701             break;
702         case ' ':
703             nodelay(stdscr, TRUE);
704             my_delay = 0;
705             break;
706         case CTRL('L'):
707             redrawwin(stdscr);
708             break;
709         case ERR:
710             if (!my_delay)
711                 napms(50);
712             break;
713         case HELP_KEY_1:
714             popup_msg(stdscr, help);
715             break;
716         default:
717             beep();
718             break;
719         }
720         if (c >= KEY_MIN || (c > 0 && !isdigit(c))) {
721             got_number = FALSE;
722             value = 0;
723         }
724     }
725
726     finish(0);                  /* we're done */
727 }