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