f4c2f067234d821017a5603711c3426bd805bdb6
[ncurses.git] / test / view.c
1 /*
2  * view.c -- a silly little viewer program
3  *
4  * written by Eric S. Raymond <esr@snark.thyrsus.com> December 1994
5  * to test the scrolling code in ncurses.
6  *
7  * modified by Thomas Dickey <dickey@clark.net> July 1995 to demonstrate
8  * the use of 'resizeterm()', and May 2000 to illustrate wide-character
9  * handling.
10  *
11  * Takes a filename argument.  It's a simple file-viewer with various
12  * scroll-up and scroll-down commands.
13  *
14  * n    -- scroll one line forward
15  * p    -- scroll one line back
16  *
17  * Either command accepts a numeric prefix interpreted as a repeat count.
18  * Thus, typing `5n' should scroll forward 5 lines in the file.
19  *
20  * The way you can tell this is working OK is that, in the trace file,
21  * there should be one scroll operation plus a small number of line
22  * updates, as opposed to a whole-page update.  This means the physical
23  * scroll operation worked, and the refresh() code only had to do a
24  * partial repaint.
25  *
26  * $Id: view.c,v 1.29 2000/05/21 01:43:03 tom Exp $
27  */
28
29 #include <test.priv.h>
30
31 #include <string.h>
32 #include <ctype.h>
33 #include <signal.h>
34
35 #if HAVE_TERMIOS_H
36 # include <termios.h>
37 #else
38 # include <sgtty.h>
39 #endif
40
41 #if !defined(sun) || !HAVE_TERMIOS_H
42 # if HAVE_SYS_IOCTL_H
43 #  include <sys/ioctl.h>
44 # endif
45 #endif
46
47 /* This is needed to compile 'struct winsize' */
48 #if NEED_PTEM_H
49 #include <sys/stream.h>
50 #include <sys/ptem.h>
51 #endif
52
53 static RETSIGTYPE finish(int sig) GCC_NORETURN;
54 static void show_all(void);
55
56 #if defined(SIGWINCH) && defined(TIOCGWINSZ) && defined(HAVE_RESIZETERM)
57 #define CAN_RESIZE 1
58 #else
59 #define CAN_RESIZE 0
60 #endif
61
62 #if CAN_RESIZE
63 static RETSIGTYPE adjust(int sig);
64 static int interrupted;
65 #endif
66
67 static int waiting;
68 static int shift;
69 static int utf8_mode = FALSE;
70
71 static char *fname;
72 static chtype **lines;
73 static chtype **lptr;
74
75 static void
76 usage(void)
77 {
78     static const char *msg[] =
79     {
80         "Usage: view [options] file"
81         ,""
82         ,"Options:"
83         ," -n NUM   specify maximum number of lines (default 1000)"
84 #if defined(KEY_RESIZE)
85         ," -r       use experimental KEY_RESIZE rather than our own handler"
86 #endif
87 #ifdef TRACE
88         ," -t       trace screen updates"
89         ," -T NUM   specify trace mask"
90 #endif
91         ," -u       translate UTF-8 data"
92     };
93     size_t n;
94     for (n = 0; n < SIZEOF(msg); n++)
95         fprintf(stderr, "%s\n", msg[n]);
96     exit(EXIT_FAILURE);
97 }
98
99 static int
100 ch_len(chtype * src)
101 {
102     int result = 0;
103     while (*src++)
104         result++;
105     return result;
106 }
107
108 /*
109  * Allocate a string into an array of chtype's.  If UTF-8 mode is
110  * active, translate the string accordingly.
111  */
112 static chtype *
113 ch_dup(char *src)
114 {
115     unsigned len = strlen(src);
116     chtype *dst = typeMalloc(chtype, len + 1);
117     unsigned j, k;
118     unsigned utf_count = 0;
119     unsigned utf_char = 0;
120
121 #define UCS_REPL 0xfffd
122
123     for (j = k = 0; j < len; j++) {
124         if (utf8_mode) {
125             unsigned c = src[j] & 0xff;
126             /* Combine UTF-8 into Unicode */
127             if (c < 0x80) {
128                 /* We received an ASCII character */
129                 if (utf_count > 0)
130                     dst[k++] = UCS_REPL;        /* prev. sequence incomplete */
131                 dst[k++] = c;
132                 utf_count = 0;
133             } else if (c < 0xc0) {
134                 /* We received a continuation byte */
135                 if (utf_count < 1) {
136                     dst[k++] = UCS_REPL;        /* ... unexpectedly */
137                 } else {
138                     if (!utf_char && !((c & 0x7f) >> (7 - utf_count))) {
139                         utf_char = UCS_REPL;
140                     }
141                     /* characters outside UCS-2 become UCS_REPL */
142                     if (utf_char > 0x03ff) {
143                         /* value would be >0xffff */
144                         utf_char = UCS_REPL;
145                     } else {
146                         utf_char <<= 6;
147                         utf_char |= (c & 0x3f);
148                     }
149                     utf_count--;
150                     if (utf_count == 0)
151                         dst[k++] = utf_char;
152                 }
153             } else {
154                 /* We received a sequence start byte */
155                 if (utf_count > 0)
156                     dst[k++] = UCS_REPL;        /* prev. sequence incomplete */
157                 if (c < 0xe0) {
158                     utf_count = 1;
159                     utf_char = (c & 0x1f);
160                     if (!(c & 0x1e))
161                         utf_char = UCS_REPL;    /* overlong sequence */
162                 } else if (c < 0xf0) {
163                     utf_count = 2;
164                     utf_char = (c & 0x0f);
165                 } else if (c < 0xf8) {
166                     utf_count = 3;
167                     utf_char = (c & 0x07);
168                 } else if (c < 0xfc) {
169                     utf_count = 4;
170                     utf_char = (c & 0x03);
171                 } else if (c < 0xfe) {
172                     utf_count = 5;
173                     utf_char = (c & 0x01);
174                 } else {
175                     dst[k++] = UCS_REPL;
176                     utf_count = 0;
177                 }
178             }
179         } else {
180             dst[k++] = src[j];
181         }
182     }
183     dst[k] = 0;
184     return dst;
185 }
186
187 int
188 main(int argc, char *argv[])
189 {
190     int MAXLINES = 1000;
191     FILE *fp;
192     char buf[BUFSIZ];
193     int i;
194     chtype **olptr;
195     int done = FALSE;
196     int length = 0;
197 #if CAN_RESIZE
198     bool use_resize = TRUE;
199 #endif
200
201     while ((i = getopt(argc, argv, "n:rtT:u")) != EOF) {
202         switch (i) {
203         case 'n':
204             if ((MAXLINES = atoi(optarg)) < 1)
205                 usage();
206             break;
207 #if CAN_RESIZE
208         case 'r':
209             use_resize = FALSE;
210             break;
211 #endif
212 #ifdef TRACE
213         case 'T':
214             trace(atoi(optarg));
215             break;
216         case 't':
217             trace(TRACE_CALLS);
218             break;
219 #endif
220         case 'u':
221             utf8_mode = TRUE;
222             break;
223         default:
224             usage();
225         }
226     }
227     if (optind + 1 != argc)
228         usage();
229
230     if ((lines = typeMalloc(chtype *, MAXLINES + 2)) == 0)
231         usage();
232
233     fname = argv[optind];
234     if ((fp = fopen(fname, "r")) == 0) {
235         perror(fname);
236         return EXIT_FAILURE;
237     }
238
239     (void) signal(SIGINT, finish);      /* arrange interrupts to terminate */
240 #if CAN_RESIZE
241     if (use_resize)
242         (void) signal(SIGWINCH, adjust);        /* arrange interrupts to resize */
243 #endif
244
245     /* slurp the file */
246     for (lptr = &lines[0]; (lptr - lines) < MAXLINES; lptr++) {
247         char temp[BUFSIZ], *s, *d;
248         int col;
249
250         if (fgets(buf, sizeof(buf), fp) == 0)
251             break;
252
253         /* convert tabs so that shift will work properly */
254         for (s = buf, d = temp, col = 0; (*d = *s) != '\0'; s++) {
255             if (*d == '\n') {
256                 *d = '\0';
257                 break;
258             } else if (*d == '\t') {
259                 col = (col | 7) + 1;
260                 while ((d - temp) != col)
261                     *d++ = ' ';
262             } else if (isprint(*d) || utf8_mode) {
263                 col++;
264                 d++;
265             } else {
266                 sprintf(d, "\\%03o", *s & 0xff);
267                 d += strlen(d);
268                 col = (d - temp);
269             }
270         }
271         *lptr = ch_dup(temp);
272     }
273     (void) fclose(fp);
274     length = lptr - lines;
275
276     (void) initscr();           /* initialize the curses library */
277     keypad(stdscr, TRUE);       /* enable keyboard mapping */
278     (void) nonl();              /* tell curses not to do NL->CR/NL on output */
279     (void) cbreak();            /* take input chars one at a time, no wait for \n */
280     (void) noecho();            /* don't echo input */
281     idlok(stdscr, TRUE);        /* allow use of insert/delete line */
282
283     lptr = lines;
284     while (!done) {
285         int n, c;
286         bool got_number;
287
288         show_all();
289
290         got_number = FALSE;
291         n = 0;
292         for (;;) {
293 #if CAN_RESIZE
294             if (interrupted)
295                 adjust(0);
296 #endif
297             waiting = TRUE;
298             c = getch();
299             waiting = FALSE;
300             if ((c < 127) && isdigit(c)) {
301                 if (!got_number) {
302                     mvprintw(0, 0, "Count: ");
303                     clrtoeol();
304                 }
305                 addch(c);
306                 n = 10 * n + (c - '0');
307                 got_number = TRUE;
308             } else
309                 break;
310         }
311         if (!got_number && n == 0)
312             n = 1;
313
314         switch (c) {
315         case KEY_DOWN:
316         case 'n':
317             olptr = lptr;
318             for (i = 0; i < n; i++)
319                 if ((lptr - lines) < (length - LINES + 1))
320                     lptr++;
321                 else
322                     break;
323             wscrl(stdscr, lptr - olptr);
324             break;
325
326         case KEY_UP:
327         case 'p':
328             olptr = lptr;
329             for (i = 0; i < n; i++)
330                 if (lptr > lines)
331                     lptr--;
332                 else
333                     break;
334             wscrl(stdscr, lptr - olptr);
335             break;
336
337         case 'h':
338         case KEY_HOME:
339             lptr = lines;
340             break;
341
342         case 'e':
343         case KEY_END:
344             if (length > LINES)
345                 lptr = lines + length - LINES + 1;
346             else
347                 lptr = lines;
348             break;
349
350         case 'r':
351         case KEY_RIGHT:
352             shift++;
353             break;
354
355         case 'l':
356         case KEY_LEFT:
357             if (shift)
358                 shift--;
359             else
360                 beep();
361             break;
362
363         case 'q':
364             done = TRUE;
365             break;
366
367 #ifdef KEY_RESIZE
368         case KEY_RESIZE:        /* ignore this; ncurses will repaint */
369             break;
370 #endif
371 #if CAN_RESIZE
372         case ERR:
373             break;
374 #endif
375         default:
376             beep();
377         }
378     }
379
380     finish(0);                  /* we're done */
381 }
382
383 static RETSIGTYPE
384 finish(int sig)
385 {
386     endwin();
387     exit(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
388 }
389
390 #if CAN_RESIZE
391 /*
392  * This uses functions that are "unsafe", but it seems to work on SunOS and
393  * Linux.  The 'wrefresh(curscr)' is needed to force the refresh to start from
394  * the top of the screen -- some xterms mangle the bitmap while resizing.
395  */
396 static RETSIGTYPE
397 adjust(int sig)
398 {
399     if (waiting || sig == 0) {
400         struct winsize size;
401
402         if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) {
403             resizeterm(size.ws_row, size.ws_col);
404             wrefresh(curscr);   /* Linux needs this */
405             show_all();
406         }
407         interrupted = FALSE;
408     } else {
409         interrupted = TRUE;
410     }
411     (void) signal(SIGWINCH, adjust);    /* some systems need this */
412 }
413 #endif /* CAN_RESIZE */
414
415 static void
416 show_all(void)
417 {
418     int i;
419     char temp[BUFSIZ];
420     chtype *s;
421
422 #if CAN_RESIZE
423     sprintf(temp, "(%3dx%3d) col %d ", LINES, COLS, shift);
424     i = strlen(temp);
425     sprintf(temp + i, "view %.*s", (int) (sizeof(temp) - 7 - i), fname);
426 #else
427     sprintf(temp, "view %.*s", (int) sizeof(temp) - 7, fname);
428 #endif
429     move(0, 0);
430     printw("%.*s", COLS, temp);
431     clrtoeol();
432
433     scrollok(stdscr, FALSE);    /* prevent screen from moving */
434     for (i = 1; i < LINES; i++) {
435         move(i, 0);
436         printw("%3d:", (lptr + i - lines));
437         clrtoeol();
438         if ((s = lptr[i - 1]) != 0) {
439             int len = ch_len(s);
440             if (len > shift)
441                 addchstr(s + shift);
442         }
443     }
444     setscrreg(1, LINES - 1);
445     scrollok(stdscr, TRUE);
446     refresh();
447 }