X-Git-Url: http://ncurses.scripts.mit.edu/?p=ncurses.git;a=blobdiff_plain;f=test%2Fview.c;h=bf4377f48e12c505e4f24082a49b236c68e38245;hp=48c86e6b80e024282951fa8458787aed9a7ef0f1;hb=b5be26931d6488adcb1ff8bc07df9de378ce0d27;hpb=772f879d17117c5b766022f28099e341ebea825b diff --git a/test/view.c b/test/view.c index 48c86e6b..bf4377f4 100644 --- a/test/view.c +++ b/test/view.c @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright (c) 1998-2006,2007 Free Software Foundation, Inc. * + * 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 * @@ -33,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. @@ -50,50 +51,20 @@ * scroll operation worked, and the refresh() code only had to do a * partial repaint. * - * $Id: view.c,v 1.66 2007/07/21 17:41:55 tom Exp $ + * $Id: view.c,v 1.135 2017/10/23 09:18:01 tom Exp $ */ #include +#include +#include +#include #include -#undef CTRL /* conflict on AIX 5.2 with */ - -#if HAVE_TERMIOS_H -# include -#else -# include -#endif - -#if !defined(sun) || !HAVE_TERMIOS_H -# if HAVE_SYS_IOCTL_H -# include -# endif -#endif +static void finish(int sig) GCC_NORETURN; #define my_pair 1 -/* This is needed to compile 'struct winsize' */ -#if NEED_PTEM_H -#include -#include -#endif - -static RETSIGTYPE finish(int sig) GCC_NORETURN; -static void show_all(const char *tag); - -#if defined(SIGWINCH) && defined(TIOCGWINSZ) && HAVE_RESIZE_TERM -#define CAN_RESIZE 1 -#else -#define CAN_RESIZE 0 -#endif - -#if CAN_RESIZE -static RETSIGTYPE adjust(int sig); -static int interrupted; -#endif - -static bool waiting = FALSE; static int shift = 0; static bool try_color = FALSE; @@ -102,28 +73,17 @@ 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 + +static void usage(void) GCC_NORETURN; + static void -usage(void) +failed(const char *msg) { - static const char *msg[] = - { - "Usage: view [options] file" - ,"" - ,"Options:" - ," -c use color if terminal supports it" - ," -i ignore INT, QUIT, TERM signals" - ," -n NUM specify maximum number of lines (default 1000)" -#if defined(KEY_RESIZE) - ," -r use old-style sigwinch handler rather than KEY_RESIZE" -#endif -#ifdef TRACE - ," -t trace screen updates" - ," -T NUM specify trace mask" -#endif - }; - size_t n; - for (n = 0; n < SIZEOF(msg); n++) - fprintf(stderr, "%s\n", msg[n]); + endwin(); + fprintf(stderr, "%s\n", msg); ExitProgram(EXIT_FAILURE); } @@ -132,11 +92,20 @@ ch_len(NCURSES_CH_T * src) { int result = 0; #if USE_WIDEC_SUPPORT + int count; #endif #if USE_WIDEC_SUPPORT - while (getcchar(src++, NULL, NULL, NULL, NULL) > 0) - result++; + for (;;) { + TEST_CCHAR(src, count, { + int len = wcwidth(test_wch[0]); + result += (len > 0) ? len : 1; + ++src; + } + , { + break; + }) + } #else while (*src++) result++; @@ -144,111 +113,345 @@ ch_len(NCURSES_CH_T * src) return result; } -/* - * Allocate a string into an array of chtype's. If UTF-8 mode is - * active, translate the string accordingly. - */ -static NCURSES_CH_T * -ch_dup(char *src) +static void +finish(int sig) { - unsigned len = strlen(src); - NCURSES_CH_T *dst = typeMalloc(NCURSES_CH_T, len + 1); - unsigned j, k; + 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); + } + + 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 - wchar_t wstr[CCHARW_MAX + 1]; - wchar_t wch; - int l = 0; - mbstate_t state; - size_t rc; + /* + * 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 + 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 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 + 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 - memset(&state, 0, sizeof(state)); + char *last = my_vec[k] + (int) strlen(my_vec[k]); + wchar_t wch[2]; + size_t rc; +#ifndef state_unused + mbstate_t state; #endif - for (j = k = 0; j < len; j++) { +#endif /* USE_WIDEC_SUPPORT */ + + werase(my_win); + wmove(my_win, 0, 0); #if USE_WIDEC_SUPPORT - rc = mbrtowc(&wch, src + j, len - j, &state); - if (rc == (size_t) -1 || rc == (size_t) -2) - break; - j += rc - 1; - if ((width = wcwidth(wch)) < 0) - break; - if ((width > 0 && l > 0) || l == CCHARW_MAX) { - wstr[l] = L'\0'; - l = 0; - if (setcchar(dst + k, wstr, 0, 0, NULL) != OK) - break; - ++k; + 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); } - if (width == 0 && l == 0) - wstr[l++] = L' '; - wstr[l++] = wch; + 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 - dst[k++] = src[j]; + winchnstr(my_win, vec_lines[k], x); #endif } + + delwin(my_win); + free(my_vec); + free(my_blob); +} + +static void +usage(void) +{ + static const char *msg[] = + { + "Usage: view [options] file" + ,"" + ,"Options:" + ," -c use color if terminal supports it" + ," -i ignore INT, QUIT, TERM signals" #if USE_WIDEC_SUPPORT - if (l > 0) { - wstr[l] = L'\0'; - if (setcchar(dst + k, wstr, 0, 0, NULL) == OK) - ++k; - } - wstr[0] = L'\0'; - setcchar(dst + k, wstr, 0, 0, NULL); -#else - dst[k] = 0; + ," -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 - return dst; + }; + size_t n; + for (n = 0; n < SIZEOF(msg); n++) + fprintf(stderr, "%s\n", msg[n]); + ExitProgram(EXIT_FAILURE); } int main(int argc, char *argv[]) { - int MAXLINES = 1000; - FILE *fp; - char buf[BUFSIZ]; + 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 + }; + int i; int my_delay = 0; NCURSES_CH_T **olptr; int value = 0; bool done = FALSE; bool got_number = FALSE; -#if CAN_RESIZE - bool nonposix_resize = FALSE; -#endif + bool ignore_sigs = FALSE; + bool single_step = FALSE; const char *my_label = "Input"; setlocale(LC_ALL, ""); -#ifndef NCURSES_VERSION - /* - * We know ncurses will catch SIGINT if we don't establish our own handler. - * Other versions of curses may/may not catch it. - */ - (void) signal(SIGINT, finish); /* arrange interrupts to terminate */ -#endif - - while ((i = getopt(argc, argv, "cin:rtT:")) != -1) { + while ((i = getopt(argc, argv, "cinstT:")) != -1) { switch (i) { case 'c': try_color = TRUE; break; case 'i': - CATCHALL(SIG_IGN); + ignore_sigs = TRUE; break; +#if USE_WIDEC_SUPPORT case 'n': - if ((MAXLINES = atoi(optarg)) < 1) - usage(); - break; -#if CAN_RESIZE - case 'r': - nonposix_resize = TRUE; + n_option = TRUE; break; #endif + case 's': + single_step = TRUE; + break; #ifdef TRACE case 'T': - trace((unsigned) 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); @@ -261,68 +464,22 @@ main(int argc, char *argv[]) if (optind + 1 != argc) usage(); - if ((vec_lines = typeMalloc(NCURSES_CH_T *, MAXLINES + 2)) == 0) - usage(); - - fname = argv[optind]; - if ((fp = fopen(fname, "r")) == 0) { - perror(fname); - ExitProgram(EXIT_FAILURE); - } -#if CAN_RESIZE - if (nonposix_resize) - (void) signal(SIGWINCH, adjust); /* arrange interrupts to resize */ -#endif - - /* slurp the file */ - for (lptr = &vec_lines[0]; (lptr - vec_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 USE_WIDEC_SUPPORT - col++, d++; -#else - if (isprint(UChar(*d))) { - col++; - d++; - } else { - sprintf(d, "\\%03o", UChar(*s)); - d += strlen(d); - col = (d - temp); - } -#endif - } - *lptr = ch_dup(temp); - } - (void) fclose(fp); - num_lines = lptr - vec_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 */ - nodelay(stdscr, TRUE); + if (!single_step) + nodelay(stdscr, TRUE); idlok(stdscr, TRUE); /* allow use of insert/delete line */ + read_file(fname = argv[optind]); + if (try_color) { if (has_colors()) { start_color(); init_pair(my_pair, COLOR_WHITE, COLOR_BLUE); - bkgd(COLOR_PAIR(my_pair)); + bkgd((chtype) COLOR_PAIR(my_pair)); } else { try_color = FALSE; } @@ -335,20 +492,11 @@ main(int argc, char *argv[]) if (!got_number) show_all(my_label); - n = 0; for (;;) { -#if CAN_RESIZE - if (interrupted) { - adjust(0); - my_label = "interrupt"; - } -#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(UChar(c)); @@ -374,7 +522,7 @@ main(int argc, char *argv[]) lptr++; else break; - scrl(lptr - olptr); + scrl((int) (lptr - olptr)); break; case KEY_UP: @@ -385,20 +533,52 @@ main(int argc, char *argv[]) lptr--; else break; - scrl(lptr - olptr); + scrl((int) (lptr - olptr)); break; case 'h': + /* FALLTHRU */ case KEY_HOME: lptr = vec_lines; break; + case '<': + if ((shift -= 8) < 0) + shift = 0; + break; + case '>': + shift += 8; + break; + case 'e': + /* FALLTHRU */ case KEY_END: if (num_lines > LINES) - lptr = vec_lines + num_lines - LINES + 1; + lptr = (vec_lines + num_lines - LINES + 1); else - lptr = vec_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': @@ -416,6 +596,8 @@ main(int argc, char *argv[]) break; case 'q': + case QUIT: + case ESCAPE: done = TRUE; break; @@ -424,21 +606,32 @@ main(int argc, char *argv[]) break; #endif 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; default: beep(); break; @@ -451,101 +644,3 @@ main(int argc, char *argv[]) finish(0); /* we're done */ } - -static RETSIGTYPE -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); -} - -#if CAN_RESIZE -/* - * This uses functions that are "unsafe", but it seems to work on SunOS and - * Linux. Usually: the "unsafe" refers to the functions that POSIX lists - * which may be called from a signal handler. Those do not include buffered - * I/O, which is used for instance in wrefresh(). To be really portable, you - * should use the KEY_RESIZE return (which relies on ncurses' sigwinch - * handler). - * - * 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) { - resize_term(size.ws_row, size.ws_col); - wrefresh(curscr); /* Linux needs this */ - show_all(sig ? "SIGWINCH" : "interrupt"); - } - interrupted = FALSE; - } else { - interrupted = TRUE; - } - (void) signal(SIGWINCH, adjust); /* some systems need this */ -} -#endif /* CAN_RESIZE */ - -static void -show_all(const char *tag) -{ - int i; - char temp[BUFSIZ]; - NCURSES_CH_T *s; - time_t this_time; - -#if CAN_RESIZE - sprintf(temp, "%s (%3dx%3d) col %d ", tag, 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(); - this_time = time((time_t *) 0); - strcpy(temp, ctime(&this_time)); - if ((i = strlen(temp)) != 0) { - temp[--i] = 0; - if (move(0, COLS - i - 2) != ERR) - printw(" %s", temp); - } - - scrollok(stdscr, FALSE); /* prevent screen from moving */ - for (i = 1; i < LINES; i++) { - move(i, 0); - printw("%3ld:", (long) (lptr + i - vec_lines)); - clrtoeol(); - if ((s = lptr[i - 1]) != 0) { - int len = ch_len(s); - if (len > shift) { -#if USE_WIDEC_SUPPORT - add_wchstr(s + shift); -#else - addchstr(s + shift); -#endif - } -#if defined(NCURSES_VERSION) || defined(HAVE_WCHGAT) - if (try_color) - wchgat(stdscr, -1, A_NORMAL, my_pair, NULL); -#endif - } - } - setscrreg(1, LINES - 1); - scrollok(stdscr, TRUE); - refresh(); -}