v16i031: Less, a pager that's more than more, Part02/04
Rich Salz
rsalz at uunet.uu.net
Sat Sep 17 04:33:09 AEST 1988
Submitted-by: ctnews!UNIX386!mark
Posting-number: Volume 16, Issue 31
Archive-name: less5/part02
#! /bin/sh
# This is a shell archive.
# Remove anything before this line, then unpack it
# by saving it into a file and typing "sh file".
echo shar: Extracting \"screen.c\"
sed "s/^X//" >'screen.c' <<'END_OF_FILE'
X/*
X * Routines which deal with the characteristics of the terminal.
X * Uses termcap to be as terminal-independent as possible.
X *
X * {{ Someday this should be rewritten to use curses. }}
X */
X
X#include "less.h"
X#if XENIX
X#include <sys/types.h>
X#include <sys/ioctl.h>
X#endif
X
X#if TERMIO
X#include <termio.h>
X#else
X#include <sgtty.h>
X#endif
X
X#ifdef TIOCGWINSZ
X#include <sys/ioctl.h>
X#else
X/*
X * For the Unix PC (ATT 7300 & 3B1):
X * Since WIOCGETD is defined in sys/window.h, we can't use that to decide
X * whether to include sys/window.h. Use SIGPHONE from sys/signal.h instead.
X */
X#include <sys/signal.h>
X#ifdef SIGPHONE
X#include <sys/window.h>
X#endif
X#endif
X
X/*
X * Strings passed to tputs() to do various terminal functions.
X */
Xstatic char
X *sc_pad, /* Pad string */
X *sc_home, /* Cursor home */
X *sc_addline, /* Add line, scroll down following lines */
X *sc_lower_left, /* Cursor to last line, first column */
X *sc_move, /* General cursor positioning */
X *sc_clear, /* Clear screen */
X *sc_eol_clear, /* Clear to end of line */
X *sc_s_in, /* Enter standout (highlighted) mode */
X *sc_s_out, /* Exit standout mode */
X *sc_u_in, /* Enter underline mode */
X *sc_u_out, /* Exit underline mode */
X *sc_b_in, /* Enter bold mode */
X *sc_b_out, /* Exit bold mode */
X *sc_visual_bell, /* Visual bell (flash screen) sequence */
X *sc_backspace, /* Backspace cursor */
X *sc_init, /* Startup terminal initialization */
X *sc_deinit; /* Exit terminal de-intialization */
X
Xpublic int auto_wrap; /* Terminal does \r\n when write past margin */
Xpublic int ignaw; /* Terminal ignores \n immediately after wrap */
Xpublic int erase_char, kill_char; /* The user's erase and line-kill chars */
Xpublic int sc_width, sc_height; /* Height & width of screen */
Xpublic int sc_window = -1; /* window size for forward and backward */
Xpublic int bo_width, be_width; /* Printing width of boldface sequences */
Xpublic int ul_width, ue_width; /* Printing width of underline sequences */
Xpublic int so_width, se_width; /* Printing width of standout sequences */
X
X/*
X * These two variables are sometimes defined in,
X * and needed by, the termcap library.
X * It may be necessary on some systems to declare them extern here.
X */
X/*extern*/ short ospeed; /* Terminal output baud rate */
X/*extern*/ char PC; /* Pad character */
X
Xextern int quiet; /* If VERY_QUIET, use visual bell for bell */
Xextern int know_dumb; /* Don't complain about a dumb terminal */
Xextern int back_scroll;
Xchar *tgetstr();
Xchar *tgoto();
X
X/*
X * Change terminal to "raw mode", or restore to "normal" mode.
X * "Raw mode" means
X * 1. An outstanding read will complete on receipt of a single keystroke.
X * 2. Input is not echoed.
X * 3. On output, \n is mapped to \r\n.
X * 4. \t is NOT expanded into spaces.
X * 5. Signal-causing characters such as ctrl-C (interrupt),
X * etc. are NOT disabled.
X * It doesn't matter whether an input \n is mapped to \r, or vice versa.
X */
X public void
Xraw_mode(on)
X int on;
X{
X#if TERMIO
X struct termio s;
X static struct termio save_term;
X
X if (on)
X {
X /*
X * Get terminal modes.
X */
X ioctl(2, TCGETA, &s);
X
X /*
X * Save modes and set certain variables dependent on modes.
X */
X save_term = s;
X ospeed = s.c_cflag & CBAUD;
X erase_char = s.c_cc[VERASE];
X kill_char = s.c_cc[VKILL];
X
X /*
X * Set the modes to the way we want them.
X */
X s.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
X s.c_oflag |= (OPOST|ONLCR|TAB3);
X s.c_oflag &= ~(OCRNL|ONOCR|ONLRET);
X s.c_cc[VMIN] = 1;
X s.c_cc[VTIME] = 0;
X } else
X {
X /*
X * Restore saved modes.
X */
X s = save_term;
X }
X ioctl(2, TCSETAW, &s);
X#else
X struct sgttyb s;
X static struct sgttyb save_term;
X
X if (on)
X {
X /*
X * Get terminal modes.
X */
X ioctl(2, TIOCGETP, &s);
X
X /*
X * Save modes and set certain variables dependent on modes.
X */
X save_term = s;
X ospeed = s.sg_ospeed;
X erase_char = s.sg_erase;
X kill_char = s.sg_kill;
X
X /*
X * Set the modes to the way we want them.
X */
X s.sg_flags |= CBREAK;
X s.sg_flags &= ~(ECHO|XTABS);
X } else
X {
X /*
X * Restore saved modes.
X */
X s = save_term;
X }
X ioctl(2, TIOCSETN, &s);
X#endif
X}
X
X static void
Xcannot(s)
X char *s;
X{
X char message[100];
X
X if (know_dumb)
X /*
X * He knows he has a dumb terminal, so don't tell him.
X */
X return;
X
X sprintf(message, "WARNING: terminal cannot \"%s\"", s);
X error(message);
X}
X
X/*
X * Get terminal capabilities via termcap.
X */
X public void
Xget_term()
X{
X char termbuf[2048];
X char *sp;
X char *term;
X int hard;
X#ifdef TIOCGWINSZ
X struct winsize w;
X#else
X#ifdef WIOCGETD
X struct uwdata w;
X#endif
X#endif
X static char sbuf[1024];
X
X char *getenv();
X
X /*
X * Find out what kind of terminal this is.
X */
X if ((term = getenv("TERM")) == NULL)
X term = "unknown";
X if (tgetent(termbuf, term) <= 0)
X strcpy(termbuf, "dumb:co#80:hc:");
X
X /*
X * Get size of the screen.
X */
X#ifdef TIOCGWINSZ
X if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_row > 0)
X sc_height = w.ws_row;
X else
X#else
X#ifdef WIOCGETD
X if (ioctl(2, WIOCGETD, &w) == 0 && w.uw_height > 0)
X sc_height = w.uw_height/w.uw_vs;
X else
X#endif
X#endif
X sc_height = tgetnum("li");
X hard = (sc_height < 0 || tgetflag("hc"));
X if (hard)
X {
X /* Oh no, this is a hardcopy terminal. */
X sc_height = 24;
X }
X
X#ifdef TIOCGWINSZ
X if (ioctl(2, TIOCGWINSZ, &w) == 0 && w.ws_col > 0)
X sc_width = w.ws_col;
X else
X#ifdef WIOCGETD
X if (ioctl(2, WIOCGETD, &w) == 0 && w.uw_width > 0)
X sc_width = w.uw_width/w.uw_hs;
X else
X#endif
X#endif
X sc_width = tgetnum("co");
X if (sc_width < 0)
X sc_width = 80;
X
X auto_wrap = tgetflag("am");
X ignaw = tgetflag("xn");
X
X /*
X * Assumes termcap variable "sg" is the printing width of
X * the standout sequence, the end standout sequence,
X * the underline sequence, the end underline sequence,
X * the boldface sequence, and the end boldface sequence.
X */
X if ((so_width = tgetnum("sg")) < 0)
X so_width = 0;
X be_width = bo_width = ue_width = ul_width = se_width = so_width;
X
X /*
X * Get various string-valued capabilities.
X */
X sp = sbuf;
X
X sc_pad = tgetstr("pc", &sp);
X if (sc_pad != NULL)
X PC = *sc_pad;
X
X sc_init = tgetstr("ti", &sp);
X if (sc_init == NULL)
X sc_init = "";
X
X sc_deinit= tgetstr("te", &sp);
X if (sc_deinit == NULL)
X sc_deinit = "";
X
X sc_eol_clear = tgetstr("ce", &sp);
X if (hard || sc_eol_clear == NULL || *sc_eol_clear == '\0')
X {
X cannot("clear to end of line");
X sc_eol_clear = "";
X }
X
X sc_clear = tgetstr("cl", &sp);
X if (hard || sc_clear == NULL || *sc_clear == '\0')
X {
X cannot("clear screen");
X sc_clear = "\n\n";
X }
X
X sc_move = tgetstr("cm", &sp);
X if (hard || sc_move == NULL || *sc_move == '\0')
X {
X /*
X * This is not an error here, because we don't
X * always need sc_move.
X * We need it only if we don't have home or lower-left.
X */
X sc_move = "";
X }
X
X sc_s_in = tgetstr("so", &sp);
X if (hard || sc_s_in == NULL)
X sc_s_in = "";
X
X sc_s_out = tgetstr("se", &sp);
X if (hard || sc_s_out == NULL)
X sc_s_out = "";
X
X sc_u_in = tgetstr("us", &sp);
X if (hard || sc_u_in == NULL)
X sc_u_in = sc_s_in;
X
X sc_u_out = tgetstr("ue", &sp);
X if (hard || sc_u_out == NULL)
X sc_u_out = sc_s_out;
X
X sc_b_in = tgetstr("md", &sp);
X if (hard || sc_b_in == NULL)
X {
X sc_b_in = sc_s_in;
X sc_b_out = sc_s_out;
X } else
X {
X sc_b_out = tgetstr("me", &sp);
X if (hard || sc_b_out == NULL)
X sc_b_out = "";
X }
X
X sc_visual_bell = tgetstr("vb", &sp);
X if (hard || sc_visual_bell == NULL)
X sc_visual_bell = "";
X
X sc_home = tgetstr("ho", &sp);
X if (hard || sc_home == NULL || *sc_home == '\0')
X {
X if (*sc_move == '\0')
X {
X cannot("home cursor");
X /*
X * This last resort for sc_home is supposed to
X * be an up-arrow suggesting moving to the
X * top of the "virtual screen". (The one in
X * your imagination as you try to use this on
X * a hard copy terminal.)
X */
X sc_home = "|\b^";
X } else
X {
X /*
X * No "home" string,
X * but we can use "move(0,0)".
X */
X strcpy(sp, tgoto(sc_move, 0, 0));
X sc_home = sp;
X sp += strlen(sp) + 1;
X }
X }
X
X sc_lower_left = tgetstr("ll", &sp);
X if (hard || sc_lower_left == NULL || *sc_lower_left == '\0')
X {
X if (*sc_move == '\0')
X {
X cannot("move cursor to lower left of screen");
X sc_lower_left = "\r";
X } else
X {
X /*
X * No "lower-left" string,
X * but we can use "move(0,last-line)".
X */
X strcpy(sp, tgoto(sc_move, 0, sc_height-1));
X sc_lower_left = sp;
X sp += strlen(sp) + 1;
X }
X }
X
X /*
X * To add a line at top of screen and scroll the display down,
X * we use "al" (add line) or "sr" (scroll reverse).
X */
X if ((sc_addline = tgetstr("al", &sp)) == NULL ||
X *sc_addline == '\0')
X sc_addline = tgetstr("sr", &sp);
X
X if (hard || sc_addline == NULL || *sc_addline == '\0')
X {
X cannot("scroll backwards");
X sc_addline = "";
X /* Force repaint on any backward movement */
X back_scroll = 0;
X }
X
X if (tgetflag("bs"))
X sc_backspace = "\b";
X else
X {
X sc_backspace = tgetstr("bc", &sp);
X if (sc_backspace == NULL || *sc_backspace == '\0')
X sc_backspace = "\b";
X }
X}
X
X
X/*
X * Below are the functions which perform all the
X * terminal-specific screen manipulation.
X */
X
X
X/*
X * Initialize terminal
X */
X public void
Xinit()
X{
X tputs(sc_init, sc_height, putchr);
X}
X
X/*
X * Deinitialize terminal
X */
X public void
Xdeinit()
X{
X tputs(sc_deinit, sc_height, putchr);
X}
X
X/*
X * Home cursor (move to upper left corner of screen).
X */
X public void
Xhome()
X{
X tputs(sc_home, 1, putchr);
X}
X
X/*
X * Add a blank line (called with cursor at home).
X * Should scroll the display down.
X */
X public void
Xadd_line()
X{
X tputs(sc_addline, sc_height, putchr);
X}
X
X/*
X * Move cursor to lower left corner of screen.
X */
X public void
Xlower_left()
X{
X tputs(sc_lower_left, 1, putchr);
X}
X
X/*
X * Ring the terminal bell.
X */
X public void
Xbell()
X{
X if (quiet == VERY_QUIET)
X vbell();
X else
X putchr('\7');
X}
X
X/*
X * Output the "visual bell", if there is one.
X */
X public void
Xvbell()
X{
X if (*sc_visual_bell == '\0')
X return;
X tputs(sc_visual_bell, sc_height, putchr);
X}
X
X/*
X * Clear the screen.
X */
X public void
Xclear()
X{
X tputs(sc_clear, sc_height, putchr);
X}
X
X/*
X * Clear from the cursor to the end of the cursor's line.
X * {{ This must not move the cursor. }}
X */
X public void
Xclear_eol()
X{
X tputs(sc_eol_clear, 1, putchr);
X}
X
X/*
X * Begin "standout" (bold, underline, or whatever).
X */
X public void
Xso_enter()
X{
X tputs(sc_s_in, 1, putchr);
X}
X
X/*
X * End "standout".
X */
X public void
Xso_exit()
X{
X tputs(sc_s_out, 1, putchr);
X}
X
X/*
X * Begin "underline" (hopefully real underlining,
X * otherwise whatever the terminal provides).
X */
X public void
Xul_enter()
X{
X tputs(sc_u_in, 1, putchr);
X}
X
X/*
X * End "underline".
X */
X public void
Xul_exit()
X{
X tputs(sc_u_out, 1, putchr);
X}
X
X/*
X * Begin "bold"
X */
X public void
Xbo_enter()
X{
X tputs(sc_b_in, 1, putchr);
X}
X
X/*
X * End "bold".
X */
X public void
Xbo_exit()
X{
X tputs(sc_b_out, 1, putchr);
X}
X
X/*
X * Erase the character to the left of the cursor
X * and move the cursor left.
X */
X public void
Xbackspace()
X{
X /*
X * Try to erase the previous character by overstriking with a space.
X */
X tputs(sc_backspace, 1, putchr);
X putchr(' ');
X tputs(sc_backspace, 1, putchr);
X}
X
X/*
X * Output a plain backspace, without erasing the previous char.
X */
X public void
Xputbs()
X{
X tputs(sc_backspace, 1, putchr);
X}
END_OF_FILE
echo shar: Extracting \"prompt.c\"
sed "s/^X//" >'prompt.c' <<'END_OF_FILE'
X/*
X * Prompting and other messages.
X * There are three flavors of prompts, SHORT, MEDIUM and LONG,
X * selected by the -m/-M options.
X * There is also the "equals message", printed by the = command.
X * A prompt is a message composed of various pieces, such as the
X * name of the file being viewed, the percentage into the file, etc.
X */
X
X#include "less.h"
X#include "position.h"
X
Xextern int pr_type;
Xextern int ispipe;
Xextern int hit_eof;
Xextern int new_file;
Xextern int sc_width;
Xextern int so_width, se_width;
Xextern char *current_file;
Xextern int ac;
Xextern char **av;
Xextern int curr_ac;
Xextern int linenums;
X
X/*
X * Prototypes for the three flavors of prompts.
X * These strings are expanded by pr_expand().
X */
Xstatic char s_proto[] =
X "?n?f%f .?m(file %i of %m) ..?e(END) ?x- Next\\: %x..%t";
Xstatic char m_proto[] =
X "?n?f%f .?m(file %i of %m) ..?e(END) ?x- Next\\: %x.:?pB%pB\\%:byte %bB?s/%s...%t";
Xstatic char M_proto[] =
X "?f%f .?n?m(file %i of %m) ..?ltline %lt :byte %bB?s/%s ..?e(END) ?x- Next\\: %x.:?pB%pB\\%..%t";
Xstatic char e_proto[] =
X "?f%f .?m(file %i of %m) .?ltline %lt .byte %bB?s/%s. ?e(END) :?pB%pB\\%..%t";
X
Xchar *prproto[3];
Xchar *eqproto = e_proto;
X
Xstatic char message[250];
Xstatic char *mp;
X
X/*
X * Initialize the prompt prototype strings.
X */
X public void
Xinit_prompt()
X{
X prproto[0] = save(s_proto);
X prproto[1] = save(m_proto);
X prproto[2] = save(M_proto);
X eqproto = save(e_proto);
X}
X
X/*
X * Set the message pointer to the end of the message string.
X */
X static void
Xsetmp()
X{
X while (*mp != '\0')
X mp++;
X}
X
X/*
X * Append a POSITION (as a decimal integer) to the end of the message.
X */
X static void
Xap_pos(pos)
X POSITION pos;
X{
X sprintf(mp, "%ld", (long)pos);
X setmp();
X}
X
X/*
X * Append an integer to the end of the message.
X */
X static void
Xap_int(n)
X int n;
X{
X sprintf(mp, "%d", n);
X setmp();
X}
X
X/*
X * Append a question mark to the end of the message.
X */
X static void
Xap_quest()
X{
X *mp++ = '?';
X}
X
X/*
X * Return the "current" byte offset in the file.
X */
X static POSITION
Xcurr_byte(where)
X int where;
X{
X POSITION pos;
X
X pos = position(where);
X if (pos == NULL_POSITION)
X pos = ch_length();
X return (pos);
X}
X
X/*
X * Return the value of a prototype conditional.
X * A prototype string may include conditionals which consist of a
X * question mark followed by a single letter.
X * Here we decode that letter and return the appropriate boolean value.
X */
X static int
Xcond(c, where)
X char c;
X int where;
X{
X switch (c)
X {
X case 'a': /* Anything in the message yet? */
X return (mp > message);
X case 'b': /* Current byte offset known? */
X return (curr_byte(where) != NULL_POSITION);
X case 'e': /* At end of file? */
X return (hit_eof);
X case 'f': /* Filename known? */
X return (!ispipe);
X case 'l': /* Line number known? */
X return (linenums);
X case 'm': /* More than one file? */
X return (ac > 1);
X case 'n': /* First prompt in a new file? */
X return (new_file);
X case 'p': /* Percent into file known? */
X return (curr_byte(where) != NULL_POSITION &&
X ch_length() > 0);
X case 's': /* Size of file known? */
X return (ch_length() != NULL_POSITION);
X case 'x': /* Is there a "next" file? */
X return (curr_ac + 1 < ac);
X }
X return (0);
X}
X
X/*
X * Decode a "percent" prototype character.
X * A prototype string may include various "percent" escapes;
X * that is, a percent sign followed by a single letter.
X * Here we decode that letter and take the appropriate action,
X * usually by appending something to the message being built.
X */
X static void
Xprotochar(c, where)
X int c;
X int where;
X{
X POSITION pos;
X POSITION len;
X int n;
X
X switch (c)
X {
X case 'b': /* Current byte offset */
X pos = curr_byte(where);
X if (pos != NULL_POSITION)
X ap_pos(pos);
X else
X ap_quest();
X break;
X case 'f': /* File name */
X strtcpy(mp, current_file,
X (unsigned int)(&message[sizeof(message)] - mp));
X setmp();
X break;
X case 'i': /* Index into list of files */
X ap_int(curr_ac + 1);
X break;
X case 'l': /* Current line number */
X n = currline(where);
X if (n != 0)
X ap_int(n);
X else
X ap_quest();
X break;
X case 'm': /* Number of files */
X ap_int(ac);
X break;
X case 'p': /* Percent into file */
X pos = curr_byte(where);
X len = ch_length();
X if (pos != NULL_POSITION && len > 0)
X ap_int((int)(100*pos / len));
X else
X ap_quest();
X break;
X case 's': /* Size of file */
X len = ch_length();
X if (len != NULL_POSITION)
X ap_pos(len);
X else
X ap_quest();
X break;
X case 't': /* Truncate trailing spaces in the message */
X while (mp > message && mp[-1] == ' ')
X mp--;
X break;
X case 'x': /* Name of next file */
X if (curr_ac + 1 < ac)
X {
X strtcpy(mp, av[curr_ac+1],
X (unsigned int)(&message[sizeof(message)] - mp));
X setmp();
X } else
X ap_quest();
X break;
X }
X}
X
X/*
X * Skip a false conditional.
X * When a false condition is found (either a false IF or the ELSE part
X * of a true IF), this routine scans the prototype string to decide
X * where to resume parsing the string.
X * We must keep track of nested IFs and skip them properly.
X */
X static char *
Xskipcond(p)
X register char *p;
X{
X register int iflevel = 1;
X
X for (;;) switch (*++p)
X {
X case '?':
X /*
X * Start of a nested IF.
X */
X iflevel++;
X break;
X case ':':
X /*
X * Else.
X * If this matches the IF we came in here with,
X * then we're done.
X */
X if (iflevel == 1)
X return (p);
X break;
X case '.':
X /*
X * Endif.
X * If this matches the IF we came in here with,
X * then we're done.
X */
X if (--iflevel == 0)
X return (p);
X break;
X case '\\':
X /*
X * Backslash escapes the next character.
X */
X ++p;
X break;
X case '\0':
X /*
X * Whoops. Hit end of string.
X * This is a malformed conditional, but just treat it
X * as if all active conditionals ends here.
X */
X return (p-1);
X }
X /*NOTREACHED*/
X}
X
X static char *
Xwherechar(p, wp)
X char *p;
X int *wp;
X{
X int c;
X
X switch (c = *p)
X {
X case 'b': case 'l': case 'p':
X switch (*++p)
X {
X case 't': *wp = TOP; break;
X case 'm': *wp = MIDDLE; break;
X case 'b': *wp = BOTTOM; break;
X case 'B': *wp = BOTTOM_PLUS_ONE; break;
X default: *wp = TOP; break;
X }
X }
X return (p);
X}
X
X/*
X * Construct a message based on a prototype string.
X */
X static char *
Xpr_expand(proto, maxwidth)
X char *proto;
X int maxwidth;
X{
X register char *p;
X register int c;
X int where;
X
X mp = message;
X
X if (*proto == '\0')
X return ("");
X
X for (p = proto; *p != '\0'; p++)
X {
X switch (*p)
X {
X default: /* Just put the character in the message */
X *mp++ = *p;
X break;
X case '\\': /* Backslash escapes the next character */
X p++;
X *mp++ = *p;
X break;
X case '?': /* Conditional (IF) */
X if ((c = *++p) == '\0')
X --p;
X else
X {
X p = wherechar(p, &where);
X if (!cond(c, where))
X p = skipcond(p);
X }
X break;
X case ':': /* ELSE */
X p = skipcond(p);
X break;
X case '.': /* ENDIF */
X break;
X case '%': /* Percent escape */
X if ((c = *++p) == '\0')
X --p;
X else
X {
X p = wherechar(p, &where);
X protochar(c, where);
X }
X break;
X }
X }
X
X new_file = 0;
X if (mp == message)
X return (NULL);
X *mp = '\0';
X if (maxwidth > 0 && mp >= message + maxwidth)
X {
X /*
X * Message is too long.
X * Return just the final portion of it.
X */
X return (mp - maxwidth);
X }
X return (message);
X}
X
X/*
X * Return a message suitable for printing by the "=" command.
X */
X public char *
Xeq_message()
X{
X return (pr_expand(eqproto, 0));
X}
X
X/*
X * Return a prompt.
X * This depends on the prompt type (SHORT, MEDIUM, LONG), etc.
X * If we can't come up with an appropriate prompt, return NULL
X * and the caller will prompt with a colon.
X */
X public char *
Xpr_string()
X{
X return (pr_expand(prproto[pr_type], sc_width-so_width-se_width-2));
X}
END_OF_FILE
echo shar: Extracting \"line.c\"
sed "s/^X//" >'line.c' <<'END_OF_FILE'
X/*
X * Routines to manipulate the "line buffer".
X * The line buffer holds a line of output as it is being built
X * in preparation for output to the screen.
X * We keep track of the PRINTABLE length of the line as it is being built.
X */
X
X#include "less.h"
X
Xstatic char linebuf[1024]; /* Buffer which holds the current output line */
Xstatic char *curr; /* Pointer into linebuf */
Xstatic int column; /* Printable length, accounting for
X backspaces, etc. */
X/*
X * A ridiculously complex state machine takes care of backspaces
X * when in BS_SPECIAL mode. The complexity arises from the attempt
X * to deal with all cases, especially involving long lines with underlining,
X * boldfacing or whatever. There are still some cases which will break it.
X *
X * There are four states:
X * LN_NORMAL is the normal state (not in underline mode).
X * LN_UNDERLINE means we are in underline mode. We expect to get
X * either a sequence like "_\bX" or "X\b_" to continue
X * underline mode, or anything else to end underline mode.
X * LN_BOLDFACE means we are in boldface mode. We expect to get sequences
X * like "X\bX\b...X\bX" to continue boldface mode, or anything
X * else to end boldface mode.
X * LN_UL_X means we are one character after LN_UNDERLINE
X * (we have gotten the '_' in "_\bX" or the 'X' in "X\b_").
X * LN_UL_XB means we are one character after LN_UL_X
X * (we have gotten the backspace in "_\bX" or "X\b_";
X * we expect one more ordinary character,
X * which will put us back in state LN_UNDERLINE).
X * LN_BO_X means we are one character after LN_BOLDFACE
X * (we have gotten the 'X' in "X\bX").
X * LN_BO_XB means we are one character after LN_BO_X
X * (we have gotten the backspace in "X\bX";
X * we expect one more 'X' which will put us back
X * in LN_BOLDFACE).
X */
Xstatic int ln_state; /* Currently in normal/underline/bold/etc mode? */
X#define LN_NORMAL 0 /* Not in underline, boldface or whatever mode */
X#define LN_UNDERLINE 1 /* In underline, need next char */
X#define LN_UL_X 2 /* In underline, got char, need \b */
X#define LN_UL_XB 3 /* In underline, got char & \b, need one more */
X#define LN_BOLDFACE 4 /* In boldface, need next char */
X#define LN_BO_X 5 /* In boldface, got char, need \b */
X#define LN_BO_XB 6 /* In boldface, got char & \b, need same char */
X
Xpublic char *line; /* Pointer to the current line.
X Usually points to linebuf. */
X
Xextern int bs_mode;
Xextern int tabstop;
Xextern int bo_width, be_width;
Xextern int ul_width, ue_width;
Xextern int sc_width, sc_height;
X
X/*
X * Rewind the line buffer.
X */
X public void
Xprewind()
X{
X line = curr = linebuf;
X ln_state = LN_NORMAL;
X column = 0;
X}
X
X/*
X * Append a character to the line buffer.
X * Expand tabs into spaces, handle underlining, boldfacing, etc.
X * Returns 0 if ok, 1 if couldn't fit in buffer.
X */
X
X#define NEW_COLUMN(newcol) if ((newcol) + ((ln_state)?ue_width:0) > sc_width) \
X return (1); else column = (newcol)
X
X public int
Xpappend(c)
X int c;
X{
X if (c == '\0')
X {
X /*
X * Terminate any special modes, if necessary.
X * Append a '\0' to the end of the line.
X */
X switch (ln_state)
X {
X case LN_UL_X:
X curr[0] = curr[-1];
X curr[-1] = UE_CHAR;
X curr++;
X break;
X case LN_BO_X:
X curr[0] = curr[-1];
X curr[-1] = BE_CHAR;
X curr++;
X break;
X case LN_UL_XB:
X case LN_UNDERLINE:
X *curr++ = UE_CHAR;
X break;
X case LN_BO_XB:
X case LN_BOLDFACE:
X *curr++ = BE_CHAR;
X break;
X }
X ln_state = LN_NORMAL;
X *curr = '\0';
X return (0);
X }
X
X if (curr > linebuf + sizeof(linebuf) - 12)
X /*
X * Almost out of room in the line buffer.
X * Don't take any chances.
X * {{ Linebuf is supposed to be big enough that this
X * will never happen, but may need to be made
X * bigger for wide screens or lots of backspaces. }}
X */
X return (1);
X
X if (bs_mode == BS_SPECIAL)
X {
X /*
X * Advance the state machine.
X */
X switch (ln_state)
X {
X case LN_NORMAL:
X if (curr <= linebuf + 1 || curr[-1] != '\b')
X break;
X
X if (c == curr[-2])
X goto enter_boldface;
X if (c == '_' || curr[-2] == '_')
X goto enter_underline;
X curr -= 2;
X break;
X
Xenter_boldface:
X /*
X * We have "X\bX" (including the current char).
X * Switch into boldface mode.
X */
X if (column + bo_width + be_width + 1 >= sc_width)
X /*
X * Not enough room left on the screen to
X * enter and exit boldface mode.
X */
X return (1);
X
X if (bo_width > 0 &&
X curr > linebuf + 2 && curr[-3] == ' ')
X {
X /*
X * Special case for magic cookie terminals:
X * if the previous char was a space, replace
X * it with the "enter boldface" sequence.
X */
X curr[-3] = BO_CHAR;
X column += bo_width-1;
X } else
X {
X curr[-1] = curr[-2];
X curr[-2] = BO_CHAR;
X column += bo_width;
X curr++;
X }
X goto ln_bo_xb_case;
X
Xenter_underline:
X /*
X * We have either "_\bX" or "X\b_" (including
X * the current char). Switch into underline mode.
X */
X if (column + ul_width + ue_width + 1 >= sc_width)
X /*
X * Not enough room left on the screen to
X * enter and exit underline mode.
X */
X return (1);
X
X if (ul_width > 0 &&
X curr > linebuf + 2 && curr[-3] == ' ')
X {
X /*
X * Special case for magic cookie terminals:
X * if the previous char was a space, replace
X * it with the "enter underline" sequence.
X */
X curr[-3] = UL_CHAR;
X column += ul_width-1;
X } else
X {
X curr[-1] = curr[-2];
X curr[-2] = UL_CHAR;
X column += ul_width;
X curr++;
X }
X goto ln_ul_xb_case;
X /*NOTREACHED*/
X case LN_UL_XB:
X /*
X * Termination of a sequence "_\bX" or "X\b_".
X */
X if (c != '_' && curr[-2] != '_' && c == curr[-2])
X {
X /*
X * We seem to have run on from underlining
X * into boldfacing - this is a nasty fix, but
X * until this whole routine is rewritten as a
X * real DFA, ... well ...
X */
X curr[0] = curr[-2];
X curr[-2] = UE_CHAR;
X curr[-1] = BO_CHAR;
X curr += 2; /* char & non-existent backspace */
X ln_state = LN_BO_XB;
X goto ln_bo_xb_case;
X }
Xln_ul_xb_case:
X if (c == '_')
X c = curr[-2];
X curr -= 2;
X ln_state = LN_UNDERLINE;
X break;
X case LN_BO_XB:
X /*
X * Termination of a sequnce "X\bX".
X */
X if (c != curr[-2] && (c == '_' || curr[-2] == '_'))
X {
X /*
X * We seem to have run on from
X * boldfacing into underlining.
X */
X curr[0] = curr[-2];
X curr[-2] = BE_CHAR;
X curr[-1] = UL_CHAR;
X curr += 2; /* char & non-existent backspace */
X ln_state = LN_UL_XB;
X goto ln_ul_xb_case;
X }
Xln_bo_xb_case:
X curr -= 2;
X ln_state = LN_BOLDFACE;
X break;
X case LN_UNDERLINE:
X if (column + ue_width + bo_width + 1 + be_width >= sc_width)
X /*
X * We have just barely enough room to
X * exit underline mode and handle a possible
X * underline/boldface run on mixup.
X */
X return (1);
X ln_state = LN_UL_X;
X break;
X case LN_BOLDFACE:
X if (c == '\b')
X {
X ln_state = LN_BO_XB;
X break;
X }
X if (column + be_width + ul_width + 1 + ue_width >= sc_width)
X /*
X * We have just barely enough room to
X * exit underline mode and handle a possible
X * underline/boldface run on mixup.
X */
X return (1);
X ln_state = LN_BO_X;
X break;
X case LN_UL_X:
X if (c == '\b')
X ln_state = LN_UL_XB;
X else
X {
X /*
X * Exit underline mode.
X * We have to shuffle the chars a bit
X * to make this work.
X */
X curr[0] = curr[-1];
X curr[-1] = UE_CHAR;
X column += ue_width;
X if (ue_width > 0 && curr[0] == ' ')
X /*
X * Another special case for magic
X * cookie terminals: if the next
X * char is a space, replace it
X * with the "exit underline" sequence.
X */
X column--;
X else
X curr++;
X ln_state = LN_NORMAL;
X }
X break;
X case LN_BO_X:
X if (c == '\b')
X ln_state = LN_BO_XB;
X else
X {
X /*
X * Exit boldface mode.
X * We have to shuffle the chars a bit
X * to make this work.
X */
X curr[0] = curr[-1];
X curr[-1] = BE_CHAR;
X column += be_width;
X if (be_width > 0 && curr[0] == ' ')
X /*
X * Another special case for magic
X * cookie terminals: if the next
X * char is a space, replace it
X * with the "exit boldface" sequence.
X */
X column--;
X else
X curr++;
X ln_state = LN_NORMAL;
X }
X break;
X }
X }
X
X if (c == '\t')
X {
X /*
X * Expand a tab into spaces.
X */
X do
X {
X NEW_COLUMN(column+1);
X } while ((column % tabstop) != 0);
X *curr++ = '\t';
X return (0);
X }
X
X if (c == '\b')
X {
X if (bs_mode == BS_CONTROL)
X {
X /*
X * Treat backspace as a control char: output "^H".
X */
X NEW_COLUMN(column+2);
X *curr++ = ('H' | 0200);
X } else
X {
X /*
X * Output a real backspace.
X */
X column--;
X *curr++ = '\b';
X }
X return (0);
X }
X
X if (control_char(c))
X {
X /*
X * Put a "^X" into the buffer.
X * The 0200 bit is used to tell put_line() to prefix
X * the char with a ^. We don't actually put the ^
X * in the buffer because we sometimes need to move
X * chars around, and such movement might separate
X * the ^ from its following character.
X * {{ This should be redone so that we can use an
X * 8 bit (e.g. international) character set. }}
X */
X NEW_COLUMN(column+2);
X *curr++ = (carat_char(c) | 0200);
X return (0);
X }
X
X /*
X * Ordinary character. Just put it in the buffer.
X */
X NEW_COLUMN(column+1);
X *curr++ = c;
X return (0);
X}
X
X/*
X * Analogous to forw_line(), but deals with "raw lines":
X * lines which are not split for screen width.
X * {{ This is supposed to be more efficient than forw_line(). }}
X */
X public POSITION
Xforw_raw_line(curr_pos)
X POSITION curr_pos;
X{
X register char *p;
X register int c;
X POSITION new_pos;
X
X if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
X (c = ch_forw_get()) == EOI)
X return (NULL_POSITION);
X
X p = linebuf;
X
X for (;;)
X {
X if (c == '\n' || c == EOI)
X {
X new_pos = ch_tell();
X break;
X }
X if (p >= &linebuf[sizeof(linebuf)-1])
X {
X /*
X * Overflowed the input buffer.
X * Pretend the line ended here.
X * {{ The line buffer is supposed to be big
X * enough that this never happens. }}
X */
X new_pos = ch_tell() - 1;
X break;
X }
X *p++ = c;
X c = ch_forw_get();
X }
X *p = '\0';
X line = linebuf;
X return (new_pos);
X}
X
X/*
X * Analogous to back_line(), but deals with "raw lines".
X * {{ This is supposed to be more efficient than back_line(). }}
X */
X public POSITION
Xback_raw_line(curr_pos)
X POSITION curr_pos;
X{
X register char *p;
X register int c;
X POSITION new_pos;
X
X if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 ||
X ch_seek(curr_pos-1))
X return (NULL_POSITION);
X
X p = &linebuf[sizeof(linebuf)];
X *--p = '\0';
X
X for (;;)
X {
X c = ch_back_get();
X if (c == '\n')
X {
X /*
X * This is the newline ending the previous line.
X * We have hit the beginning of the line.
X */
X new_pos = ch_tell() + 1;
X break;
X }
X if (c == EOI)
X {
X /*
X * We have hit the beginning of the file.
X * This must be the first line in the file.
X * This must, of course, be the beginning of the line.
X */
X new_pos = (POSITION)0;
X break;
X }
X if (p <= linebuf)
X {
X /*
X * Overflowed the input buffer.
X * Pretend the line ended here.
X */
X new_pos = ch_tell() + 1;
X break;
X }
X *--p = c;
X }
X line = p;
X return (new_pos);
X}
END_OF_FILE
echo shar: Extracting \"signal.c\"
sed "s/^X//" >'signal.c' <<'END_OF_FILE'
X/*
X * Routines dealing with signals.
X *
X * A signal usually merely causes a bit to be set in the "signals" word.
X * At some convenient time, the mainline code checks to see if any
X * signals need processing by calling psignal().
X * If we happen to be reading from a file [in iread()] at the time
X * the signal is received, we call intread to interrupt the iread.
X */
X
X#include "less.h"
X#include <signal.h>
X
X/*
X * "sigs" contains bits indicating signals which need to be processed.
X */
Xpublic int sigs;
X
X#define S_INTERRUPT 01
X#ifdef SIGTSTP
X#define S_STOP 02
X#endif
X#if defined(SIGWINCH) || defined(SIGWIND)
X#define S_WINCH 04
X#endif
X
Xextern int sc_width, sc_height;
Xextern int screen_trashed;
Xextern int lnloop;
Xextern int linenums;
Xextern int scroll;
Xextern int reading;
X
X/*
X * Interrupt signal handler.
X */
X static HANDLER
Xinterrupt()
X{
X SIGNAL(SIGINT, interrupt);
X sigs |= S_INTERRUPT;
X if (reading)
X intread();
X}
X
X#ifdef SIGTSTP
X/*
X * "Stop" (^Z) signal handler.
X */
X static HANDLER
Xstop()
X{
X SIGNAL(SIGTSTP, stop);
X sigs |= S_STOP;
X if (reading)
X intread();
X}
X#endif
X
X#ifdef SIGWINCH
X/*
X * "Window" change handler
X */
X public HANDLER
Xwinch()
X{
X SIGNAL(SIGWINCH, winch);
X sigs |= S_WINCH;
X if (reading)
X intread();
X}
X#else
X#ifdef SIGWIND
X/*
X * "Window" change handler
X */
X public HANDLER
Xwinch()
X{
X SIGNAL(SIGWIND, winch);
X sigs |= S_WINCH;
X if (reading)
X intread();
X}
X#endif
X#endif
X
X/*
X * Set up the signal handlers.
X */
X public void
Xinit_signals(on)
X int on;
X{
X if (on)
X {
X /*
X * Set signal handlers.
X */
X (void) SIGNAL(SIGINT, interrupt);
X#ifdef SIGTSTP
X (void) SIGNAL(SIGTSTP, stop);
X#endif
X#ifdef SIGWINCH
X (void) SIGNAL(SIGWINCH, winch);
X#else
X#ifdef SIGWIND
X (void) SIGNAL(SIGWIND, winch);
X#endif
X#endif
X } else
X {
X /*
X * Restore signals to defaults.
X */
X (void) SIGNAL(SIGINT, SIG_DFL);
X#ifdef SIGTSTP
X (void) SIGNAL(SIGTSTP, SIG_DFL);
X#endif
X#ifdef SIGWINCH
X (void) SIGNAL(SIGWINCH, SIG_IGN);
X#endif
X#ifdef SIGWIND
X (void) SIGNAL(SIGWIND, SIG_IGN);
X#endif
X }
X}
X
X/*
X * Process any signals we have received.
X * A received signal cause a bit to be set in "sigs".
X */
X public int
Xpsignals()
X{
X register int tsignals;
X
X if ((tsignals = sigs) == 0)
X return (0);
X sigs = 0;
X
X#ifdef S_WINCH
X if (tsignals & S_WINCH)
X {
X int old_width, old_height;
X /*
X * Re-execute get_term() to read the new window size.
X */
X old_width = sc_width;
X old_height = sc_height;
X get_term();
X if (sc_width != old_width || sc_height != old_height)
X {
X scroll = (sc_height + 1) / 2;
X screen_trashed = 1;
X }
X }
X#endif
X#ifdef SIGTSTP
X if (tsignals & S_STOP)
X {
X /*
X * Clean up the terminal.
X */
X#ifdef SIGTTOU
X SIGNAL(SIGTTOU, SIG_IGN);
X#endif
X lower_left();
X clear_eol();
X deinit();
X flush();
X raw_mode(0);
X#ifdef SIGTTOU
X SIGNAL(SIGTTOU, SIG_DFL);
X#endif
X SIGNAL(SIGTSTP, SIG_DFL);
X kill(getpid(), SIGTSTP);
X /*
X * ... Bye bye. ...
X * Hopefully we'll be back later and resume here...
X * Reset the terminal and arrange to repaint the
X * screen when we get back to the main command loop.
X */
X SIGNAL(SIGTSTP, stop);
X raw_mode(1);
X init();
X screen_trashed = 1;
X }
X#endif
X if (tsignals & S_INTERRUPT)
X {
X bell();
X /*
X * {{ You may wish to replace the bell() with
X * error("Interrupt"); }}
X */
X
X /*
X * If we were interrupted while in the "calculating
X * line numbers" loop, turn off line numbers.
X */
X if (lnloop)
X {
X lnloop = 0;
X linenums = 0;
X error("Line numbers turned off");
X }
X
X }
X
X return (1);
X}
END_OF_FILE
echo shar: Extracting \"os.c\"
sed "s/^X//" >'os.c' <<'END_OF_FILE'
X/*
X * Operating system dependent routines.
X *
X * Most of the stuff in here is based on Unix, but an attempt
X * has been made to make things work on other operating systems.
X * This will sometimes result in a loss of functionality, unless
X * someone rewrites code specifically for the new operating system.
X *
X * The makefile provides defines to decide whether various
X * Unix features are present.
X */
X
X#include <stdio.h>
X#include <signal.h>
X#include <setjmp.h>
X#include "less.h"
X
Xchar *getenv();
X
Xpublic int reading;
X
Xextern int screen_trashed;
X
Xstatic jmp_buf read_label;
X
X/*
X * Pass the specified command to a shell to be executed.
X * Like plain "system()", but handles resetting terminal modes, etc.
X */
X public void
Xlsystem(cmd)
X char *cmd;
X{
X int inp;
X char cmdbuf[256];
X char *shell;
X
X /*
X * Print the command which is to be executed,
X * unless the command starts with a "-".
X */
X if (cmd[0] == '-')
X cmd++;
X else
X {
X lower_left();
X clear_eol();
X putstr("!");
X putstr(cmd);
X putstr("\n");
X }
X
X /*
X * De-initialize the terminal and take out of raw mode.
X */
X deinit();
X flush();
X raw_mode(0);
X
X /*
X * Restore signals to their defaults.
X */
X init_signals(0);
X
X /*
X * Force standard input to be the terminal, "/dev/tty",
X * even if less's standard input is coming from a pipe.
X */
X inp = dup(0);
X close(0);
X if (open("/dev/tty", 0) < 0)
X dup(inp);
X
X /*
X * Pass the command to the system to be executed.
X * If we have a SHELL environment variable, use
X * <$SHELL -c "command"> instead of just <command>.
X * If the command is empty, just invoke a shell.
X */
X if ((shell = getenv("SHELL")) != NULL && *shell != '\0')
X {
X if (*cmd == '\0')
X cmd = shell;
X else
X {
X sprintf(cmdbuf, "%s -c \"%s\"", shell, cmd);
X cmd = cmdbuf;
X }
X }
X if (*cmd == '\0')
X cmd = "sh";
X
X system(cmd);
X
X /*
X * Restore standard input, reset signals, raw mode, etc.
X */
X close(0);
X dup(inp);
X close(inp);
X
X init_signals(1);
X raw_mode(1);
X init();
X screen_trashed = 1;
X#if defined(SIGWINCH) || defined(SIGWIND)
X /*
X * Since we were ignoring window change signals while we executed
X * the system command, we must assume the window changed.
X */
X winch();
X#endif
X}
X
X/*
X * Like read() system call, but is deliberately interruptable.
X * A call to intread() from a signal handler will interrupt
X * any pending iread().
X */
X public int
Xiread(fd, buf, len)
X int fd;
X char *buf;
X int len;
X{
X register int n;
X
X if (setjmp(read_label))
X /*
X * We jumped here from intread.
X */
X return (READ_INTR);
X
X flush();
X reading = 1;
X n = read(fd, buf, len);
X reading = 0;
X if (n < 0)
X return (-1);
X return (n);
X}
X
X public void
Xintread()
X{
X#if SIGSETMASK
X sigsetmask(0);
X#endif
X longjmp(read_label, 1);
X}
X
X#if GET_TIME
X public long
Xget_time()
X{
X long t;
X
X time(&t);
X return (t);
X}
X#endif
X
X/*
X * Expand a filename, substituting any environment variables, etc.
X * The implementation of this is necessarily very operating system
X * dependent. This implementation is unabashedly only for Unix systems.
X */
X#if GLOB
X
XFILE *popen();
X
X public char *
Xglob(filename)
X char *filename;
X{
X FILE *f;
X char *p;
X int ch;
X char *cmd;
X static char buffer[FILENAME];
X
X if (filename[0] == '#')
X return (filename);
X
X /*
X * We get the shell to expand the filename for us by passing
X * an "echo" command to the shell and reading its output.
X */
X p = getenv("SHELL");
X if (p == NULL || *p == '\0')
X {
X /*
X * Read the output of <echo filename>.
X */
X cmd = calloc(strlen(filename)+8, sizeof(char));
X if (cmd == NULL)
X return (filename);
X sprintf(cmd, "echo \"%s\"", filename);
X } else
X {
X /*
X * Read the output of <$SHELL -c "echo filename">.
X */
X cmd = calloc(strlen(p)+12);
X if (cmd == NULL)
X return (filename);
X sprintf(cmd, "%s -c \"echo %s\"", p, filename);
X }
X
X if ((f = popen(cmd, "r")) == NULL)
X return (filename);
X free(cmd);
X
X for (p = buffer; p < &buffer[sizeof(buffer)-1]; p++)
X {
X if ((ch = getc(f)) == '\n' || ch == EOF)
X break;
X *p = ch;
X }
X *p = '\0';
X pclose(f);
X return (buffer);
X}
X
X#else
X
X public char *
Xglob(filename)
X char *filename;
X{
X return (filename);
X}
X
X#endif
X
X
X/*
X * Returns NULL if the file can be opened and
X * is an ordinary file, otherwise an error message
X * (if it cannot be opened or is a directory, etc.)
X */
X
X#if STAT
X
X#include <sys/types.h>
X#include <sys/stat.h>
X
X public char *
Xbad_file(filename, message, len)
X char *filename;
X char *message;
X unsigned int len;
X{
X struct stat statbuf;
X
X if (stat(filename, &statbuf) < 0)
X return (errno_message(filename, message, len));
X
X if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
X {
X static char is_dir[] = " is a directory";
X strtcpy(message, filename, len-sizeof(is_dir)-1);
X strcat(message, is_dir);
X return (message);
X }
X if ((statbuf.st_mode & S_IFMT) != S_IFREG)
X {
X static char not_reg[] = " is not a regular file";
X strtcpy(message, filename, len-sizeof(not_reg)-1);
X strcat(message, not_reg);
X return (message);
X }
X return (NULL);
X}
X
X#else
X
X public char *
Xbad_file(filename, message, len)
X char *filename;
X char *message;
X unsigned int len;
X{
X return (NULL);
X}
X
X#endif
X
X/*
X * errno_message: Return an error message based on the value of "errno".
X * okreadfail: Return true if the previous failure of a read
X * (on the input tty) should be considered ok.
X */
X
X#if PERROR
X
Xextern char *sys_errlist[];
Xextern int sys_nerr;
Xextern int errno;
X
X public char *
Xerrno_message(filename, message, len)
X char *filename;
X char *message;
X unsigned int len;
X{
X char *p;
X char msg[16];
X
X if (errno < sys_nerr)
X p = sys_errlist[errno];
X else
X {
X sprintf(msg, "Error %d", errno);
X p = msg;
X }
X strtcpy(message, filename, len-strlen(p)-3);
X strcat(message, ": ");
X strcat(message, p);
X return (message);
X}
X
X#else
X
X public char *
Xerrno_message(filename, message, len)
X char *filename;
X char *message;
X unsigned int len;
X{
X static char msg[] = ": cannot open";
X
X strtcpy(message, filename, len-sizeof(msg)-1);
X strcat(message, msg);
X return (message);
X}
X
X#endif
END_OF_FILE
echo shar: Extracting \"help.c\"
sed "s/^X//" >'help.c' <<'END_OF_FILE'
X#include "less.h"
X
X/*
X * Display some help.
X * Just invoke another "less" to display the help file.
X *
X * {{ This makes this function very simple, and makes changing the
X * help file very easy, but it may present difficulties on
X * (non-Unix) systems which do not supply the "system()" function. }}
X */
X
X public void
Xhelp()
X{
X char cmd[FILENAME+100];
X
X sprintf(cmd,
X "-less -m '-PmHELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done ' %s",
X HELPFILE);
X lsystem(cmd);
X error("End of help");
X}
END_OF_FILE
echo shar: Extracting \"ttyin.c\"
sed "s/^X//" >'ttyin.c' <<'END_OF_FILE'
X/*
X * Routines dealing with getting input from the keyboard (i.e. from the user).
X */
X
X#include "less.h"
X
Xstatic int tty;
X
X/*
X * Open keyboard for input.
X * (Just use file descriptor 2.)
X */
X public void
Xopen_getchr()
X{
X tty = 2;
X}
X
X/*
X * Get a character from the keyboard.
X */
X public int
Xgetchr()
X{
X char c;
X int result;
X
X do
X {
X result = iread(tty, &c, 1);
X if (result == READ_INTR)
X return (READ_INTR);
X if (result < 0)
X {
X /*
X * Don't call error() here,
X * because error calls getchr!
X */
X quit();
X }
X } while (result != 1);
X return (c & 0177);
X}
END_OF_FILE
echo shar: Extracting \"command.c\"
sed "s/^X//" >'command.c' <<'END_OF_FILE'
X/*
X * User-level command processor.
X */
X
X#include "less.h"
X#include "position.h"
X#include "cmd.h"
X
X#define NO_MCA 0
X#define MCA_DONE 1
X#define MCA_MORE 2
X
Xextern int erase_char, kill_char;
Xextern int ispipe;
Xextern int sigs;
Xextern int quit_at_eof;
Xextern int hit_eof;
Xextern int sc_width;
Xextern int sc_height;
Xextern int sc_window;
Xextern int curr_ac;
Xextern int ac;
Xextern int quitting;
Xextern int scroll;
Xextern char *first_cmd;
Xextern char *every_first_cmd;
Xextern char version[];
Xextern char *current_file;
X#if EDITOR
Xextern char *editor;
X#endif
Xextern int screen_trashed; /* The screen has been overwritten */
X
Xstatic char cmdbuf[120]; /* Buffer for holding a multi-char command */
X#if SHELL_ESCAPE
Xstatic char *shellcmd = NULL; /* For holding last shell command for "!!" */
X#endif
Xstatic char *cp; /* Pointer into cmdbuf */
Xstatic int cmd_col; /* Current column of the multi-char command */
Xstatic int mca; /* The multicharacter command (action) */
Xstatic int last_mca; /* The previous mca */
Xstatic int number; /* The number typed by the user */
Xstatic int wsearch; /* Search for matches (1) or non-matches (0) */
X
X/*
X * Reset command buffer (to empty).
X */
Xcmd_reset()
X{
X cp = cmdbuf;
X}
X
X/*
X * Backspace in command buffer.
X */
X static int
Xcmd_erase()
X{
X if (cp == cmdbuf)
X /*
X * Backspace past beginning of the string:
X * this usually means abort the command.
X */
X return (1);
X
X if (control_char(*--cp))
X {
X /*
X * Erase an extra character, for the carat.
X */
X backspace();
X cmd_col--;
X }
X backspace();
X cmd_col--;
X return (0);
X}
X
X/*
X * Set up the display to start a new multi-character command.
X */
Xstart_mca(action, prompt)
X int action;
X char *prompt;
X{
X lower_left();
X clear_eol();
X putstr(prompt);
X cmd_col = strlen(prompt);
X mca = action;
X}
X
X/*
X * Process a single character of a multi-character command, such as
X * a number, or the pattern of a search command.
X */
X static int
Xcmd_char(c)
X int c;
X{
X if (c == erase_char)
X {
X if (cmd_erase())
X return (1);
X } else if (c == kill_char)
X {
X /* {{ Could do this faster, but who cares? }} */
X while (cmd_erase() == 0)
X ;
X } else if (cp >= &cmdbuf[sizeof(cmdbuf)-1])
X {
X /*
X * No room in the command buffer.
X */
X bell();
X } else if (cmd_col >= sc_width-3)
X {
X /*
X * No room on the screen.
X * {{ Could get fancy here; maybe shift the displayed
X * line and make room for more chars, like ksh. }}
X */
X bell();
X } else
X {
X /*
X * Append the character to the string.
X */
X *cp++ = c;
X if (control_char(c))
X {
X putchr('^');
X cmd_col++;
X c = carat_char(c);
X }
X putchr(c);
X cmd_col++;
X }
X return (0);
X}
X
X/*
X * Return the number currently in the command buffer.
X */
X static int
Xcmd_int()
X{
X *cp = '\0';
X cp = cmdbuf;
X return (atoi(cmdbuf));
X}
X
X/*
X * Move the cursor to lower left before executing a command.
X * This looks nicer if the command takes a long time before
X * updating the screen.
X */
X static void
Xcmd_exec()
X{
X lower_left();
X flush();
X}
X
X/*
X * Display the appropriate prompt.
X */
X static void
Xprompt()
X{
X register char *p;
X
X if (first_cmd != NULL && *first_cmd != '\0')
X {
X /*
X * No prompt necessary if commands are from first_cmd
X * rather than from the user.
X */
X return;
X }
X
X /*
X * If nothing is displayed yet, display starting from line 1.
X */
X if (position(TOP) == NULL_POSITION)
X jump_back(1);
X else if (screen_trashed)
X repaint();
X
X /*
X * If the -E flag is set and we've hit EOF on the last file, quit.
X */
X if (quit_at_eof == 2 && hit_eof && curr_ac + 1 >= ac)
X quit();
X
X /*
X * Select the proper prompt and display it.
X */
X lower_left();
X clear_eol();
X p = pr_string();
X if (p == NULL)
X putchr(':');
X else
X {
X so_enter();
X putstr(p);
X so_exit();
X }
X}
X
X/*
X * Get command character.
X * The character normally comes from the keyboard,
X * but may come from the "first_cmd" string.
X */
X static int
Xgetcc()
X{
X if (first_cmd == NULL)
X return (getchr());
X
X if (*first_cmd == '\0')
X {
X /*
X * Reached end of first_cmd input.
X */
X first_cmd = NULL;
X if (cp > cmdbuf && position(TOP) == NULL_POSITION)
X {
X /*
X * Command is incomplete, so try to complete it.
X * There are only two cases:
X * 1. We have "/string" but no newline. Add the \n.
X * 2. We have a number but no command. Treat as #g.
X * (This is all pretty hokey.)
X */
X if (mca != A_DIGIT)
X /* Not a number; must be search string */
X return ('\n');
X else
X /* A number; append a 'g' */
X return ('g');
X }
X return (getchr());
X }
X return (*first_cmd++);
X}
X
X/*
X * Execute a multicharacter command.
X */
X static void
Xexec_mca()
X{
X register char *p;
X register int n;
X
X *cp = '\0';
X cmd_exec();
X switch (mca)
X {
X case A_F_SEARCH:
X search(1, cmdbuf, number, wsearch);
X break;
X case A_B_SEARCH:
X search(0, cmdbuf, number, wsearch);
X break;
X case A_FIRSTCMD:
X /*
X * Skip leading spaces or + signs in the string.
X */
X for (p = cmdbuf; *p == '+' || *p == ' '; p++)
X ;
X if (every_first_cmd != NULL)
X free(every_first_cmd);
X if (*p == '\0')
X every_first_cmd = NULL;
X else
X every_first_cmd = save(p);
X break;
X case A_TOGGLE_OPTION:
X toggle_option(cmdbuf, 1);
X break;
X case A_EXAMINE:
X /*
X * Ignore leading spaces in the filename.
X */
X for (p = cmdbuf; *p == ' '; p++)
X ;
X edit(glob(p));
X break;
X#if SHELL_ESCAPE
X case A_SHELL:
X /*
X * !! just uses whatever is in shellcmd.
X * Otherwise, copy cmdbuf to shellcmd,
X * replacing any '%' with the current
X * file name.
X */
X if (*cmdbuf != '!')
X {
X register char *fr, *to;
X
X /*
X * Make one pass to see how big a buffer we
X * need to allocate for the expanded shell cmd.
X */
X for (fr = cmdbuf; *fr != '\0'; fr++)
X if (*fr == '%')
X n += strlen(current_file);
X else
X n++;
X
X if (shellcmd != NULL)
X free(shellcmd);
X shellcmd = calloc(n+1, sizeof(char));
X if (shellcmd == NULL)
X {
X error("cannot allocate memory");
X break;
X }
X
X /*
X * Now copy the shell cmd, expanding any "%"
X * into the current filename.
X */
X to = shellcmd;
X for (fr = cmdbuf; *fr != '\0'; fr++)
X {
X if (*fr != '%')
X *to++ = *fr;
X else
X {
X strcpy(to, current_file);
X to += strlen(to);
X }
X }
X *to = '\0';
X }
X
X if (shellcmd == NULL)
X lsystem("");
X else
X lsystem(shellcmd);
X error("!done");
X break;
X#endif
X }
X}
X
X/*
X * Add a character to a multi-character command.
X */
X static int
Xmca_char(c)
X int c;
X{
X switch (mca)
X {
X case 0:
X /*
X * Not in a multicharacter command.
X */
X return (NO_MCA);
X
X case A_PREFIX:
X /*
X * In the prefix of a command.
X */
X return (NO_MCA);
X
X case A_DIGIT:
X /*
X * Entering digits of a number.
X * Terminated by a non-digit.
X */
X if ((c < '0' || c > '9') &&
X c != erase_char && c != kill_char)
X {
X /*
X * Not part of the number.
X * Treat as a normal command character.
X */
X number = cmd_int();
X mca = 0;
X return (NO_MCA);
X }
X break;
X
X case A_TOGGLE_OPTION:
X /*
X * Special case for the TOGGLE_OPTION command.
X * if the option letter which was entered is a
X * single-char option, execute the command immediately,
X * so he doesn't have to hit RETURN.
X */
X if (cp == cmdbuf && c != erase_char && c != kill_char &&
X single_char_option(c))
X {
X cmdbuf[0] = c;
X cmdbuf[1] = '\0';
X toggle_option(cmdbuf, 1);
X return (MCA_DONE);
X }
X break;
X }
X
X /*
X * Any other multicharacter command
X * is terminated by a newline.
X */
X if (c == '\n' || c == '\r')
X {
X /*
X * Execute the command.
X */
X exec_mca();
X return (MCA_DONE);
X }
X /*
X * Append the char to the command buffer.
X */
X if (cmd_char(c))
X /*
X * Abort the multi-char command.
X */
X return (MCA_DONE);
X /*
X * Need another character.
X */
X return (MCA_MORE);
X}
X
X/*
X * Main command processor.
X * Accept and execute commands until a quit command, then return.
X */
X public void
Xcommands()
X{
X register int c;
X register int action;
X
X last_mca = 0;
X scroll = (sc_height + 1) / 2;
X
X for (;;)
X {
X mca = 0;
X number = 0;
X
X /*
X * See if any signals need processing.
X */
X if (sigs)
X {
X psignals();
X if (quitting)
X quit();
X }
X
X /*
X * Display prompt and accept a character.
X */
X cmd_reset();
X prompt();
X noprefix();
X c = getcc();
X
X again:
X if (sigs)
X continue;
X
X /*
X * If we are in a multicharacter command, call mca_char.
X * Otherwise we call cmd_decode to determine the
X * action to be performed.
X */
X if (mca)
X switch (mca_char(c))
X {
X case MCA_MORE:
X /*
X * Need another character.
X */
X c = getcc();
X goto again;
X case MCA_DONE:
X /*
X * Command has been handled by mca_char.
X * Start clean with a prompt.
X */
X continue;
X case NO_MCA:
X /*
X * Not a multi-char command
X * (at least, not anymore).
X */
X break;
X }
X
X /*
X * Decode the command character and decide what to do.
X */
X switch (action = cmd_decode(c))
X {
X case A_DIGIT:
X /*
X * First digit of a number.
X */
X start_mca(A_DIGIT, ":");
X goto again;
X
X case A_F_SCREEN:
X /*
X * Forward one screen.
X */
X if (number <= 0)
X number = sc_window;
X if (number <= 0)
X number = sc_height - 1;
X cmd_exec();
X forward(number, 1);
X break;
X
X case A_B_SCREEN:
X /*
X * Backward one screen.
X */
X if (number <= 0)
X number = sc_window;
X if (number <= 0)
X number = sc_height - 1;
X cmd_exec();
X backward(number, 1);
X break;
X
X case A_F_LINE:
X /*
X * Forward N (default 1) line.
X */
X if (number <= 0)
X number = 1;
X cmd_exec();
X forward(number, 0);
X break;
X
X case A_B_LINE:
X /*
X * Backward N (default 1) line.
X */
X if (number <= 0)
X number = 1;
X cmd_exec();
X backward(number, 0);
X break;
X
X case A_F_SCROLL:
X /*
X * Forward N lines
X * (default same as last 'd' or 'u' command).
X */
X if (number > 0)
X scroll = number;
X cmd_exec();
X forward(scroll, 0);
X break;
X
X case A_B_SCROLL:
X /*
X * Forward N lines
X * (default same as last 'd' or 'u' command).
X */
X if (number > 0)
X scroll = number;
X cmd_exec();
X backward(scroll, 0);
X break;
X
X case A_FREPAINT:
X /*
X * Flush buffers, then repaint screen.
X * Don't flush the buffers on a pipe!
X */
X if (!ispipe)
X {
X ch_init(0, 0);
X clr_linenum();
X }
X /* FALLTHRU */
X case A_REPAINT:
X /*
X * Repaint screen.
X */
X cmd_exec();
X repaint();
X break;
X
X case A_GOLINE:
X /*
X * Go to line N, default beginning of file.
X */
X if (number <= 0)
X number = 1;
X cmd_exec();
X jump_back(number);
X break;
X
X case A_PERCENT:
X /*
X * Go to a specified percentage into the file.
X */
X if (number < 0)
X number = 0;
X if (number > 100)
X number = 100;
X cmd_exec();
X jump_percent(number);
X break;
X
X case A_GOEND:
X /*
X * Go to line N, default end of file.
X */
X cmd_exec();
X if (number <= 0)
X jump_forw();
X else
X jump_back(number);
X break;
X
X case A_STAT:
X /*
X * Print file name, etc.
X */
X cmd_exec();
X error(eq_message());
X break;
X
X case A_VERSION:
X /*
X * Print version number, without the "@(#)".
X */
X cmd_exec();
X error(version+4);
X break;
X
X case A_QUIT:
X /*
X * Exit.
X */
X quit();
X
X case A_F_SEARCH:
X case A_B_SEARCH:
X /*
X * Search for a pattern.
X * Accept chars of the pattern until \n.
X */
X if (number <= 0)
X number = 1;
X start_mca(action, (action==A_F_SEARCH) ? "/" : "?");
X last_mca = mca;
X wsearch = 1;
X c = getcc();
X if (c == '!')
X {
X /*
X * Invert the sense of the search.
X * Set wsearch to 0 and get a new
X * character for the start of the pattern.
X */
X start_mca(action,
X (action==A_F_SEARCH) ? "!/" : "!?");
X wsearch = 0;
X c = getcc();
X }
X goto again;
X
X case A_AGAIN_SEARCH:
X /*
X * Repeat previous search.
X */
X if (number <= 0)
X number = 1;
X if (wsearch)
X start_mca(last_mca,
X (last_mca==A_F_SEARCH) ? "/" : "?");
X else
X start_mca(last_mca,
X (last_mca==A_F_SEARCH) ? "!/" : "!?");
X cmd_exec();
X search(mca==A_F_SEARCH, (char *)NULL, number, wsearch);
X break;
X
X case A_HELP:
X /*
X * Help.
X */
X lower_left();
X clear_eol();
X putstr("help");
X cmd_exec();
X help();
X break;
X
X case A_EXAMINE:
X /*
X * Edit a new file. Get the filename.
X */
X cmd_reset();
X start_mca(A_EXAMINE, "Examine: ");
X c = getcc();
X goto again;
X
X case A_VISUAL:
X /*
X * Invoke an editor on the input file.
X */
X#if EDITOR
X if (ispipe)
X {
X error("Cannot edit standard input");
X break;
X }
X /*
X * Try to pass the line number to the editor.
X */
X cmd_exec();
X c = currline(MIDDLE);
X if (c == 0)
X sprintf(cmdbuf, "%s %s",
X editor, current_file);
X else
X sprintf(cmdbuf, "%s +%d %s",
X editor, c, current_file);
X lsystem(cmdbuf);
X ch_init(0, 0);
X clr_linenum();
X break;
X#else
X error("Command not available");
X break;
X#endif
X
X case A_NEXT_FILE:
X /*
X * Examine next file.
X */
X if (number <= 0)
X number = 1;
X next_file(number);
X break;
X
X case A_PREV_FILE:
X /*
X * Examine previous file.
X */
X if (number <= 0)
X number = 1;
X prev_file(number);
X break;
X
X case A_TOGGLE_OPTION:
X /*
X * Toggle a flag setting.
X */
X cmd_reset();
X start_mca(A_TOGGLE_OPTION, "-");
X c = getcc();
X goto again;
X
X case A_DISP_OPTION:
X /*
X * Report a flag setting.
X */
X cmd_reset();
X start_mca(A_DISP_OPTION, "_");
X c = getcc();
X if (c == erase_char || c == kill_char)
X break;
X cmdbuf[0] = c;
X cmdbuf[1] = '\0';
X toggle_option(cmdbuf, 0);
X break;
X
X case A_FIRSTCMD:
X /*
X * Set an initial command for new files.
X */
X cmd_reset();
X start_mca(A_FIRSTCMD, "+");
X c = getcc();
X goto again;
X
X case A_SHELL:
X /*
X * Shell escape.
X */
X#if SHELL_ESCAPE
X cmd_reset();
X start_mca(A_SHELL, "!");
X c = getcc();
X goto again;
X#else
X error("Command not available");
X break;
X#endif
X
X case A_SETMARK:
X /*
X * Set a mark.
X */
X lower_left();
X clear_eol();
X start_mca(A_SETMARK, "mark: ");
X c = getcc();
X if (c == erase_char || c == kill_char)
X break;
X setmark(c);
X break;
X
X case A_GOMARK:
X /*
X * Go to a mark.
X */
X lower_left();
X clear_eol();
X start_mca(A_GOMARK, "goto mark: ");
X c = getcc();
X if (c == erase_char || c == kill_char)
X break;
X gomark(c);
X break;
X
X case A_PREFIX:
X /*
X * The command is incomplete (more chars are needed).
X * Display the current char so the user knows
X * what's going on and get another character.
X */
X if (mca != A_PREFIX)
X start_mca(A_PREFIX, "& ");
X if (control_char(c))
X {
X putchr('^');
X c = carat_char(c);
X }
X putchr(c);
X c = getcc();
X goto again;
X
X default:
X bell();
X break;
X }
X }
X}
END_OF_FILE
--
Please send comp.sources.unix-related mail to rsalz at uunet.uu.net.
More information about the Comp.sources.unix
mailing list