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