Subject: v16i031: Less, a pager that's more than more, Part02/04 Newsgroups: comp.sources.unix Sender: sources Approved: rsalz@uunet.UU.NET 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 X#include X#endif X X#if TERMIO X#include X#else X#include X#endif X X#ifdef TIOCGWINSZ X#include 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 X#ifdef SIGPHONE X#include 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 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 X#include X#include 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 . 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 . 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 X#include 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