Subject: v16i032: Less, a pager that's more than more, Part03/04 Newsgroups: comp.sources.unix Sender: sources Approved: rsalz@uunet.UU.NET Submitted-by: ctnews!UNIX386!mark Posting-number: Volume 16, Issue 32 Archive-name: less5/part03 #! /bin/sh # This is a shell archive. # Remove anything before this line, then unpack it # by saving it into a file and typing "sh file". echo shar: Extracting \"main.c\" sed "s/^X//" >'main.c' <<'END_OF_FILE' X/* X * Entry point, initialization, miscellaneous routines. X */ X X#include "less.h" X#include "position.h" X Xpublic int ispipe; Xpublic char * first_cmd; Xpublic char * every_first_cmd; Xpublic int new_file; Xpublic int is_tty; Xpublic char *current_file; Xpublic char *previous_file; Xpublic POSITION prev_pos; Xpublic int any_display; Xpublic int scroll; Xpublic int ac; Xpublic char ** av; Xpublic int curr_ac; Xpublic int quitting; X Xextern int file; Xextern int quit_at_eof; Xextern int cbufs; Xextern int errmsgs; X X#if LOGFILE Xpublic int logfile = -1; Xpublic int force_logfile = 0; Xpublic char * namelogfile = NULL; X#endif X X#if EDITOR Xpublic char * editor; X#endif X X#if TAGS Xextern char * tagfile; Xextern char * tagpattern; Xextern int tagoption; X#endif X X X/* X * Edit a new file. X * Filename "-" means standard input. X * No filename means the "current" file, from the command line. X */ X public void Xedit(filename) X register char *filename; X{ X register int f; X register char *m; X POSITION initial_pos; X char message[100]; X static int didpipe; X X initial_pos = NULL_POSITION; X if (filename == NULL || *filename == '\0') X { X if (curr_ac >= ac) X { X error("No current file"); X return; X } X filename = save(av[curr_ac]); X } else if (strcmp(filename, "#") == 0) X { X if (*previous_file == '\0') X { X error("no previous file"); X return; X } X filename = save(previous_file); X initial_pos = prev_pos; X } else X filename = save(filename); X X if (strcmp(filename, "-") == 0) X { X /* X * Use standard input. X */ X if (didpipe) X { X error("Can view standard input only once"); X return; X } X f = 0; X } else if ((m = bad_file(filename, message, sizeof(message))) != NULL) X { X error(m); X free(filename); X return; X } else if ((f = open(filename, 0)) < 0) X { X error(errno_message(filename, message, sizeof(message))); X free(filename); X return; X } X X if (isatty(f)) X { X /* X * Not really necessary to call this an error, X * but if the control terminal (for commands) X * and the input file (for data) are the same, X * we get weird results at best. X */ X error("Can't take input from a terminal"); X if (f > 0) X close(f); X free(filename); X return; X } X X#if LOGFILE X if (f == 0 && namelogfile != NULL && is_tty) X use_logfile(); X#endif X X /* X * We are now committed to using the new file. X * Close the current input file and set up to use the new one. X */ X if (file > 0) X close(file); X new_file = 1; X if (previous_file != NULL) X free(previous_file); X previous_file = current_file; X current_file = filename; X prev_pos = position(TOP); X ispipe = (f == 0); X if (ispipe) X didpipe = 1; X file = f; X ch_init(cbufs, 0); X init_mark(); X X if (every_first_cmd != NULL) X first_cmd = every_first_cmd; X X if (is_tty) X { X int no_display = !any_display; X any_display = 1; X if (no_display && errmsgs > 0) X { X /* X * We displayed some messages on error output X * (file descriptor 2; see error() function). X * Before erasing the screen contents, X * display the file name and wait for a keystroke. X */ X error(filename); X } X /* X * Indicate there is nothing displayed yet. X */ X pos_clear(); X if (initial_pos != NULL_POSITION) X jump_loc(initial_pos); X clr_linenum(); X } X} X X/* X * Edit the next file in the command line list. X */ X public void Xnext_file(n) X int n; X{ X if (curr_ac + n >= ac) X { X if (quit_at_eof) X quit(); X error("No (N-th) next file"); X } else X edit(av[curr_ac += n]); X} X X/* X * Edit the previous file in the command line list. X */ X public void Xprev_file(n) X int n; X{ X if (curr_ac - n < 0) X error("No (N-th) previous file"); X else X edit(av[curr_ac -= n]); X} X X/* X * Copy a file directly to standard output. X * Used if standard output is not a tty. X */ X static void Xcat_file() X{ X register int c; X X while ((c = ch_forw_get()) != EOI) X putchr(c); X flush(); X} X X#if LOGFILE X Xuse_logfile() X{ X int exists; X int answer; X char message[100]; X X /* X * If he asked for a log file and we have opened standard input, X * create the log file. X * We take care not to blindly overwrite an existing file. X */ X end_logfile(); X X /* X * {{ We could use access() here. }} X */ X exists = open(namelogfile, 0); X close(exists); X exists = (exists >= 0); X X if (exists && !force_logfile) X { X static char w[] = "WARNING: log file exists: "; X strcpy(message, w); X strtcpy(message+sizeof(w)-1, namelogfile, X sizeof(message)-sizeof(w)); X error(message); X answer = 'X'; /* Ask the user what to do */ X } else X answer = 'O'; /* Create the log file */ X Xloop: X switch (answer) X { X case 'O': case 'o': X logfile = creat(namelogfile, 0644); X break; X case 'A': case 'a': X logfile = open(namelogfile, 1); X if (lseek(logfile, (offset_t)0, 2) < 0) X { X close(logfile); X logfile = -1; X } X break; X case 'D': case 'd': X answer = 0; /* Don't print an error message */ X break; X case 'q': X quit(); X default: X putstr("\n Overwrite, Append, or Don't log? "); X answer = getchr(); X putstr("\n"); X flush(); X goto loop; X } X X if (logfile < 0 && answer != 0) X { X sprintf(message, "Cannot write to \"%s\"", X namelogfile); X error(message); X } X} X X#endif X X/* X * Entry point. X */ Xmain(argc, argv) X int argc; X char *argv[]; X{ X char *getenv(); X X X /* X * Process command line arguments and LESS environment arguments. X * Command line arguments override environment arguments. X */ X init_prompt(); X init_option(); X scan_option(getenv("LESS")); X argv++; X while ( (--argc > 0) && X (argv[0][0] == '-' || argv[0][0] == '+') && X argv[0][1] != '\0') X scan_option(*argv++); X X#if EDITOR X editor = getenv("EDITOR"); X if (editor == NULL || *editor == '\0') X editor = EDIT_PGM; X#endif X X /* X * Set up list of files to be examined. X */ X ac = argc; X av = argv; X curr_ac = 0; X X /* X * Set up terminal, etc. X */ X is_tty = isatty(1); X if (!is_tty) X { X /* X * Output is not a tty. X * Just copy the input file(s) to output. X */ X if (ac < 1) X { X edit("-"); X cat_file(); X } else X { X do X { X edit((char *)NULL); X if (file >= 0) X cat_file(); X } while (++curr_ac < ac); X } X exit(0); X } X X raw_mode(1); X get_term(); X open_getchr(); X init(); X init_cmd(); X X init_signals(1); X X /* X * Select the first file to examine. X */ X#if TAGS X if (tagoption) X { X /* X * A -t option was given. X * Verify that no filenames were also given. X * Edit the file selected by the "tags" search, X * and search for the proper line in the file. X */ X if (ac > 0) X { X error("No filenames allowed with -t option"); X quit(); X } X if (tagfile == NULL) X quit(); X edit(tagfile); X if (file < 0) X quit(); X if (tagsearch()) X quit(); X } else X#endif X if (ac < 1) X edit("-"); /* Standard input */ X else X { X /* X * Try all the files named as command arguments. X * We are simply looking for one which can be X * opened without error. X */ X do X { X edit((char *)NULL); X } while (file < 0 && ++curr_ac < ac); X } X X if (file >= 0) X commands(); X quit(); X /*NOTREACHED*/ X} X X/* X * Copy a string, truncating to the specified length if necessary. X * Unlike strncpy(), the resulting string is guaranteed to be null-terminated. X */ X public void Xstrtcpy(to, from, len) X char *to; X char *from; X unsigned int len; X{ X strncpy(to, from, len); X to[len-1] = '\0'; X} X X/* X * Copy a string to a "safe" place X * (that is, to a buffer allocated by calloc). X */ X public char * Xsave(s) X char *s; X{ X register char *p; X X p = calloc(strlen(s)+1, sizeof(char)); X if (p == NULL) X { X error("cannot allocate memory"); X quit(); X } X strcpy(p, s); X return (p); X} X X/* X * Exit the program. X */ X public void Xquit() X{ X /* X * Put cursor at bottom left corner, clear the line, X * reset the terminal modes, and exit. X */ X quitting = 1; X#if LOGFILE X end_logfile(); X#endif X lower_left(); X clear_eol(); X deinit(); X flush(); X raw_mode(0); X exit(0); X} END_OF_FILE echo shar: Extracting \"option.c\" sed "s/^X//" >'option.c' <<'END_OF_FILE' X/* X * Process command line options. X * Each option is a single letter which controls a program variable. X * The options have defaults which may be changed via X * the command line option, or toggled via the "-" command. X */ X X#include "less.h" X X#define toupper(c) ((c)-'a'+'A') X X#define END_OPTION_STRING ('$') X X/* X * Types of options. X */ X#define BOOL 01 /* Boolean option: 0 or 1 */ X#define TRIPLE 02 /* Triple-valued option: 0, 1 or 2 */ X#define NUMBER 04 /* Numeric option */ X#define REPAINT 040 /* Repaint screen after toggling option */ X#define NO_TOGGLE 0100 /* Option cannot be toggled with "-" cmd */ X X/* X * Variables controlled by command line options. X */ Xpublic int clean_data; /* Can we assume the data is "clean"? X (That is, free of nulls, etc) */ Xpublic int quiet; /* Should we suppress the audible bell? */ Xpublic int how_search; /* Where should forward searches start? */ Xpublic int top_scroll; /* Repaint screen from top? X (alternative is scroll from bottom) */ Xpublic int pr_type; /* Type of prompt (short, medium, long) */ Xpublic int bs_mode; /* How to process backspaces */ Xpublic int know_dumb; /* Don't complain about dumb terminals */ Xpublic int quit_at_eof; /* Quit after hitting end of file twice */ Xpublic int squeeze; /* Squeeze multiple blank lines into one */ Xpublic int tabstop; /* Tab settings */ Xpublic int back_scroll; /* Repaint screen on backwards movement */ Xpublic int twiddle; /* Display "~" for lines after EOF */ Xpublic int caseless; /* Do "caseless" searches */ Xpublic int linenums; /* Use line numbers */ Xpublic int cbufs; /* Current number of buffers */ Xpublic int autobuf; Xpublic int plusoption; X Xextern char *prproto[]; Xextern char *eqproto; Xextern int nbufs; Xextern int sc_window; Xextern int ispipe; Xextern char *first_cmd; Xextern char *every_first_cmd; X#if LOGFILE Xextern char *namelogfile; Xextern int force_logfile; Xextern int logfile; X#endif X#if TAGS Xextern char *tagfile; Xextern char *tagpattern; Xpublic int tagoption = 0; X#endif X Xstatic char *opt_P(); X Xstatic struct option X{ X char oletter; /* The controlling letter (a-z) */ X char otype; /* Type of the option */ X int odefault; /* Default value */ X int *ovar; /* Pointer to the associated variable */ X char *odesc[3]; /* Description of each value */ X} option[] = X{ X { 'a', TRIPLE, 0, &how_search, X { "Forward search starts at second REAL line displayed", X "Forward search starts at bottom of screen", X "Forward search starts at second SCREEN line displayed" X } X }, X { 'b', NUMBER, 10, &cbufs, X { "%d buffers", X NULL, NULL X } X }, X { 'B', BOOL, 1, &autobuf, X { "Don't automatically allocate buffers", X "Automatically allocate buffers when needed", X NULL X } X }, X { 'c', TRIPLE, 0, &top_scroll, X { "Repaint by scrolling from bottom of screen", X "Repaint by clearing each line", X "Repaint by painting from top of screen" X } X }, X { 'd', BOOL|NO_TOGGLE, 0, &know_dumb, X { NULL, NULL, NULL} X }, X { 'e', TRIPLE, 0, &quit_at_eof, X { "Don't quit at end-of-file", X "Quit at end-of-file", X "Quit immediately at end-of-file" X } X }, X { 'h', NUMBER, -1, &back_scroll, X { "Backwards scroll limit is %d lines", X NULL, NULL X } X }, X { 'i', BOOL, 0, &caseless, X { "Case is significant in searches", X "Ignore case in searches", X NULL X } X }, X { 'm', TRIPLE, 0, &pr_type, X { "Short prompt", X "Medium prompt", X "Long prompt" X } X }, X { 'n', BOOL, 1, &linenums, X { "Don't use line numbers", X "Use line numbers", X NULL X } X }, X { 'q', TRIPLE, 0, &quiet, X { "Ring the bell for errors AND at eof/bof", X "Ring the bell for errors but not at eof/bof", X "Never ring the bell" X } X }, X { 's', BOOL|REPAINT, 0, &squeeze, X { "Don't squeeze multiple blank lines", X "Squeeze multiple blank lines", X NULL X } X }, X { 'u', TRIPLE|REPAINT, 0, &bs_mode, X { "Underlined text displayed in underline mode", X "Backspaces cause overstrike", X "Backspaces print as ^H" X } X }, X { 'w', BOOL|REPAINT, 1, &twiddle, X { "Display nothing for lines after end-of-file", X "Display ~ for lines after end-of-file", X NULL X } X }, X { 'x', NUMBER|REPAINT, 8, &tabstop, X { "Tab stops every %d spaces", X NULL, NULL X } X }, X { 'z', NUMBER|REPAINT, -1, &sc_window, X { "Scroll window size is %d lines", X NULL, NULL X } X }, X { '\0' } X}; X X/* X * Initialize each option to its default value. X */ X public void Xinit_option() X{ X register struct option *o; X X first_cmd = every_first_cmd = NULL; X X for (o = option; o->oletter != '\0'; o++) X { X /* X * Set each variable to its default. X */ X *(o->ovar) = o->odefault; X } X} X X/* X * Toggle command line flags from within the program. X * Used by the "-" and "_" commands. X * If do_toggle is zero, just report the current setting, without changing it. X */ X public void Xtoggle_option(s, do_toggle) X char *s; X int do_toggle; X{ X int c; X register struct option *o; X char *msg; X int n; X int dorepaint; X char message[100]; X X c = *s++; X X switch (c) X { X case 'P': X /* X * Special case for -P. X */ X if (*s == '\0') X error(prproto[pr_type]); X else X (void) opt_P(s); X return; X#if TAGS X case 't': X /* X * Special case for -t. X */ X if (*s == '\0') X { X error("no tag"); X return; X } X findtag(s); X if (tagfile != NULL) X { X edit(tagfile); X (void) tagsearch(); X } X return; X#endif X#if LOGFILE X case 'L': X /* X * Special case for -l and -L. X */ X force_logfile = 1; X goto case_l; X case 'l': X force_logfile = 0; X case_l: X if (*s == '\0') X { X if (logfile < 0) X error("no log file"); X else X { X sprintf(message, "log file \"%s\"", X namelogfile); X error(message); X } X return; X } X if (!ispipe) X { X error("input is not a pipe"); X return; X } X if (logfile >= 0) X { X error("log file is already in use"); X return; X } X namelogfile = save(s); X use_logfile(); X sync_logfile(); X return; X#endif X } X X msg = NULL; X for (o = option; o->oletter != '\0'; o++) X { X if (o->otype & NO_TOGGLE) X continue; X dorepaint = (o->otype & REPAINT); X if ((o->otype & BOOL) && (o->oletter == c)) X { X /* X * Boolean option: X * just toggle it. X */ X if (do_toggle) X *(o->ovar) = ! *(o->ovar); X } else if ((o->otype & TRIPLE) && (o->oletter == c)) X { X /* X * Triple-valued option with lower case letter: X * make it 1 unless already 1, then make it 0. X */ X if (do_toggle) X *(o->ovar) = (*(o->ovar) == 1) ? 0 : 1; X } else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c)) X { X /* X * Triple-valued option with upper case letter: X * make it 2 unless already 2, then make it 0. X */ X if (do_toggle) X *(o->ovar) = (*(o->ovar) == 2) ? 0 : 2; X } else if ((o->otype & NUMBER) && (o->oletter == c)) X { X n = getnum(&s, '\0'); X if (n < 0) X { X /* X * No number; just a query. X * No need to repaint screen. X */ X dorepaint = 0; X } else X { X /* X * Number follows the option letter. X * Set the variable to that number. X */ X if (do_toggle) X *(o->ovar) = n; X } X X /* X * Special case for -b. X * Call ch_init to set new number of buffers. X */ X if (o->ovar == &cbufs) X ch_init(cbufs, 1); X X sprintf(message, o->odesc[0], X (o->ovar == &back_scroll) ? X get_back_scroll() : *(o->ovar)); X msg = message; X } else X continue; X X /* X * Print a message describing the new setting. X */ X if (msg == NULL) X msg = o->odesc[*(o->ovar)]; X error(msg); X X if (do_toggle && dorepaint) X repaint(); X return; X } X X if (control_char(c)) X sprintf(message, "-^%c", carat_char(c)); X else X sprintf(message, "-%c", c); X strcat(message, ": no such flag."); X error(message); X} X X/* X * Determine if an option is a single character option (BOOL or TRIPLE), X * or if it a multi-character option (NUMBER). X */ X public int Xsingle_char_option(c) X int c; X{ X register struct option *o; X X if (c == 'P') X return (0); X#if TAGS X if (c == 't') X return (0); X#endif X#if LOGFILE X if (c == 'l' || c == 'L') X return (0); X#endif X for (o = option; o->oletter != '\0'; o++) X if (o->oletter == c) X return (o->otype & (BOOL|TRIPLE)); X return (1); X} X X/* X * Scan to end of string or to an END_OPTION_STRING character. X * In the latter case, replace the char with a null char. X * Return a pointer to the remainder of the string, if any. X */ X static char * Xoptstring(s, c) X char *s; X int c; X{ X register char *p; X char message[80]; X X if (*s == '\0') X { X sprintf(message, "string is required after -%c", c); X error(message); X exit(1); X } X for (p = s; *p != '\0'; p++) X if (*p == END_OPTION_STRING) X { X *p = '\0'; X return (p+1); X } X return (p); X} X X/* X * Scan an argument (either from command line or from LESS environment X * variable) and process it. X */ X public void Xscan_option(s) X char *s; X{ X register struct option *o; X register int c; X int set_default; X char message[80]; X X if (s == NULL) X return; X X set_default = 0; X next: X if (*s == '\0') X return; X switch (c = *s++) X { X case ' ': X case '\t': X case END_OPTION_STRING: X goto next; X case '-': X if (set_default = (*s == '+')) X s++; X goto next; X case '+': X plusoption = 1; X if (*s == '+') X every_first_cmd = save(++s); X first_cmd = s; X s = optstring(s, c); X goto next; X#if LOGFILE X case 'L': X force_logfile = 1; X /* FALLTHRU */ X case 'l': X namelogfile = s; X s = optstring(s, c); X goto next; X#endif X#if TAGS X case 't': X { X char *p; X tagoption = 1; X p = s; X s = optstring(s, c); X findtag(p); X goto next; X } X#endif X case 'P': X s = opt_P(s); X goto next; X case '0': case '1': case '2': case '3': case '4': X case '5': case '6': case '7': case '8': case '9': X /* X * Handle special "more" compatibility form "-number" X * (instead of -znumber) to set the scrolling window size. X */ X s--; X c = 'z'; X break; X } X X for (o = option; o->oletter != '\0'; o++) X { X if ((o->otype & BOOL) && (o->oletter == c)) X { X if (set_default) X *(o->ovar) = o->odefault; X else X *(o->ovar) = ! o->odefault; X goto next; X } else if ((o->otype & TRIPLE) && (o->oletter == c)) X { X if (set_default) X *(o->ovar) = o->odefault; X else X *(o->ovar) = (o->odefault == 1) ? 0 : 1; X goto next; X } else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c)) X { X if (set_default) X *(o->ovar) = o->odefault; X else X *(o->ovar) = (o->odefault == 2) ? 0 : 2; X goto next; X } else if ((o->otype & NUMBER) && (o->oletter == c)) X { X *(o->ovar) = getnum(&s, c); X goto next; X } X } X X sprintf(message, "\"-%c\": invalid flag", c); X error(message); X exit(1); X} X X/* X * Special case for -P. X */ X static char * Xopt_P(s) X register char *s; X{ X register char *es; X register char **proto; X X es = optstring(s, 'P'); X X /* X * Figure out which prototype string should be changed. X */ X switch (*s) X { X case 'm': proto = &prproto[PR_MEDIUM]; s++; break; X case 'M': proto = &prproto[PR_LONG]; s++; break; X case '=': proto = &eqproto; s++; break; X default: proto = &prproto[pr_type]; break; X } X X free(*proto); X *proto = save(s); X X return (es); X} X X/* X * Translate a string into a number. X * Like atoi(), but takes a pointer to a char *, and updates X * the char * to point after the translated number. X */ X public int Xgetnum(sp, c) X char **sp; X int c; X{ X register char *s; X register int n; X char message[80]; X X s = *sp; X if (*s < '0' || *s > '9') X { X if (c == '\0') X return (-1); X sprintf(message, "number is required after -%c", c); X error(message); X exit(1); X } X X n = 0; X while (*s >= '0' && *s <= '9') X n = 10 * n + *s++ - '0'; X *sp = s; X return (n); X} END_OF_FILE echo shar: Extracting \"prim.c\" sed "s/^X//" >'prim.c' <<'END_OF_FILE' X/* X * Primitives for displaying the file on the screen. X */ X X#include "less.h" X#include "position.h" X Xpublic int hit_eof; /* Keeps track of how many times we hit end of file */ Xpublic int screen_trashed; X Xstatic int squished; X Xextern int quiet; Xextern int sigs; Xextern int how_search; Xextern int top_scroll; Xextern int back_scroll; Xextern int sc_width, sc_height; Xextern int quit_at_eof; Xextern int caseless; Xextern int linenums; Xextern int plusoption; Xextern char *line; Xextern char *first_cmd; X#if TAGS Xextern int tagoption; X#endif X X/* X * Sound the bell to indicate he is trying to move past end of file. X */ X static void Xeof_bell() X{ X if (quiet == NOT_QUIET) X bell(); X else X vbell(); X} X X/* X * Check to see if the end of file is currently "displayed". X */ X static void Xeof_check() X{ X POSITION pos; X X if (sigs) X return; X /* X * If the bottom line is empty, we are at EOF. X * If the bottom line ends at the file length, X * we must be just at EOF. X */ X pos = position(BOTTOM_PLUS_ONE); X if (pos == NULL_POSITION || pos == ch_length()) X hit_eof++; X} X X/* X * If the screen is "squished", repaint it. X * "Squished" means the first displayed line is not at the top X * of the screen; this can happen when we display a short file X * for the first time. X */ X static void Xsquish_check() X{ X if (!squished) X return; X squished = 0; X repaint(); X} X X/* X * Display n lines, scrolling forward, X * starting at position pos in the input file. X * "force" means display the n lines even if we hit end of file. X * "only_last" means display only the last screenful if n > screen size. X */ X static void Xforw(n, pos, force, only_last) X register int n; X POSITION pos; X int force; X int only_last; X{ X int eof = 0; X int nlines = 0; X int do_repaint; X static int first_time = 1; X X squish_check(); X X /* X * do_repaint tells us not to display anything till the end, X * then just repaint the entire screen. X */ X do_repaint = (only_last && n > sc_height-1); X X if (!do_repaint) X { X if (top_scroll && n >= sc_height - 1) X { X /* X * Start a new screen. X * {{ This is not really desirable if we happen X * to hit eof in the middle of this screen, X * but we don't yet know if that will happen. }} X */ X if (top_scroll == 2) X clear(); X home(); X force = 1; X } else X { X lower_left(); X clear_eol(); X } X X if (pos != position(BOTTOM_PLUS_ONE)) X { X /* X * This is not contiguous with what is X * currently displayed. Clear the screen image X * (position table) and start a new screen. X */ X pos_clear(); X add_forw_pos(pos); X force = 1; X if (top_scroll) X { X if (top_scroll == 2) X clear(); X home(); X } else if (!first_time) X { X putstr("...skipping...\n"); X } X } X } X X while (--n >= 0) X { X /* X * Read the next line of input. X */ X pos = forw_line(pos); X if (pos == NULL_POSITION) X { X /* X * End of file: stop here unless the top line X * is still empty, or "force" is true. X */ X eof = 1; X if (!force && position(TOP) != NULL_POSITION) X break; X line = NULL; X } X /* X * Add the position of the next line to the position table. X * Display the current line on the screen. X */ X add_forw_pos(pos); X nlines++; X if (do_repaint) X continue; X /* X * If this is the first screen displayed and X * we hit an early EOF (i.e. before the requested X * number of lines), we "squish" the display down X * at the bottom of the screen. X * But don't do this if a + option or a -t option X * was given. These options can cause us to X * start the display after the beginning of the file, X * and it is not appropriate to squish in that case. X */ X if (first_time && line == NULL && !top_scroll && X#if TAGS X !tagoption && X#endif X !plusoption) X { X squished = 1; X continue; X } X if (top_scroll == 1) X clear_eol(); X put_line(); X } X X if (eof && !sigs) X hit_eof++; X else X eof_check(); X if (nlines == 0) X eof_bell(); X else if (do_repaint) X repaint(); X first_time = 0; X (void) currline(BOTTOM); X} X X/* X * Display n lines, scrolling backward. X */ X static void Xback(n, pos, force, only_last) X register int n; X POSITION pos; X int force; X int only_last; X{ X int nlines = 0; X int do_repaint; X X squish_check(); X do_repaint = (n > get_back_scroll() || (only_last && n > sc_height-1)); X hit_eof = 0; X while (--n >= 0) X { X /* X * Get the previous line of input. X */ X pos = back_line(pos); X if (pos == NULL_POSITION) X { X /* X * Beginning of file: stop here unless "force" is true. X */ X if (!force) X break; X line = NULL; X } X /* X * Add the position of the previous line to the position table. X * Display the line on the screen. X */ X add_back_pos(pos); X nlines++; X if (!do_repaint) X { X home(); X add_line(); X put_line(); X } X } X X eof_check(); X if (nlines == 0) X eof_bell(); X else if (do_repaint) X repaint(); X (void) currline(BOTTOM); X} X X/* X * Display n more lines, forward. X * Start just after the line currently displayed at the bottom of the screen. X */ X public void Xforward(n, only_last) X int n; X int only_last; X{ X POSITION pos; X X if (quit_at_eof && hit_eof) X { X /* X * If the -e flag is set and we're trying to go X * forward from end-of-file, go on to the next file. X */ X next_file(1); X return; X } X X pos = position(BOTTOM_PLUS_ONE); X if (pos == NULL_POSITION) X { X eof_bell(); X hit_eof++; X return; X } X forw(n, pos, 0, only_last); X} X X/* X * Display n more lines, backward. X * Start just before the line currently displayed at the top of the screen. X */ X public void Xbackward(n, only_last) X int n; X int only_last; X{ X POSITION pos; X X pos = position(TOP); X if (pos == NULL_POSITION) X { X /* X * This will almost never happen, X * because the top line is almost never empty. X */ X eof_bell(); X return; X } X back(n, pos, 0, only_last); X} X X/* X * Repaint the screen, starting from a specified position. X */ X static void Xprepaint(pos) X POSITION pos; X{ X hit_eof = 0; X forw(sc_height-1, pos, 1, 0); X screen_trashed = 0; X} X X/* X * Repaint the screen. X */ X public void Xrepaint() X{ X /* X * Start at the line currently at the top of the screen X * and redisplay the screen. X */ X prepaint(position(TOP)); X} X X/* X * Jump to the end of the file. X * It is more convenient to paint the screen backward, X * from the end of the file toward the beginning. X */ X public void Xjump_forw() X{ X POSITION pos; X X if (ch_end_seek()) X { X error("Cannot seek to end of file"); X return; X } X lastmark(); X pos = ch_tell(); X clear(); X pos_clear(); X add_back_pos(pos); X back(sc_height - 1, pos, 0, 0); X} X X/* X * Jump to line n in the file. X */ X public void Xjump_back(n) X register int n; X{ X register int c; X int nlines; X X /* X * This is done the slow way, by starting at the beginning X * of the file and counting newlines. X * X * {{ Now that we have line numbering (in linenum.c), X * we could improve on this by starting at the X * nearest known line rather than at the beginning. }} X */ X if (ch_seek((POSITION)0)) X { X /* X * Probably a pipe with beginning of file no longer buffered. X * If he wants to go to line 1, we do the best we can, X * by going to the first line which is still buffered. X */ X if (n <= 1 && ch_beg_seek() == 0) X jump_loc(ch_tell()); X error("Cannot get to beginning of file"); X return; X } X X /* X * Start counting lines. X */ X for (nlines = 1; nlines < n; nlines++) X { X while ((c = ch_forw_get()) != '\n') X if (c == EOI) X { X char message[40]; X sprintf(message, "File has only %d lines", X nlines-1); X error(message); X return; X } X } X X jump_loc(ch_tell()); X} X X/* X * Jump to a specified percentage into the file. X * This is a poor compensation for not being able to X * quickly jump to a specific line number. X */ X public void Xjump_percent(percent) X int percent; X{ X POSITION pos, len; X register int c; X X /* X * Determine the position in the file X * (the specified percentage of the file's length). X */ X if ((len = ch_length()) == NULL_POSITION) X { X error("Don't know length of file"); X return; X } X pos = (percent * len) / 100; X X /* X * Back up to the beginning of the line. X */ X if (ch_seek(pos) == 0) X { X while ((c = ch_back_get()) != '\n' && c != EOI) X ; X if (c == '\n') X (void) ch_forw_get(); X pos = ch_tell(); X } X jump_loc(pos); X} X X/* X * Jump to a specified position in the file. X */ X public void Xjump_loc(pos) X POSITION pos; X{ X register int nline; X POSITION tpos; X X if ((nline = onscreen(pos)) >= 0) X { X /* X * The line is currently displayed. X * Just scroll there. X */ X forw(nline, position(BOTTOM_PLUS_ONE), 1, 0); X return; X } X X /* X * Line is not on screen. X * Seek to the desired location. X */ X if (ch_seek(pos)) X { X error("Cannot seek to that position"); X return; X } X X /* X * See if the desired line is BEFORE the currently X * displayed screen. If so, then move forward far X * enough so the line we're on will be at the bottom X * of the screen, in order to be able to call back() X * to make the screen scroll backwards & put the line X * at the top of the screen. X * {{ This seems inefficient, but it's not so bad, X * since we can never move forward more than a X * screenful before we stop to redraw the screen. }} X */ X tpos = position(TOP); X if (tpos != NULL_POSITION && pos < tpos) X { X POSITION npos = pos; X /* X * Note that we can't forw_line() past tpos here, X * so there should be no EOI at this stage. X */ X for (nline = 0; npos < tpos && nline < sc_height - 1; nline++) X npos = forw_line(npos); X X if (npos < tpos) X { X /* X * More than a screenful back. X */ X lastmark(); X clear(); X pos_clear(); X add_back_pos(npos); X } X X /* X * Note that back() will repaint() if nline > back_scroll. X */ X back(nline, npos, 1, 0); X return; X } X /* X * Remember where we were; clear and paint the screen. X */ X lastmark(); X prepaint(pos); X} X X/* X * The table of marks. X * A mark is simply a position in the file. X */ X#define NMARKS (27) /* 26 for a-z plus one for quote */ X#define LASTMARK (NMARKS-1) /* For quote */ Xstatic POSITION marks[NMARKS]; X X/* X * Initialize the mark table to show no marks are set. X */ X public void Xinit_mark() X{ X int i; X X for (i = 0; i < NMARKS; i++) X marks[i] = NULL_POSITION; X} X X/* X * See if a mark letter is valid (between a and z). X */ X static int Xbadmark(c) X int c; X{ X if (c < 'a' || c > 'z') X { X error("Choose a letter between 'a' and 'z'"); X return (1); X } X return (0); X} X X/* X * Set a mark. X */ X public void Xsetmark(c) X int c; X{ X if (badmark(c)) X return; X marks[c-'a'] = position(TOP); X} X X public void Xlastmark() X{ X marks[LASTMARK] = position(TOP); X} X X/* X * Go to a previously set mark. X */ X public void Xgomark(c) X int c; X{ X POSITION pos; X X if (c == '\'') X pos = marks[LASTMARK]; X else if (badmark(c)) X return; X else X pos = marks[c-'a']; X X if (pos == NULL_POSITION) X error("mark not set"); X else X jump_loc(pos); X} X X/* X * Get the backwards scroll limit. X * Must call this function instead of just using the value of X * back_scroll, because the default case depends on sc_height and X * top_scroll, as well as back_scroll. X */ X public int Xget_back_scroll() X{ X if (back_scroll >= 0) X return (back_scroll); X if (top_scroll) X return (sc_height - 2); X return (sc_height - 1); X} X X/* X * Search for the n-th occurence of a specified pattern, X * either forward or backward. X */ X public void Xsearch(search_forward, pattern, n, wantmatch) X register int search_forward; X register char *pattern; X register int n; X int wantmatch; X{ X POSITION pos, linepos; X register char *p; X register char *q; X int linenum; X int linematch; X#if RECOMP X char *re_comp(); X char *errmsg; X#else X#if REGCMP X char *regcmp(); X static char *cpattern = NULL; X#else X static char lpbuf[100]; X static char *last_pattern = NULL; X#endif X#endif X X if (caseless && pattern != NULL) X { X /* X * For a caseless search, convert any uppercase X * in the pattern to lowercase. X */ X for (p = pattern; *p != '\0'; p++) X if (*p >= 'A' && *p <= 'Z') X *p += 'a' - 'A'; X } X#if RECOMP X X /* X * (re_comp handles a null pattern internally, X * so there is no need to check for a null pattern here.) X */ X if ((errmsg = re_comp(pattern)) != NULL) X { X error(errmsg); X return; X } X#else X#if REGCMP X if (pattern == NULL || *pattern == '\0') X { X /* X * A null pattern means use the previous pattern. X * The compiled previous pattern is in cpattern, so just use it. X */ X if (cpattern == NULL) X { X error("No previous regular expression"); X return; X } X } else X { X /* X * Otherwise compile the given pattern. X */ X char *s; X if ((s = regcmp(pattern, 0)) == NULL) X { X error("Invalid pattern"); X return; X } X if (cpattern != NULL) X free(cpattern); X cpattern = s; X } X#else X if (pattern == NULL || *pattern == '\0') X { X /* X * Null pattern means use the previous pattern. X */ X if (last_pattern == NULL) X { X error("No previous regular expression"); X return; X } X pattern = last_pattern; X } else X { X strcpy(lpbuf, pattern); X last_pattern = lpbuf; X } X#endif X#endif X X /* X * Figure out where to start the search. X */ X X if (position(TOP) == NULL_POSITION) X { X /* X * Nothing is currently displayed. X * Start at the beginning of the file. X * (This case is mainly for first_cmd searches, X * for example, "+/xyz" on the command line.) X */ X pos = (POSITION)0; X } else if (!search_forward) X { X /* X * Backward search: start just before the top line X * displayed on the screen. X */ X pos = position(TOP); X } else if (how_search == 0) X { X /* X * Start at the second real line displayed on the screen. X */ X pos = position(TOP); X do X pos = forw_raw_line(pos); X while (pos < position(TOP+1)); X } else if (how_search == 1) X { X /* X * Start just after the bottom line displayed on the screen. X */ X pos = position(BOTTOM_PLUS_ONE); X } else X { X /* X * Start at the second screen line displayed on the screen. X */ X pos = position(TOP_PLUS_ONE); X } X X if (pos == NULL_POSITION) X { X /* X * Can't find anyplace to start searching from. X */ X error("Nothing to search"); X return; X } X X linenum = find_linenum(pos); X for (;;) X { X /* X * Get lines until we find a matching one or X * until we hit end-of-file (or beginning-of-file X * if we're going backwards). X */ X if (sigs) X /* X * A signal aborts the search. X */ X return; X X if (search_forward) X { X /* X * Read the next line, and save the X * starting position of that line in linepos. X */ X linepos = pos; X pos = forw_raw_line(pos); X if (linenum != 0) X linenum++; X } else X { X /* X * Read the previous line and save the X * starting position of that line in linepos. X */ X pos = back_raw_line(pos); X linepos = pos; X if (linenum != 0) X linenum--; X } X X if (pos == NULL_POSITION) X { X /* X * We hit EOF/BOF without a match. X */ X error("Pattern not found"); X return; X } X X /* X * If we're using line numbers, we might as well X * remember the information we have now (the position X * and line number of the current line). X */ X if (linenums) X add_lnum(linenum, pos); X X if (caseless) X { X /* X * If this is a caseless search, convert X * uppercase in the input line to lowercase. X * While we're at it, remove any backspaces X * along with the preceeding char. X * This allows us to match text which is X * underlined or overstruck. X */ X for (p = q = line; *p != '\0'; p++, q++) X { X if (*p >= 'A' && *p <= 'Z') X /* Convert uppercase to lowercase. */ X *q = *p + 'a' - 'A'; X else if (q > line && *p == '\b') X /* Delete BS and preceeding char. */ X q -= 2; X else X /* Otherwise, just copy. */ X *q = *p; X } X } X X /* X * Test the next line to see if we have a match. X * This is done in a variety of ways, depending X * on what pattern matching functions are available. X */ X#if REGCMP X linematch = (regex(cpattern, line) != NULL); X#else X#if RECOMP X linematch = (re_exec(line) == 1); X#else X linematch = match(pattern, line); X#endif X#endif X /* X * We are successful if wantmatch and linematch are X * both true (want a match and got it), X * or both false (want a non-match and got it). X */ X if (((wantmatch && linematch) || (!wantmatch && !linematch)) && X --n <= 0) X /* X * Found the line. X */ X break; X } X X jump_loc(linepos); X} X X#if (!REGCMP) && (!RECOMP) X/* X * We have neither regcmp() nor re_comp(). X * We use this function to do simple pattern matching. X * It supports no metacharacters like *, etc. X */ X static int Xmatch(pattern, buf) X char *pattern, *buf; X{ X register char *pp, *lp; X X for ( ; *buf != '\0'; buf++) X { X for (pp = pattern, lp = buf; *pp == *lp; pp++, lp++) X if (*pp == '\0' || *lp == '\0') X break; X if (*pp == '\0') X return (1); X } X return (0); X} X#endif END_OF_FILE echo shar: Extracting \"ch.c\" sed "s/^X//" >'ch.c' <<'END_OF_FILE' X/* X * Low level character input from the input file. X * We use these special purpose routines which optimize moving X * both forward and backward from the current read pointer. X */ X X#include "less.h" X Xpublic int file = -1; /* File descriptor of the input file */ X X/* X * Pool of buffers holding the most recently used blocks of the input file. X */ X#define BUFSIZ 1024 Xstruct buf { X struct buf *next, *prev; X long block; X int datasize; X char data[BUFSIZ]; X}; Xpublic int nbufs; X X/* X * The buffer pool is kept as a doubly-linked circular list, X * in order from most- to least-recently used. X * The circular list is anchored by buf_anchor. X */ X#define END_OF_CHAIN ((struct buf *)&buf_anchor) X#define buf_head buf_anchor.next X#define buf_tail buf_anchor.prev X Xstatic struct { X struct buf *next, *prev; X} buf_anchor = { END_OF_CHAIN, END_OF_CHAIN }; X Xextern int clean_data; Xextern int ispipe; Xextern int autobuf; Xextern int cbufs; Xextern int sigs; X#if LOGFILE Xextern int logfile; X#endif X X/* X * Current position in file. X * Stored as a block number and an offset into the block. X */ Xstatic long ch_block; Xstatic int ch_offset; X X/* X * Length of file, needed if input is a pipe. X */ Xstatic POSITION ch_fsize; X X/* X * Number of bytes read, if input is standard input (a pipe). X */ Xstatic POSITION last_piped_pos; X X/* X * Get the character pointed to by the read pointer. X * ch_get() is a macro which is more efficient to call X * than fch_get (the function), in the usual case X * that the block desired is at the head of the chain. X */ X#define ch_get() ((buf_head->block == ch_block && \ X ch_offset < buf_head->datasize) ? \ X buf_head->data[ch_offset] : fch_get()) X static int Xfch_get() X{ X register struct buf *bp; X register int n; X register char *p; X POSITION pos; X X /* X * Look for a buffer holding the desired block. X */ X for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) X if (bp->block == ch_block) X { X if (ch_offset >= bp->datasize) X /* X * Need more data in this buffer. X */ X goto read_more; X /* X * On a pipe, we don't sort the buffers LRU X * because this can cause gaps in the buffers. X * For example, suppose we've got 12 1K buffers, X * and a 15K input stream. If we read the first 12K X * sequentially, then jump to line 1, then jump to X * the end, the buffers have blocks 0,4,5,6,..,14. X * If we then jump to line 1 again and try to X * read sequentially, we're out of luck when we X * get to block 1 (we'd get the "pipe error" below). X * To avoid this, we only sort buffers on a pipe X * when we actually READ the data, not when we X * find it already buffered. X */ X if (ispipe) X return (bp->data[ch_offset]); X goto found; X } X /* X * Block is not in a buffer. X * Take the least recently used buffer X * and read the desired block into it. X * If the LRU buffer has data in it, X * and autobuf is true, and input is a pipe, X * then try to allocate a new buffer first. X */ X if (autobuf && ispipe && buf_tail->block != (long)(-1)) X (void) ch_addbuf(1); X bp = buf_tail; X bp->block = ch_block; X bp->datasize = 0; X X read_more: X pos = (ch_block * BUFSIZ) + bp->datasize; X if (ispipe) X { X /* X * The data requested should be immediately after X * the last data read from the pipe. X */ X if (pos != last_piped_pos) X { X error("pipe error"); X quit(); X } X } else X lseek(file, pos, 0); X X /* X * Read the block. X * If we read less than a full block, we just return the X * partial block and pick up the rest next time. X */ X n = iread(file, &bp->data[bp->datasize], BUFSIZ - bp->datasize); X if (n == READ_INTR) X return (EOI); X if (n < 0) X { X error("read error"); X quit(); X } X if (ispipe) X last_piped_pos += n; X X#if LOGFILE X /* X * If we have a log file, write the new data to it. X */ X if (logfile >= 0 && n > 0) X write(logfile, &bp->data[bp->datasize], n); X#endif X X bp->datasize += n; X X /* X * Set an EOI marker in the buffered data itself. X * Then ensure the data is "clean": there are no X * extra EOI chars in the data and that the "meta" X * bit (the 0200 bit) is reset in each char. X */ X if (n == 0) X { X ch_fsize = pos; X bp->data[bp->datasize++] = EOI; X } X X if (!clean_data) X { X p = &bp->data[bp->datasize]; X while (--n >= 0) X { X *--p &= 0177; X if (*p == EOI) X *p = '@'; X } X } X X found: X if (buf_head != bp) X { X /* X * Move the buffer to the head of the buffer chain. X * This orders the buffer chain, most- to least-recently used. X */ X bp->next->prev = bp->prev; X bp->prev->next = bp->next; X X bp->next = buf_head; X bp->prev = END_OF_CHAIN; X buf_head->prev = bp; X buf_head = bp; X } X X if (ch_offset >= bp->datasize) X /* X * After all that, we still don't have enough data. X * Go back and try again. X */ X goto read_more; X X return (bp->data[ch_offset]); X} X X#if LOGFILE X/* X * Close the logfile. X * If we haven't read all of standard input into it, do that now. X */ X public void Xend_logfile() X{ X static int tried = 0; X X if (logfile < 0) X return; X if (!tried && ch_fsize == NULL_POSITION) X { X tried = 1; X ierror("finishing logfile"); X while (ch_forw_get() != EOI) X if (sigs) X break; X } X close(logfile); X logfile = -1; X} X X/* X * Start a log file AFTER less has already been running. X * Invoked from the - command; see toggle_option(). X * Write all the existing buffered data to the log file. X */ X public void Xsync_logfile() X{ X register struct buf *bp; X register int n; X long block; X long last_block; X X last_block = (last_piped_pos + BUFSIZ - 1) / BUFSIZ; X for (block = 0; block <= last_block; block++) X for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) X if (bp->block == block) X { X n = bp->datasize; X if (bp->data[n-1] == EOI) X n--; X write(logfile, bp->data, n); X break; X } X} X X#endif X X/* X * Determine if a specific block is currently in one of the buffers. X */ X static int Xbuffered(block) X long block; X{ X register struct buf *bp; X X for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) X if (bp->block == block) X return (1); X return (0); X} X X/* X * Seek to a specified position in the file. X * Return 0 if successful, non-zero if can't seek there. X */ X public int Xch_seek(pos) X register POSITION pos; X{ X long new_block; X X new_block = pos / BUFSIZ; X if (!ispipe || pos == last_piped_pos || buffered(new_block)) X { X /* X * Set read pointer. X */ X ch_block = new_block; X ch_offset = pos % BUFSIZ; X return (0); X } X return (1); X} X X/* X * Seek to the end of the file. X */ X public int Xch_end_seek() X{ X if (!ispipe) X return (ch_seek(ch_length())); X X /* X * Do it the slow way: read till end of data. X */ X while (ch_forw_get() != EOI) X if (sigs) X return (1); X return (0); X} X X/* X * Seek to the beginning of the file, or as close to it as we can get. X * We may not be able to seek there if input is a pipe and the X * beginning of the pipe is no longer buffered. X */ X public int Xch_beg_seek() X{ X register struct buf *bp, *firstbp; X X /* X * Try a plain ch_seek first. X */ X if (ch_seek((POSITION)0) == 0) X return (0); X X /* X * Can't get to position 0. X * Look thru the buffers for the one closest to position 0. X */ X firstbp = bp = buf_head; X if (bp == END_OF_CHAIN) X return (1); X while ((bp = bp->next) != END_OF_CHAIN) X if (bp->block < firstbp->block) X firstbp = bp; X ch_block = firstbp->block; X ch_offset = 0; X return (0); X} X X/* X * Return the length of the file, if known. X */ X public POSITION Xch_length() X{ X if (ispipe) X return (ch_fsize); X return ((POSITION)(lseek(file, (offset_t)0, 2))); X} X X/* X * Return the current position in the file. X */ X public POSITION Xch_tell() X{ X return (ch_block * BUFSIZ + ch_offset); X} X X/* X * Get the current char and post-increment the read pointer. X */ X public int Xch_forw_get() X{ X register int c; X X c = ch_get(); X if (c != EOI && ++ch_offset >= BUFSIZ) X { X ch_offset = 0; X ch_block ++; X } X return (c); X} X X/* X * Pre-decrement the read pointer and get the new current char. X */ X public int Xch_back_get() X{ X if (--ch_offset < 0) X { X if (ch_block <= 0 || (ispipe && !buffered(ch_block-1))) X { X ch_offset = 0; X return (EOI); X } X ch_offset = BUFSIZ - 1; X ch_block--; X } X return (ch_get()); X} X X/* X * Allocate buffers. X * Caller wants us to have a total of at least want_nbufs buffers. X * keep==1 means keep the data in the current buffers; X * otherwise discard the old data. X */ X public void Xch_init(want_nbufs, keep) X int want_nbufs; X int keep; X{ X register struct buf *bp; X char message[80]; X X cbufs = nbufs; X if (nbufs < want_nbufs && ch_addbuf(want_nbufs - nbufs)) X { X /* X * Cannot allocate enough buffers. X * If we don't have ANY, then quit. X * Otherwise, just report the error and return. X */ X sprintf(message, "cannot allocate %d buffers", X want_nbufs - nbufs); X error(message); X if (nbufs == 0) X quit(); X return; X } X X if (keep) X return; X X /* X * We don't want to keep the old data, X * so initialize all the buffers now. X */ X for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) X bp->block = (long)(-1); X last_piped_pos = (POSITION)0; X ch_fsize = NULL_POSITION; X (void) ch_seek((POSITION)0); X} X X/* X * Allocate some new buffers. X * The buffers are added to the tail of the buffer chain. X */ X static int Xch_addbuf(nnew) X int nnew; X{ X register struct buf *bp; X register struct buf *newbufs; X X /* X * We don't have enough buffers. X * Allocate some new ones. X */ X newbufs = (struct buf *) calloc(nnew, sizeof(struct buf)); X if (newbufs == NULL) X return (1); X X /* X * Initialize the new buffers and link them together. X * Link them all onto the tail of the buffer list. X */ X nbufs += nnew; X cbufs = nbufs; X for (bp = &newbufs[0]; bp < &newbufs[nnew]; bp++) X { X bp->next = bp + 1; X bp->prev = bp - 1; X bp->block = (long)(-1); X } X newbufs[nnew-1].next = END_OF_CHAIN; X newbufs[0].prev = buf_tail; X buf_tail->next = &newbufs[0]; X buf_tail = &newbufs[nnew-1]; X return (0); X} END_OF_FILE echo shar: Extracting \"position.c\" sed "s/^X//" >'position.c' <<'END_OF_FILE' X/* X * Routines dealing with the "position" table. X * This is a table which tells the position (in the input file) of the X * first char on each currently displayed line. X * X * {{ The position table is scrolled by moving all the entries. X * Would be better to have a circular table X * and just change a couple of pointers. }} X */ X X#include "less.h" X#include "position.h" X X#define NPOS 100 /* {{ sc_height must be less than NPOS }} */ Xstatic POSITION table[NPOS]; /* The position table */ X Xextern int sc_width, sc_height; X X/* X * Return the starting file position of a line displayed on the screen. X * The line may be specified as a line number relative to the top X * of the screen, but is usually one of these special cases: X * the top (first) line on the screen X * the second line on the screen X * the bottom line on the screen X * the line after the bottom line on the screen X */ X public POSITION Xposition(where) X int where; X{ X switch (where) X { X case BOTTOM: X where = sc_height - 2; X break; X case BOTTOM_PLUS_ONE: X where = sc_height - 1; X break; X case MIDDLE: X where = sc_height / 2; X } X return (table[where]); X} X X/* X * Add a new file position to the bottom of the position table. X */ X public void Xadd_forw_pos(pos) X POSITION pos; X{ X register int i; X X /* X * Scroll the position table up. X */ X for (i = 1; i < sc_height; i++) X table[i-1] = table[i]; X table[sc_height - 1] = pos; X} X X/* X * Add a new file position to the top of the position table. X */ X public void Xadd_back_pos(pos) X POSITION pos; X{ X register int i; X X /* X * Scroll the position table down. X */ X for (i = sc_height - 1; i > 0; i--) X table[i] = table[i-1]; X table[0] = pos; X} X X/* X * Initialize the position table, done whenever we clear the screen. X */ X public void Xpos_clear() X{ X register int i; X X for (i = 0; i < sc_height; i++) X table[i] = NULL_POSITION; X} X X/* X * See if the byte at a specified position is currently on the screen. X * Check the position table to see if the position falls within its range. X * Return the position table entry if found, -1 if not. X */ X public int Xonscreen(pos) X POSITION pos; X{ X register int i; X X if (pos < table[0]) X return (-1); X for (i = 1; i < sc_height; i++) X if (pos < table[i]) X return (i-1); X return (-1); X} END_OF_FILE echo shar: Extracting \"input.c\" sed "s/^X//" >'input.c' <<'END_OF_FILE' X/* X * High level routines dealing with getting lines of input X * from the file being viewed. X * X * When we speak of "lines" here, we mean PRINTABLE lines; X * lines processed with respect to the screen width. X * We use the term "raw line" to refer to lines simply X * delimited by newlines; not processed with respect to screen width. X */ X X#include "less.h" X Xextern int squeeze; Xextern int sigs; Xextern char *line; X X/* X * Get the next line. X * A "current" position is passed and a "new" position is returned. X * The current position is the position of the first character of X * a line. The new position is the position of the first character X * of the NEXT line. The line obtained is the line starting at curr_pos. X */ X public POSITION Xforw_line(curr_pos) X POSITION curr_pos; X{ X POSITION new_pos; X register int c; X X if (curr_pos == NULL_POSITION || ch_seek(curr_pos)) X return (NULL_POSITION); X X c = ch_forw_get(); X if (c == EOI) X return (NULL_POSITION); X X prewind(); X for (;;) X { X if (sigs) X return (NULL_POSITION); X if (c == '\n' || c == EOI) X { X /* X * End of the line. X */ X new_pos = ch_tell(); X break; X } X X /* X * Append the char to the line and get the next char. X */ X if (pappend(c)) X { X /* X * The char won't fit in the line; the line X * is too long to print in the screen width. X * End the line here. X */ X new_pos = ch_tell() - 1; X break; X } X c = ch_forw_get(); X } X (void) pappend('\0'); X X if (squeeze && *line == '\0') X { X /* X * This line is blank. X * Skip down to the last contiguous blank line X * and pretend it is the one which we are returning. X */ X while ((c = ch_forw_get()) == '\n') X if (sigs) X return (NULL_POSITION); X if (c != EOI) X (void) ch_back_get(); X new_pos = ch_tell(); X } X X return (new_pos); X} X X/* X * Get the previous line. X * A "current" position is passed and a "new" position is returned. X * The current position is the position of the first character of X * a line. The new position is the position of the first character X * of the PREVIOUS line. The line obtained is the one starting at new_pos. X */ X public POSITION Xback_line(curr_pos) X POSITION curr_pos; X{ X POSITION new_pos, begin_new_pos; X int c; X X if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 || X ch_seek(curr_pos-1)) X return (NULL_POSITION); X X if (squeeze) X { X /* X * Find out if the "current" line was blank. X */ X (void) ch_forw_get(); /* Skip the newline */ X c = ch_forw_get(); /* First char of "current" line */ X (void) ch_back_get(); /* Restore our position */ X (void) ch_back_get(); X X if (c == '\n') X { X /* X * The "current" line was blank. X * Skip over any preceeding blank lines, X * since we skipped them in forw_line(). X */ X while ((c = ch_back_get()) == '\n') X if (sigs) X return (NULL_POSITION); X if (c == EOI) X return (NULL_POSITION); X (void) ch_forw_get(); X } X } X X /* X * Scan backwards until we hit the beginning of the line. X */ X for (;;) X { X if (sigs) X return (NULL_POSITION); X c = ch_back_get(); X if (c == '\n') X { X /* X * This is the newline ending the previous line. X * We have hit the beginning of the line. X */ X new_pos = ch_tell() + 1; X break; X } X if (c == EOI) X { X /* X * We have hit the beginning of the file. X * This must be the first line in the file. X * This must, of course, be the beginning of the line. X */ X new_pos = ch_tell(); X break; X } X } X X /* X * Now scan forwards from the beginning of this line. X * We keep discarding "printable lines" (based on screen width) X * until we reach the curr_pos. X * X * {{ This algorithm is pretty inefficient if the lines X * are much longer than the screen width, X * but I don't know of any better way. }} X */ X if (ch_seek(new_pos)) X return (NULL_POSITION); X loop: X begin_new_pos = new_pos; X prewind(); X X do X { X c = ch_forw_get(); X if (c == EOI || sigs) X return (NULL_POSITION); X new_pos++; X if (c == '\n') X break; X if (pappend(c)) X { X /* X * Got a full printable line, but we haven't X * reached our curr_pos yet. Discard the line X * and start a new one. X */ X (void) pappend('\0'); X (void) ch_back_get(); X new_pos--; X goto loop; X } X } while (new_pos < curr_pos); X X (void) pappend('\0'); X X return (begin_new_pos); X} END_OF_FILE echo shar: Extracting \"linenum.c\" sed "s/^X//" >'linenum.c' <<'END_OF_FILE' X/* X * Code to handle displaying line numbers. X * X * Finding the line number of a given file position is rather tricky. X * We don't want to just start at the beginning of the file and X * count newlines, because that is slow for large files (and also X * wouldn't work if we couldn't get to the start of the file; e.g. X * if input is a long pipe). X * X * So we use the function add_lnum to cache line numbers. X * We try to be very clever and keep only the more interesting X * line numbers when we run out of space in our table. A line X * number is more interesting than another when it is far from X * other line numbers. For example, we'd rather keep lines X * 100,200,300 than 100,101,300. 200 is more interesting than X * 101 because 101 can be derived very cheaply from 100, while X * 200 is more expensive to derive from 100. X * X * The function currline() returns the line number of a given X * position in the file. As a side effect, it calls add_lnum X * to cache the line number. Therefore currline is occasionally X * called to make sure we cache line numbers often enough. X */ X X#include "less.h" X#include "position.h" X X/* X * Structure to keep track of a line number and the associated file position. X * A doubly-linked circular list of line numbers is kept ordered by line number. X */ Xstruct linenum X{ X struct linenum *next; /* Link to next in the list */ X struct linenum *prev; /* Line to previous in the list */ X POSITION pos; /* File position */ X POSITION gap; /* Gap between prev and next */ X int line; /* Line number */ X}; X/* X * "gap" needs some explanation: the gap of any particular line number X * is the distance between the previous one and the next one in the list. X * ("Distance" means difference in file position.) In other words, the X * gap of a line number is the gap which would be introduced if this X * line number were deleted. It is used to decide which one to replace X * when we have a new one to insert and the table is full. X */ X X#define NPOOL 50 /* Size of line number pool */ X X#define LONGTIME (2) /* In seconds */ X Xpublic int lnloop = 0; /* Are we in the line num loop? */ X Xstatic struct linenum anchor; /* Anchor of the list */ Xstatic struct linenum *freelist; /* Anchor of the unused entries */ Xstatic struct linenum pool[NPOOL]; /* The pool itself */ Xstatic struct linenum *spare; /* We always keep one spare entry */ X Xextern int linenums; Xextern int sigs; X X/* X * Initialize the line number structures. X */ X public void Xclr_linenum() X{ X register struct linenum *p; X X /* X * Put all the entries on the free list. X * Leave one for the "spare". X */ X for (p = pool; p < &pool[NPOOL-2]; p++) X p->next = p+1; X pool[NPOOL-2].next = NULL; X freelist = pool; X X spare = &pool[NPOOL-1]; X X /* X * Initialize the anchor. X */ X anchor.next = anchor.prev = &anchor; X anchor.gap = 0; X anchor.pos = (POSITION)0; X anchor.line = 1; X} X X/* X * Calculate the gap for an entry. X */ X static void Xcalcgap(p) X register struct linenum *p; X{ X /* X * Don't bother to compute a gap for the anchor. X * Also don't compute a gap for the last one in the list. X * The gap for that last one should be considered infinite, X * but we never look at it anyway. X */ X if (p == &anchor || p->next == &anchor) X return; X p->gap = p->next->pos - p->prev->pos; X} X X/* X * Add a new line number to the cache. X * The specified position (pos) should be the file position of the X * FIRST character in the specified line. X */ X public void Xadd_lnum(line, pos) X int line; X POSITION pos; X{ X register struct linenum *p; X register struct linenum *new; X register struct linenum *nextp; X register struct linenum *prevp; X register POSITION mingap; X X /* X * Find the proper place in the list for the new one. X * The entries are sorted by position. X */ X for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next) X if (p->line == line) X /* We already have this one. */ X return; X nextp = p; X prevp = p->prev; X X if (freelist != NULL) X { X /* X * We still have free (unused) entries. X * Use one of them. X */ X new = freelist; X freelist = freelist->next; X } else X { X /* X * No free entries. X * Use the "spare" entry. X */ X new = spare; X spare = NULL; X } X X /* X * Fill in the fields of the new entry, X * and insert it into the proper place in the list. X */ X new->next = nextp; X new->prev = prevp; X new->pos = pos; X new->line = line; X X nextp->prev = new; X prevp->next = new; X X /* X * Recalculate gaps for the new entry and the neighboring entries. X */ X calcgap(new); X calcgap(nextp); X calcgap(prevp); X X if (spare == NULL) X { X /* X * We have used the spare entry. X * Scan the list to find the one with the smallest X * gap, take it out and make it the spare. X * We should never remove the last one, so stop when X * we get to p->next == &anchor. This also avoids X * looking at the gap of the last one, which is X * not computed by calcgap. X */ X mingap = anchor.next->gap; X for (p = anchor.next; p->next != &anchor; p = p->next) X { X if (p->gap <= mingap) X { X spare = p; X mingap = p->gap; X } X } X spare->next->prev = spare->prev; X spare->prev->next = spare->next; X } X} X X/* X * If we get stuck in a long loop trying to figure out the X * line number, print a message to tell the user what we're doing. X */ X static void Xlongloopmessage() X{ X ierror("Calculating line numbers"); X /* X * Set the lnloop flag here, so if the user interrupts while X * we are calculating line numbers, the signal handler will X * turn off line numbers (linenums=0). X */ X lnloop = 1; X} X X/* X * Find the line number associated with a given position. X * Return 0 if we can't figure it out. X */ X public int Xfind_linenum(pos) X POSITION pos; X{ X register struct linenum *p; X register int lno; X register int loopcount; X POSITION cpos; X#if GET_TIME X long startime; X#endif X X if (!linenums) X /* X * We're not using line numbers. X */ X return (0); X if (pos == NULL_POSITION) X /* X * Caller doesn't know what he's talking about. X */ X return (0); X if (pos == (POSITION)0) X /* X * Beginning of file is always line number 1. X */ X return (1); X X /* X * Find the entry nearest to the position we want. X */ X for (p = anchor.next; p != &anchor && p->pos < pos; p = p->next) X continue; X if (p->pos == pos) X /* Found it exactly. */ X return (p->line); X X /* X * This is the (possibly) time-consuming part. X * We start at the line we just found and start X * reading the file forward or backward till we X * get to the place we want. X * X * First decide whether we should go forward from the X * previous one or backwards from the next one. X * The decision is based on which way involves X * traversing fewer bytes in the file. X */ X flush(); X#if GET_TIME X startime = get_time(); X#endif X if (p == &anchor || pos - p->prev->pos < p->pos - pos) X { X /* X * Go forward. X */ X p = p->prev; X if (ch_seek(p->pos)) X return (0); X loopcount = 0; X for (lno = p->line, cpos = p->pos; cpos < pos; lno++) X { X /* X * Allow a signal to abort this loop. X */ X cpos = forw_raw_line(cpos); X if (sigs || cpos == NULL_POSITION) X return (0); X#if GET_TIME X if (loopcount >= 0 && ++loopcount > 100) X { X loopcount = 0; X if (get_time() >= startime + LONGTIME) X { X longloopmessage(); X loopcount = -1; X } X } X#else X if (loopcount >= 0 && ++loopcount > LONGLOOP) X { X longloopmessage(); X loopcount = -1; X } X#endif X } X lnloop = 0; X /* X * If the given position is not at the start of a line, X * make sure we return the correct line number. X */ X if (cpos > pos) X lno--; X } else X { X /* X * Go backward. X */ X if (ch_seek(p->pos)) X return (0); X loopcount = 0; X for (lno = p->line, cpos = p->pos; cpos > pos; lno--) X { X /* X * Allow a signal to abort this loop. X */ X cpos = back_raw_line(cpos); X if (sigs || cpos == NULL_POSITION) X return (0); X#if GET_TIME X if (loopcount >= 0 && ++loopcount > 100) X { X loopcount = 0; X if (get_time() >= startime + LONGTIME) X { X longloopmessage(); X loopcount = -1; X } X } X#else X if (loopcount >= 0 && ++loopcount > LONGLOOP) X { X longloopmessage(); X loopcount = -1; X } X#endif X } X lnloop = 0; X } X X /* X * We might as well cache it. X */ X add_lnum(lno, cpos); X return (lno); X} X X/* X * Return the line number of the "current" line. X * The argument "where" tells which line is to be considered X * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc). X */ X public int Xcurrline(where) X int where; X{ X POSITION pos; X X pos = position(where); X if (pos == NULL_POSITION) X pos = ch_length(); X return (find_linenum(pos)); X} X X#if DEBUG_STUFF Xdebug() X{ X register struct linenum *p; X char buf[20]; X X lower_left(); X clear_eol(); X for (p = anchor.next; p != &anchor; p = p->next) X { X sprintf(buf, "%d-%d ", p->line, p->pos); X putstr(buf); X } X putstr("\n"); X error("DEBUG"); X} X#endif /*DEBUG_STUFF*/ END_OF_FILE