X-Git-Url: http://ncurses.scripts.mit.edu/?p=ncurses.git;a=blobdiff_plain;f=test%2Fview.c;h=bf4377f48e12c505e4f24082a49b236c68e38245;hp=f4c2f067234d821017a5603711c3426bd805bdb6;hb=b5be26931d6488adcb1ff8bc07df9de378ce0d27;hpb=b1f61d9f3aa244512045a6b02e759825d7049d34 diff --git a/test/view.c b/test/view.c index f4c2f067..bf4377f4 100644 --- a/test/view.c +++ b/test/view.c @@ -1,3 +1,30 @@ +/**************************************************************************** + * Copyright (c) 1998-2016,2017 Free Software Foundation, Inc. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a * + * copy of this software and associated documentation files (the * + * "Software"), to deal in the Software without restriction, including * + * without limitation the rights to use, copy, modify, merge, publish, * + * distribute, distribute with modifications, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included * + * in all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * + * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * + * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + * * + * Except as contained in this notice, the name(s) of the above copyright * + * holders shall not be used in advertising or otherwise to promote the * + * sale, use or other dealings in this Software without prior written * + * authorization. * + ****************************************************************************/ /* * view.c -- a silly little viewer program * @@ -6,7 +33,8 @@ * * modified by Thomas Dickey July 1995 to demonstrate * the use of 'resizeterm()', and May 2000 to illustrate wide-character - * handling. + * handling. This program intentionally does not use pads, to allow testing + * with less-capable implementations of curses. * * Takes a filename argument. It's a simple file-viewer with various * scroll-up and scroll-down commands. @@ -23,54 +51,316 @@ * scroll operation worked, and the refresh() code only had to do a * partial repaint. * - * $Id: view.c,v 1.29 2000/05/21 01:43:03 tom Exp $ + * $Id: view.c,v 1.135 2017/10/23 09:18:01 tom Exp $ */ #include +#include +#include -#include -#include -#include +#include +#include -#if HAVE_TERMIOS_H -# include -#else -# include +static void finish(int sig) GCC_NORETURN; + +#define my_pair 1 + +static int shift = 0; +static bool try_color = FALSE; + +static char *fname; +static NCURSES_CH_T **vec_lines; +static NCURSES_CH_T **lptr; +static int num_lines; + +#if USE_WIDEC_SUPPORT +static bool n_option = FALSE; #endif -#if !defined(sun) || !HAVE_TERMIOS_H -# if HAVE_SYS_IOCTL_H -# include -# endif +static void usage(void) GCC_NORETURN; + +static void +failed(const char *msg) +{ + endwin(); + fprintf(stderr, "%s\n", msg); + ExitProgram(EXIT_FAILURE); +} + +static int +ch_len(NCURSES_CH_T * src) +{ + int result = 0; +#if USE_WIDEC_SUPPORT + int count; #endif -/* This is needed to compile 'struct winsize' */ -#if NEED_PTEM_H -#include -#include +#if USE_WIDEC_SUPPORT + for (;;) { + TEST_CCHAR(src, count, { + int len = wcwidth(test_wch[0]); + result += (len > 0) ? len : 1; + ++src; + } + , { + break; + }) + } +#else + while (*src++) + result++; #endif + return result; +} -static RETSIGTYPE finish(int sig) GCC_NORETURN; -static void show_all(void); +static void +finish(int sig) +{ + endwin(); +#if NO_LEAKS + if (vec_lines != 0) { + int n; + for (n = 0; n < num_lines; ++n) { + free(vec_lines[n]); + } + free(vec_lines); + } +#endif + ExitProgram(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS); +} + +static void +show_all(const char *tag) +{ + int i; + int digits; + char temp[BUFSIZ]; + NCURSES_CH_T *s; + time_t this_time; + + for (digits = 1, i = num_lines; i > 0; i /= 10) { + ++digits; + } + + _nc_SPRINTF(temp, _nc_SLIMIT(sizeof(temp)) + "view %.*s", (int) strlen(tag), tag); + i = (int) strlen(temp); + _nc_SPRINTF(temp + i, _nc_SLIMIT(sizeof(temp) - i) + " %.*s", (int) sizeof(temp) - i - 2, fname); + move(0, 0); + printw("%.*s", COLS, temp); + clrtoeol(); + this_time = time((time_t *) 0); + _nc_STRNCPY(temp, ctime(&this_time), (size_t) 30); + if ((i = (int) strlen(temp)) != 0) { + temp[--i] = 0; + if (move(0, COLS - i - 2) != ERR) + printw(" %s", temp); + } -#if defined(SIGWINCH) && defined(TIOCGWINSZ) && defined(HAVE_RESIZETERM) -#define CAN_RESIZE 1 + scrollok(stdscr, FALSE); /* prevent screen from moving */ + for (i = 1; i < LINES; i++) { + int len; + int actual = (int) (lptr + i - vec_lines); + if (actual > num_lines) { + if (i < LINES - 1) { + int y, x; + getyx(stdscr, y, x); + move(i, 0); + clrtobot(); + move(y, x); + } + break; + } + move(i, 0); + printw("%*d:", digits, actual); + clrtoeol(); + if ((s = lptr[i - 1]) == 0) { + continue; + } + len = ch_len(s); + if (len > shift) { +#if USE_WIDEC_SUPPORT + /* + * An index into an array of cchar_t's is not necessarily the same + * as the column-offset. A pad would do this directly. Here we + * must translate (or compute a table of offsets). + */ + { + int j; + int width = 1, count; + for (j = actual = 0; j < shift; ++j) { + TEST_CCHAR(s + j, count, { + width = wcwidth(test_wch[0]); + } + , { + width = 1; + }); + actual += width; + if (actual > shift) { + break; + } else if (actual == shift) { + ++j; + break; + } + } + if (actual < len) { + if (actual > shift) + addch('<'); + add_wchstr(s + j + (actual > shift)); + } + } #else -#define CAN_RESIZE 0 + addchstr(s + shift); #endif + } +#if defined(NCURSES_VERSION) || defined(HAVE_WCHGAT) + if (try_color) + wchgat(stdscr, -1, WA_NORMAL, my_pair, NULL); +#endif + } + setscrreg(1, LINES - 1); + scrollok(stdscr, TRUE); + refresh(); +} + +static void +read_file(const char *filename) +{ + FILE *fp; + int pass; + int k; + int width; + size_t j; + size_t len; + struct stat sb; + char *my_blob; + char **my_vec = 0; + WINDOW *my_win; + + if (stat(filename, &sb) != 0 + || (sb.st_mode & S_IFMT) != S_IFREG) { + failed("input is not a file"); + } + + if (sb.st_size == 0) { + failed("input is empty"); + } + + if ((fp = fopen(filename, "r")) == 0) { + failed("cannot open input-file"); + } + + if ((my_blob = malloc((size_t) sb.st_size + 1)) == 0) { + failed("cannot allocate memory for input-file"); + } + + len = fread(my_blob, sizeof(char), (size_t) sb.st_size, fp); + my_blob[sb.st_size] = '\0'; + fclose(fp); + + for (pass = 0; pass < 2; ++pass) { + char *base = my_blob; + k = 0; + for (j = 0; j < len; ++j) { + if (my_blob[j] == '\n') { + if (pass) { + my_vec[k] = base; + my_blob[j] = '\0'; + } + base = my_blob + j + 1; + ++k; + } + } + num_lines = k; + if (base != (my_blob + j)) + ++num_lines; + if (!pass && + ((my_vec = typeCalloc(char *, (size_t) k + 2)) == 0)) { + failed("cannot allocate line-vector #1"); + } + } -#if CAN_RESIZE -static RETSIGTYPE adjust(int sig); -static int interrupted; +#if USE_WIDEC_SUPPORT + if (!memcmp("\357\273\277", my_blob, 3)) { + char *s = my_blob + 3; + char *d = my_blob; + Trace(("trim BOM")); + do { + } while ((*d++ = *s++) != '\0'); + } #endif -static int waiting; -static int shift; -static int utf8_mode = FALSE; + width = (int) strlen(my_vec[0]); + for (k = 1; my_vec[k]; ++k) { + int check = (int) (my_vec[k] - my_vec[k - 1]); + if (width < check) + width = check; + } + width = (width + 1) * 5; + my_win = newwin(2, width, 0, 0); + if (my_win == 0) + failed("cannot allocate temporary window"); + + if ((vec_lines = typeCalloc(NCURSES_CH_T *, (size_t) num_lines + 2)) == 0) + failed("cannot allocate line-vector #2"); + + /* + * Use the curses library for rendering, including tab-conversion. This + * will not make the resulting array's indices correspond to column for + * lines containing double-width cells because the "in_wch" functions will + * ignore the skipped cells. Use pads for that sort of thing. + */ + Trace(("slurp the file")); + for (k = 0; my_vec[k]; ++k) { + char *s; + int y, x; +#if USE_WIDEC_SUPPORT + char *last = my_vec[k] + (int) strlen(my_vec[k]); + wchar_t wch[2]; + size_t rc; +#ifndef state_unused + mbstate_t state; +#endif +#endif /* USE_WIDEC_SUPPORT */ -static char *fname; -static chtype **lines; -static chtype **lptr; + werase(my_win); + wmove(my_win, 0, 0); +#if USE_WIDEC_SUPPORT + wch[1] = 0; + reset_mbytes(state); +#endif + for (s = my_vec[k]; *s != '\0'; ++s) { +#if USE_WIDEC_SUPPORT + if (!n_option) { + rc = (size_t) check_mbytes(wch[0], s, (size_t) (last - s), state); + if ((long) rc == -1 || (long) rc == -2) { + break; + } + s += rc - 1; + waddwstr(my_win, wch); + } else +#endif + waddch(my_win, *s & 0xff); + } + getyx(my_win, y, x); + if (y) + x = width - 1; + wmove(my_win, 0, 0); + /* "x + 1" works with standard curses; some implementations are buggy */ + if ((vec_lines[k] = typeCalloc(NCURSES_CH_T, x + width + 1)) == 0) + failed("cannot allocate line-vector #3"); +#if USE_WIDEC_SUPPORT + win_wchnstr(my_win, vec_lines[k], x); +#else + winchnstr(my_win, vec_lines[k], x); +#endif + } + + delwin(my_win); + free(my_vec); + free(my_blob); +} static void usage(void) @@ -80,146 +370,93 @@ usage(void) "Usage: view [options] file" ,"" ,"Options:" - ," -n NUM specify maximum number of lines (default 1000)" -#if defined(KEY_RESIZE) - ," -r use experimental KEY_RESIZE rather than our own handler" + ," -c use color if terminal supports it" + ," -i ignore INT, QUIT, TERM signals" +#if USE_WIDEC_SUPPORT + ," -n use waddch (bytes) rather then wadd_wch (wide-chars)" #endif + ," -s start in single-step mode, waiting for input" #ifdef TRACE ," -t trace screen updates" ," -T NUM specify trace mask" #endif - ," -u translate UTF-8 data" }; size_t n; for (n = 0; n < SIZEOF(msg); n++) fprintf(stderr, "%s\n", msg[n]); - exit(EXIT_FAILURE); -} - -static int -ch_len(chtype * src) -{ - int result = 0; - while (*src++) - result++; - return result; -} - -/* - * Allocate a string into an array of chtype's. If UTF-8 mode is - * active, translate the string accordingly. - */ -static chtype * -ch_dup(char *src) -{ - unsigned len = strlen(src); - chtype *dst = typeMalloc(chtype, len + 1); - unsigned j, k; - unsigned utf_count = 0; - unsigned utf_char = 0; - -#define UCS_REPL 0xfffd - - for (j = k = 0; j < len; j++) { - if (utf8_mode) { - unsigned c = src[j] & 0xff; - /* Combine UTF-8 into Unicode */ - if (c < 0x80) { - /* We received an ASCII character */ - if (utf_count > 0) - dst[k++] = UCS_REPL; /* prev. sequence incomplete */ - dst[k++] = c; - utf_count = 0; - } else if (c < 0xc0) { - /* We received a continuation byte */ - if (utf_count < 1) { - dst[k++] = UCS_REPL; /* ... unexpectedly */ - } else { - if (!utf_char && !((c & 0x7f) >> (7 - utf_count))) { - utf_char = UCS_REPL; - } - /* characters outside UCS-2 become UCS_REPL */ - if (utf_char > 0x03ff) { - /* value would be >0xffff */ - utf_char = UCS_REPL; - } else { - utf_char <<= 6; - utf_char |= (c & 0x3f); - } - utf_count--; - if (utf_count == 0) - dst[k++] = utf_char; - } - } else { - /* We received a sequence start byte */ - if (utf_count > 0) - dst[k++] = UCS_REPL; /* prev. sequence incomplete */ - if (c < 0xe0) { - utf_count = 1; - utf_char = (c & 0x1f); - if (!(c & 0x1e)) - utf_char = UCS_REPL; /* overlong sequence */ - } else if (c < 0xf0) { - utf_count = 2; - utf_char = (c & 0x0f); - } else if (c < 0xf8) { - utf_count = 3; - utf_char = (c & 0x07); - } else if (c < 0xfc) { - utf_count = 4; - utf_char = (c & 0x03); - } else if (c < 0xfe) { - utf_count = 5; - utf_char = (c & 0x01); - } else { - dst[k++] = UCS_REPL; - utf_count = 0; - } - } - } else { - dst[k++] = src[j]; - } - } - dst[k] = 0; - return dst; + ExitProgram(EXIT_FAILURE); } int main(int argc, char *argv[]) { - int MAXLINES = 1000; - FILE *fp; - char buf[BUFSIZ]; - int i; - chtype **olptr; - int done = FALSE; - int length = 0; -#if CAN_RESIZE - bool use_resize = TRUE; -#endif + static const char *help[] = + { + "Commands:", + " q,^Q,ESC - quit this program", + "", + " p, - scroll the viewport up by one row", + " n, - scroll the viewport down by one row", + " l, - scroll the viewport left by one column", + " r, - scroll the viewport right by one column", + " <,> - scroll the viewport left/right by 8 columns", + "", + " h, - scroll the viewport to top of file", + " ^F, - scroll to the next page", + " ^B, - scroll to the previous page", + " e, - scroll the viewport to end of file", + "", + " ^L - repaint using redrawwin()", + "", + " 0 through 9 - enter digits for count", + " s - use entered count for halfdelay() parameter", + " - if no entered count, stop nodelay()", + " - begin nodelay()", + 0 + }; - while ((i = getopt(argc, argv, "n:rtT:u")) != EOF) { + int i; + int my_delay = 0; + NCURSES_CH_T **olptr; + int value = 0; + bool done = FALSE; + bool got_number = FALSE; + bool ignore_sigs = FALSE; + bool single_step = FALSE; + const char *my_label = "Input"; + + setlocale(LC_ALL, ""); + + while ((i = getopt(argc, argv, "cinstT:")) != -1) { switch (i) { - case 'n': - if ((MAXLINES = atoi(optarg)) < 1) - usage(); + case 'c': + try_color = TRUE; break; -#if CAN_RESIZE - case 'r': - use_resize = FALSE; + case 'i': + ignore_sigs = TRUE; + break; +#if USE_WIDEC_SUPPORT + case 'n': + n_option = TRUE; break; #endif + case 's': + single_step = TRUE; + break; #ifdef TRACE case 'T': - trace(atoi(optarg)); + { + char *next = 0; + int tvalue = (int) strtol(optarg, &next, 0); + if (tvalue < 0 || (next != 0 && *next != 0)) + usage(); + trace((unsigned) tvalue); + } break; case 't': trace(TRACE_CALLS); break; #endif - case 'u': - utf8_mode = TRUE; - break; default: usage(); } @@ -227,140 +464,140 @@ main(int argc, char *argv[]) if (optind + 1 != argc) usage(); - if ((lines = typeMalloc(chtype *, MAXLINES + 2)) == 0) - usage(); - - fname = argv[optind]; - if ((fp = fopen(fname, "r")) == 0) { - perror(fname); - return EXIT_FAILURE; - } - - (void) signal(SIGINT, finish); /* arrange interrupts to terminate */ -#if CAN_RESIZE - if (use_resize) - (void) signal(SIGWINCH, adjust); /* arrange interrupts to resize */ -#endif - - /* slurp the file */ - for (lptr = &lines[0]; (lptr - lines) < MAXLINES; lptr++) { - char temp[BUFSIZ], *s, *d; - int col; - - if (fgets(buf, sizeof(buf), fp) == 0) - break; - - /* convert tabs so that shift will work properly */ - for (s = buf, d = temp, col = 0; (*d = *s) != '\0'; s++) { - if (*d == '\n') { - *d = '\0'; - break; - } else if (*d == '\t') { - col = (col | 7) + 1; - while ((d - temp) != col) - *d++ = ' '; - } else if (isprint(*d) || utf8_mode) { - col++; - d++; - } else { - sprintf(d, "\\%03o", *s & 0xff); - d += strlen(d); - col = (d - temp); - } - } - *lptr = ch_dup(temp); - } - (void) fclose(fp); - length = lptr - lines; - - (void) initscr(); /* initialize the curses library */ + InitAndCatch(initscr(), ignore_sigs ? SIG_IGN : finish); keypad(stdscr, TRUE); /* enable keyboard mapping */ (void) nonl(); /* tell curses not to do NL->CR/NL on output */ (void) cbreak(); /* take input chars one at a time, no wait for \n */ (void) noecho(); /* don't echo input */ + if (!single_step) + nodelay(stdscr, TRUE); idlok(stdscr, TRUE); /* allow use of insert/delete line */ - lptr = lines; + read_file(fname = argv[optind]); + + if (try_color) { + if (has_colors()) { + start_color(); + init_pair(my_pair, COLOR_WHITE, COLOR_BLUE); + bkgd((chtype) COLOR_PAIR(my_pair)); + } else { + try_color = FALSE; + } + } + + lptr = vec_lines; while (!done) { int n, c; - bool got_number; - show_all(); + if (!got_number) + show_all(my_label); - got_number = FALSE; - n = 0; for (;;) { -#if CAN_RESIZE - if (interrupted) - adjust(0); -#endif - waiting = TRUE; c = getch(); - waiting = FALSE; if ((c < 127) && isdigit(c)) { if (!got_number) { - mvprintw(0, 0, "Count: "); + MvPrintw(0, 0, "Count: "); clrtoeol(); } - addch(c); - n = 10 * n + (c - '0'); + addch(UChar(c)); + value = 10 * value + (c - '0'); got_number = TRUE; } else break; } - if (!got_number && n == 0) + if (got_number && value) { + n = value; + } else { n = 1; + } + if (c != ERR) + my_label = keyname(c); switch (c) { case KEY_DOWN: case 'n': olptr = lptr; for (i = 0; i < n; i++) - if ((lptr - lines) < (length - LINES + 1)) + if ((lptr - vec_lines) < (num_lines - LINES + 1)) lptr++; else break; - wscrl(stdscr, lptr - olptr); + scrl((int) (lptr - olptr)); break; case KEY_UP: case 'p': olptr = lptr; for (i = 0; i < n; i++) - if (lptr > lines) + if (lptr > vec_lines) lptr--; else break; - wscrl(stdscr, lptr - olptr); + scrl((int) (lptr - olptr)); break; case 'h': + /* FALLTHRU */ case KEY_HOME: - lptr = lines; + lptr = vec_lines; + break; + + case '<': + if ((shift -= 8) < 0) + shift = 0; + break; + case '>': + shift += 8; break; case 'e': + /* FALLTHRU */ case KEY_END: - if (length > LINES) - lptr = lines + length - LINES + 1; + if (num_lines > LINES) + lptr = (vec_lines + num_lines - LINES + 1); else - lptr = lines; + lptr = (vec_lines + (num_lines - 2)); + break; + + case CTRL('F'): + /* FALLTHRU */ + case KEY_NPAGE: + for (i = 0; i < n; i++) { + if ((lptr - vec_lines) < (num_lines - 5)) + lptr += (LINES - 1); + else + lptr = (vec_lines + num_lines - 2); + } + break; + + case CTRL('B'): + /* FALLTHRU */ + case KEY_PPAGE: + for (i = 0; i < n; i++) { + if ((lptr - vec_lines) >= LINES) + lptr -= (LINES - 1); + else + lptr = vec_lines; + } break; case 'r': case KEY_RIGHT: - shift++; + shift += n; break; case 'l': case KEY_LEFT: - if (shift) - shift--; - else + shift -= n; + if (shift < 0) { + shift = 0; beep(); + } break; case 'q': + case QUIT: + case ESCAPE: done = TRUE; break; @@ -368,80 +605,42 @@ main(int argc, char *argv[]) case KEY_RESIZE: /* ignore this; ncurses will repaint */ break; #endif -#if CAN_RESIZE + case 's': +#if HAVE_HALFDELAY + if (got_number) { + halfdelay(my_delay = n); + } else { + nodelay(stdscr, FALSE); + my_delay = -1; + } +#else + nodelay(stdscr, FALSE); + my_delay = -1; +#endif + break; + case ' ': + nodelay(stdscr, TRUE); + my_delay = 0; + break; + case CTRL('L'): + redrawwin(stdscr); + break; case ERR: + if (!my_delay) + napms(50); + break; + case HELP_KEY_1: + popup_msg(stdscr, help); break; -#endif default: beep(); + break; } - } - - finish(0); /* we're done */ -} - -static RETSIGTYPE -finish(int sig) -{ - endwin(); - exit(sig != 0 ? EXIT_FAILURE : EXIT_SUCCESS); -} - -#if CAN_RESIZE -/* - * This uses functions that are "unsafe", but it seems to work on SunOS and - * Linux. The 'wrefresh(curscr)' is needed to force the refresh to start from - * the top of the screen -- some xterms mangle the bitmap while resizing. - */ -static RETSIGTYPE -adjust(int sig) -{ - if (waiting || sig == 0) { - struct winsize size; - - if (ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0) { - resizeterm(size.ws_row, size.ws_col); - wrefresh(curscr); /* Linux needs this */ - show_all(); + if (c >= KEY_MIN || (c > 0 && !isdigit(c))) { + got_number = FALSE; + value = 0; } - interrupted = FALSE; - } else { - interrupted = TRUE; } - (void) signal(SIGWINCH, adjust); /* some systems need this */ -} -#endif /* CAN_RESIZE */ - -static void -show_all(void) -{ - int i; - char temp[BUFSIZ]; - chtype *s; - -#if CAN_RESIZE - sprintf(temp, "(%3dx%3d) col %d ", LINES, COLS, shift); - i = strlen(temp); - sprintf(temp + i, "view %.*s", (int) (sizeof(temp) - 7 - i), fname); -#else - sprintf(temp, "view %.*s", (int) sizeof(temp) - 7, fname); -#endif - move(0, 0); - printw("%.*s", COLS, temp); - clrtoeol(); - scrollok(stdscr, FALSE); /* prevent screen from moving */ - for (i = 1; i < LINES; i++) { - move(i, 0); - printw("%3d:", (lptr + i - lines)); - clrtoeol(); - if ((s = lptr[i - 1]) != 0) { - int len = ch_len(s); - if (len > shift) - addchstr(s + shift); - } - } - setscrreg(1, LINES - 1); - scrollok(stdscr, TRUE); - refresh(); + finish(0); /* we're done */ }