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