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