mark@nsc-pdc.UUCP (Mark Nudelman) (07/10/85)
This is part 2 of 2 of the less distribution. To install, delete everything down to the cut line below, put the file in an empty directory, and run sh on the file. Mark Nudelman nsc!nsc-pdc!mark National Semiconductor tektronix!reed!nsc-pdc!mark #!/bin/sh-----cut here-----cut here-----cut here-----cut here----- # shar: Shell Archiver # Run the following text with /bin/sh to create: # main.c # output.c # position.c # prim.c # screen.c # signal.c # ttyin.c # version.c # funcs.h # less.h # position.h # mkfuncs.awk cat - << \SHAR_EOF > main.c /* * Entry point, initialization, miscellaneous routines. */ #include "less.h" #include "position.h" #include <setjmp.h> public int pipe; public jmp_buf main_loop; public char * first_cmd; public int new_file; /* * Command line options. */ public int p_nbufs, f_nbufs; /* Number of buffers. There are two values, one used for input from a pipe and the other for input from a file. */ public int clean_data; /* Can we assume the data is "clean"? (That is, free of nulls, etc) */ public int quiet; /* Should we suppress the audible bell? */ public int top_search; /* Should forward searches start at the top of the screen? (alternative is bottom) */ public int pr_type; /* Type of prompt (short, medium, long) */ public int bs_mode; /* How to process backspaces */ public int know_dumb; /* Don't complain about dumb terminals */ /* * Defaults for command line options. */ #define NBUFS_PIPED 12 /* # buffers if input is a pipe */ #define NBUFS_NON_PIPED 5 /* # buffers if input is a file */ #define CLEAN_DATA 0 /* Don't assume data is clean */ #define TOP_SEARCH 1 /* Start forw search from top of screen */ #define KNOW_DUMB 0 /* Warn me if I have a dumb terminal */ #define PR_TYPE PR_SHORT #define BS_MODE BS_UNDERLINE extern int file; extern int nbufs; extern int sigs; static char current_file[128]; static int ac; static char **av; static int curr_ac; /* * Edit a new file. * Filename "-" means standard input. * No filename means the "current" file, from the command line. */ public void edit(filename) char *filename; { register int f; char message[100]; if (filename == NULL || *filename == '\0') { if (curr_ac >= ac) { error("No current file"); return; } filename = av[curr_ac]; } if (strcmp(filename, "-") == 0) f = 0; /* Standard input */ else if ((f = open(filename, 0)) < 0) { sprintf(message, "Cannot open %.60s", filename); error(message); return; } if (isatty(f)) { /* * Not really necessary to call this an error, * but if the control terminal (for commands) * and the input file (for data) are the same, * we get weird results at best. */ error("Can't take input from a terminal"); if (f > 0) close(f); return; } /* * Close the current input file and set up to use the new one. */ if (file > 0) close(file); new_file = 1; file = f; strcpy(current_file, filename); pipe = (f == 0); ch_init( (pipe) ? p_nbufs : f_nbufs ); if (first_cmd == NULL) /* Display the first screen. */ jump_back(1); else /* Indicate there is nothing yet on the screen. */ pos_clear(); } /* * Construct a message suitable for printing by the "=" command. * Also used by verbose prompting (-m) * and as the first prompt of a new file. * The second argument is a bit mask describing * what to include in the message. * Truncate the file name if it is too long to print. */ public char * eq_message(width, what) int width; int what; { POSITION pos, len; int namelen, tlen; int i; char buf2[200]; static char buffer[200]; if (pipe) { /* * To avoid ending up with an empty message, * we force inclusion of the byte offset. */ what |= MBYTE; } pos = position(BOTTOM_PLUS_ONE); len = ch_length(); /* * Build the message in "buffer", * temporarily ignoring the width limit. * {{ buffer is supposed to be big enough that it won't overflow }} */ if ((what & MNAME) && !pipe) { strcpy(buffer, current_file); namelen = strlen(buffer); } else { buffer[0] = '\0'; namelen = 0; } if ((what & MOF) && (ac > 1)) { sprintf(buf2, " (file %d of %d)", curr_ac+1, ac); strcat(buffer, buf2); } if (what & MBYTE) { if (pos == NULL_POSITION) sprintf(buf2, " at bottom"); else sprintf(buf2, " at byte %d", pos); strcat(buffer, buf2); if (len > 0) { sprintf(buf2, "/%d", len); strcat(buffer, buf2); } } if ((what & MPCT) && (len > 0) && (pos != NULL_POSITION)) { sprintf(buf2, " (%d%%)", (100 * pos) / len); strcat(buffer, buf2); } /* * If we have exceeded the width limit, * shift the last part of the message to the left, * overwriting the tail of the filename, * and append ".." to the end of the filename. */ if ((tlen = strlen(buffer)) > width) { int shift = tlen - width; for (i = namelen+1; i <= tlen; i++) buffer[i-shift] = buffer[i]; buffer[namelen-shift] = buffer[namelen-shift-1] = '.'; } return (buffer); } /* * Edit the next file in the command line list. */ public void next_file(n) int n; { if (curr_ac + n >= ac) error("No (N-th) next file"); else edit(av[curr_ac += n]); } /* * Edit the previous file in the command line list. */ public void prev_file(n) int n; { if (curr_ac - n < 0) error("No (N-th) previous file"); else edit(av[curr_ac -= n]); } /* * Toggle command line flags from within the program. * Used by the "-" command. */ public void toggle_flag(c) int c; { register char *s; char message[100]; switch (c) { case 'c': if (clean_data = !clean_data) s = "Assume data is clean"; else s = "Don't assume data is clean"; break; case 'q': quiet = (quiet == LITTLE_QUIET) ? NOT_QUIET : LITTLE_QUIET; goto qflag; case 'Q': quiet = (quiet == VERY_QUIET) ? NOT_QUIET : VERY_QUIET; qflag: switch (quiet) { case NOT_QUIET: s = "Ring the bell for errors AND at eof/bof"; break; case LITTLE_QUIET: s = "Ring the bell for errors but not at eof/bof"; break; case VERY_QUIET: s = "Never ring the bell"; break; } break; case 'm': pr_type = (pr_type == PR_MEDIUM) ? PR_SHORT : PR_MEDIUM; goto mflag; case 'M': pr_type = (pr_type == PR_LONG) ? PR_SHORT : PR_LONG; mflag: switch (pr_type) { case PR_SHORT: s = "Prompt with a colon"; break; case PR_MEDIUM: s = "Prompt with a message"; break; case PR_LONG: s = "Prompt with a verbose message"; break; } break; case 't': if (top_search = !top_search) s = "Forward search starts from top of screen"; else s = "Forward search starts from bottom of screen"; break; case 'u': bs_mode = (bs_mode == BS_NORMAL) ? BS_UNDERLINE : BS_NORMAL; goto uflag; case 'U': bs_mode = (bs_mode == BS_CONTROL) ? BS_UNDERLINE : BS_CONTROL; uflag: switch (bs_mode) { case BS_UNDERLINE: s = "Underlined text displayed in underline mode"; break; case BS_NORMAL: s = "All backspaces cause overstrike"; break; case BS_CONTROL: s = "Backspaces print as ^H"; break; } break; case 'b': sprintf(message, "Currently using %d buffers", nbufs); s = message; break; default: if (control_char(c)) sprintf(message, "\"-^%c\"", carat_char(c)); else sprintf(message, "\"-%c\"", c); strcat(message, ": no such flag. Use one of \"bctmMuUqQ\""); s = message; break; } error(s); } /* * Scan an argument (either from command line or from LESS environment * variable) and process it. */ static void arg_scan(s) register char *s; { register int n; char bflag; if (s == NULL) return; while (*s != '\0') switch (*s++) { case 'b': bflag = '\0'; if (*s == 'f' || *s == 'p') { bflag = *s; ++s; } if (*s < '0' || *s > '9') { printf("-b requires number: \"-b<n>\"\n"); exit(1); /*NOTREACHED*/ } n = 0; while (*s >= '0' && *s <= '9') n = 10 * n + *s++ - '0'; if (n <= 0 || n > 200) { printf("invalid number (%d) for -b\n", n); exit(1); /*NOTREACHED*/ } switch (bflag) { case 'p': p_nbufs = n; break; case 'f': f_nbufs = n; break; case '\0': p_nbufs = f_nbufs = n; break; } break; case 'c': clean_data = !CLEAN_DATA; break; case 'd': know_dumb = !KNOW_DUMB; break; case 'q': quiet = LITTLE_QUIET; break; case 'Q': quiet = VERY_QUIET; break; case 'm': pr_type = PR_MEDIUM; break; case 'M': pr_type = PR_LONG; break; case 't': top_search = !TOP_SEARCH; break; case 'u': bs_mode = BS_NORMAL; break; case 'U': bs_mode = BS_CONTROL; break; case '+': first_cmd = s; return; case '-': case ' ': case '\t': break; default: printf("\"-%c\": invalid flag\n", *--s); exit(1); /*NOTREACHED*/ } } /* * Entry point. */ main(argc, argv) int argc; char *argv[]; { char *getenv(); /* * Initialize command line flags to their defaults. */ p_nbufs = NBUFS_PIPED; f_nbufs = NBUFS_NON_PIPED; clean_data = CLEAN_DATA; quiet = NOT_QUIET; top_search = TOP_SEARCH; pr_type = PR_TYPE; bs_mode = BS_MODE; first_cmd = NULL; know_dumb = KNOW_DUMB; /* * Process command line arguments and LESS environment arguments. * Command line arguments override environment arguments. */ arg_scan(getenv("LESS")); argv++; while ((--argc > 0) && (**argv == '-' || **argv == '+')) arg_scan(*argv++); /* * Set up list of files to be examined. */ ac = argc; av = argv; curr_ac = 0; /* * Set up terminal, etc. */ raw_mode(1); get_term(); open_getc(); init(); if (setjmp(main_loop) == 0) { init_signals(); /* * Select the first file to examine. */ if (ac < 1) edit("-"); /* Standard input */ else { /* * Try all the files named as command arguments. * We are simply looking for one which can be * opened without error. */ do { edit((char *)NULL); if (file >= 0) /* We can open this file. */ break; putc('\n'); flush(); } while (++curr_ac < ac); } } if (file >= 0) commands(); quit(); } /* * Exit the program. */ public void quit() { /* * Put cursor at bottom left corner, clear the line, * reset the terminal modes, and exit. */ lower_left(); clear_eol(); deinit(); flush(); raw_mode(0); exit(0); } SHAR_EOF cat - << \SHAR_EOF > output.c /* * High level routines dealing with the output to the screen. */ #include "less.h" extern int sigs; extern int sc_width, sc_height; extern int ul_width, ue_width; extern char *line; /* * Display the line which is in the line buffer. */ public void put_line() { register char *p; register int c; register int column; extern int auto_wrap, ignaw; if (sigs) /* * Don't output if a signal is pending. */ return; if (line == NULL) line = "~"; column = 0; for (p = line; *p != '\0'; p++) { c = *p; if (c == UL_CHAR) { ul_enter(); column += ul_width; } else if (c == UE_CHAR) { ul_exit(); column += ue_width; } else if (c & 0200) { putc('^'); putc(c & 0177); column += 2; } else if (c == '\b') { putbs(); column--; } else { putc(c); column++; } } if (column < sc_width || !auto_wrap || ignaw) putc('\n'); } /* * Is a given character a "control" character? * {{ ASCII DEPENDENT }} */ public int control_char(c) int c; { return (c < ' ' || c == '\177'); } /* * Return the printable character used to identify a control character * (printed after a carat; e.g. '\3' => "^C"). * {{ ASCII DEPENDENT }} */ public int carat_char(c) int c; { return ((c == '\177') ? '?' : (c | 0100)); } static char obuf[1024]; static char *ob = obuf; /* * Flush buffered output. */ public void flush() { write(1, obuf, ob-obuf); ob = obuf; } /* * Discard buffered output. */ public void dropout() { ob = obuf; } /* * Output a character. */ public void putc(c) int c; { if (ob >= &obuf[sizeof(obuf)]) flush(); *ob++ = c; } /* * Output a string. */ public void puts(s) register char *s; { while (*s != '\0') putc(*s++); } /* * Output a message in the lower left corner of the screen * and wait for carriage return. */ static char return_to_continue[] = " (press RETURN)"; public void error(s) char *s; { register int c; lower_left(); clear_eol(); so_enter(); puts(s); puts(return_to_continue); so_exit(); while ((c = getc()) != '\n' && c != '\r') bell(); } public int error_width() { /* * Allow 2 extra spaces at each end for terminals that eat spaces * to enter/exit standout mode. * Also don't use the last position, because some terminals * will scroll if you write in the last char of the last line. * * {{ Could use termcap "sg" to determine how many spaces * the terminal eats for enter/exit standout. * Maybe something in termcap also tells whether terminal * scrolls if you write in lower right corner? * Probably not worth it to gain the (max) 5 extra spaces. }} */ return (sc_width - sizeof(return_to_continue) - 5); } SHAR_EOF cat - << \SHAR_EOF > position.c /* * Routines dealing with the "position" table. * This is a table which tells the position (in the input file) of the * first char on each currently displayed line. * * {{ The position table is scrolled by moving all the entries. * Would be better to have a circular table * and just change a couple of pointers. }} */ #include "less.h" #include "position.h" #define NPOS 100 /* {{ sc_height must be less than NPOS }} */ static POSITION table[NPOS]; /* The position table */ extern int sc_width, sc_height; /* * Return the position of one of: * the top (first) line on the screen * the second line on the screen * the bottom line on the screen * the line after the bottom line on the screen */ public POSITION position(where) int where; { register int n; switch (where) { case TOP: n = 0; break; case TOP_PLUS_ONE: n = 1; break; case BOTTOM: n = sc_height - 2; break; case BOTTOM_PLUS_ONE: n = sc_height - 1; break; } return (table[n]); } /* * Add a new file position to the bottom of the position table. */ public void add_forw_pos(pos) POSITION pos; { register int i; /* * Scroll the position table up. */ for (i = 1; i < sc_height; i++) table[i-1] = table[i]; table[sc_height - 1] = pos; } /* * Add a new file position to the top of the position table. */ public void add_back_pos(pos) POSITION pos; { register int i; /* * Scroll the position table down. */ for (i = sc_height - 1; i > 0; i--) table[i] = table[i-1]; table[0] = pos; } /* * Initialize the position table, done whenever we clear the screen. */ public void pos_clear() { register int i; for (i = 0; i < sc_height; i++) table[i] = NULL_POSITION; } SHAR_EOF cat - << \SHAR_EOF > prim.c /* * Primitives for displaying the file on the screen. */ #include "less.h" #include "position.h" extern int quiet; extern int top_search; extern int sc_width, sc_height; extern int sigs; extern char *line; /* * Display n lines, scrolling forward, * starting at position pos in the input file. */ static void forw(n, pos, force) register int n; POSITION pos; int force; { while (--n >= 0) { /* * Read the next line of input. */ pos = forw_line(pos); if (line == NULL) { /* * End of file: stop here unless the top line * is still empty, or "force" is true. */ if (!force && position(TOP) != NULL_POSITION) { if (quiet == NOT_QUIET) bell(); else vbell(); break; } pos = NULL_POSITION; } /* * Add the position of the next line to the position table. * Display the current line on the screen. */ add_forw_pos(pos); lower_left(); put_line(); } } /* * Display n lines, scrolling backward. */ static void back(n, pos, force) register int n; POSITION pos; int force; { while (--n >= 0) { /* * Get the previous line of input. */ pos = back_line(pos); if (line == NULL) { /* * Beginning of file: stop here unless "force" is true. */ if (!force) { if (quiet == NOT_QUIET) bell(); else vbell(); break; } pos = NULL_POSITION; } /* * Add the position of the previous line to the position table. * Display the line on the screen. */ add_back_pos(pos); home(); add_line(); put_line(); } } /* * Display n more lines, forward. * Start just after the line currently displayed at the bottom of the screen. */ public void forward(n) int n; { POSITION pos; pos = position(BOTTOM_PLUS_ONE); if (pos == NULL_POSITION) return; forw(n, pos, 0); } /* * Display n more lines, backward. * Start just before the line currently displayed at the top of the screen. */ public void backward(n) int n; { POSITION pos; pos = position(TOP); if (pos == NULL_POSITION) /* * This will almost never happen, * because the top line is almost never empty. */ return; back(n, pos, 0); } /* * Repaint the screen. */ public void repaint() { POSITION pos; /* * Start at the line currently at the top of the screen * and redisplay the screen. */ pos = position(TOP); clear(); pos_clear(); add_forw_pos(pos); forw(sc_height - 1, pos, 0); } /* * Jump to the end of the file. * It is more convenient to paint the screen backward, * from the end of the file toward the beginning. */ public void jump_forw() { POSITION pos; if (ch_end_seek()) { error("Cannot seek to end of file"); return; } pos = ch_tell(); clear(); pos_clear(); add_back_pos(pos); back(sc_height - 1, pos, 0); } /* * Jump to line n in the file. */ public void jump_back(n) register int n; { register int c; POSITION pos; /* * This is done the slow way, by starting at the beginning * of the file and counting newlines. */ if (ch_seek((POSITION)0)) { /* * Probably a pipe with beginning of file no longer buffered. */ error("Cannot get to beginning of file"); return; } /* * Start counting lines. */ while (--n > 0) { while ((c = ch_forw_get()) != '\n') if (c == EOF) { error("File is not that long"); /* {{ Maybe tell him how long it is? }} */ return; } } /* * Finally found the place to start. * Clear and redisplay the screen from there. * * {{ We *could* figure out if the new position is * close enough to just scroll there without clearing * the screen, but it's not worth it. }} */ pos = ch_tell(); clear(); pos_clear(); add_forw_pos(pos); forw(sc_height - 1, pos, 0); } /* * Jump to a specified percentage into the file. * This is a poor compensation for not being able to * quickly jump to a specific line number. */ public void jump_percent(percent) int percent; { POSITION pos, len; int c; /* * Determine the position in the file * (the specified percentage of the file's length). */ if ((len = ch_length()) == NULL_POSITION) { error("Don't know length of file"); return; } pos = (percent * len) / 100; /* * Seek to the specified percentage into the file. */ if (ch_seek(pos)) { error("Cannot seek to that position"); return; } /* * Back up to the beginning of the current line. */ while ((c = ch_back_get()) != '\n' && c != EOF) ; if (c == '\n') (void) ch_forw_get(); pos = ch_tell(); /* * Clear and paint the screen. */ clear(); pos_clear(); add_forw_pos(pos); forw(sc_height - 1, pos, 0); } /* * Search for the n-th occurence of a specified pattern, * either forward (direction == '/'), or backwards (direction == '?'). */ public void search(direction, pattern, n) int direction; char *pattern; register int n; { register int search_forward = (direction == '/'); POSITION pos, new_pos; register int lines_from_top; #if RECOMP char *re_comp(); char *errmsg; /* * (re_comp handles a null pattern internally, * so there is no need to check for a null pattern here.) */ if ((errmsg = re_comp(pattern)) != NULL) { error(errmsg); return; } #else #if REGCMP char *regcmp(); static char *cpattern = NULL; if (pattern == NULL || *pattern == '\0') { /* * A null pattern means use the previous pattern. * The compiled previous pattern is in cpattern, so just use it. */ if (cpattern == NULL) { error("No previous regular expression"); return; } } else { /* * Otherwise compile the given pattern. */ char *s; if ((s = regcmp(pattern, 0)) == NULL) { error("Invalid pattern"); return; } if (cpattern != NULL) free(cpattern); cpattern = s; } #else static char lpbuf[100]; static char *last_pattern = NULL; if (pattern == NULL || *pattern == '\0') { /* * Null pattern means use the previous pattern. */ if (last_pattern == NULL) { error("No previous regular expression"); return; } pattern = last_pattern; } else { strcpy(lpbuf, pattern); last_pattern = lpbuf; } #endif #endif /* * Figure out where to start the search. */ if (position(TOP) == NULL_POSITION) { /* * Nothing is currently displayed. * Start at the beginning of the file. * (This case is mainly for first_cmd searches, * for example, "+/xyz" on the command line.) */ pos = (POSITION)0; lines_from_top = sc_height + 1; } else if (!search_forward) { /* * Backward search: start just before the top line * displayed on the screen. */ pos = position(TOP); lines_from_top = 0; } else if (top_search) { /* * Forward search and "start from top". * Start at the second line displayed on the screen. */ pos = position(TOP_PLUS_ONE); lines_from_top = 1; } else { /* * Forward search but don't "start from top". * Start just after the bottom line displayed on the screen. */ pos = position(BOTTOM_PLUS_ONE); lines_from_top = sc_height + 1; } if (pos == NULL_POSITION) { /* * Can't find anyplace to start searching from. */ error("Nothing to search"); return; } new_pos = pos; for (;;) { /* * Get lines until we find a matching one or * until we hit end-of-file (or beginning-of-file * if we're going backwards). */ if (sigs) /* * A signal aborts the search. */ return; if (search_forward) { /* * Read the next line, and remember the * starting position of the line after that. */ pos = new_pos; new_pos = forw_line(pos); lines_from_top++; } else { /* * Read the previous line. */ pos = back_line(pos); lines_from_top--; } if (line == NULL) { /* * We hit EOF/BOF without a match. */ error("Pattern not found"); return; } /* * Test the next line to see if we have a match. * This is done in a variety of ways, depending * on what pattern matching functions are available. */ #if REGCMP if ( (regex(cpattern, line) != NULL) #else #if RECOMP if ( (re_exec(line) == 1) #else if ( (match(pattern, line)) #endif #endif && (--n <= 0) ) /* * Found the matching line. */ break; } if (lines_from_top > 0 && lines_from_top < sc_height) { /* * Scroll forward. */ clear_eol(); forw(lines_from_top-1, position(BOTTOM_PLUS_ONE), 1); } else if (lines_from_top < 0 && -lines_from_top < sc_height) { /* * Scroll backwards. */ clear_eol(); back(-lines_from_top, position(TOP), 1); } else { /* * Clear and paint screen. */ clear(); pos_clear(); add_forw_pos(pos); forw(sc_height - 1, pos, 1); } } #if (!REGCMP) && (!RECOMP) /* * We have neither regcmp() nor re_comp(). * We use this function to do simple pattern matching. * It supports no metacharacters like *, etc. */ static int match(pattern, buf) char *pattern, *buf; { register char *pp, *lp; for ( ; *buf != '\0'; buf++) { for (pp = pattern, lp = buf; *pp == *lp; pp++, lp++) if (*pp == '\0' || *lp == '\0') break; if (*pp == '\0') return (1); } return (0); } #endif SHAR_EOF cat - << \SHAR_EOF > screen.c /* * Routines which deal with the characteristics of the terminal. * Uses termcap to be as terminal-independent as possible. * * {{ Someday this should be rewritten to use curses. }} */ #include "less.h" #if XENIX #include <sys/types.h> #include <sys/ioctl.h> #endif #if TERMIO #include <termio.h> #else #include <sgtty.h> #endif /* * Strings passed to tputs() to do various terminal functions. */ static char *sc_pad, /* Pad string */ *sc_home, /* Cursor home */ *sc_addline, /* Add line, scroll down following lines */ *sc_lower_left, /* Cursor to last line, first column */ *sc_move, /* General cursor positioning */ *sc_clear, /* Clear screen */ *sc_eol_clear, /* Clear to end of line */ *sc_s_in, /* Enter standout (highlighted) mode */ *sc_s_out, /* Exit standout mode */ *sc_u_in, /* Enter underline mode */ *sc_u_out, /* Exit underline mode */ *sc_visual_bell, /* Visual bell (flash screen) sequence */ *sc_backspace, /* Backspace cursor */ *sc_init, /* Startup terminal initialization */ *sc_deinit; /* Exit terminal de-intialization */ static int dumb; static int hard; public int auto_wrap; /* Terminal does \r\n when write past margin */ public int ignaw; /* Terminal ignores \n immediately after wrap */ public int erase_char, kill_char; /* The user's erase and line-kill chars */ public int sc_width, sc_height; /* Height & width of screen */ public int ul_width, ue_width; /* Printing width of underline sequences */ /* * These two variables are sometimes defined in, * and needed by, the termcap library. * It may be necessary on some systems to declare them extern here. */ /*extern*/ short ospeed; /* Terminal output baud rate */ /*extern*/ char PC; /* Pad character */ extern int quiet; /* If VERY_QUIET, use visual bell for bell */ extern int know_dumb; /* Don't complain about a dumb terminal */ char *tgetstr(); char *tgoto(); /* * Change terminal to "raw mode", or restore to "normal" mode. * "Raw mode" means * 1. An outstanding read will complete on receipt of a single keystroke. * 2. Input is not echoed. * 3. On output, \n is mapped to \r\n. * 4. \t is NOT be expanded into spaces. * 5. Signal-causing characters such as ctrl-C (interrupt), * etc. are NOT disabled. * It doesn't matter whether an input \n is mapped to \r, or vice versa. */ public void raw_mode(on) int on; { #if TERMIO struct termio s; static struct termio save_term; if (on) { /* * Get terminal modes. */ ioctl(2, TCGETA, &s); /* * Save modes and set certain variables dependent on modes. */ save_term = s; ospeed = s.c_cflag & CBAUD; erase_char = s.c_cc[VERASE]; kill_char = s.c_cc[VKILL]; /* * Set the modes to the way we want them. */ s.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL); s.c_oflag |= (OPOST|ONLCR|TAB3); s.c_oflag &= ~(OCRNL|ONOCR|ONLRET); s.c_cc[VMIN] = 1; s.c_cc[VTIME] = 0; } else { /* * Restore saved modes. */ s = save_term; } ioctl(2, TCSETAW, &s); #else struct sgttyb s; static struct sgttyb save_term; if (on) { /* * Get terminal modes. */ ioctl(2, TIOCGETP, &s); /* * Save modes and set certain variables dependent on modes. */ save_term = s; ospeed = s.sg_ospeed; erase_char = s.sg_erase; kill_char = s.sg_kill; /* * Set the modes to the way we want them. */ s.sg_flags |= CBREAK; s.sg_flags &= ~(ECHO|XTABS); } else { /* * Restore saved modes. */ s = save_term; } ioctl(2, TIOCSETP, &s); #endif } static int couldnt = 0; static void cannot(s) char *s; { if (know_dumb) /* * He knows he has a dumb terminal, so don't tell him. */ return; printf("WARNING: terminal cannot \"%s\"\n", s); couldnt = 1; } /* * Get terminal capabilities via termcap. */ public void get_term() { char termbuf[1024]; char *sp; static char sbuf[150]; char *getenv(); /* * Find out what kind of terminal this is. */ if (tgetent(termbuf, getenv("TERM")) <= 0) dumb = 1; /* * Get size of the screen. */ if (dumb || (sc_height = tgetnum("li")) < 0 || tgetflag("hc")) { /* Oh no, this is a hardcopy terminal. */ hard = 1; sc_height = 24; } if (dumb || (sc_width = tgetnum("co")) < 0) sc_width = 80; auto_wrap = tgetflag("am"); ignaw = tgetflag("xn"); /* * Assumes termcap variable "sg" is the printing width of * the standout sequence, the end standout sequence, * the underline sequence, and the end underline sequence. */ if ((ul_width = tgetnum("sg")) < 0) ul_width = 0; ue_width = ul_width; /* * Get various string-valued capabilities. */ sp = sbuf; sc_pad = (dumb) ? NULL : tgetstr("pc", &sp); if (sc_pad != NULL) PC = *sc_pad; sc_init = (dumb) ? NULL : tgetstr("ti", &sp); if (sc_init == NULL) sc_init = ""; sc_deinit= (dumb) ? NULL : tgetstr("te", &sp); if (sc_deinit == NULL) sc_deinit = ""; sc_eol_clear = (dumb) ? NULL : tgetstr("ce", &sp); if (hard || sc_eol_clear == NULL || *sc_eol_clear == '\0') { cannot("clear to end of line"); sc_eol_clear = ""; } sc_clear = (dumb) ? NULL : tgetstr("cl", &sp); if (hard || sc_clear == NULL || *sc_clear == '\0') { cannot("clear screen"); sc_clear = "\n\n"; } sc_move = (dumb) ? NULL : tgetstr("cm", &sp); if (hard || sc_move == NULL || *sc_move == '\0') { /* * This is not an error here, because we don't * always need sc_move. * We need it only if we don't have home or lower-left. */ sc_move = ""; } sc_s_in = (dumb) ? NULL : tgetstr("so", &sp); if (hard || sc_s_in == NULL) sc_s_in = ""; sc_s_out = (dumb) ? NULL : tgetstr("se", &sp); if (hard || sc_s_out == NULL) sc_s_out = ""; sc_u_in = (dumb) ? NULL : tgetstr("us", &sp); if (hard || sc_u_in == NULL) sc_u_in = sc_s_in; sc_u_out = (dumb) ? NULL : tgetstr("ue", &sp); if (hard || sc_u_out == NULL) sc_u_out = sc_s_out; sc_visual_bell = (dumb) ? NULL : tgetstr("vb", &sp); if (hard || sc_visual_bell == NULL) sc_visual_bell = ""; sc_home = (dumb) ? NULL : tgetstr("ho", &sp); if (hard || sc_home == NULL || *sc_home == '\0') { if (*sc_move == '\0') { cannot("home cursor"); /* * This last resort for sc_home is supposed to * be an up-arrow suggesting moving to the * top of the "virtual screen". (The one in * your imagination as you try to use this on * a hard copy terminal.) */ sc_home = "|\b^"; } else { /* * No "home" string, * but we can use "move(0,0)". */ strcpy(sp, tgoto(sc_move, 0, 0)); sc_home = sp; sp += strlen(sp) + 1; } } sc_lower_left = (dumb) ? NULL : tgetstr("ll", &sp); if (hard || sc_lower_left == NULL || *sc_lower_left == '\0') { if (*sc_move == '\0') { cannot("move cursor to lower left of screen"); sc_lower_left = "\r"; } else { /* * No "lower-left" string, * but we can use "move(0,last-line)". */ strcpy(sp, tgoto(sc_move, 0, sc_height-1)); sc_lower_left = sp; sp += strlen(sp) + 1; } } /* * To add a line at top of screen and scroll the display down, * we use "al" (add line) or "sr" (scroll reverse). */ if (dumb) sc_addline = NULL; else if ((sc_addline = tgetstr("al", &sp)) == NULL || *sc_addline == '\0') sc_addline = tgetstr("sr", &sp); if (hard || sc_addline == NULL || *sc_addline == '\0') { cannot("scroll backwards"); sc_addline = ""; } if (dumb || tgetflag("bs")) sc_backspace = "\b"; else { sc_backspace = tgetstr("bc", &sp); if (sc_backspace == NULL || *sc_backspace == '\0') sc_backspace = "\b"; } if (couldnt) /* Give him time to read all the "cannot" messages. */ error(""); } /* * Below are the functions which perform all the * terminal-specific screen manipulation. */ /* * Initialize terminal */ public void init() { tputs(sc_init, sc_height, putc); } /* * Deinitialize terminal */ public void deinit() { tputs(sc_deinit, sc_height, putc); } /* * Home cursor (move to upper left corner of screen). */ public void home() { tputs(sc_home, 1, putc); } /* * Add a blank line (called with cursor at home). * Should scroll the display down. */ public void add_line() { tputs(sc_addline, sc_height, putc); } /* * Move cursor to lower left corner of screen. */ public void lower_left() { tputs(sc_lower_left, 1, putc); } /* * Ring the terminal bell. */ public void bell() { if (quiet == VERY_QUIET) vbell(); else putc('\7'); } /* * Output the "visual bell", if there is one. */ public void vbell() { if (*sc_visual_bell == '\0') return; tputs(sc_visual_bell, sc_height, putc); } /* * Clear the screen. */ public void clear() { tputs(sc_clear, sc_height, putc); } /* * Clear from the cursor to the end of the cursor's line. * {{ This must not move the cursor. }} */ public void clear_eol() { tputs(sc_eol_clear, 1, putc); } /* * Begin "standout" (bold, underline, or whatever). */ public void so_enter() { tputs(sc_s_in, 1, putc); } /* * End "standout". */ public void so_exit() { tputs(sc_s_out, 1, putc); } /* * Begin "underline" (hopefully real underlining, * otherwise whatever the terminal provides). */ public void ul_enter() { tputs(sc_u_in, 1, putc); } /* * End "underline". */ public void ul_exit() { tputs(sc_u_out, 1, putc); } /* * Erase the character to the left of the cursor * and move the cursor left. */ public void backspace() { /* * Try to erase the previous character by overstriking with a space. */ tputs(sc_backspace, 1, putc); putc(' '); tputs(sc_backspace, 1, putc); } /* * Output a plain backspace, without erasing the previous char. */ public void putbs() { tputs(sc_backspace, 1, putc); } SHAR_EOF cat - << \SHAR_EOF > signal.c /* * Routines dealing with signals. * * A signal usually merely causes a bit to be set in the "signals" word. * At some convenient time, the mainline code checks to see if any * signals need processing by calling psignal(). * An exception is made if we are reading from the keyboard when the * signal is received. Some operating systems will simply call the * signal handler and NOT return from the read (with EINTR). * To handle this case, we service the interrupt directly from * the handler if we are reading from the keyboard. */ #include "less.h" #include <signal.h> #include <setjmp.h> /* * The type of signal handler functions. * Usually int, although it should be void. */ typedef int HANDLER; /* * "sigs" contains bits indicating signals which need to be processed. */ public int sigs; #define S_INTERRUPT 01 #ifdef SIGTSTP #define S_STOP 02 #endif extern int reading; extern char *first_cmd; extern jmp_buf main_loop; /* * Interrupt signal handler. */ static HANDLER interrupt() { SIGNAL(SIGINT, interrupt); sigs |= S_INTERRUPT; if (reading) psignals(); } #ifdef SIGTSTP /* * "Stop" (^Z) signal handler. */ static HANDLER stop() { SIGNAL(SIGTSTP, stop); sigs |= S_STOP; if (reading) psignals(); } #endif /* * Set up the signal handlers. */ public void init_signals() { (void) SIGNAL(SIGINT, interrupt); #ifdef SIGTSTP (void) SIGNAL(SIGTSTP, stop); #endif } /* * Process any signals we have recieved. * A received signal cause a bit to be set in "sigs". */ public void psignals() { register int tsignals; tsignals = sigs; sigs = 0; if (tsignals == 0) return; dropout(); /* Discard any buffered output */ #ifdef SIGTSTP if (tsignals & S_STOP) { /* * Clean up the terminal. */ lower_left(); clear_eol(); flush(); raw_mode(0); SIGNAL(SIGTSTP, SIG_DFL); #if SIGSETMASK /* * This system will not allow us to send a * stop signal (SIGTSTP) to ourself * while we are in the signal handler, like maybe now. * (This can be the case if we are reading; see comment above.) * So we ask the silly system for permission to do so. */ sigsetmask(0); #endif kill(getpid(), SIGTSTP); /* * ... Bye bye. ... * Hopefully we'll be back later and resume here... * Reset the terminal and arrange to repaint the * screen when we get back to the main command loop. */ SIGNAL(SIGTSTP, stop); raw_mode(1); first_cmd = "r"; longjmp(main_loop, 1); } #endif if (tsignals & S_INTERRUPT) { bell(); /* * {{ You may wish to replace the bell() with * error("Interrupt"); }} */ } longjmp(main_loop, 1); } SHAR_EOF cat - << \SHAR_EOF > ttyin.c /* * Routines dealing with getting input from the keyboard (i.e. from the user). */ #include "less.h" /* * The boolean "reading" is set true or false according to whether * we are currently reading from the keyboard. * This information is used by the signal handling stuff in signal.c. * {{ There are probably some race conditions here * involving the variable "reading". }} */ public int reading; static int tty; /* * Open keyboard for input. * (Just use file descriptor 2.) */ public void open_getc() { tty = 2; } /* * Get a character from the keyboard. */ public int getc() { char c; int result; reading = 1; do { flush(); result = read(tty, &c, 1); } while (result != 1); reading = 0; return (c & 0177); } SHAR_EOF cat - << \SHAR_EOF > version.c /* * less * Copyright (c) 1984,1985 Mark Nudelman * * This program may be freely used and/or modified, * with the following provisions: * 1. This notice and the above copyright notice must remain intact. * 2. Neither this program, nor any modification of it, * may not be sold for profit without written consent of the author. * * ----------------------------------------------------------------- * * usage: less [-cdtmMqQuU] [-b[fp]<n>] [+<cmd>] [file-name] ... * * This program is a paginator similar to "more", * but allows you to move both forward and backward in the file. * Commands are based on "more" and "vi". * * ----------------------- CHANGES --------------------------------- * * Allowed use on standard input 1/29/84 markn * Added E, N, P commands 2/1/84 markn * Added '=' command, 'stop' signal handling 4/17/84 markn * Added line folding 4/20/84 markn * v2: Fixed '=' command to use BOTTOM_PLUS_ONE, * instead of TOP, added 'p' & 'v' commands 4/27/84 markn * v3: Added -m and -t options, '-' command 5/3/84 markn * v4: Added LESS environment variable 5/3/84 markn * v5: New comments, fixed '-' command slightly 5/3/84 markn * v6: Added -Q, visual bell 5/15/84 markn * v7: Fixed jump_back(n) bug: n should count real * lines, not folded lines. Also allow number * on G command. 5/24/84 markn * v8: Re-do -q and -Q commands 5/30/84 markn * v9: Added "+<cmd>" argument 9/25/84 markn * v10: Fixed bug in -b<n> argument processing 10/10/84 markn * v11: Made error() ring bell if \n not entered. 10/18/84 markn * ----------------------------------------------------------------- * v12: Reorganized signal handling and made * portable to 4.2bsd. 2/13/85 mark * v13: Reword error message for '-' command. 2/16/85 mark * v14: Added -bf and -bp variants of -b. 2/22/85 mark * v15: Miscellaneous changes. 2/25/85 mark * v16: Added -u flag for backspace processing. 3/13/85 mark * v17: Added j and k commands, * changed -t default. 4/13/85 mark * v18: Rewrote signal handling code. 4/20/85 mark * v19: Got rid of "verbose" eq_message(). 5/2/85 mark * Made search() scroll in some cases. * v20: Fixed screen.c ioctls for System V. 5/21/85 mark * v21: Fixed some first_cmd bugs. 5/23/85 mark * v22: Added support for no RECOMP nor REGCMP. 5/24/85 mark * v23: Miscellanous changes and prettying up. 5/25/85 mark * v24: Added ti,te terminal init & de-init 6/3/85 Mike Kersenbrock * v25: Added -U flag, standout mode underlining. 6/8/85 mark * v26: Added -M flag. 6/9/85 mark * Use underline termcap (us) if it exists. * v27: Renamed some variables to make unique in 6/15/85 mark * 6 chars. Minor fix to -m. * v28: Fixed right margin bug. 6/28/85 mark * v29: Incorporated M.Rose's changes to signal.c 6/28/85 mark * v30: Fixed stupid bug in argument processing. 6/29/85 mark * ----------------------------------------------------------------- */ char version[] = "@(#) less version 30"; SHAR_EOF cat - << \SHAR_EOF > funcs.h public void edit (); public char * eq_message (); public void next_file (); public void prev_file (); public void toggle_flag (); public void quit (); public void forward (); public void backward (); public void repaint (); public void jump_forw (); public void jump_back (); public void jump_percent (); public void search (); public int ch_seek (); public int ch_end_seek (); public POSITION ch_length (); public POSITION ch_tell (); public int ch_forw_get (); public int ch_back_get (); public void ch_init (); public POSITION position (); public void add_forw_pos (); public void add_back_pos (); public void pos_clear (); public POSITION forw_line (); public POSITION back_line (); public void put_line (); public int control_char (); public int carat_char (); public void flush (); public void dropout (); public void putc (); public void puts (); public void error (); public int error_width (); public void raw_mode (); public void get_term (); public void init (); public void deinit (); public void home (); public void add_line (); public void lower_left (); public void bell (); public void vbell (); public void clear (); public void clear_eol (); public void so_enter (); public void so_exit (); public void ul_enter (); public void ul_exit (); public void backspace (); public void putbs (); public void prewind (); public int pappend (); public void init_signals (); public void psignals (); public void help (); public void open_getc (); public int getc (); public void commands (); SHAR_EOF cat - << \SHAR_EOF > less.h /* * Standard include file for "less". */ /* * Language details. */ #if !VOID #define void int #endif #define public /* PUBLIC FUNCTION */ /* * Special types and constants. */ typedef long POSITION; #define END_POSITION ((POSITION)(-2)) #define NULL_POSITION ((POSITION)(-1)) #define EOF (0) #define NULL (0) /* How quiet should we be? */ #define NOT_QUIET 0 /* Ring bell at eof and for errors */ #define LITTLE_QUIET 1 /* Ring bell only for errors */ #define VERY_QUIET 2 /* Never ring bell */ /* How should we prompt? */ #define PR_SHORT 0 /* Prompt with colon */ #define PR_MEDIUM 1 /* Prompt with message */ #define PR_LONG 2 /* Prompt with longer message */ /* How should we handle backspaces? */ #define BS_UNDERLINE 0 /* Underlining converted to underline mode */ #define BS_NORMAL 1 /* \b treated as normal char; actually output */ #define BS_CONTROL 2 /* \b treated as control char; prints as ^H */ /* Flag to eq_message() telling what to put in the message */ #define MNAME 001 /* File name */ #define MOF 002 /* "file x of y" */ #define MBYTE 004 /* "byte x/y" */ #define MPCT 010 /* Percentage into the file */ /* Special chars used to tell put_line() to do something special */ #define UL_CHAR '\201' /* Enter underline mode */ #define UE_CHAR '\202' /* Exit underline mode */ #define SIGNAL(sig,func) signal(sig,func) off_t lseek(); #include "funcs.h" SHAR_EOF cat - << \SHAR_EOF > position.h /* * Include file for interfacing to position.c modules. */ #define TOP 0 #define BOTTOM 1 #define BOTTOM_PLUS_ONE 2 #define TOP_PLUS_ONE 3 SHAR_EOF cat - << \SHAR_EOF > mkfuncs.awk BEGIN { FS="("; state = 0 } /^ public/ { ftype = $0; state = 1 } { if (state == 1) state = 2 else if (state == 2) { print ftype,$1,"();"; state = 0 } } SHAR_EOF
mark@nsc-pdc.UUCP (Mark Nudelman) (09/07/85)
#!/bin/sh-----cut here-----cut here-----cut here-----cut here----- # shar: Shell Archiver # Run the following text with /bin/sh to create: # position.c # input.c # output.c # screen.c # prompt.c # line.c # signal.c # help.c # ttyin.c # command.c # version.c cat - << \SHAR_EOF > position.c /* * Routines dealing with the "position" table. * This is a table which tells the position (in the input file) of the * first char on each currently displayed line. * * {{ The position table is scrolled by moving all the entries. * Would be better to have a circular table * and just change a couple of pointers. }} */ #include "less.h" #include "position.h" #define NPOS 100 /* {{ sc_height must be less than NPOS }} */ static POSITION table[NPOS]; /* The position table */ extern int sc_width, sc_height; /* * Return the position of one of: * the top (first) line on the screen * the second line on the screen * the bottom line on the screen * the line after the bottom line on the screen */ public POSITION position(where) int where; { register int n; switch (where) { case TOP: n = 0; break; case TOP_PLUS_ONE: n = 1; break; case BOTTOM: n = sc_height - 2; break; case BOTTOM_PLUS_ONE: n = sc_height - 1; break; } return (table[n]); } /* * Add a new file position to the bottom of the position table. */ public void add_forw_pos(pos) POSITION pos; { register int i; /* * Scroll the position table up. */ for (i = 1; i < sc_height; i++) table[i-1] = table[i]; table[sc_height - 1] = pos; } /* * Add a new file position to the top of the position table. */ public void add_back_pos(pos) POSITION pos; { register int i; /* * Scroll the position table down. */ for (i = sc_height - 1; i > 0; i--) table[i] = table[i-1]; table[0] = pos; } /* * Initialize the position table, done whenever we clear the screen. */ public void pos_clear() { register int i; for (i = 0; i < sc_height; i++) table[i] = NULL_POSITION; } SHAR_EOF cat - << \SHAR_EOF > input.c /* * High level routines dealing with getting input from the file being viewed. * When we speak of "lines" here, we mean PRINTABLE lines. */ #include "less.h" extern int do_bs; extern int squeeze; extern char *line; /* * Get the next line. * A "current" position is passed and a "new" position is returned. * The current position is the position of the first character of * a line. The new position is the position of the first character * of the NEXT line. The line obtained is the line starting at curr_pos. */ public POSITION forw_line(curr_pos) POSITION curr_pos; { POSITION new_pos; register int c; if (curr_pos == NULL_POSITION || ch_seek(curr_pos)) { line = NULL; return (NULL_POSITION); } c = ch_forw_get(); if (c == EOF) { line = NULL; return (NULL_POSITION); } prewind(); for (;;) { if (c == '\n' || c == EOF) { /* * End of the line. */ new_pos = ch_tell(); break; } /* * Append the char to the line and get the next char. */ if (pappend(c)) { /* * The char won't fit in the line; the line * is too long to print in the screen width. * End the line here. */ new_pos = ch_tell() - 1; break; } c = ch_forw_get(); } (void) pappend('\0'); if (squeeze && *line == '\0') { /* * This line is blank. * Skip down to the last contiguous blank line * and pretend it is the one which we are returning. */ while ((c = ch_forw_get()) == '\n') ; if (c != EOF) (void) ch_back_get(); new_pos = ch_tell(); } return (new_pos); } /* * Get the previous line. * A "current" position is passed and a "new" position is returned. * The current position is the position of the first character of * a line. The new position is the position of the first character * of the PREVIOUS line. The line obtained is the one starting at new_pos. */ public POSITION back_line(curr_pos) POSITION curr_pos; { POSITION new_pos, begin_new_pos; int c; if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 || ch_seek(curr_pos-1)) { line = NULL; return (NULL_POSITION); } if (squeeze) { /* * Find out if the "current" line was blank. */ (void) ch_forw_get(); /* Skip the newline */ c = ch_forw_get(); /* First char of "current" line */ (void) ch_back_get(); /* Restore our position */ (void) ch_back_get(); if (c == '\n') { /* * The "current" line was blank. * Skip over any preceeding blank lines, * since we skipped them in forw_line(). */ while ((c = ch_back_get()) == '\n') ; if (c == EOF) { line = NULL; return (NULL_POSITION); } (void) ch_forw_get(); } } /* * Scan backwards until we hit the beginning of the line. */ for (;;) { c = ch_back_get(); if (c == '\n') { /* * This is the newline ending the previous line. * We have hit the beginning of the line. */ new_pos = ch_tell() + 1; break; } if (c == EOF) { /* * We have hit the beginning of the file. * This must be the first line in the file. * This must, of course, be the beginning of the line. */ new_pos = (POSITION)0; break; } } /* * Now scan forwards from the beginning of this line. * We keep discarding "printable lines" (based on screen width) * until we reach the curr_pos. * * {{ This algorithm is pretty inefficient if the lines * are much longer than the screen width, * but I don't know of any better way. }} */ if (ch_seek(new_pos)) { line = NULL; return (NULL_POSITION); } loop: begin_new_pos = new_pos; prewind(); do { c = ch_forw_get(); new_pos++; if (c == '\n') break; if (pappend(c)) { /* * Got a full printable line, but we haven't * reached our curr_pos yet. Discard the line * and start a new one. */ (void) pappend('\0'); (void) ch_back_get(); new_pos--; goto loop; } } while (new_pos < curr_pos); (void) pappend('\0'); return (begin_new_pos); } SHAR_EOF cat - << \SHAR_EOF > output.c /* * High level routines dealing with the output to the screen. */ #include "less.h" extern int sigs; extern int sc_width, sc_height; extern int ul_width, ue_width; extern int so_width, se_width; extern char *line; /* * Display the line which is in the line buffer. */ public void put_line() { register char *p; register int c; register int column; extern int auto_wrap, ignaw; if (sigs) /* * Don't output if a signal is pending. */ return; if (line == NULL) line = "~"; column = 0; for (p = line; *p != '\0'; p++) { c = *p; if (c == UL_CHAR) { ul_enter(); column += ul_width; } else if (c == UE_CHAR) { ul_exit(); column += ue_width; } else if (c & 0200) { putc('^'); putc(c & 0177); column += 2; } else if (c == '\b') { putbs(); column--; } else { putc(c); column++; } } if (column < sc_width || !auto_wrap || ignaw) putc('\n'); } /* * Is a given character a "control" character? * {{ ASCII DEPENDENT }} */ public int control_char(c) int c; { return (c < ' ' || c == '\177'); } /* * Return the printable character used to identify a control character * (printed after a carat; e.g. '\3' => "^C"). * {{ ASCII DEPENDENT }} */ public int carat_char(c) int c; { return ((c == '\177') ? '?' : (c | 0100)); } static char obuf[1024]; static char *ob = obuf; /* * Flush buffered output. */ public void flush() { write(1, obuf, ob-obuf); ob = obuf; } /* * Discard buffered output. */ public void dropout() { ob = obuf; } /* * Output a character. */ public void putc(c) int c; { if (ob >= &obuf[sizeof(obuf)]) flush(); *ob++ = c; } /* * Output a string. */ public void puts(s) register char *s; { while (*s != '\0') putc(*s++); } /* * Output a message in the lower left corner of the screen * and wait for carriage return. */ static char return_to_continue[] = " (press RETURN)"; public void error(s) char *s; { register int c; lower_left(); clear_eol(); so_enter(); puts(s); puts(return_to_continue); so_exit(); while ((c = getc()) != '\n' && c != '\r') bell(); if (strlen(s) > sc_width) repaint(); } public int error_width() { /* * Don't use the last position, because some terminals * will scroll if you write in the last char of the last line. */ return (sc_width - (sizeof(return_to_continue) + so_width + se_width + 1)); } SHAR_EOF cat - << \SHAR_EOF > screen.c /* * Routines which deal with the characteristics of the terminal. * Uses termcap to be as terminal-independent as possible. * * {{ Someday this should be rewritten to use curses. }} */ #include "less.h" #if XENIX #include <sys/types.h> #include <sys/ioctl.h> #endif #if TERMIO #include <termio.h> #else #include <sgtty.h> #endif /* * Strings passed to tputs() to do various terminal functions. */ static char *sc_pad, /* Pad string */ *sc_home, /* Cursor home */ *sc_addline, /* Add line, scroll down following lines */ *sc_lower_left, /* Cursor to last line, first column */ *sc_move, /* General cursor positioning */ *sc_clear, /* Clear screen */ *sc_eol_clear, /* Clear to end of line */ *sc_s_in, /* Enter standout (highlighted) mode */ *sc_s_out, /* Exit standout mode */ *sc_u_in, /* Enter underline mode */ *sc_u_out, /* Exit underline mode */ *sc_visual_bell, /* Visual bell (flash screen) sequence */ *sc_backspace, /* Backspace cursor */ *sc_init, /* Startup terminal initialization */ *sc_deinit; /* Exit terminal de-intialization */ static int dumb; static int hard; public int auto_wrap; /* Terminal does \r\n when write past margin */ public int ignaw; /* Terminal ignores \n immediately after wrap */ public int erase_char, kill_char; /* The user's erase and line-kill chars */ public int sc_width, sc_height; /* Height & width of screen */ public int ul_width, ue_width; /* Printing width of underline sequences */ public int so_width, se_width; /* Printing width of standout sequences */ /* * These two variables are sometimes defined in, * and needed by, the termcap library. * It may be necessary on some systems to declare them extern here. */ /*extern*/ short ospeed; /* Terminal output baud rate */ /*extern*/ char PC; /* Pad character */ extern int quiet; /* If VERY_QUIET, use visual bell for bell */ extern int know_dumb; /* Don't complain about a dumb terminal */ char *tgetstr(); char *tgoto(); /* * Change terminal to "raw mode", or restore to "normal" mode. * "Raw mode" means * 1. An outstanding read will complete on receipt of a single keystroke. * 2. Input is not echoed. * 3. On output, \n is mapped to \r\n. * 4. \t is NOT be expanded into spaces. * 5. Signal-causing characters such as ctrl-C (interrupt), * etc. are NOT disabled. * It doesn't matter whether an input \n is mapped to \r, or vice versa. */ public void raw_mode(on) int on; { #if TERMIO struct termio s; static struct termio save_term; if (on) { /* * Get terminal modes. */ ioctl(2, TCGETA, &s); /* * Save modes and set certain variables dependent on modes. */ save_term = s; ospeed = s.c_cflag & CBAUD; erase_char = s.c_cc[VERASE]; kill_char = s.c_cc[VKILL]; /* * Set the modes to the way we want them. */ s.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL); s.c_oflag |= (OPOST|ONLCR|TAB3); s.c_oflag &= ~(OCRNL|ONOCR|ONLRET); s.c_cc[VMIN] = 1; s.c_cc[VTIME] = 0; } else { /* * Restore saved modes. */ s = save_term; } ioctl(2, TCSETAW, &s); #else struct sgttyb s; static struct sgttyb save_term; if (on) { /* * Get terminal modes. */ ioctl(2, TIOCGETP, &s); /* * Save modes and set certain variables dependent on modes. */ save_term = s; ospeed = s.sg_ospeed; erase_char = s.sg_erase; kill_char = s.sg_kill; /* * Set the modes to the way we want them. */ s.sg_flags |= CBREAK; s.sg_flags &= ~(ECHO|XTABS); } else { /* * Restore saved modes. */ s = save_term; } ioctl(2, TIOCSETP, &s); #endif } static int couldnt = 0; static void cannot(s) char *s; { if (know_dumb) /* * He knows he has a dumb terminal, so don't tell him. */ return; printf("WARNING: terminal cannot \"%s\"\n", s); couldnt = 1; } /* * Get terminal capabilities via termcap. */ public void get_term() { char termbuf[1024]; char *sp; static char sbuf[150]; char *getenv(); /* * Find out what kind of terminal this is. */ if (tgetent(termbuf, getenv("TERM")) <= 0) dumb = 1; /* * Get size of the screen. */ if (dumb || (sc_height = tgetnum("li")) < 0 || tgetflag("hc")) { /* Oh no, this is a hardcopy terminal. */ hard = 1; sc_height = 24; } if (dumb || (sc_width = tgetnum("co")) < 0) sc_width = 80; auto_wrap = tgetflag("am"); ignaw = tgetflag("xn"); /* * Assumes termcap variable "sg" is the printing width of * the standout sequence, the end standout sequence, * the underline sequence, and the end underline sequence. */ if ((ul_width = tgetnum("sg")) < 0) ul_width = 0; so_width = se_width = ue_width = ul_width; /* * Get various string-valued capabilities. */ sp = sbuf; sc_pad = (dumb) ? NULL : tgetstr("pc", &sp); if (sc_pad != NULL) PC = *sc_pad; sc_init = (dumb) ? NULL : tgetstr("ti", &sp); if (sc_init == NULL) sc_init = ""; sc_deinit= (dumb) ? NULL : tgetstr("te", &sp); if (sc_deinit == NULL) sc_deinit = ""; sc_eol_clear = (dumb) ? NULL : tgetstr("ce", &sp); if (hard || sc_eol_clear == NULL || *sc_eol_clear == '\0') { cannot("clear to end of line"); sc_eol_clear = ""; } sc_clear = (dumb) ? NULL : tgetstr("cl", &sp); if (hard || sc_clear == NULL || *sc_clear == '\0') { cannot("clear screen"); sc_clear = "\n\n"; } sc_move = (dumb) ? NULL : tgetstr("cm", &sp); if (hard || sc_move == NULL || *sc_move == '\0') { /* * This is not an error here, because we don't * always need sc_move. * We need it only if we don't have home or lower-left. */ sc_move = ""; } sc_s_in = (dumb) ? NULL : tgetstr("so", &sp); if (hard || sc_s_in == NULL) sc_s_in = ""; sc_s_out = (dumb) ? NULL : tgetstr("se", &sp); if (hard || sc_s_out == NULL) sc_s_out = ""; sc_u_in = (dumb) ? NULL : tgetstr("us", &sp); if (hard || sc_u_in == NULL) sc_u_in = sc_s_in; sc_u_out = (dumb) ? NULL : tgetstr("ue", &sp); if (hard || sc_u_out == NULL) sc_u_out = sc_s_out; sc_visual_bell = (dumb) ? NULL : tgetstr("vb", &sp); if (hard || sc_visual_bell == NULL) sc_visual_bell = ""; sc_home = (dumb) ? NULL : tgetstr("ho", &sp); if (hard || sc_home == NULL || *sc_home == '\0') { if (*sc_move == '\0') { cannot("home cursor"); /* * This last resort for sc_home is supposed to * be an up-arrow suggesting moving to the * top of the "virtual screen". (The one in * your imagination as you try to use this on * a hard copy terminal.) */ sc_home = "|\b^"; } else { /* * No "home" string, * but we can use "move(0,0)". */ strcpy(sp, tgoto(sc_move, 0, 0)); sc_home = sp; sp += strlen(sp) + 1; } } sc_lower_left = (dumb) ? NULL : tgetstr("ll", &sp); if (hard || sc_lower_left == NULL || *sc_lower_left == '\0') { if (*sc_move == '\0') { cannot("move cursor to lower left of screen"); sc_lower_left = "\r"; } else { /* * No "lower-left" string, * but we can use "move(0,last-line)". */ strcpy(sp, tgoto(sc_move, 0, sc_height-1)); sc_lower_left = sp; sp += strlen(sp) + 1; } } /* * To add a line at top of screen and scroll the display down, * we use "al" (add line) or "sr" (scroll reverse). */ if (dumb) sc_addline = NULL; else if ((sc_addline = tgetstr("al", &sp)) == NULL || *sc_addline == '\0') sc_addline = tgetstr("sr", &sp); if (hard || sc_addline == NULL || *sc_addline == '\0') { cannot("scroll backwards"); sc_addline = ""; } if (dumb || tgetflag("bs")) sc_backspace = "\b"; else { sc_backspace = tgetstr("bc", &sp); if (sc_backspace == NULL || *sc_backspace == '\0') sc_backspace = "\b"; } if (couldnt) /* Give him time to read all the "cannot" messages. */ error(""); } /* * Below are the functions which perform all the * terminal-specific screen manipulation. */ /* * Initialize terminal */ public void init() { tputs(sc_init, sc_height, putc); } /* * Deinitialize terminal */ public void deinit() { tputs(sc_deinit, sc_height, putc); } /* * Home cursor (move to upper left corner of screen). */ public void home() { tputs(sc_home, 1, putc); } /* * Add a blank line (called with cursor at home). * Should scroll the display down. */ public void add_line() { tputs(sc_addline, sc_height, putc); } /* * Move cursor to lower left corner of screen. */ public void lower_left() { tputs(sc_lower_left, 1, putc); } /* * Ring the terminal bell. */ public void bell() { if (quiet == VERY_QUIET) vbell(); else putc('\7'); } /* * Output the "visual bell", if there is one. */ public void vbell() { if (*sc_visual_bell == '\0') return; tputs(sc_visual_bell, sc_height, putc); } /* * Clear the screen. */ public void clear() { tputs(sc_clear, sc_height, putc); } /* * Clear from the cursor to the end of the cursor's line. * {{ This must not move the cursor. }} */ public void clear_eol() { tputs(sc_eol_clear, 1, putc); } /* * Begin "standout" (bold, underline, or whatever). */ public void so_enter() { tputs(sc_s_in, 1, putc); } /* * End "standout". */ public void so_exit() { tputs(sc_s_out, 1, putc); } /* * Begin "underline" (hopefully real underlining, * otherwise whatever the terminal provides). */ public void ul_enter() { tputs(sc_u_in, 1, putc); } /* * End "underline". */ public void ul_exit() { tputs(sc_u_out, 1, putc); } /* * Erase the character to the left of the cursor * and move the cursor left. */ public void backspace() { /* * Try to erase the previous character by overstriking with a space. */ tputs(sc_backspace, 1, putc); putc(' '); tputs(sc_backspace, 1, putc); } /* * Output a plain backspace, without erasing the previous char. */ public void putbs() { tputs(sc_backspace, 1, putc); } SHAR_EOF cat - << \SHAR_EOF > prompt.c /* * Prompting and other messages. * There are three flavors of prompts, SHORT, MEDIUM and LONG, * selected by the -m/-M options. * A prompt is either a colon or a message composed of various * pieces, such as the name of the file being viewed, the percentage * into the file, etc. */ #include "less.h" #include "position.h" extern int pr_type; extern int pipe; extern int hit_eof; extern int new_file; extern int sc_width; extern char current_file[]; extern int ac; extern char **av; extern int curr_ac; static char message[500]; /* * Append the name of the current file (to the message buffer). */ static void ap_filename() { if (!pipe) sprintf(message + strlen(message), "%s", current_file); } /* * Append the "file N of M" message. */ static void ap_of() { if (ac > 1) sprintf(message + strlen(message), " (file %d of %d)", curr_ac+1, ac); } /* * Append the byte offset into the current file. */ static void ap_byte() { POSITION pos, len; pos = position(BOTTOM_PLUS_ONE); if (pos != NULL_POSITION) { sprintf(message + strlen(message), " byte %ld", pos); len = ch_length(); if (len > 0) sprintf(message + strlen(message), "/%ld", len); } } /* * Append the percentage into the current file. */ static void ap_percent() { POSITION pos,len; pos = position(BOTTOM_PLUS_ONE); len = ch_length(); if (len > 0 && pos != NULL_POSITION) sprintf(message + strlen(message), " (%ld%%)", (100 * pos) / len); } /* * Append the end-of-file message. */ static void ap_eof() { strcat(message, " END"); if (curr_ac + 1 < ac) sprintf(message + strlen(message), " - Next: %s", av[curr_ac+1]); } /* * Return a message suitable for printing by the "=" command. */ public char * eq_message() { message[0] = '\0'; ap_filename(); ap_of(); ap_byte(); ap_percent(); /* * Truncate to the screen width. * {{ This isn't very nice. }} */ message[error_width()] = '\0'; return (message); } /* * Return a prompt. * This depends on the prompt type (SHORT, MEDIUM, LONG), etc. * If we can't come up with an appropriate prompt, return NULL * and the caller will prompt with a colon. */ public char * pr_string() { message[0] = '\0'; switch (pr_type) { case PR_SHORT: if (new_file) { ap_filename(); ap_of(); } if (hit_eof) ap_eof(); break; case PR_MEDIUM: if (new_file) { ap_filename(); ap_of(); } if (hit_eof) ap_eof(); else if (pipe) ap_byte(); else ap_percent(); break; case PR_LONG: ap_filename(); if (new_file) ap_of(); ap_byte(); if (hit_eof) ap_eof(); else ap_percent(); break; } new_file = 0; if (message[0] == '\0') return (NULL); /* * Truncate to the screen width. * {{ This isn't very nice. }} */ message[sc_width-2] = '\0'; return (message); } SHAR_EOF cat - << \SHAR_EOF > line.c /* * Routines to manipulate the "line buffer". * The line buffer holds a line of output as it is being built * in preparation for output to the screen. * We keep track of the PRINTABLE length of the line as it is being built. */ #include "less.h" static char linebuf[1024]; /* Buffer which holds the current output line */ static char *curr; /* Pointer into linebuf */ static int column; /* Printable length, accounting for backspaces, etc. */ /* * A ridiculously complex state machine takes care of backspaces * when in BS_UNDERLINE mode. The complexity arises from the attempt * to deal with all cases, especially involving long lines with underlining. * There are still some cases which will break it. * * There are four states: * UL_NORMAL is the normal state (not in underline mode). * UL_YES means we are in underline mode. We expect to get * either a sequence like "_\bX" or "X\b_" to continue * underline mode, or just some ordinary characters * (no backspaces) to end underline mode. * UL_X means we are one character after UL_YES * (we have gotten the '_' in "_\bX" or the 'X' in "X\b_"). * UL_XB means we are one character after UL_X * (we have gotten the backspace in "_\bX" or "X\b_"; * we expect one more ordinary character, * which will put us back in state UL_YES). */ static int ul_state; /* Currently in underline mode? */ #define UL_NORMAL 0 /* Not in underline mode */ #define UL_YES 1 /* In underline, need next char */ #define UL_X 2 /* In underline, got char, need \b */ #define UL_XB 3 /* In underline, got char & \b, need one more */ public char *line; /* Pointer to the current line. Usually points to linebuf. */ extern int bs_mode; extern int tabstop; extern int ul_width, ue_width; extern int sc_width, sc_height; /* * Rewind the line buffer. */ public void prewind() { line = curr = linebuf; ul_state = UL_NORMAL; column = 0; } /* * Append a character to the line buffer. * Expand tabs into spaces, handle underlining. * Returns 0 if ok, 1 if couldn't fit in buffer. */ #define NEW_COLUMN(newcol) if ((newcol) + ((ul_state)?ue_width:0) > sc_width) \ return (1); else column = (newcol) public int pappend(c) int c; { if (c == '\0') { /* * Terminate underline mode, if necessary. * Append a '\0' to the end of the line. */ switch (ul_state) { case UL_X: curr[0] = curr[-1]; curr[-1] = UE_CHAR; curr++; break; case UL_XB: case UL_YES: *curr++ = UE_CHAR; break; } ul_state = UL_NORMAL; *curr = '\0'; return (0); } if (curr > linebuf + sizeof(linebuf) - 12) /* * Almost out of room in the line buffer. * Don't take any chances. * {{ Linebuf is supposed to be big enough that this * will never happen, but may need to be made * bigger for wide screens or lots of backspaces. }} */ return (1); if (bs_mode == BS_UNDERLINE) { /* * Advance the state machine. */ switch (ul_state) { case UL_NORMAL: if (curr <= linebuf + 1 || curr[-1] != '\b' || (c != '_' && curr[-2] != '_')) break; /* * We have either "_\bX" or "X\b_" (including * the current char). Switch into underline mode. */ if (column + ul_width + ue_width + 1 >= sc_width) /* * Not enough room left on the screen to * enter and exit underline mode. */ return (1); if (ul_width > 0 && curr > linebuf + 2 && curr[-3] == ' ') { /* * Special case for magic cookie terminals: * if the previous char was a space, replace * it with the "enter underline" sequence. */ curr[-3] = UL_CHAR; column += ul_width-1; } else { curr[-1] = curr[-2]; curr[-2] = UL_CHAR; column += ul_width; curr++; } /* Fall thru */ case UL_XB: /* * Termination of a sequnce "_\bX" or "X\b_". */ if (c == '_') c = curr[-2]; curr -= 2; ul_state = UL_YES; break; case UL_YES: if (column + ue_width + 1 >= sc_width) /* * We have just barely enough room to * exit underline mode. */ return (1); ul_state = UL_X; break; case UL_X: if (c == '\b') ul_state = UL_XB; else { /* * Exit underline mode. * We have to shuffle the chars a bit * to make this work. */ curr[0] = curr[-1]; curr[-1] = UE_CHAR; column += ue_width; if (ul_width > 0 && curr[0] == ' ') /* * Another special case for magic * cookie terminals: if the next * char is a space, replace it * with the "exit underline" sequence. */ column--; else curr++; ul_state = UL_NORMAL; } break; } } if (c == '\t') { /* * Expand a tab into spaces. */ do { NEW_COLUMN(column+1); *curr++ = ' '; } while ((column % tabstop) != 0); return (0); } if (c == '\b') { if (bs_mode == BS_CONTROL) { /* * Treat backspace as a control char: output "^H". */ NEW_COLUMN(column+2); *curr++ = ('H' | 0200); } else { /* * Output a real backspace. */ column--; *curr++ = '\b'; } return (0); } if (control_char(c)) { /* * Put a "^X" into the buffer. * The 0200 bit is used to tell put_line() to prefix * the char with a ^. We don't actually put the ^ * in the buffer because we sometimes need to move * chars around, and such movement might separate * the ^ from its following character. */ NEW_COLUMN(column+2); *curr++ = (carat_char(c) | 0200); return (0); } /* * Ordinary character. Just put it in the buffer. */ NEW_COLUMN(column+1); *curr++ = c; return (0); } SHAR_EOF cat - << \SHAR_EOF > signal.c /* * Routines dealing with signals. * * A signal usually merely causes a bit to be set in the "signals" word. * At some convenient time, the mainline code checks to see if any * signals need processing by calling psignal(). * An exception is made if we are reading from the keyboard when the * signal is received. Some operating systems will simply call the * signal handler and NOT return from the read (with EINTR). * To handle this case, we service the interrupt directly from * the handler if we are reading from the keyboard. */ #include "less.h" #include <signal.h> #include <setjmp.h> /* * The type of signal handler functions. * Usually int, although it should be void. */ typedef int HANDLER; /* * "sigs" contains bits indicating signals which need to be processed. */ public int sigs; #define S_INTERRUPT 01 #ifdef SIGTSTP #define S_STOP 02 #endif extern int reading; extern char *first_cmd; extern jmp_buf main_loop; /* * Interrupt signal handler. */ static HANDLER interrupt() { SIGNAL(SIGINT, interrupt); sigs |= S_INTERRUPT; if (reading) psignals(); } #ifdef SIGTSTP /* * "Stop" (^Z) signal handler. */ static HANDLER stop() { SIGNAL(SIGTSTP, stop); sigs |= S_STOP; if (reading) psignals(); } #endif /* * Set up the signal handlers. */ public void init_signals() { (void) SIGNAL(SIGINT, interrupt); #ifdef SIGTSTP (void) SIGNAL(SIGTSTP, stop); #endif } /* * Process any signals we have recieved. * A received signal cause a bit to be set in "sigs". */ public void psignals() { register int tsignals; tsignals = sigs; sigs = 0; if (tsignals == 0) return; dropout(); /* Discard any buffered output */ #ifdef SIGTSTP if (tsignals & S_STOP) { /* * Clean up the terminal. */ lower_left(); clear_eol(); flush(); raw_mode(0); SIGNAL(SIGTSTP, SIG_DFL); #if SIGSETMASK /* * This system will not allow us to send a * stop signal (SIGTSTP) to ourself * while we are in the signal handler, like maybe now. * (This can be the case if we are reading; see comment above.) * So we ask the silly system for permission to do so. */ sigsetmask(0); #endif kill(getpid(), SIGTSTP); /* * ... Bye bye. ... * Hopefully we'll be back later and resume here... * Reset the terminal and arrange to repaint the * screen when we get back to the main command loop. */ SIGNAL(SIGTSTP, stop); raw_mode(1); first_cmd = "r"; longjmp(main_loop, 1); } #endif if (tsignals & S_INTERRUPT) { bell(); /* * {{ You may wish to replace the bell() with * error("Interrupt"); }} */ } longjmp(main_loop, 1); } SHAR_EOF cat - << \SHAR_EOF > help.c #include "less.h" /* * Display some help. * Help is in two pages. */ static void help0() { puts("f, SPACE Forward one screen.\n"); puts("b Backward one screen.\n"); puts("e, j, CR * Forward N lines, default 1.\n"); puts("y, k * Backward N lines, default 1.\n"); puts("d * Forward N lines, default 10 or last N to d or u command.\n"); puts("u * Backward N lines, default 10 or last N to d or u command.\n"); puts("r Repaint screen.\n"); puts("g * Go to line N, default 1.\n"); puts("G * Like g, but default is last line in file.\n"); puts("= Print current file name\n"); puts("/pattern * Search forward for N-th occurence of pattern.\n"); puts("?pattern * Search backward for N-th occurence of pattern.\n"); puts("n * Repeat previous search (for N-th occurence).\n"); puts("q Exit.\n"); error("More help..."); } static void help1() { char message[100]; extern char all_options[]; puts("R Repaint screen, discarding buffered input.\n"); puts("p * Position to N percent into the file.\n"); sprintf(message, "-X Toggle a flag (X may be one of \"%s\").\n", all_options); puts(message); puts("E [file] Examine a new file.\n"); puts("N Examine the next file (from the command line).\n"); puts("P Examine the previous file (from the command line).\n"); puts("V Print version number.\n"); #if SHELL_ESCAPE puts("!command Passes the command to a shell to be executed.\n"); #endif #if EDITOR puts("v Edit the current file with $EDITOR (default vi).\n"); #endif error(""); } public void help() { register int i; for (i = 0; i < 2; i++) { clear(); puts("Commands marked with * may be preceeded by a number, N.\n\n"); switch (i) { case 0: help0(); break; case 1: help1(); break; } } } SHAR_EOF cat - << \SHAR_EOF > ttyin.c /* * Routines dealing with getting input from the keyboard (i.e. from the user). */ #include "less.h" /* * The boolean "reading" is set true or false according to whether * we are currently reading from the keyboard. * This information is used by the signal handling stuff in signal.c. * {{ There are probably some race conditions here * involving the variable "reading". }} */ public int reading; static int tty; /* * Open keyboard for input. * (Just use file descriptor 2.) */ public void open_getc() { tty = 2; } /* * Get a character from the keyboard. */ public int getc() { char c; int result; reading = 1; do { flush(); result = read(tty, &c, 1); } while (result != 1); reading = 0; return (c & 0177); } SHAR_EOF cat - << \SHAR_EOF > command.c /* * User-level command processor. */ #include "less.h" extern int erase_char, kill_char; extern int pr_type; extern int sigs; extern int pipe; extern int quit_at_eof; extern int hit_eof; extern int sc_width, sc_height; extern char *first_cmd; extern char version[]; extern char current_file[]; extern char *editor; static char cmdbuf[90]; /* Buffer for holding a multi-char command */ static char *cp; /* Pointer into cmdbuf */ static int cmd_col; /* Current column of the multi-char command */ static char mcc; /* The multi-char command letter (e.g. '/') */ /* * Reset command buffer (to empty). */ cmd_reset() { cp = cmdbuf; } /* * Backspace in command buffer. */ static int cmd_erase() { if (cp == cmdbuf) /* * Backspace past beginning of the string: * this usually means abort the command. */ return (1); if (control_char(*--cp)) { /* * Erase an extra character, for the carat. */ backspace(); cmd_col--; } backspace(); cmd_col--; return (0); } /* * Set up the display to start a new multi-character command. */ start_mcc() { lower_left(); clear_eol(); putc(mcc); cmd_col = 1; } /* * Process a single character of a multi-character command, such as * a number, or the pattern of a search command. */ static int cmd_char(c) int c; { if (c == erase_char) { if (cmd_erase()) return (1); } else if (c == kill_char) { /* {{ Could do this faster, but who cares? }} */ while (cmd_erase() == 0) ; } else { /* * Append the character to the string, * if there is room in the buffer and on the screen. */ if (cp < &cmdbuf[sizeof(cmdbuf)-1] && cmd_col < sc_width-3) { *cp++ = c; if (control_char(c)) { putc('^'); cmd_col++; c = carat_char(c); } putc(c); cmd_col++; } else bell(); } return (0); } /* * Return the number currently in the command buffer. */ static int cmd_int() { *cp = '\0'; return (atoi(cmdbuf)); } /* * Move the cursor to lower left before executing a command. * This looks nicer if the command takes a long time before * updating the screen. */ static void cmd_exec() { lower_left(); flush(); } /* * Display the appropriate prompt. */ static void prompt() { register char *p; if (first_cmd != NULL && *first_cmd != '\0') /* * No prompt necessary if commands are from first_cmd * rather than from the user. */ return; /* * Select the proper prompt and display it. */ p = pr_string(); if (p == NULL) putc(':'); else { so_enter(); puts(p); so_exit(); } } /* * Get command character. * The character normally comes from the keyboard, * but may come from the "first_cmd" string. */ static int getcc() { if (first_cmd == NULL) return (getc()); if (*first_cmd == '\0') { /* * Reached end of first_cmd input. */ first_cmd = NULL; if (cp > cmdbuf) { /* * Command is incomplete, so try to complete it. * There are only two cases: * 1. We have "/string" but no newline. Add the \n. * 2. We have a number but no command. Treat as #g. * (This is all pretty hokey.) */ if (mcc != ':') return ('\n'); else return ('g'); } return (getc()); } return (*first_cmd++); } /* * Main command processor. * Accept and execute commands until a quit command, then return. */ public void commands() { register int c; register int n; register int scroll = 10; register int last_mcc = 0; mcc = 0; for (;;) { /* * Display prompt and accept a character. */ psignals(); /* See if any signals need processing */ if (quit_at_eof && hit_eof > 1) /* * After hitting end-of-file for the second time, * automatically advance to the next file. * If there are no more files, quit. */ next_file(1); cmd_reset(); lower_left(); clear_eol(); prompt(); c = getcc(); again: if (sigs) continue; if (mcc) { /* * We are in a multi-character command. * All chars until newline go into the command buffer. * (Note that mcc == ':' is a special case that * means a number is being entered.) */ if (mcc != ':' && (c == '\n' || c == '\r')) { /* * Execute the command. */ *cp = '\0'; cmd_exec(); if (mcc == 'E') { char *p; /* * Ignore leading spaces * in the filename. */ for (p = cmdbuf; *p == ' '; p++) ; edit(p); #if SHELL_ESCAPE } else if (mcc == '!') { lower_left(); putc('\n'); flush(); raw_mode(0); system(cmdbuf); raw_mode(1); error("!done"); first_cmd = "r"; /* Repaint */ #endif } else search(mcc, cmdbuf, n); mcc = 0; } else { if (mcc == ':' && (c < '0' || c > '9') && c != erase_char && c != kill_char) { /* * This is not part of the number * we were entering. Process * it as a regular character. */ mcc = 0; goto again; } /* * Append the char to the command buffer. */ if (cmd_char(c)) { /* Abort the multi-char command. */ mcc = 0; continue; } c = getcc(); goto again; } } else switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* * First digit of a number. */ mcc = ':'; start_mcc(); goto again; case 'f': case ' ': /* * Forward one screen. */ forward(sc_height - 1); break; case 'b': /* * Backward one screen. */ backward(sc_height - 1); break; case 'e': case 'j': case '\r': case '\n': /* * Forward N (default 1) line. */ n = cmd_int(); if (n <= 0) n = 1; forward(n); break; case 'y': case 'k': /* * Backward N (default 1) line. */ n = cmd_int(); if (n <= 0) n = 1; backward(n); break; case 'd': /* * Forward N lines * (default same as last 'd' or 'u' command). */ n = cmd_int(); if (n > 0) scroll = n; forward(scroll); break; case 'u': /* * Forward N lines * (default same as last 'd' or 'u' command). */ n = cmd_int(); if (n > 0) scroll = n; backward(scroll); break; case 'R': /* * Flush buffers, then repaint screen. */ ch_init(0); /* Fall thru */ case 'r': /* * Repaint screen. */ repaint(); break; case 'g': /* * Go to line N, default beginning of file. */ n = cmd_int(); if (n <= 0) n = 1; cmd_exec(); jump_back(n); break; case 'p': /* * Go to a specified percentage into the file. */ n = cmd_int(); if (n < 0) n = 0; if (n > 100) n = 100; cmd_exec(); jump_percent(n); break; case 'G': /* * Go to line N, default end of file. */ n = cmd_int(); cmd_exec(); if (n <= 0) jump_forw(); else jump_back(n); break; case '=': /* * Print file name, etc. */ error(eq_message()); break; case 'V': /* * Print version number, without the "@(#)". */ error(version+4); break; case 'q': /* * Exit. */ return; case '/': case '?': /* * Search for a pattern. * Accept chars of the pattern until \n. */ n = cmd_int(); if (n <= 0) n = 1; mcc = last_mcc = c; start_mcc(); c = getcc(); goto again; case 'n': /* * Repeat previous search. */ n = cmd_int(); if (n <= 0) n = 1; mcc = last_mcc; start_mcc(); cmd_exec(); search(mcc, (char *)NULL, n); mcc = 0; break; case 'h': /* * Help. */ help(); repaint(); break; case 'E': /* * Edit a new file. Get the filename. */ cmd_reset(); mcc = 'E'; start_mcc(); puts("dit: "); /* This looks nicer */ cmd_col += 5; c = getcc(); goto again; #if SHELL_ESCAPE case '!': /* * Shell escape. */ cmd_reset(); mcc = '!'; start_mcc(); c = getcc(); goto again; #endif #if EDITOR case 'v': if (pipe) { error("Cannot edit standard input"); break; } lower_left(); clear_eol(); flush(); raw_mode(0); sprintf(cmdbuf, "%s %s", editor, current_file); system(cmdbuf); raw_mode(1); first_cmd = "R"; break; #endif case 'N': /* * Examine next file. */ n = cmd_int(); if (n <= 0) n = 1; next_file(n); break; case 'P': /* * Examine previous file. */ n = cmd_int(); if (n <= 0) n = 1; prev_file(n); break; case '-': /* * Toggle a flag setting. */ mcc = '-'; start_mcc(); c = getcc(); mcc = 0; if (c == erase_char || c == kill_char) /* Abort the "-" command. */ break; toggle_option(c); break; default: bell(); break; } } } SHAR_EOF cat - << \SHAR_EOF > version.c /* * less * Copyright (c) 1984,1985 Mark Nudelman * * This program may be freely used and/or modified, * with the following provisions: * 1. This notice and the above copyright notice must remain intact. * 2. Neither this program, nor any modification of it, * may not be sold for profit without written consent of the author. * * ----------------------------------------------------------------- * * This program is a paginator similar to "more", * but allows you to move both forward and backward in the file. * Commands are based on "more" and "vi". * * ----------------------- CHANGES --------------------------------- * * Allowed use on standard input 1/29/84 markn * Added E, N, P commands 2/1/84 markn * Added '=' command, 'stop' signal handling 4/17/84 markn * Added line folding 4/20/84 markn * v2: Fixed '=' command to use BOTTOM_PLUS_ONE, * instead of TOP, added 'p' & 'v' commands 4/27/84 markn * v3: Added -m and -t options, '-' command 5/3/84 markn * v4: Added LESS environment variable 5/3/84 markn * v5: New comments, fixed '-' command slightly 5/3/84 markn * v6: Added -Q, visual bell 5/15/84 markn * v7: Fixed jump_back(n) bug: n should count real * lines, not folded lines. Also allow number * on G command. 5/24/84 markn * v8: Re-do -q and -Q commands 5/30/84 markn * v9: Added "+<cmd>" argument 9/25/84 markn * v10: Fixed bug in -b<n> argument processing 10/10/84 markn * v11: Made error() ring bell if \n not entered. 10/18/84 markn * ----------------------------------------------------------------- * v12: Reorganized signal handling and made * portable to 4.2bsd. 2/13/85 mark * v13: Reword error message for '-' command. 2/16/85 mark * v14: Added -bf and -bp variants of -b. 2/22/85 mark * v15: Miscellaneous changes. 2/25/85 mark * v16: Added -u flag for backspace processing. 3/13/85 mark * v17: Added j and k commands, * changed -t default. 4/13/85 mark * v18: Rewrote signal handling code. 4/20/85 mark * v19: Got rid of "verbose" eq_message(). 5/2/85 mark * Made search() scroll in some cases. * v20: Fixed screen.c ioctls for System V. 5/21/85 mark * v21: Fixed some first_cmd bugs. 5/23/85 mark * v22: Added support for no RECOMP nor REGCMP. 5/24/85 mark * v23: Miscellanous changes and prettying up. 5/25/85 mark * v24: Added ti,te terminal init & de-init 6/3/85 Mike Kersenbrock * v25: Added -U flag, standout mode underlining. 6/8/85 mark * v26: Added -M flag. 6/9/85 mark * Use underline termcap (us) if it exists. * v27: Renamed some variables to make unique in 6/15/85 mark * 6 chars. Minor fix to -m. * v28: Fixed right margin bug. 6/28/85 mark * v29: Incorporated M.Rose's changes to signal.c 6/28/85 mark * v30: Fixed stupid bug in argument processing. 6/29/85 mark * v31: Added -p flag, changed repaint algorithm. 7/15/85 mark * Added kludge for magic cookie terminals. * v32: Added cat_file if output not a tty. 7/16/85 mark * v33: Added -e flag and EDITOR. 7/23/85 mark * v34: Added -s flag. 7/26/85 mark * v35: Rewrote option handling; added option.c. 7/27/85 mark * v36: Fixed -e flag to work if not last file. 7/29/85 mark * v37: Added -x flag. 8/10/85 mark * v38: Changed prompting; created prompt.c. 8/19/85 mark * v39: (Not -p) does not initially clear screen. 8/24/85 mark * v40: Added "skipping" indicator in forw(). 8/26/85 mark * ----------------------------------------------------------------- */ char version[] = "@(#) less version 40"; SHAR_EOF
mark@nsc-pdc.UUCP (Mark Nudelman) (11/19/85)
#!/bin/sh-----cut here-----cut here-----cut here-----cut here----- # shar: Shell Archiver # Run the following text with /bin/sh to create: # ch.c # position.c # input.c # output.c # screen.c # prompt.c # line.c # signal.c # help.c # ttyin.c # command.c # version.c echo shar: extracting ch.c cat - << \SHAR_EOF > ch.c /* * Low level character input from the input file. * We use these special purpose routines which optimize moving * both forward and backward from the current read pointer. */ #include "less.h" public int file = -1; /* File descriptor of the input file */ /* * Pool of buffers holding the most recently used blocks of the input file. */ #define BUFSIZ 1024 static struct buf { struct buf *next, *prev; long block; char data[BUFSIZ]; }; static struct buf *bufs = NULL; public int nbufs; /* * The buffer pool is kept as a doubly-linked circular list, * in order from most- to least-recently used. * The circular list is anchored by buf_anchor. */ static struct { struct buf *next, *prev; } buf_anchor; #define END_OF_CHAIN ((struct buf *)&buf_anchor) #define buf_head buf_anchor.next #define buf_tail buf_anchor.prev /* * If we fail to allocate enough memory for buffers, we try to limp * along with a minimum number of buffers. */ #define DEF_NBUFS 2 /* Minimum number of buffers */ extern int clean_data; extern int ispipe; /* * Current position in file. * Stored as a block number and an offset into the block. */ static long ch_block; static int ch_offset; /* * Length of file, needed if input is a pipe. */ static POSITION ch_fsize; /* * Largest block number read if input is standard input (a pipe). */ static long last_piped_block; /* * Get the character pointed to by the read pointer. * ch_get() is a macro which is more efficient to call * than fch_get (the function), in the usual case * that the block desired is at the head of the chain. */ #define ch_get() ((buf_head->block == ch_block) ? \ buf_head->data[ch_offset] : fch_get()) static int fch_get() { register struct buf *bp; register int n; register int end; POSITION pos; /* * Look for a buffer holding the desired block. */ for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) if (bp->block == ch_block) goto found; /* * Block is not in a buffer. * Take the least recently used buffer * and read the desired block into it. */ bp = buf_tail; bp->block = ch_block; pos = ch_block * BUFSIZ; if (ispipe) { /* * The block requested should be one more than * the last block read. */ if (ch_block != ++last_piped_block) { /* This "should not happen". */ char message[80]; sprintf(message, "Pipe error: last %ld, want %ld\n", last_piped_block-1, ch_block); error(message); quit(); } } else lseek(file, pos, 0); /* * Read the block. This may take several reads if the input * is coming from standard input, due to the nature of pipes. */ end = 0; while ((n = read(file, &bp->data[end], BUFSIZ-end)) > 0) if ((end += n) >= BUFSIZ) break; if (n < 0) { error("read error"); quit(); } /* * Set an EOF marker in the buffered data itself. * Then ensure the data is "clean": there are no * extra EOF chars in the data and that the "meta" * bit (the 0200 bit) is reset in each char. */ if (end < BUFSIZ) { ch_fsize = pos + end; bp->data[end] = EOF; } if (!clean_data) while (--end >= 0) { bp->data[end] &= 0177; if (bp->data[end] == EOF) bp->data[end] = '@'; } found: /* if (buf_head != bp) {this is guaranteed by the ch_get macro} */ { /* * Move the buffer to the head of the buffer chain. * This orders the buffer chain, most- to least-recently used. */ bp->next->prev = bp->prev; bp->prev->next = bp->next; bp->next = buf_head; bp->prev = END_OF_CHAIN; buf_head->prev = bp; buf_head = bp; } return (bp->data[ch_offset]); } /* * Determine if a specific block is currently in one of the buffers. */ static int buffered(block) long block; { register struct buf *bp; for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) if (bp->block == block) return (1); return (0); } /* * Seek to a specified position in the file. * Return 0 if successful, non-zero if can't seek there. */ public int ch_seek(pos) register POSITION pos; { long new_block; new_block = pos / BUFSIZ; if (!ispipe || new_block == last_piped_block + 1 || buffered(new_block)) { /* * Set read pointer. */ ch_block = new_block; ch_offset = pos % BUFSIZ; return (0); } return (1); } /* * Seek to the end of the file. */ public int ch_end_seek() { if (ispipe) { /* * Do it the slow way: read till end of data. */ while (ch_forw_get() != EOF) ; } else { (void) ch_seek((POSITION)(lseek(file, (off_t)0, 2))); } return (0); } /* * Return the length of the file, if known. */ public POSITION ch_length() { if (ispipe) return (ch_fsize); return ((POSITION)(lseek(file, (off_t)0, 2))); } /* * Return the current position in the file. */ public POSITION ch_tell() { return (ch_block * BUFSIZ + ch_offset); } /* * Get the current char and post-increment the read pointer. */ public int ch_forw_get() { register int c; c = ch_get(); if (c != EOF && ++ch_offset >= BUFSIZ) { ch_offset = 0; ch_block ++; } return (c); } /* * Pre-decrement the read pointer and get the new current char. */ public int ch_back_get() { register int c; if (--ch_offset < 0) { if (ch_block <= 0 || (ispipe && !buffered(ch_block-1))) { ch_offset = 0; return (EOF); } ch_offset = BUFSIZ - 1; ch_block--; } c = ch_get(); return (c); } /* * Initialize the buffer pool to all empty. * Caller suggests that we use want_nbufs buffers. */ public void ch_init(want_nbufs) int want_nbufs; { register struct buf *bp; char *calloc(); if (nbufs < want_nbufs) { /* * We don't have enough buffers. * Free what we have (if any) and allocate some new ones. */ if (bufs != NULL) free((char *)bufs); bufs = (struct buf *) calloc(want_nbufs, sizeof(struct buf)); nbufs = want_nbufs; if (bufs == NULL) { /* * Couldn't get that many. * Try for a small default number of buffers. */ char message[80]; sprintf(message, "Cannot allocate %d buffers. Using %d buffers.", nbufs, DEF_NBUFS); error(message); bufs = (struct buf *) calloc(DEF_NBUFS, sizeof(struct buf)); nbufs = DEF_NBUFS; if (bufs == NULL) { /* * Couldn't even get the smaller number of bufs. * Something is wrong here, don't continue. */ sprintf(message, "Cannot even allocate %d buffers! Quitting.\n", DEF_NBUFS); error(message); quit(); /*NOTREACHED*/ } } } /* * Initialize the buffers to empty. * Set up the circular list. */ for (bp = &bufs[0]; bp < &bufs[nbufs]; bp++) { bp->next = bp + 1; bp->prev = bp - 1; bp->block = (long)(-1); } bufs[0].prev = bufs[nbufs-1].next = END_OF_CHAIN; buf_head = &bufs[0]; buf_tail = &bufs[nbufs-1]; last_piped_block = -1; ch_fsize = NULL_POSITION; (void) ch_seek((POSITION)0); } SHAR_EOF echo shar: extracting position.c cat - << \SHAR_EOF > position.c /* * Routines dealing with the "position" table. * This is a table which tells the position (in the input file) of the * first char on each currently displayed line. * * {{ The position table is scrolled by moving all the entries. * Would be better to have a circular table * and just change a couple of pointers. }} */ #include "less.h" #include "position.h" #define NPOS 100 /* {{ sc_height must be less than NPOS }} */ static POSITION table[NPOS]; /* The position table */ extern int sc_width, sc_height; /* * Return the position of one of: * the top (first) line on the screen * the second line on the screen * the bottom line on the screen * the line after the bottom line on the screen */ public POSITION position(where) int where; { switch (where) { case BOTTOM: where = sc_height - 2; break; case BOTTOM_PLUS_ONE: where = sc_height - 1; break; } return (table[where]); } /* * Add a new file position to the bottom of the position table. */ public void add_forw_pos(pos) POSITION pos; { register int i; /* * Scroll the position table up. */ for (i = 1; i < sc_height; i++) table[i-1] = table[i]; table[sc_height - 1] = pos; } /* * Add a new file position to the top of the position table. */ public void add_back_pos(pos) POSITION pos; { register int i; /* * Scroll the position table down. */ for (i = sc_height - 1; i > 0; i--) table[i] = table[i-1]; table[0] = pos; } /* * Initialize the position table, done whenever we clear the screen. */ public void pos_clear() { register int i; for (i = 0; i < sc_height; i++) table[i] = NULL_POSITION; } /* * See if the byte at a specified position is currently on the screen. * Check the position table to see if the position falls within its range. * Return the position table entry if found, -1 if not. */ public int onscreen(pos) POSITION pos; { register int i; if (pos < table[0]) return (-1); for (i = 1; i < sc_height; i++) if (pos < table[i]) return (i-1); return (-1); } SHAR_EOF echo shar: extracting input.c cat - << \SHAR_EOF > input.c /* * High level routines dealing with getting lines of input * from the file being viewed. * * When we speak of "lines" here, we mean PRINTABLE lines; * lines processed with respect to the screen width. * We use the term "raw line" to refer to lines simply * delimited by newlines; not processed with respect to screen width. */ #include "less.h" extern int do_bs; extern int squeeze; extern char *line; /* * Get the next line. * A "current" position is passed and a "new" position is returned. * The current position is the position of the first character of * a line. The new position is the position of the first character * of the NEXT line. The line obtained is the line starting at curr_pos. */ public POSITION forw_line(curr_pos) POSITION curr_pos; { POSITION new_pos; register int c; if (curr_pos == NULL_POSITION || ch_seek(curr_pos)) return (NULL_POSITION); c = ch_forw_get(); if (c == EOF) return (NULL_POSITION); prewind(); for (;;) { if (c == '\n' || c == EOF) { /* * End of the line. */ new_pos = ch_tell(); break; } /* * Append the char to the line and get the next char. */ if (pappend(c)) { /* * The char won't fit in the line; the line * is too long to print in the screen width. * End the line here. */ new_pos = ch_tell() - 1; break; } c = ch_forw_get(); } (void) pappend('\0'); if (squeeze && *line == '\0') { /* * This line is blank. * Skip down to the last contiguous blank line * and pretend it is the one which we are returning. */ while ((c = ch_forw_get()) == '\n') ; if (c != EOF) (void) ch_back_get(); new_pos = ch_tell(); } return (new_pos); } /* * Get the previous line. * A "current" position is passed and a "new" position is returned. * The current position is the position of the first character of * a line. The new position is the position of the first character * of the PREVIOUS line. The line obtained is the one starting at new_pos. */ public POSITION back_line(curr_pos) POSITION curr_pos; { POSITION new_pos, begin_new_pos; int c; if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 || ch_seek(curr_pos-1)) return (NULL_POSITION); if (squeeze) { /* * Find out if the "current" line was blank. */ (void) ch_forw_get(); /* Skip the newline */ c = ch_forw_get(); /* First char of "current" line */ (void) ch_back_get(); /* Restore our position */ (void) ch_back_get(); if (c == '\n') { /* * The "current" line was blank. * Skip over any preceeding blank lines, * since we skipped them in forw_line(). */ while ((c = ch_back_get()) == '\n') ; if (c == EOF) return (NULL_POSITION); (void) ch_forw_get(); } } /* * Scan backwards until we hit the beginning of the line. */ for (;;) { c = ch_back_get(); if (c == '\n') { /* * This is the newline ending the previous line. * We have hit the beginning of the line. */ new_pos = ch_tell() + 1; break; } if (c == EOF) { /* * We have hit the beginning of the file. * This must be the first line in the file. * This must, of course, be the beginning of the line. */ new_pos = (POSITION)0; break; } } /* * Now scan forwards from the beginning of this line. * We keep discarding "printable lines" (based on screen width) * until we reach the curr_pos. * * {{ This algorithm is pretty inefficient if the lines * are much longer than the screen width, * but I don't know of any better way. }} */ if (ch_seek(new_pos)) return (NULL_POSITION); loop: begin_new_pos = new_pos; prewind(); do { c = ch_forw_get(); new_pos++; if (c == '\n') break; if (pappend(c)) { /* * Got a full printable line, but we haven't * reached our curr_pos yet. Discard the line * and start a new one. */ (void) pappend('\0'); (void) ch_back_get(); new_pos--; goto loop; } } while (new_pos < curr_pos); (void) pappend('\0'); return (begin_new_pos); } SHAR_EOF echo shar: extracting output.c cat - << \SHAR_EOF > output.c /* * High level routines dealing with the output to the screen. */ #include "less.h" extern int sigs; extern int sc_width, sc_height; extern int ul_width, ue_width; extern int so_width, se_width; extern int tabstop; extern int twiddle; extern char *line; extern char *first_cmd; /* * Display the line which is in the line buffer. */ public void put_line() { register char *p; register int c; register int column; extern int auto_wrap, ignaw; if (sigs) /* * Don't output if a signal is pending. */ return; if (line == NULL) line = (twiddle) ? "~" : ""; column = 0; for (p = line; *p != '\0'; p++) { switch (c = *p) { case UL_CHAR: ul_enter(); column += ul_width; break; case UE_CHAR: ul_exit(); column += ue_width; break; case '\t': do { putc(' '); column++; } while ((column % tabstop) != 0); break; case '\b': putbs(); column--; break; default: if (c & 0200) { putc('^'); putc(c & 0177); column += 2; } else { putc(c); column++; } } } if (column < sc_width || !auto_wrap || ignaw) putc('\n'); } /* * Is a given character a "control" character? * {{ ASCII DEPENDENT }} */ public int control_char(c) int c; { return (c < ' ' || c == '\177'); } /* * Return the printable character used to identify a control character * (printed after a carat; e.g. '\3' => "^C"). * {{ ASCII DEPENDENT }} */ public int carat_char(c) int c; { return ((c == '\177') ? '?' : (c | 0100)); } static char obuf[1024]; static char *ob = obuf; /* * Flush buffered output. */ public void flush() { write(1, obuf, ob-obuf); ob = obuf; } /* * Discard buffered output. */ public void dropout() { ob = obuf; } /* * Output a character. */ public void putc(c) int c; { if (ob >= &obuf[sizeof(obuf)]) flush(); *ob++ = c; } /* * Output a string. */ public void puts(s) register char *s; { while (*s != '\0') putc(*s++); } /* * Output a message in the lower left corner of the screen * and wait for carriage return. */ static char return_to_continue[] = " (press RETURN)"; public void error(s) char *s; { register int c; static char buf[2]; lower_left(); clear_eol(); so_enter(); puts(s); puts(return_to_continue); so_exit(); #if ONLY_RETURN while ((c = getc()) != '\n' && c != '\r') bell(); #else c = getc(); if (c != '\n' && c != '\r' && c != ' ') { buf[0] = c; first_cmd = buf; } #endif if (strlen(s) > sc_width) repaint(); } public int error_width() { /* * Don't use the last position, because some terminals * will scroll if you write in the last char of the last line. */ return (sc_width - (sizeof(return_to_continue) + so_width + se_width + 1)); } SHAR_EOF echo shar: extracting screen.c cat - << \SHAR_EOF > screen.c /* * Routines which deal with the characteristics of the terminal. * Uses termcap to be as terminal-independent as possible. * * {{ Someday this should be rewritten to use curses. }} */ #include "less.h" #if XENIX #include <sys/types.h> #include <sys/ioctl.h> #endif #if TERMIO #include <termio.h> #else #include <sgtty.h> #endif /* * Strings passed to tputs() to do various terminal functions. */ static char *sc_pad, /* Pad string */ *sc_home, /* Cursor home */ *sc_addline, /* Add line, scroll down following lines */ *sc_lower_left, /* Cursor to last line, first column */ *sc_move, /* General cursor positioning */ *sc_clear, /* Clear screen */ *sc_eol_clear, /* Clear to end of line */ *sc_s_in, /* Enter standout (highlighted) mode */ *sc_s_out, /* Exit standout mode */ *sc_u_in, /* Enter underline mode */ *sc_u_out, /* Exit underline mode */ *sc_visual_bell, /* Visual bell (flash screen) sequence */ *sc_backspace, /* Backspace cursor */ *sc_init, /* Startup terminal initialization */ *sc_deinit; /* Exit terminal de-intialization */ static int dumb; static int hard; public int auto_wrap; /* Terminal does \r\n when write past margin */ public int ignaw; /* Terminal ignores \n immediately after wrap */ public int erase_char, kill_char; /* The user's erase and line-kill chars */ public int sc_width, sc_height; /* Height & width of screen */ public int ul_width, ue_width; /* Printing width of underline sequences */ public int so_width, se_width; /* Printing width of standout sequences */ /* * These two variables are sometimes defined in, * and needed by, the termcap library. * It may be necessary on some systems to declare them extern here. */ /*extern*/ short ospeed; /* Terminal output baud rate */ /*extern*/ char PC; /* Pad character */ extern int quiet; /* If VERY_QUIET, use visual bell for bell */ extern int know_dumb; /* Don't complain about a dumb terminal */ extern int back_scroll; char *tgetstr(); char *tgoto(); /* * Change terminal to "raw mode", or restore to "normal" mode. * "Raw mode" means * 1. An outstanding read will complete on receipt of a single keystroke. * 2. Input is not echoed. * 3. On output, \n is mapped to \r\n. * 4. \t is NOT be expanded into spaces. * 5. Signal-causing characters such as ctrl-C (interrupt), * etc. are NOT disabled. * It doesn't matter whether an input \n is mapped to \r, or vice versa. */ public void raw_mode(on) int on; { #if TERMIO struct termio s; static struct termio save_term; if (on) { /* * Get terminal modes. */ ioctl(2, TCGETA, &s); /* * Save modes and set certain variables dependent on modes. */ save_term = s; ospeed = s.c_cflag & CBAUD; erase_char = s.c_cc[VERASE]; kill_char = s.c_cc[VKILL]; /* * Set the modes to the way we want them. */ s.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL); s.c_oflag |= (OPOST|ONLCR|TAB3); s.c_oflag &= ~(OCRNL|ONOCR|ONLRET); s.c_cc[VMIN] = 1; s.c_cc[VTIME] = 0; } else { /* * Restore saved modes. */ s = save_term; } ioctl(2, TCSETAW, &s); #else struct sgttyb s; static struct sgttyb save_term; if (on) { /* * Get terminal modes. */ ioctl(2, TIOCGETP, &s); /* * Save modes and set certain variables dependent on modes. */ save_term = s; ospeed = s.sg_ospeed; erase_char = s.sg_erase; kill_char = s.sg_kill; /* * Set the modes to the way we want them. */ s.sg_flags |= CBREAK; s.sg_flags &= ~(ECHO|XTABS); } else { /* * Restore saved modes. */ s = save_term; } ioctl(2, TIOCSETN, &s); #endif } static int couldnt = 0; static void cannot(s) char *s; { if (know_dumb) /* * He knows he has a dumb terminal, so don't tell him. */ return; printf("WARNING: terminal cannot \"%s\"\n", s); couldnt = 1; } /* * Get terminal capabilities via termcap. */ public void get_term() { char termbuf[1024]; char *sp; static char sbuf[150]; char *getenv(); /* * Find out what kind of terminal this is. */ if (tgetent(termbuf, getenv("TERM")) <= 0) dumb = 1; /* * Get size of the screen. */ if (dumb || (sc_height = tgetnum("li")) < 0 || tgetflag("hc")) { /* Oh no, this is a hardcopy terminal. */ hard = 1; sc_height = 24; } if (dumb || (sc_width = tgetnum("co")) < 0) sc_width = 80; auto_wrap = tgetflag("am"); ignaw = tgetflag("xn"); /* * Assumes termcap variable "sg" is the printing width of * the standout sequence, the end standout sequence, * the underline sequence, and the end underline sequence. */ if ((ul_width = tgetnum("sg")) < 0) ul_width = 0; so_width = se_width = ue_width = ul_width; /* * Get various string-valued capabilities. */ sp = sbuf; sc_pad = (dumb) ? NULL : tgetstr("pc", &sp); if (sc_pad != NULL) PC = *sc_pad; sc_init = (dumb) ? NULL : tgetstr("ti", &sp); if (sc_init == NULL) sc_init = ""; sc_deinit= (dumb) ? NULL : tgetstr("te", &sp); if (sc_deinit == NULL) sc_deinit = ""; sc_eol_clear = (dumb) ? NULL : tgetstr("ce", &sp); if (hard || sc_eol_clear == NULL || *sc_eol_clear == '\0') { cannot("clear to end of line"); sc_eol_clear = ""; } sc_clear = (dumb) ? NULL : tgetstr("cl", &sp); if (hard || sc_clear == NULL || *sc_clear == '\0') { cannot("clear screen"); sc_clear = "\n\n"; } sc_move = (dumb) ? NULL : tgetstr("cm", &sp); if (hard || sc_move == NULL || *sc_move == '\0') { /* * This is not an error here, because we don't * always need sc_move. * We need it only if we don't have home or lower-left. */ sc_move = ""; } sc_s_in = (dumb) ? NULL : tgetstr("so", &sp); if (hard || sc_s_in == NULL) sc_s_in = ""; sc_s_out = (dumb) ? NULL : tgetstr("se", &sp); if (hard || sc_s_out == NULL) sc_s_out = ""; sc_u_in = (dumb) ? NULL : tgetstr("us", &sp); if (hard || sc_u_in == NULL) sc_u_in = sc_s_in; sc_u_out = (dumb) ? NULL : tgetstr("ue", &sp); if (hard || sc_u_out == NULL) sc_u_out = sc_s_out; sc_visual_bell = (dumb) ? NULL : tgetstr("vb", &sp); if (hard || sc_visual_bell == NULL) sc_visual_bell = ""; sc_home = (dumb) ? NULL : tgetstr("ho", &sp); if (hard || sc_home == NULL || *sc_home == '\0') { if (*sc_move == '\0') { cannot("home cursor"); /* * This last resort for sc_home is supposed to * be an up-arrow suggesting moving to the * top of the "virtual screen". (The one in * your imagination as you try to use this on * a hard copy terminal.) */ sc_home = "|\b^"; } else { /* * No "home" string, * but we can use "move(0,0)". */ strcpy(sp, tgoto(sc_move, 0, 0)); sc_home = sp; sp += strlen(sp) + 1; } } sc_lower_left = (dumb) ? NULL : tgetstr("ll", &sp); if (hard || sc_lower_left == NULL || *sc_lower_left == '\0') { if (*sc_move == '\0') { cannot("move cursor to lower left of screen"); sc_lower_left = "\r"; } else { /* * No "lower-left" string, * but we can use "move(0,last-line)". */ strcpy(sp, tgoto(sc_move, 0, sc_height-1)); sc_lower_left = sp; sp += strlen(sp) + 1; } } /* * To add a line at top of screen and scroll the display down, * we use "al" (add line) or "sr" (scroll reverse). */ if (dumb) sc_addline = NULL; else if ((sc_addline = tgetstr("al", &sp)) == NULL || *sc_addline == '\0') sc_addline = tgetstr("sr", &sp); if (hard || sc_addline == NULL || *sc_addline == '\0') { cannot("scroll backwards"); sc_addline = ""; /* Force repaint on any backward movement */ back_scroll = 0; } if (dumb || tgetflag("bs")) sc_backspace = "\b"; else { sc_backspace = tgetstr("bc", &sp); if (sc_backspace == NULL || *sc_backspace == '\0') sc_backspace = "\b"; } if (couldnt) /* Give him time to read all the "cannot" messages. */ error(""); } /* * Below are the functions which perform all the * terminal-specific screen manipulation. */ /* * Initialize terminal */ public void init() { tputs(sc_init, sc_height, putc); } /* * Deinitialize terminal */ public void deinit() { tputs(sc_deinit, sc_height, putc); } /* * Home cursor (move to upper left corner of screen). */ public void home() { tputs(sc_home, 1, putc); } /* * Add a blank line (called with cursor at home). * Should scroll the display down. */ public void add_line() { tputs(sc_addline, sc_height, putc); } /* * Move cursor to lower left corner of screen. */ public void lower_left() { tputs(sc_lower_left, 1, putc); } /* * Ring the terminal bell. */ public void bell() { if (quiet == VERY_QUIET) vbell(); else putc('\7'); } /* * Output the "visual bell", if there is one. */ public void vbell() { if (*sc_visual_bell == '\0') return; tputs(sc_visual_bell, sc_height, putc); } /* * Clear the screen. */ public void clear() { tputs(sc_clear, sc_height, putc); } /* * Clear from the cursor to the end of the cursor's line. * {{ This must not move the cursor. }} */ public void clear_eol() { tputs(sc_eol_clear, 1, putc); } /* * Begin "standout" (bold, underline, or whatever). */ public void so_enter() { tputs(sc_s_in, 1, putc); } /* * End "standout". */ public void so_exit() { tputs(sc_s_out, 1, putc); } /* * Begin "underline" (hopefully real underlining, * otherwise whatever the terminal provides). */ public void ul_enter() { tputs(sc_u_in, 1, putc); } /* * End "underline". */ public void ul_exit() { tputs(sc_u_out, 1, putc); } /* * Erase the character to the left of the cursor * and move the cursor left. */ public void backspace() { /* * Try to erase the previous character by overstriking with a space. */ tputs(sc_backspace, 1, putc); putc(' '); tputs(sc_backspace, 1, putc); } /* * Output a plain backspace, without erasing the previous char. */ public void putbs() { tputs(sc_backspace, 1, putc); } SHAR_EOF echo shar: extracting prompt.c cat - << \SHAR_EOF > prompt.c /* * Prompting and other messages. * There are three flavors of prompts, SHORT, MEDIUM and LONG, * selected by the -m/-M options. * A prompt is either a colon or a message composed of various * pieces, such as the name of the file being viewed, the percentage * into the file, etc. */ #include "less.h" #include "position.h" extern int pr_type; extern int ispipe; extern int hit_eof; extern int new_file; extern int sc_width; extern char current_file[]; extern int ac; extern char **av; extern int curr_ac; static char message[500]; /* * Append the name of the current file (to the message buffer). */ static void ap_filename() { if (!ispipe) sprintf(message + strlen(message), "%s", current_file); } /* * Append the "file N of M" message. */ static void ap_of() { if (ac > 1) sprintf(message + strlen(message), " (file %d of %d)", curr_ac+1, ac); } /* * Append the byte offset into the current file. */ static void ap_byte() { POSITION pos, len; pos = position(BOTTOM_PLUS_ONE); if (pos != NULL_POSITION) { sprintf(message + strlen(message), " byte %ld", pos); len = ch_length(); if (len > 0) sprintf(message + strlen(message), "/%ld", len); } } /* * Append the percentage into the current file. * If we cannot find the percentage and must_print is true, * the use the byte offset. */ static void ap_percent(must_print) { POSITION pos,len; pos = position(BOTTOM_PLUS_ONE); len = ch_length(); if (len > 0 && pos != NULL_POSITION) sprintf(message + strlen(message), " (%ld%%)", (100 * pos) / len); else if (must_print) ap_byte(); } /* * Append the end-of-file message. */ static void ap_eof() { strcat(message, " END"); if (curr_ac + 1 < ac) sprintf(message + strlen(message), " - Next: %s", av[curr_ac+1]); } /* * Return a message suitable for printing by the "=" command. */ public char * eq_message() { message[0] = '\0'; ap_filename(); ap_of(); ap_byte(); ap_percent(0); /* * Truncate to the screen width. * {{ This isn't very nice. }} */ message[error_width()] = '\0'; return (message); } /* * Return a prompt. * This depends on the prompt type (SHORT, MEDIUM, LONG), etc. * If we can't come up with an appropriate prompt, return NULL * and the caller will prompt with a colon. */ public char * pr_string() { message[0] = '\0'; switch (pr_type) { case PR_SHORT: if (new_file) { ap_filename(); ap_of(); } if (hit_eof) ap_eof(); break; case PR_MEDIUM: if (new_file) { ap_filename(); ap_of(); } if (hit_eof) ap_eof(); else ap_percent(1); break; case PR_LONG: ap_filename(); if (new_file) ap_of(); ap_byte(); if (hit_eof) ap_eof(); else ap_percent(0); break; } new_file = 0; if (message[0] == '\0') return (NULL); /* * Truncate to the screen width. * {{ This isn't very nice. }} */ message[sc_width-2] = '\0'; return (message); } SHAR_EOF echo shar: extracting line.c cat - << \SHAR_EOF > line.c /* * Routines to manipulate the "line buffer". * The line buffer holds a line of output as it is being built * in preparation for output to the screen. * We keep track of the PRINTABLE length of the line as it is being built. */ #include "less.h" static char linebuf[1024]; /* Buffer which holds the current output line */ static char *curr; /* Pointer into linebuf */ static int column; /* Printable length, accounting for backspaces, etc. */ /* * A ridiculously complex state machine takes care of backspaces * when in BS_UNDERLINE mode. The complexity arises from the attempt * to deal with all cases, especially involving long lines with underlining. * There are still some cases which will break it. * * There are four states: * UL_NORMAL is the normal state (not in underline mode). * UL_YES means we are in underline mode. We expect to get * either a sequence like "_\bX" or "X\b_" to continue * underline mode, or just some ordinary characters * (no backspaces) to end underline mode. * UL_X means we are one character after UL_YES * (we have gotten the '_' in "_\bX" or the 'X' in "X\b_"). * UL_XB means we are one character after UL_X * (we have gotten the backspace in "_\bX" or "X\b_"; * we expect one more ordinary character, * which will put us back in state UL_YES). */ static int ul_state; /* Currently in underline mode? */ #define UL_NORMAL 0 /* Not in underline mode */ #define UL_YES 1 /* In underline, need next char */ #define UL_X 2 /* In underline, got char, need \b */ #define UL_XB 3 /* In underline, got char & \b, need one more */ public char *line; /* Pointer to the current line. Usually points to linebuf. */ extern int bs_mode; extern int tabstop; extern int ul_width, ue_width; extern int sc_width, sc_height; /* * Rewind the line buffer. */ public void prewind() { line = curr = linebuf; ul_state = UL_NORMAL; column = 0; } /* * Append a character to the line buffer. * Expand tabs into spaces, handle underlining. * Returns 0 if ok, 1 if couldn't fit in buffer. */ #define NEW_COLUMN(newcol) if ((newcol) + ((ul_state)?ue_width:0) > sc_width) \ return (1); else column = (newcol) public int pappend(c) int c; { if (c == '\0') { /* * Terminate underline mode, if necessary. * Append a '\0' to the end of the line. */ switch (ul_state) { case UL_X: curr[0] = curr[-1]; curr[-1] = UE_CHAR; curr++; break; case UL_XB: case UL_YES: *curr++ = UE_CHAR; break; } ul_state = UL_NORMAL; *curr = '\0'; return (0); } if (curr > linebuf + sizeof(linebuf) - 12) /* * Almost out of room in the line buffer. * Don't take any chances. * {{ Linebuf is supposed to be big enough that this * will never happen, but may need to be made * bigger for wide screens or lots of backspaces. }} */ return (1); if (bs_mode == BS_UNDERLINE) { /* * Advance the state machine. */ switch (ul_state) { case UL_NORMAL: if (curr <= linebuf + 1 || curr[-1] != '\b') break; if (c != '_' && curr[-2] != '_') { curr -= 2; break; } /* * We have either "_\bX" or "X\b_" (including * the current char). Switch into underline mode. */ if (column + ul_width + ue_width + 1 >= sc_width) /* * Not enough room left on the screen to * enter and exit underline mode. */ return (1); if (ul_width > 0 && curr > linebuf + 2 && curr[-3] == ' ') { /* * Special case for magic cookie terminals: * if the previous char was a space, replace * it with the "enter underline" sequence. */ curr[-3] = UL_CHAR; column += ul_width-1; } else { curr[-1] = curr[-2]; curr[-2] = UL_CHAR; column += ul_width; curr++; } /* Fall thru */ case UL_XB: /* * Termination of a sequnce "_\bX" or "X\b_". */ if (c == '_') c = curr[-2]; curr -= 2; ul_state = UL_YES; break; case UL_YES: if (column + ue_width + 1 >= sc_width) /* * We have just barely enough room to * exit underline mode. */ return (1); ul_state = UL_X; break; case UL_X: if (c == '\b') ul_state = UL_XB; else { /* * Exit underline mode. * We have to shuffle the chars a bit * to make this work. */ curr[0] = curr[-1]; curr[-1] = UE_CHAR; column += ue_width; if (ul_width > 0 && curr[0] == ' ') /* * Another special case for magic * cookie terminals: if the next * char is a space, replace it * with the "exit underline" sequence. */ column--; else curr++; ul_state = UL_NORMAL; } break; } } if (c == '\t') { /* * Expand a tab into spaces. */ do { NEW_COLUMN(column+1); } while ((column % tabstop) != 0); *curr++ = '\t'; return (0); } if (c == '\b') { if (bs_mode == BS_CONTROL) { /* * Treat backspace as a control char: output "^H". */ NEW_COLUMN(column+2); *curr++ = ('H' | 0200); } else { /* * Output a real backspace. */ column--; *curr++ = '\b'; } return (0); } if (control_char(c)) { /* * Put a "^X" into the buffer. * The 0200 bit is used to tell put_line() to prefix * the char with a ^. We don't actually put the ^ * in the buffer because we sometimes need to move * chars around, and such movement might separate * the ^ from its following character. */ NEW_COLUMN(column+2); *curr++ = (carat_char(c) | 0200); return (0); } /* * Ordinary character. Just put it in the buffer. */ NEW_COLUMN(column+1); *curr++ = c; return (0); } /* * Analogous to forw_line(), but deals with "raw lines": * lines which are not split for screen width. * {{ This is supposed to be more efficient than forw_line(). }} */ public POSITION forw_raw_line(curr_pos) POSITION curr_pos; { register char *p; register int c; POSITION new_pos; if (curr_pos == NULL_POSITION || ch_seek(curr_pos) || (c = ch_forw_get()) == EOF) return (NULL_POSITION); p = linebuf; for (;;) { if (c == '\n' || c == EOF) { new_pos = ch_tell(); break; } if (p >= &linebuf[sizeof(linebuf)-1]) { /* * Overflowed the input buffer. * Pretend the line ended here. * {{ The line buffer is supposed to be big * enough that this never happens. }} */ new_pos = ch_tell() - 1; break; } *p++ = c; c = ch_forw_get(); } *p = '\0'; line = linebuf; return (new_pos); } /* * Analogous to back_line(), but deals with "raw lines". * {{ This is supposed to be more efficient than back_line(). }} */ public POSITION back_raw_line(curr_pos) POSITION curr_pos; { register char *p; register int c; POSITION new_pos; if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 || ch_seek(curr_pos-1)) return (NULL_POSITION); p = &linebuf[sizeof(linebuf)]; *--p = '\0'; for (;;) { c = ch_back_get(); if (c == '\n') { /* * This is the newline ending the previous line. * We have hit the beginning of the line. */ new_pos = ch_tell() + 1; break; } if (c == EOF) { /* * We have hit the beginning of the file. * This must be the first line in the file. * This must, of course, be the beginning of the line. */ new_pos = (POSITION)0; break; } if (p <= linebuf) { /* * Overflowed the input buffer. * Pretend the line ended here. */ new_pos = ch_tell() + 1; break; } *--p = c; } line = p; return (new_pos); } SHAR_EOF echo shar: extracting signal.c cat - << \SHAR_EOF > signal.c /* * Routines dealing with signals. * * A signal usually merely causes a bit to be set in the "signals" word. * At some convenient time, the mainline code checks to see if any * signals need processing by calling psignal(). * An exception is made if we are reading from the keyboard when the * signal is received. Some operating systems will simply call the * signal handler and NOT return from the read (with EINTR). * To handle this case, we service the interrupt directly from * the handler if we are reading from the keyboard. */ #include "less.h" #include <signal.h> #include <setjmp.h> /* * The type of signal handler functions. * Usually int, although it should be void. */ typedef int HANDLER; /* * "sigs" contains bits indicating signals which need to be processed. */ public int sigs; #define S_INTERRUPT 01 #ifdef SIGTSTP #define S_STOP 02 #endif extern int reading; extern char *first_cmd; extern jmp_buf main_loop; /* * Interrupt signal handler. */ static HANDLER interrupt() { SIGNAL(SIGINT, interrupt); sigs |= S_INTERRUPT; if (reading) psignals(); } #ifdef SIGTSTP /* * "Stop" (^Z) signal handler. */ static HANDLER stop() { SIGNAL(SIGTSTP, stop); sigs |= S_STOP; if (reading) psignals(); } #endif /* * Set up the signal handlers. */ public void init_signals() { (void) SIGNAL(SIGINT, interrupt); #ifdef SIGTSTP (void) SIGNAL(SIGTSTP, stop); #endif } /* * Process any signals we have recieved. * A received signal cause a bit to be set in "sigs". */ public void psignals() { register int tsignals; tsignals = sigs; sigs = 0; if (tsignals == 0) return; dropout(); /* Discard any buffered output */ #ifdef SIGTSTP if (tsignals & S_STOP) { /* * Clean up the terminal. */ #ifdef SIGTTOU SIGNAL(SIGTTOU, SIG_IGN); #endif lower_left(); clear_eol(); flush(); raw_mode(0); #ifdef SIGTTOU SIGNAL(SIGTTOU, SIG_DFL); #endif SIGNAL(SIGTSTP, SIG_DFL); #if SIGSETMASK /* * This system will not allow us to send a * stop signal (SIGTSTP) to ourself * while we are in the signal handler, like maybe now. * (This can be the case if we are reading; see comment above.) * So we ask the silly system for permission to do so. */ sigsetmask(0); #endif kill(getpid(), SIGTSTP); /* * ... Bye bye. ... * Hopefully we'll be back later and resume here... * Reset the terminal and arrange to repaint the * screen when we get back to the main command loop. */ SIGNAL(SIGTSTP, stop); raw_mode(1); first_cmd = "r"; longjmp(main_loop, 1); } #endif if (tsignals & S_INTERRUPT) { bell(); /* * {{ You may wish to replace the bell() with * error("Interrupt"); }} */ } longjmp(main_loop, 1); } /* * Pass the specified command to a shell to be executed. * Like plain "system()", but handles resetting terminal modes, etc. */ public void lsystem(cmd) char *cmd; { int inp; /* * Print the command which is to be executed. */ lower_left(); clear_eol(); puts("!"); puts(cmd); puts("\n"); /* * De-initialize the terminal and take out of raw mode. */ deinit(); flush(); raw_mode(0); /* * Restore signals to their defaults. */ SIGNAL(SIGINT, SIG_DFL); #ifdef SIGTSTP SIGNAL(SIGTSTP, SIG_DFL); #endif /* * Pass the command to the system to be executed. */ inp = dup(0); close(0); open("/dev/tty", 0); system(cmd); close(0); dup(inp); close(inp); /* * Reset signals, raw mode, etc. */ init_signals(); raw_mode(1); init(); } SHAR_EOF echo shar: extracting help.c cat - << \SHAR_EOF > help.c #include "less.h" /* * Display some help. * Help is in two pages. */ static void help0() { puts("f, SPACE Forward one screen.\n"); puts("b Backward one screen.\n"); puts("e, j, CR * Forward N lines, default 1.\n"); puts("y, k * Backward N lines, default 1.\n"); puts("d * Forward N lines, default 10 or last N to d or u command.\n"); puts("u * Backward N lines, default 10 or last N to d or u command.\n"); puts("r Repaint screen.\n"); puts("g * Go to line N, default 1.\n"); puts("G * Like g, but default is last line in file.\n"); puts("= Print current file name\n"); puts("/pattern * Search forward for N-th occurence of pattern.\n"); puts("?pattern * Search backward for N-th occurence of pattern.\n"); puts("n * Repeat previous search (for N-th occurence).\n"); puts("q Exit.\n"); error("More help..."); } static void help1() { char message[100]; extern char all_options[]; puts("R Repaint screen, discarding buffered input.\n"); puts("p, % * Position to N percent into the file.\n"); puts("m<letter> Mark the current position with <letter>.\n"); puts("'<letter> Return to a previously marked position.\n"); sprintf(message, "-X Toggle a flag (X may be one of \"%s\").\n", all_options); puts(message); puts("E [file] Examine a new file.\n"); puts("N Examine the next file (from the command line).\n"); puts("P Examine the previous file (from the command line).\n"); puts("V Print version number.\n"); #if SHELL_ESCAPE puts("!command Passes the command to a shell to be executed.\n"); #endif #if EDITOR sprintf(message, "v Edit the current file with $EDITOR (default %s).\n", EDIT_PGM); puts(message); #endif error(""); } public void help() { register int i; for (i = 0; i < 2; i++) { clear(); puts("Commands marked with * may be preceeded by a number, N.\n\n"); switch (i) { case 0: help0(); break; case 1: help1(); break; } } } SHAR_EOF echo shar: extracting ttyin.c cat - << \SHAR_EOF > ttyin.c /* * Routines dealing with getting input from the keyboard (i.e. from the user). */ #include "less.h" /* * The boolean "reading" is set true or false according to whether * we are currently reading from the keyboard. * This information is used by the signal handling stuff in signal.c. * {{ There are probably some race conditions here * involving the variable "reading". }} */ public int reading; static int tty; /* * Open keyboard for input. * (Just use file descriptor 2.) */ public void open_getc() { tty = 2; } /* * Get a character from the keyboard. */ public int getc() { char c; int result; reading = 1; do { flush(); result = read(tty, &c, 1); } while (result != 1); reading = 0; return (c & 0177); } SHAR_EOF echo shar: extracting command.c cat - << \SHAR_EOF > command.c /* * User-level command processor. */ #include "less.h" #include "position.h" #include <setjmp.h> extern jmp_buf main_loop; extern int erase_char, kill_char; extern int pr_type; extern int sigs; extern int ispipe; extern int quit_at_eof; extern int hit_eof; extern int sc_width, sc_height; extern char *first_cmd; extern char version[]; extern char current_file[]; extern char *editor; static char cmdbuf[90]; /* Buffer for holding a multi-char command */ static char *cp; /* Pointer into cmdbuf */ static int cmd_col; /* Current column of the multi-char command */ static char mcc; /* The multi-char command letter (e.g. '/') */ static char last_mcc; /* The previous mcc */ /* * Reset command buffer (to empty). */ cmd_reset() { cp = cmdbuf; } /* * Backspace in command buffer. */ static int cmd_erase() { if (cp == cmdbuf) /* * Backspace past beginning of the string: * this usually means abort the command. */ return (1); if (control_char(*--cp)) { /* * Erase an extra character, for the carat. */ backspace(); cmd_col--; } backspace(); cmd_col--; return (0); } /* * Set up the display to start a new multi-character command. */ start_mcc() { lower_left(); clear_eol(); putc(mcc); cmd_col = 1; } /* * Process a single character of a multi-character command, such as * a number, or the pattern of a search command. */ static int cmd_char(c) int c; { if (c == erase_char) { if (cmd_erase()) return (1); } else if (c == kill_char) { /* {{ Could do this faster, but who cares? }} */ while (cmd_erase() == 0) ; } else { /* * Append the character to the string, * if there is room in the buffer and on the screen. */ if (cp < &cmdbuf[sizeof(cmdbuf)-1] && cmd_col < sc_width-3) { *cp++ = c; if (control_char(c)) { putc('^'); cmd_col++; c = carat_char(c); } putc(c); cmd_col++; } else bell(); } return (0); } /* * Return the number currently in the command buffer. */ static int cmd_int() { *cp = '\0'; cp = cmdbuf; return (atoi(cmdbuf)); } /* * Move the cursor to lower left before executing a command. * This looks nicer if the command takes a long time before * updating the screen. */ static void cmd_exec() { lower_left(); flush(); } /* * Display the appropriate prompt. */ static void prompt() { register char *p; if (first_cmd != NULL && *first_cmd != '\0') /* * No prompt necessary if commands are from first_cmd * rather than from the user. */ return; /* * Select the proper prompt and display it. */ p = pr_string(); if (p == NULL) putc(':'); else { so_enter(); puts(p); so_exit(); } } /* * Get command character. * The character normally comes from the keyboard, * but may come from the "first_cmd" string. */ static int getcc() { if (first_cmd == NULL) return (getc()); if (*first_cmd == '\0') { /* * Reached end of first_cmd input. */ first_cmd = NULL; if (cp > cmdbuf && position(TOP) == NULL_POSITION) { /* * Command is incomplete, so try to complete it. * There are only two cases: * 1. We have "/string" but no newline. Add the \n. * 2. We have a number but no command. Treat as #g. * (This is all pretty hokey.) */ if (mcc != ':') return ('\n'); else return ('g'); } return (getc()); } return (*first_cmd++); } /* * Main command processor. * Accept and execute commands until a quit command, then return. */ public void commands() { register int c; register int n; register int scroll = 10; mcc = last_mcc = 0; setjmp(main_loop); for (;;) { /* * Display prompt and accept a character. */ psignals(); /* See if any signals need processing */ if (quit_at_eof && hit_eof > 1) /* * After hitting end-of-file for the second time, * automatically advance to the next file. * If there are no more files, quit. */ next_file(1); cmd_reset(); lower_left(); clear_eol(); prompt(); c = getcc(); again: if (sigs) continue; if (mcc) { /* * We are in a multi-character command. * All chars until newline go into the command buffer. * (Note that mcc == ':' is a special case that * means a number is being entered.) */ if (mcc != ':' && (c == '\n' || c == '\r')) { /* * Execute the command. */ *cp = '\0'; cmd_exec(); if (mcc == 'E') { char *p; /* * Ignore leading spaces * in the filename. */ for (p = cmdbuf; *p == ' '; p++) ; edit(p); #if SHELL_ESCAPE } else if (mcc == '!') { lsystem(cmdbuf); error("!done"); first_cmd = "r"; /* Repaint */ #endif } else search(mcc, cmdbuf, n); mcc = 0; } else { if (mcc == ':' && (c < '0' || c > '9') && c != erase_char && c != kill_char) { /* * This is not part of the number * we were entering. Process * it as a regular character. */ mcc = 0; goto again; } /* * Append the char to the command buffer. */ if (cmd_char(c)) { /* Abort the multi-char command. */ mcc = 0; continue; } c = getcc(); goto again; } } else switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* * First digit of a number. */ mcc = ':'; start_mcc(); goto again; case 'f': case ' ': case CONTROL('F'): /* * Forward one screen. */ n = cmd_int(); if (n <= 0) n = sc_height - 1; forward(n, 1); break; case 'b': case CONTROL('B'): /* * Backward one screen. */ n = cmd_int(); if (n <= 0) n = sc_height - 1; backward(n, 1); break; case 'e': case 'j': case '\r': case '\n': case CONTROL('E'): /* * Forward N (default 1) line. */ n = cmd_int(); if (n <= 0) n = 1; forward(n, 0); break; case 'y': case 'k': case CONTROL('K'): case CONTROL('Y'): /* * Backward N (default 1) line. */ n = cmd_int(); if (n <= 0) n = 1; backward(n, 0); break; case 'd': case CONTROL('D'): /* * Forward N lines * (default same as last 'd' or 'u' command). */ n = cmd_int(); if (n > 0) scroll = n; forward(scroll, 0); break; case 'u': case CONTROL('U'): /* * Forward N lines * (default same as last 'd' or 'u' command). */ n = cmd_int(); if (n > 0) scroll = n; backward(scroll, 0); break; case 'R': /* * Flush buffers, then repaint screen. */ ch_init(0); /* Fall thru */ case 'r': case CONTROL('R'): case CONTROL('L'): /* * Repaint screen. */ repaint(); break; case 'g': /* * Go to line N, default beginning of file. */ n = cmd_int(); if (n <= 0) n = 1; cmd_exec(); jump_back(n); break; case 'p': case '%': /* * Go to a specified percentage into the file. */ n = cmd_int(); if (n < 0) n = 0; if (n > 100) n = 100; cmd_exec(); jump_percent(n); break; case 'G': /* * Go to line N, default end of file. */ n = cmd_int(); cmd_exec(); if (n <= 0) jump_forw(); else jump_back(n); break; case '=': case CONTROL('G'): /* * Print file name, etc. */ error(eq_message()); break; case 'V': /* * Print version number, without the "@(#)". */ error(version+4); break; case 'q': /* * Exit. */ return; case '/': case '?': /* * Search for a pattern. * Accept chars of the pattern until \n. */ n = cmd_int(); if (n <= 0) n = 1; mcc = last_mcc = c; start_mcc(); c = getcc(); goto again; case 'n': /* * Repeat previous search. */ n = cmd_int(); if (n <= 0) n = 1; mcc = last_mcc; start_mcc(); cmd_exec(); search(mcc, (char *)NULL, n); mcc = 0; break; case 'h': /* * Help. */ help(); repaint(); break; case 'E': /* * Edit a new file. Get the filename. */ cmd_reset(); mcc = 'E'; start_mcc(); puts("dit: "); /* This looks nicer */ cmd_col += 5; c = getcc(); goto again; #if SHELL_ESCAPE case '!': /* * Shell escape. */ cmd_reset(); mcc = '!'; start_mcc(); c = getcc(); goto again; #endif #if EDITOR case 'v': if (ispipe) { error("Cannot edit standard input"); break; } sprintf(cmdbuf, "%s %s", editor, current_file); lsystem(cmdbuf); first_cmd = "R"; break; #endif case 'N': /* * Examine next file. */ n = cmd_int(); if (n <= 0) n = 1; next_file(n); break; case 'P': /* * Examine previous file. */ n = cmd_int(); if (n <= 0) n = 1; prev_file(n); break; case '-': /* * Toggle a flag setting. */ mcc = '-'; start_mcc(); c = getcc(); mcc = 0; if (c == erase_char || c == kill_char) break; toggle_option(c); break; case 'm': /* * Set a mark. */ lower_left(); clear_eol(); puts("mark: "); c = getcc(); if (c == erase_char || c == kill_char) break; setmark(c); break; case '\'': /* * Go to a mark. */ lower_left(); clear_eol(); puts("goto mark: "); c = getcc(); if (c == erase_char || c == kill_char) break; gomark(c); break; default: bell(); break; } } } SHAR_EOF echo shar: extracting version.c cat - << \SHAR_EOF > version.c /* * less * Copyright (c) 1984,1985 Mark Nudelman * * This program may be freely used and/or modified, * with the following provisions: * 1. This notice and the above copyright notice must remain intact. * 2. Neither this program, nor any modification of it, * may not be sold for profit without written consent of the author. * * ----------------------------------------------------------------- * * This program is a paginator similar to "more", * but allows you to move both forward and backward in the file. * Commands are based on "more" and "vi". * * ----------------------- CHANGES --------------------------------- * * Allowed use on standard input 1/29/84 markn * Added E, N, P commands 2/1/84 markn * Added '=' command, 'stop' signal handling 4/17/84 markn * Added line folding 4/20/84 markn * v2: Fixed '=' command to use BOTTOM_PLUS_ONE, * instead of TOP, added 'p' & 'v' commands 4/27/84 markn * v3: Added -m and -t options, '-' command 5/3/84 markn * v4: Added LESS environment variable 5/3/84 markn * v5: New comments, fixed '-' command slightly 5/3/84 markn * v6: Added -Q, visual bell 5/15/84 markn * v7: Fixed jump_back(n) bug: n should count real * lines, not folded lines. Also allow number * on G command. 5/24/84 markn * v8: Re-do -q and -Q commands 5/30/84 markn * v9: Added "+<cmd>" argument 9/25/84 markn * v10: Fixed bug in -b<n> argument processing 10/10/84 markn * v11: Made error() ring bell if \n not entered. 10/18/84 markn * ----------------------------------------------------------------- * v12: Reorganized signal handling and made * portable to 4.2bsd. 2/13/85 mark * v13: Reword error message for '-' command. 2/16/85 mark * v14: Added -bf and -bp variants of -b. 2/22/85 mark * v15: Miscellaneous changes. 2/25/85 mark * v16: Added -u flag for backspace processing. 3/13/85 mark * v17: Added j and k commands, * changed -t default. 4/13/85 mark * v18: Rewrote signal handling code. 4/20/85 mark * v19: Got rid of "verbose" eq_message(). 5/2/85 mark * Made search() scroll in some cases. * v20: Fixed screen.c ioctls for System V. 5/21/85 mark * v21: Fixed some first_cmd bugs. 5/23/85 mark * v22: Added support for no RECOMP nor REGCMP. 5/24/85 mark * v23: Miscellanous changes and prettying up. 5/25/85 mark * v24: Added ti,te terminal init & de-init 6/3/85 Mike Kersenbrock * v25: Added -U flag, standout mode underlining. 6/8/85 mark * v26: Added -M flag. 6/9/85 mark * Use underline termcap (us) if it exists. * v27: Renamed some variables to make unique in 6/15/85 mark * 6 chars. Minor fix to -m. * v28: Fixed right margin bug. 6/28/85 mark * v29: Incorporated M.Rose's changes to signal.c 6/28/85 mark * v30: Fixed stupid bug in argument processing. 6/29/85 mark * v31: Added -p flag, changed repaint algorithm. 7/15/85 mark * Added kludge for magic cookie terminals. * v32: Added cat_file if output not a tty. 7/16/85 mark * v33: Added -e flag and EDITOR. 7/23/85 mark * v34: Added -s flag. 7/26/85 mark * v35: Rewrote option handling; added option.c. 7/27/85 mark * v36: Fixed -e flag to work if not last file. 7/29/85 mark * v37: Added -x flag. 8/10/85 mark * v38: Changed prompting; created prompt.c. 8/19/85 mark * v39: (Not -p) does not initially clear screen. 8/24/85 mark * v40: Added "skipping" indicator in forw(). 8/26/85 mark * v41: ONLY_RETURN, control char commands, 9/17/85 mark * faster search, other minor fixes. * v42: Added ++ command line syntax; 9/25/85 mark * ch_fsize for pipes. * v43: Added -h flag, changed prim.c algorithms. 10/15/85 mark * v44: Made END print in all cases of eof; 10/16/85 mark * ignore SIGTTOU after receiving SIGTSTP. * v45: Never print backspaces unless -u. 10/16/85 mark * v46: Backwards scroll in jump_loc. 10/24/85 mark * v47: Fixed bug in edit(): *first_cmd==0 10/30/85 mark * v48: Use TIOCSETN instead of TIOCSETP. 11/16/85 mark * Added marks (m and ' commands). * ----------------------------------------------------------------- */ char version[] = "@(#) less version 48"; SHAR_EOF