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