Path: utzoo!utgpu!cs.utexas.edu!usc!apple!amdcad!pyramid!ctnews!unix386!mark From: mark@unix386.Convergent.COM (Mark Nudelman) Newsgroups: alt.sources Subject: less (part 3 of 6) Message-ID: <6422@unix386.Convergent.COM> Date: 6 Mar 91 00:13:38 GMT Organization: Unisys/Convergent, San Jose, CA Lines: 2950 #! /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 \"lesskey.c\" sed "s/^X//" >'lesskey.c' <<'END_OF_FILE' X/* X * lesskey [-o output] [input] X * X * Make a .less file. X * If no input file is specified, standard input is used. X * If no output file is specified, $HOME/.less is used. X * X * The .less file is used to specify (to "less") user-defined X * key bindings. Basically any sequence of 1 to MAX_CMDLEN X * keystrokes may be bound to an existing less function. X * X * The input file is an ascii file consisting of a X * sequence of lines of the form: X * string action [chars] X * X * "string" is a sequence of command characters which form X * the new user-defined command. The command X * characters may be: X * 1. The actual character itself. X * 2. A character preceded by ^ to specify a X * control character (e.g. ^X means control-X). X * 3. Any character (other than an octal digit) preceded by X * a \ to specify the character itself (characters which X * must be preceded by \ include ^, \, and whitespace. X * 4. A backslash followed by one to three octal digits X * to specify a character by its octal value. X * "action" is the name of a "less" action, from the table below. X * "chars" is an optional sequence of characters which is treated X * as keyboard input after the command is executed. X * X * Blank lines and lines which start with # are ignored. X * X * X * The output file is a non-ascii file, consisting of X * zero or more byte sequences of the form: X * string <0> X * or X * string <0> chars <0> X * X * "string" is the command string. X * "<0>" is one null byte. X * "" is one byte containing the action code (the A_xxx value). X * If action is ORed with A_EXTRA, the action byte is followed X * by the null-terminated "chars" string. X */ X X#include X#include "less.h" X#include "cmd.h" X Xchar usertable[MAX_USERCMD]; X Xstruct cmdname X{ X char *cn_name; X int cn_action; X} cmdnames[] = X{ X "back-bracket", A_B_BRACKET, X "back-line", A_B_LINE, X "back-line-force", A_BF_LINE, X "back-screen", A_B_SCREEN, X "back-scroll", A_B_SCROLL, X "back-search", A_B_SEARCH, X "back-window", A_B_WINDOW, X "debug", A_DEBUG, X "display-flag", A_DISP_OPTION, X "display-option", A_DISP_OPTION, X "end", A_GOEND, X "examine", A_EXAMINE, X "first-cmd", A_FIRSTCMD, X "firstcmd", A_FIRSTCMD, X "flush-repaint", A_FREPAINT, X "forw-bracket", A_F_BRACKET, X "forw-forever", A_F_FOREVER, X "forw-line", A_F_LINE, X "forw-line-force", A_FF_LINE, X "forw-screen", A_F_SCREEN, X "forw-scroll", A_F_SCROLL, X "forw-search", A_F_SEARCH, X "forw-window", A_F_WINDOW, X "goto-end", A_GOEND, X "goto-line", A_GOLINE, X "goto-mark", A_GOMARK, X "help", A_HELP, X "index-file", A_INDEX_FILE, X "invalid", A_UINVALID, X "next-file", A_NEXT_FILE, X "noaction", A_NOACTION, X "percent", A_PERCENT, X "pipe", A_PIPE, X "prev-file", A_PREV_FILE, X "quit", A_QUIT, X "repaint", A_REPAINT, X "repaint-flush", A_FREPAINT, X "repeat-search", A_AGAIN_SEARCH, X "repeat-search-all", A_T_AGAIN_SEARCH, X "reverse-search", A_REVERSE_SEARCH, X "reverse-search-all", A_T_REVERSE_SEARCH, X "set-mark", A_SETMARK, X "shell", A_SHELL, X "status", A_STAT, X "toggle-flag", A_OPT_TOGGLE, X "toggle-option", A_OPT_TOGGLE, X "version", A_VERSION, X "visual", A_VISUAL, X NULL, 0 X}; X Xmain(argc, argv) X int argc; X char *argv[]; X{ X char *p; /* {{ Can't be register since we use &p }} */ X register char *up; /* Pointer into usertable */ X FILE *desc; /* Description file (input) */ X FILE *out; /* Output file */ X int linenum; /* Line number in input file */ X char *currcmd; /* Start of current command string */ X int errors; X int i, j; X char line[200]; X char *outfile; X X extern char *getenv(); X X /* X * Process command line arguments. X */ X outfile = NULL; X while (--argc > 0 && **(++argv) == '-') X { X switch (argv[0][1]) X { X case 'o': X outfile = &argv[0][2]; X if (*outfile == '\0') X { X if (--argc <= 0) X usage(); X outfile = *(++argv); X } X break; X default: X usage(); X } X } X if (argc > 1) X usage(); X X X /* X * Open the input file, or use standard input if none specified. X */ X if (argc > 0) X { X if ((desc = fopen(*argv, "r")) == NULL) X { X perror(*argv); X exit(1); X } X } else X desc = stdin; X X /* X * Read the input file, one line at a time. X * Each line consists of a command string, X * followed by white space, followed by an action name. X */ X linenum = 0; X errors = 0; X up = usertable; X while (fgets(line, sizeof(line), desc) != NULL) X { X ++linenum; X X /* X * Skip leading white space. X * Replace the final newline with a null byte. X * Ignore blank lines and comment lines. X */ X p = line; X while (*p == ' ' || *p == '\t') X ++p; X for (i = 0; p[i] != '\n' && p[i] != '\0'; i++) X ; X p[i] = '\0'; X if (*p == '#' || *p == '\0') X continue; X X /* X * Parse the command string and store it in the usertable. X */ X currcmd = up; X do X { X if (up >= usertable + MAX_USERCMD) X { X fprintf(stderr, "too many commands, line %d\n", X linenum); X exit(1); X } X if (up >= currcmd + MAX_CMDLEN) X { X fprintf(stderr, "command too long on line %d\n", X linenum); X errors++; X break; X } X X *up++ = tchar(&p); X X } while (*p != ' ' && *p != '\t' && *p != '\0'); X X /* X * Terminate the command string with a null byte. X */ X *up++ = '\0'; X X /* X * Skip white space between the command string X * and the action name. X * Terminate the action name with a null byte if it X * is followed by whitespace or a # comment. X */ X if (*p == '\0') X { X fprintf(stderr, "missing whitespace on line %d\n", X linenum); X errors++; X continue; X } X while (*p == ' ' || *p == '\t') X ++p; X for (j = 0; p[j] != ' ' && p[j] != '\t' && X p[j] != '#' && p[j] != '\0'; j++) X ; X p[j] = '\0'; X X /* X * Parse the action name and store it in the usertable. X */ X for (i = 0; cmdnames[i].cn_name != NULL; i++) X if (strcmp(cmdnames[i].cn_name, p) == 0) X break; X if (cmdnames[i].cn_name == NULL) X { X fprintf(stderr, "unknown action <%s> on line %d\n", X p, linenum); X errors++; X continue; X } X *up++ = cmdnames[i].cn_action; X X /* X * See if an extra string follows the action name. X */ X for (j = j+1; p[j] == ' ' || p[j] == '\t'; j++) X ; X p += j; X if (*p != '\0') X { X /* X * OR the special value A_EXTRA into the action byte. X * Put the extra string after the action byte. X */ X up[-1] |= A_EXTRA; X while (*p != '\0') X *up++ = tchar(&p); X *up++ = '\0'; X } X } X X if (errors > 0) X { X fprintf(stderr, "%d errors; no output produced\n", errors); X exit(1); X } X X /* X * Write the output file. X * If no output file was specified, use "$HOME/.less" X */ X if (outfile == NULL) X { X p = getenv("HOME"); X if (p == NULL || *p == '\0') X { X fprintf(stderr, "cannot find $HOME - using current directory\n"); X#if __MSDOS__ X strcpy(line, "_less"); X#else X strcpy(line, ".less"); X#endif X } else X { X strcpy(line, p); X#if __MSDOS__ X strcat(line, "\\_less"); X#else X strcat(line, "/.less"); X#endif X } X outfile = line; X } X if ((out = fopen(outfile, "w")) == NULL) X perror(outfile); X else X fwrite((char *)usertable, 1, up-usertable, out); X} X X/* X * Parse one character of a string. X */ Xtchar(pp) X char **pp; X{ X register char *p; X register char ch; X register int i; X X p = *pp; X switch (*p) X { X case '\\': X if (*++p >= '0' && *p <= '7') X { X /* X * Parse an octal number. X */ X ch = 0; X i = 0; X do X ch = 8*ch + (*p - '0'); X while (*++p >= '0' && *p <= '7' && ++i < 3); X *pp = p; X return (ch); X } X /* X * Backslash followed by a char just means that char. X */ X *pp = p+1; X return (*p); X case '^': X /* X * Carat means CONTROL. X */ X *pp = p+2; X return (CONTROL(p[1])); X } X *pp = p+1; X return (*p); X} X Xusage() X{ X fprintf(stderr, "usage: lesskey [-o output] [input]\n"); X exit(1); X} END_OF_FILE echo shar: Extracting \"ch.c\" sed "s/^X//" >'ch.c' <<'END_OF_FILE' X/* X * Low level character input from the input file. X * We use these special purpose routines which optimize moving X * both forward and backward from the current read pointer. X */ X X#include "less.h" X Xpublic int file = -1; /* File descriptor of the input file */ Xpublic int ignore_eoi; X X/* X * Pool of buffers holding the most recently used blocks of the input file. X */ X#define BUFSIZ 1024 Xstruct buf { X struct buf *next, *prev; /* Must be first to match struct filestate */ X long block; X unsigned int datasize; X unsigned char data[BUFSIZ]; X}; X X/* X * The buffer pool is kept as a doubly-linked circular list, X * in order from most- to least-recently used. X * The circular list is anchored by the file state "thisfile". X * X * The file state is maintained in a filestate structure. X * There are two such structures, one used when input is a pipe X * and the other when input is an ordinary file. X * This is so that we can leave a pipe, look and other files, X * and return to the pipe without losing buffered data. X * Buffered data can be reconstructed for a non-pipe file by X * simply re-reading the file, but a pipe cannot be re-read. X */ X Xstatic struct filestate { X struct buf *next, *prev; /* Must be first to match struct buf */ X POSITION fpos; X int nbufs; X long block; X int offset; X POSITION fsize; X}; X X#define END_OF_CHAIN ((struct buf *)thisfile) X#define buf_head thisfile->next X#define buf_tail thisfile->prev X#define ch_nbufs thisfile->nbufs X#define ch_block thisfile->block X#define ch_offset thisfile->offset X#define ch_fpos thisfile->fpos X#define ch_fsize thisfile->fsize X Xstatic struct filestate pipefile = X { (struct buf *)&pipefile, (struct buf *)&pipefile }; X Xstatic struct filestate nonpipefile = X { (struct buf *)&nonpipefile, (struct buf *)&nonpipefile }; X Xstatic struct filestate *thisfile; X Xextern int ispipe; Xextern int autobuf; Xextern int sigs; X#if LOGFILE Xextern int logfile; X#endif X Xstatic int ch_addbuf(); X X X/* X * Get the character pointed to by the read pointer. X * ch_get() is a macro which is more efficient to call X * than fch_get (the function), in the usual case X * that the block desired is at the head of the chain. X */ X#define ch_get() ((ch_block == buf_head->block && \ X ch_offset < buf_head->datasize) ? \ X buf_head->data[ch_offset] : fch_get()) X static int Xfch_get() X{ X register struct buf *bp; X register int n; X register int slept; X POSITION pos; X POSITION len; X X slept = 0; X X /* X * Look for a buffer holding the desired block. X */ X for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) X if (bp->block == ch_block) X { X if (ch_offset >= bp->datasize) X /* X * Need more data in this buffer. X */ X goto read_more; X goto found; X } X /* X * Block is not in a buffer. X * Take the least recently used buffer X * and read the desired block into it. X * If the LRU buffer has data in it, X * and autobuf is true, and input is a pipe, X * then try to allocate a new buffer first. X */ X if (autobuf && ispipe && buf_tail->block != (long)(-1)) X if (ch_addbuf(1)) X /* X * Allocation failed: turn off autobuf. X */ X autobuf = 0; X bp = buf_tail; X bp->block = ch_block; X bp->datasize = 0; X X read_more: X pos = (ch_block * BUFSIZ) + bp->datasize; X if ((len = ch_length()) != NULL_POSITION && pos >= len) X /* X * At end of file. X */ X return (EOI); X X if (pos != ch_fpos) X { X /* X * Not at the correct position: must seek. X * If input is a pipe, we're in trouble (can't seek on a pipe). X * Some data has been lost: just return "?". X */ X if (ispipe) X return ('?'); X if (lseek(file, (offset_t)pos, 0) == BAD_LSEEK) X { X error("seek error", NULL_PARG); X quit(1); X } X ch_fpos = pos; X } X X /* X * Read the block. X * If we read less than a full block, that's ok. X * We use partial block and pick up the rest next time. X */ X n = iread(file, &bp->data[bp->datasize], X (unsigned int)(BUFSIZ - bp->datasize)); X if (n == READ_INTR) X return (EOI); X if (n < 0) X { X error("read error", NULL_PARG); X quit(1); X } X ch_fpos += n; X X#if LOGFILE X /* X * If we have a log file, write the new data to it. X */ X if (logfile >= 0 && n > 0) X write(logfile, &bp->data[bp->datasize], n); X#endif X X bp->datasize += n; X X /* X * If we have read to end of file, set ch_fsize to indicate X * the position of the end of file. X */ X if (n == 0) X { X ch_fsize = pos; X if (ignore_eoi) X { X /* X * We are ignoring EOF. X * Wait a while, then try again. X */ X if (!slept) X ierror("Waiting for data", NULL_PARG); X sleep(1); X slept = 1; X } X if (sigs) X return (EOI); X } X X found: X if (buf_head != bp) X { X /* X * Move the buffer to the head of the buffer chain. X * This orders the buffer chain, most- to least-recently used. X */ X bp->next->prev = bp->prev; X bp->prev->next = bp->next; X X bp->next = buf_head; X bp->prev = END_OF_CHAIN; X buf_head->prev = bp; X buf_head = bp; X } X X if (ch_offset >= bp->datasize) X /* X * After all that, we still don't have enough data. X * Go back and try again. X */ X goto read_more; X X return (bp->data[ch_offset]); X} X X#if LOGFILE X/* X * Close the logfile. X * If we haven't read all of standard input into it, do that now. X */ X public void Xend_logfile() X{ X static int tried = 0; X X if (logfile < 0) X return; X if (!tried && ch_fsize == NULL_POSITION) X { X tried = 1; X ierror("Finishing logfile", NULL_PARG); X while (ch_forw_get() != EOI) X if (sigs) X break; X } X close(logfile); X logfile = -1; X} X X/* X * Start a log file AFTER less has already been running. X * Invoked from the - command; see toggle_option(). X * Write all the existing buffered data to the log file. X */ X public void Xsync_logfile() X{ X register struct buf *bp; X long block; X long last_block; X X last_block = (ch_fpos + BUFSIZ - 1) / BUFSIZ; X for (block = 0; block <= last_block; block++) X for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) X if (bp->block == block) X { X write(logfile, bp->data, bp->datasize); X break; X } X} X X#endif X X/* X * Determine if a specific block is currently in one of the buffers. X */ X static int Xbuffered(block) X long block; X{ X register struct buf *bp; X X for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) X if (bp->block == block) X return (1); X return (0); X} X X/* X * Seek to a specified position in the file. X * Return 0 if successful, non-zero if can't seek there. X */ X public int Xch_seek(pos) X register POSITION pos; X{ X long new_block; X POSITION len; X X len = ch_length(); X if (pos < ch_zero() || (len != NULL_POSITION && pos > len)) X return (1); X X new_block = pos / BUFSIZ; X if (ispipe && pos != ch_fpos && !buffered(new_block)) X return (1); X /* X * Set read pointer. X */ X ch_block = new_block; X ch_offset = pos % BUFSIZ; X return (0); X} X X/* X * Seek to the end of the file. X */ X public int Xch_end_seek() X{ X POSITION len; X X if (!ispipe) X ch_fsize = filesize(file); X X len = ch_length(); X if (len != NULL_POSITION) X return (ch_seek(len)); X X /* X * Do it the slow way: read till end of data. X */ X while (ch_forw_get() != EOI) X if (sigs) X return (1); X return (0); X} X X/* X * Seek to the beginning of the file, or as close to it as we can get. X * We may not be able to seek there if input is a pipe and the X * beginning of the pipe is no longer buffered. X */ X public int Xch_beg_seek() X{ X register struct buf *bp, *firstbp; X X /* X * Try a plain ch_seek first. X */ X if (ch_seek(ch_zero()) == 0) X return (0); X X /* X * Can't get to position 0. X * Look thru the buffers for the one closest to position 0. X */ X firstbp = bp = buf_head; X if (bp == END_OF_CHAIN) X return (1); X while ((bp = bp->next) != END_OF_CHAIN) X if (bp->block < firstbp->block) X firstbp = bp; X ch_block = firstbp->block; X ch_offset = 0; X return (0); X} X X/* X * Return the length of the file, if known. X */ X public POSITION Xch_length() X{ X if (ignore_eoi) X return (NULL_POSITION); X return (ch_fsize); X} X X/* X * Return the current position in the file. X */ X#define tellpos(blk,off) ((POSITION)((((long)(blk)) * BUFSIZ) + (off))) X X public POSITION Xch_tell() X{ X return (tellpos(ch_block, ch_offset)); X} X X/* X * Get the current char and post-increment the read pointer. X */ X public int Xch_forw_get() X{ X register int c; X X c = ch_get(); X if (c == EOI) X return (EOI); X if (ch_offset < BUFSIZ-1) X ch_offset++; X else X { X#if __ZOFFSET /* NOT WORKING */ X if (ch_fsize != NULL_POSITION && X tellpos(ch_block+1, 0) >= ch_fsize) X return (EOI); X#endif X ch_block ++; X ch_offset = 0; X } X return (c); X} X X/* X * Pre-decrement the read pointer and get the new current char. X */ X public int Xch_back_get() X{ X if (ch_offset > 0) X ch_offset --; X else X { X#if __ZOFFSET /* NOT WORKING */ X if (tellpos(ch_block-1, BUFSIZ-1) < ch_zero()) X return (EOI); X#else X if (ch_block <= 0) X return (EOI); X#endif X if (ispipe && !buffered(ch_block-1)) X return (EOI); X ch_block--; X ch_offset = BUFSIZ-1; X } X return (ch_get()); X} X X/* X * Allocate buffers. X * Caller wants us to have a total of at least want_nbufs buffers. X */ X public int Xch_nbuf(want_nbufs) X int want_nbufs; X{ X PARG parg; X X if (ch_nbufs < want_nbufs && ch_addbuf(want_nbufs - ch_nbufs)) X { X /* X * Cannot allocate enough buffers. X * If we don't have ANY, then quit. X * Otherwise, just report the error and return. X */ X parg.p_int = want_nbufs - ch_nbufs; X error("Cannot allocate %d buffers", &parg); X if (ch_nbufs == 0) X quit(1); X } X return (ch_nbufs); X} X X/* X * Flush any saved file state, including buffer contents. X */ X public void Xch_flush() X{ X register struct buf *bp; X X if (ispipe) X { X /* X * If input is a pipe, we don't flush buffer contents, X * since the contents can't be recovered. X */ X ch_fsize = NULL_POSITION; X return; X } X X /* X * Initialize all the buffers. X */ X for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next) X bp->block = (long)(-1); X X /* X * Figure out the size of the file, if we can. X */ X ch_fsize = filesize(file); X X /* X * Seek to a known position: the beginning of the file. X */ X ch_fpos = 0; X ch_block = ch_fpos / BUFSIZ; X ch_offset = ch_fpos % BUFSIZ; X X if (lseek(file, (offset_t)0, 0) == BAD_LSEEK) X { X /* X * Warning only; even if the seek fails for some reason, X * there's a good chance we're at the beginning anyway. X * {{ I think this is bogus reasoning. }} X */ X error("seek error to 0", NULL_PARG); X } X} X X/* X * Allocate some new buffers. X * The buffers are added to the tail of the buffer chain. X */ X static int Xch_addbuf(nnew) X int nnew; X{ X register struct buf *bp; X register struct buf *newbufs; X X /* X * We don't have enough buffers. X * Allocate some new ones. X */ X newbufs = (struct buf *) calloc(nnew, sizeof(struct buf)); X if (newbufs == NULL) X return (1); X X /* X * Initialize the new buffers and link them together. X * Link them all onto the tail of the buffer list. X */ X ch_nbufs += nnew; X for (bp = &newbufs[0]; bp < &newbufs[nnew]; bp++) X { X bp->next = bp + 1; X bp->prev = bp - 1; X bp->block = (long)(-1); X } X newbufs[nnew-1].next = END_OF_CHAIN; X newbufs[0].prev = buf_tail; X buf_tail->next = &newbufs[0]; X buf_tail = &newbufs[nnew-1]; X return (0); X} X X/* X * Use the pipe file state. X */ X public void Xch_pipe() X{ X thisfile = &pipefile; X} X X/* X * Use the non-pipe file state. X */ X public void Xch_nonpipe() X{ X thisfile = &nonpipefile; X} END_OF_FILE echo shar: Extracting \"cmdbuf.c\" sed "s/^X//" >'cmdbuf.c' <<'END_OF_FILE' X/* X * Functions which manipulate the command buffer. X * Used only by command() and related functions. X */ X X#include "less.h" X Xextern int erase_char, kill_char; Xextern int sc_width; X Xstatic char cmdbuf[120]; /* Buffer for holding a multi-char command */ Xstatic int cmd_col; /* Current column of the multi-char command */ Xstatic char *cp; /* Pointer into cmdbuf */ X X/* X * Reset command buffer (to empty). X */ X public void Xcmd_reset() X{ X cp = cmdbuf; X *cp = '\0'; X cmd_col = 0; X} X X/* X * How many characters are in the command buffer? X */ X public int Xlen_cmdbuf() X{ X return (cp - cmdbuf); X} X X/* X * Backspace in the command buffer. X */ X public int Xcmd_erase() X{ X register char *s; 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 --cp; X if (*cp == ESC) X s = "ESC"; X else X s = prchar(*cp); X while (*s++ != '\0') X { X backspace(); X cmd_col--; X } X *cp = '\0'; X return (0); 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 public int Xcmd_char(c) X int c; X{ X char *s; 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-4) 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 *cp = '\0'; X if (c == ESC) X s = "ESC"; X else X s = prchar(c); X putstr(s); X cmd_col += strlen(s); X } X return (0); X} X X/* X * Return the number currently in the command buffer. X */ X public int Xcmd_int() X{ X return (atoi(cmdbuf)); X} X X/* X * Display a string, usually as a prompt for input into the command buffer. X */ X public void Xcmd_putstr(s) X char *s; X{ X putstr(s); X cmd_col += strlen(s); X} X X/* X * Return a pointer to the command buffer. X */ X public char * Xget_cmdbuf() X{ X return (cmdbuf); 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 "option.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 swindow; Xextern int jump_sline; Xextern int quitting; Xextern int scroll; Xextern int nohelp; Xextern int ignore_eoi; Xextern char *every_first_cmd; Xextern char version[]; Xextern struct scrpos initial_scrpos; Xextern IFILE curr_ifile; X#if EDITOR Xextern char *editor; Xextern char *editproto; X#endif Xextern int screen_trashed; /* The screen has been overwritten */ X Xstatic char ungot[100]; Xstatic char *ungotp = NULL; X#if SHELL_ESCAPE Xstatic char *shellcmd = NULL; /* For holding last shell command for "!!" */ X#endif Xstatic int mca; /* The multicharacter command (action) */ Xstatic int search_type; /* The previous type of search */ Xstatic int number; /* The number typed by the user */ Xstatic char optchar; Xstatic int optflag; X#if PIPEC Xstatic char pipec; X#endif X Xstatic void multi_search(); 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 * Set up the display to start a new multi-character command. X */ X static void Xstart_mca(action, prompt) X int action; X char *prompt; X{ X mca = action; X lower_left(); X clear_eol(); X cmd_putstr(prompt); X} X X/* X * Set up the display to start a new search command. X */ X static void Xsearch_mca() X{ X switch (SRCH_DIR(search_type)) X { X case SRCH_FORW: X mca = A_F_SEARCH; X break; X case SRCH_BACK: X mca = A_B_SEARCH; X break; X } X X lower_left(); X clear_eol(); X X if (search_type & SRCH_FIRST_FILE) X cmd_putstr("@"); X else X cmd_putstr(" "); X X if (search_type & SRCH_PAST_EOF) X cmd_putstr("*"); X else X cmd_putstr(" "); X X cmd_putstr(" "); X X if (search_type & SRCH_NOMATCH) X cmd_putstr("!"); X else X cmd_putstr(" "); X X switch (SRCH_DIR(search_type)) X { X case SRCH_FORW: X cmd_putstr("/"); X break; X case SRCH_BACK: X cmd_putstr("?"); X break; X } X} X X/* X * Execute a multicharacter command. X */ X static void Xexec_mca() X{ X register char *cbuf; X register char *s; X X cmd_exec(); X cbuf = get_cmdbuf(); X X switch (mca) X { X case A_F_SEARCH: X case A_B_SEARCH: X multi_search(cbuf, number); X break; X case A_FIRSTCMD: X /* X * Skip leading spaces or + signs in the string. X */ X while (*cbuf == '+' || *cbuf == ' ') X cbuf++; X if (every_first_cmd != NULL) X free(every_first_cmd); X if (*cbuf == '\0') X every_first_cmd = NULL; X else X every_first_cmd = save(cbuf); X break; X case A_OPT_TOGGLE: X toggle_option(optchar, cbuf, optflag); X optchar = '\0'; X break; X case A_F_BRACKET: X match_brac(cbuf[0], cbuf[1], 1, number); X break; X case A_B_BRACKET: X match_brac(cbuf[1], cbuf[0], 0, number); X break; X case A_EXAMINE: X /* X * Ignore leading spaces and glob the filename. X */ X cbuf = skipsp(cbuf); X s = glob(cbuf); X if (s != NULL) X { X edit_list(s); X free(s); X } else X edit_list(cbuf); 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 * expanding any special characters ("%" or "#"). X */ X if (*cbuf != '!') X { X if (shellcmd != NULL) X free(shellcmd); X shellcmd = fexpand(cbuf); X if (shellcmd == NULL) X break; X } X X if (shellcmd == NULL) X lsystem(""); X else X lsystem(shellcmd); X error("!done", NULL_PARG); X break; X#endif X#if PIPEC X case A_PIPE: X (void) pipe_mark(pipec, cbuf); X error("|done", NULL_PARG); 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 char *p; X int flag; X char buf[3]; 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 * This not considered a multichar command X * (even tho it uses cmdbuf, etc.). X * It is handled in the commands() switch. 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_OPT_TOGGLE: 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 user doesn't have to hit RETURN. X * If the first char is + or -, this indicates X * OPT_UNSET or OPT_SET respectively, instead of OPT_TOGGLE. X */ X if (c == erase_char || c == kill_char) X break; X if (optchar != '\0' && optchar != '+' && optchar != '-') X /* X * We already have the option letter. X */ X break; X switch (c) X { X case '+': X optflag = OPT_UNSET; X break; X case '-': X optflag = OPT_SET; X break; X default: X optchar = c; X if (optflag != OPT_TOGGLE || single_char_option(c)) X { X toggle_option(c, "", optflag); X return (MCA_DONE); X } X break; X } X if (optchar == '+' || optchar == '-') X { X optchar = c; X break; X } X /* X * Display a prompt appropriate for the option letter. X */ X if ((p = opt_prompt(c)) == NULL) X { X buf[0] = '-'; X buf[1] = c; X buf[2] = '\0'; X p = buf; X } X start_mca(A_OPT_TOGGLE, p); X return (MCA_MORE); X X case A_F_SEARCH: X case A_B_SEARCH: X /* X * Special case for search commands. X * Certain characters as the first char of X * the pattern have special meaning: X * ! Toggle the NOMATCH flag X * * Toggle the PAST_EOF flag X * @ Toggle the FIRST_FILE flag X */ X if (len_cmdbuf() > 0) X /* X * Only works for the first char of the pattern. X */ X break; X X flag = 0; X switch (c) X { X case '!': X flag = SRCH_NOMATCH; X break; X case '@': X flag = SRCH_FIRST_FILE; X break; X case '*': X flag = SRCH_PAST_EOF; X break; X } X if (flag != 0) X { X search_type ^= flag; X search_mca(); X return (MCA_MORE); 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 if ((mca == A_F_BRACKET || mca == A_B_BRACKET) && len_cmdbuf() >= 2) X { X /* X * Special case for the bracket-matching commands. X * Execute the command after getting exactly two X * characters from the user. X */ X exec_mca(); X return (MCA_DONE); X } X X /* X * Need another character. X */ X return (MCA_MORE); X} X X/* X * Display the appropriate prompt. X */ X static void Xprompt() X{ X register char *p; X X if (ungotp != NULL && ungotp > ungot) X { X /* X * No prompt necessary if commands are from X * ungotten chars rather than from the user. X */ X return; X } X X /* X * If nothing is displayed yet, display starting from initial_scrpos. X */ X if (empty_screen()) X { X if (initial_scrpos.pos == NULL_POSITION) X /* X * {{ Maybe this should be: X * jump_loc(ch_zero(), jump_sline); X * but this behavior seems rather unexpected X * on the first screen. }} X */ X jump_loc(ch_zero(), 1); X else X jump_loc(initial_scrpos.pos, initial_scrpos.ln); 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 && X next_ifile(curr_ifile) == NULL_IFILE) X quit(0); 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#if __MSDOS__ X scroll_bar(); X#endif X} X X/* X * Get command character. X * The character normally comes from the keyboard, X * but may come from ungotten characters X * (characters previously given to ungetcc or ungetsc). X */ X static int Xgetcc() X{ X if (ungotp == NULL) X /* X * Normal case: no ungotten chars, so get one from the user. X */ X return (getchr()); X X if (ungotp > ungot) X /* X * Return the next ungotten char. X */ X return (*--ungotp); X X /* X * We have just run out of ungotten chars. X */ X ungotp = NULL; X if (len_cmdbuf() == 0 || !empty_screen()) X return (getchr()); X /* X * Command is incomplete, so try to complete it. X */ X switch (mca) X { X case A_DIGIT: X /* X * We have a number but no command. Treat as #g. X */ X return ('g'); X X case A_F_SEARCH: X case A_B_SEARCH: X /* X * We have "/string" but no newline. Add the \n. X */ X return ('\n'); X X default: X /* X * Some other incomplete command. Let user complete it. X */ X return (getchr()); X } X} X X/* X * "Unget" a command character. X * The next getcc() will return this character. X */ X public void Xungetcc(c) X int c; X{ X if (ungotp == NULL) X ungotp = ungot; X if (ungotp >= ungot + sizeof(ungot)) X { X error("ungetcc overflow", NULL_PARG); X quit(1); X } X *ungotp++ = c; X} X X/* X * Unget a whole string of command characters. X * The next sequence of getcc()'s will return this string. X */ X public void Xungetsc(s) X char *s; X{ X register char *p; X X for (p = s + strlen(s) - 1; p >= s; p--) X ungetcc(*p); X} X X/* X * Search for a pattern, possibly in multiple files. X * If SRCH_FIRST_FILE is set, begin searching at the first file. X * If SRCH_PAST_EOF is set, continue the search thru multiple files. X */ X static void Xmulti_search(pattern, n) X char *pattern; X int n; X{ X register int nomore; X char *curr_filename; X int changed_file; X struct scrpos scrpos; X X changed_file = 0; X curr_filename = get_filename(curr_ifile); X X if (search_type & SRCH_FIRST_FILE) X { X /* X * Start at the first (or last) file X * in the command line list. X */ X if (SRCH_DIR(search_type) == SRCH_FORW) X nomore = edit_first(); X else X nomore = edit_last(); X if (nomore) X return; X changed_file = 1; X search_type &= ~SRCH_FIRST_FILE; X } X X for (;;) X { X if ((n = search(search_type, pattern, n)) == 0) X /* X * Found it. X */ X return; X X if (n < 0) X /* X * Some kind of error in the search. X * Error message has been printed by search(). X */ X break; X X if ((search_type & SRCH_PAST_EOF) == 0) X /* X * We didn't find a match, but we're X * supposed to search only one file. X */ X break; X /* X * Move on to the next file. X */ X if (SRCH_DIR(search_type) == SRCH_BACK) X nomore = edit_prev(1); X else X nomore = edit_next(1); X if (nomore) X break; X changed_file = 1; X } X X /* X * Didn't find it. X * Print an error message if we haven't already. X */ X if (n > 0) X error("Pattern not found", NULL_PARG); X X if (changed_file) X /* X * Restore the file we were originally viewing. X */ X (void) edit(curr_filename, 0); X} X X/* X * Main command processor. X * Accept and execute commands until a quit command. X */ X public void Xcommands() X{ X register int c; X register int action; X register char *cbuf; X char *s; X char tbuf[2]; X PARG parg; X X search_type = SRCH_FORW; X scroll = (sc_height + 1) / 2; X X for (;;) X { X mca = 0; X number = 0; X optchar = '\0'; X X /* X * See if any signals need processing. X */ X if (sigs) X { X psignals(); X if (quitting) X quit(-1); X } X X /* X * Display prompt and accept a character. X */ X cmd_reset(); X prompt(); X if (sigs) X continue; 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 if (mca) X { X /* X * We're in a multichar command. X * Add the character to the command buffer X * and display it on the screen. X * If the user backspaces past the start X * of the line, abort the command. X */ X if (cmd_char(c) || len_cmdbuf() == 0) X continue; X cbuf = get_cmdbuf(); X } else X { X /* X * Don't use cmd_char if we're starting fresh X * at the beginning of a command, because we X * don't want to echo the command until we know X * it is a multichar command. We also don't X * want erase_char/kill_char to be treated X * as line editing characters. X */ X tbuf[0] = c; X tbuf[1] = '\0'; X cbuf = tbuf; X } X s = NULL; X action = cmd_decode(cbuf, &s); X /* X * If an "extra" string was returned, X * process it as a string of command characters. X */ X if (s != NULL) X ungetsc(s); X /* X * Clear the cmdbuf string. X * (But not if we're in the prefix of a command, X * because the partial command string is kept there.) X */ X if (action != A_PREFIX) X cmd_reset(); X X switch (action) 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_WINDOW: X /* X * Forward one window (and set the window size). X */ X if (number > 0) X swindow = number; X /* FALLTHRU */ X case A_F_SCREEN: X /* X * Forward one screen. X */ X if (number <= 0) X number = swindow; X cmd_exec(); X forward(number, 0, 1); X break; X X case A_B_WINDOW: X /* X * Backward one window (and set the window size). X */ X if (number > 0) X swindow = number; X /* FALLTHRU */ X case A_B_SCREEN: X /* X * Backward one screen. X */ X if (number <= 0) X number = swindow; X cmd_exec(); X backward(number, 0, 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, 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, 0); X break; X X case A_FF_LINE: X /* X * Force forward N (default 1) line. X */ X if (number <= 0) X number = 1; X cmd_exec(); X forward(number, 1, 0); X break; X X case A_BF_LINE: X /* X * Force backward N (default 1) line. X */ X if (number <= 0) X number = 1; X cmd_exec(); X backward(number, 1, 0); X break; X X case A_F_FOREVER: X /* X * Forward forever, ignoring EOF. X */ X cmd_exec(); X ignore_eoi = 1; X while (sigs == 0) X forward(1, 0, 0); X ignore_eoi = 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, 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, 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 ch_flush(); X if (!ispipe) X clr_linenum(); 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_GOPOS: X /* X * Go to a specified byte position in the file. X */ X cmd_exec(); X if (number < 0) X number = 0; X jump_line_loc((POSITION)number, jump_sline); X break; X X case A_STAT: X /* X * Print file name, etc. X */ X cmd_exec(); X parg.p_string = eq_message(); X error("%s", &parg); X break; X X case A_VERSION: X /* X * Print version number, without the "@(#)". X */ X cmd_exec(); X parg.p_string = version+4; X error("%s", &parg); X break; X X case A_QUIT: X /* X * Exit. X */ X quit(0); X X case A_B_SEARCH: X search_type = SRCH_BACK; X goto do_search; X case A_F_SEARCH: X search_type = SRCH_FORW; X do_search: X /* X * Search for a pattern. X * Get the first char of the pattern. X */ X if (number <= 0) X number = 1; X search_mca(); X c = getcc(); X goto again; X X case A_T_REVERSE_SEARCH: X search_type |= SRCH_PAST_EOF; X /* FALLTHRU */ X X case A_REVERSE_SEARCH: X /* X * Repeat previous search, in reverse direction. X */ X c = SRCH_FLAG(search_type); X if (SRCH_DIR(search_type) == SRCH_BACK) X search_type = SRCH_FORW; X else X search_type = SRCH_BACK; X search_type |= c; X goto do_again_search; X X case A_T_AGAIN_SEARCH: X search_type |= SRCH_PAST_EOF; X goto do_again_search; X X case A_AGAIN_SEARCH: X /* X * Repeat previous search. X */ X do_again_search: X if (number <= 0) X number = 1; X search_mca(); X cmd_exec(); X multi_search((char *)NULL, number); X break; X X case A_HELP: X /* X * Help. X */ X if (nohelp) X { X bell(); X break; 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 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 (strcmp(get_filename(curr_ifile), "-") == 0) X { X error("Cannot edit standard input", NULL_PARG); X break; X } X /* X * Expand the editor prototype string X * and pass it to the system to execute. X */ X cmd_exec(); X lsystem(pr_expand(editproto, 0)); X /* X * Re-edit the file, since data may have changed. X * Some editors even recreate the file, so flushing X * buffers is not sufficient. X */ X (void) edit(get_filename(curr_ifile), 0); X break; X#else X error("Command not available", NULL_PARG); X break; X#endif X X case A_NEXT_FILE: X /* X * Examine next file. X */ X if (number <= 0) X number = 1; X if (edit_next(number)) X { X if (quit_at_eof && hit_eof) X quit(0); X parg.p_string = (number > 1) ? "(N-th) " : ""; X error("No %snext file", &parg); X } X break; X X case A_PREV_FILE: X /* X * Examine previous file. X */ X if (number <= 0) X number = 1; X if (edit_prev(number)) X { X parg.p_string = (number > 1) ? "(N-th) " : ""; X error("No %sprevious file", &parg); X } X break; X X case A_INDEX_FILE: X /* X * Examine a particular file. X */ X if (number <= 0) X number = 1; X if (edit_index(number)) X error("No such file", NULL_PARG); X break; X X case A_OPT_TOGGLE: X start_mca(A_OPT_TOGGLE, "-"); X optflag = OPT_TOGGLE; X c = getcc(); X goto again; X X case A_DISP_OPTION: X /* X * Report a flag setting. X */ X start_mca(A_DISP_OPTION, "_"); X c = getcc(); X if (c == erase_char || c == kill_char) X break; X toggle_option(c, "", OPT_NO_TOGGLE); X break; X X case A_FIRSTCMD: X /* X * Set an initial command for new files. X */ 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 start_mca(A_SHELL, "!"); X c = getcc(); X goto again; X#else X error("Command not available", NULL_PARG); X break; X#endif X X case A_SETMARK: X /* X * Set a mark. X */ X start_mca(A_SETMARK, "mark: "); X c = getcc(); X if (c == erase_char || c == kill_char || X c == '\n' || c == '\r') X break; X setmark(c); X break; X X case A_GOMARK: X /* X * Go to a mark. X */ X start_mca(A_GOMARK, "goto mark: "); X c = getcc(); X if (c == erase_char || c == kill_char || X c == '\n' || c == '\r') X break; X gomark(c); X break; X X#if PIPEC X case A_PIPE: X start_mca(A_PIPE, "|mark: "); X c = getcc(); X if (c == erase_char || c == kill_char) X break; X if (c == '\n' || c == '\r') X c = '.'; X if (badmark(c)) X break; X pipec = c; X start_mca(A_PIPE, "!"); X c = getcc(); X goto again; X#endif X X case A_B_BRACKET: X case A_F_BRACKET: X start_mca(action, "Brackets: "); X c = getcc(); X goto again; 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 { X start_mca(A_PREFIX, " "); X cmd_reset(); X (void) cmd_char(c); X } X c = getcc(); X goto again; X X case A_NOACTION: X break; X X default: X bell(); X break; X } X } X} END_OF_FILE echo shar: Extracting \"decode.c\" sed "s/^X//" >'decode.c' <<'END_OF_FILE' X/* X * Routines to decode user commands. X * X * This is all table driven. X * A command table is a sequence of command descriptors. X * Each command descriptor is a sequence of bytes with the following format: X * ...<0> X * The characters c1,c2,...,cN are the command string; that is, X * the characters which the user must type. X * It is terminated by a null <0> byte. X * The byte after the null byte is the action code associated X * with the command string. X * If an action byte is OR-ed with A_EXTRA, this indicates X * that the option byte is followed by an extra string. X * X * There may be many command tables. X * The first (default) table is built-in. X * Other tables are read in from "lesskey" files. X * All the tables are linked together and are searched in order. X */ X X#include "less.h" X#include "cmd.h" X#if __MSDOS__ X#include X#include X#endif X X/* X * Command table is ordered roughly according to expected X * frequency of use, so the common commands are near the beginning. X */ Xstatic char cmdtable[] = X{ X#if __MSDOS__ X /* X * PC function keys. X * Note that '\0' is converted to '\200' on input. X */ X '\200','\120',0, A_F_LINE, /* down arrow */ X '\200','\121',0, A_F_SCREEN, /* page down */ X '\200','\110',0, A_B_LINE, /* up arrow */ X '\200','\111',0, A_B_SCREEN, /* page up */ X '\200','\107',0, A_GOLINE, /* home */ X '\200','\117',0, A_GOEND, /* end */ X '\200','\073',0, A_HELP, /* F1 */ X '\200','\104',0, A_MODIFY_WINDOW, /* F10 */ X '\200','\103',0, A_MODIFY_COLOURS, /* F9 */ X#endif X '\r',0, A_F_LINE, X '\n',0, A_F_LINE, X 'e',0, A_F_LINE, X 'j',0, A_F_LINE, X CONTROL('E'),0, A_F_LINE, X CONTROL('N'),0, A_F_LINE, X 'k',0, A_B_LINE, X 'y',0, A_B_LINE, X CONTROL('Y'),0, A_B_LINE, X CONTROL('K'),0, A_B_LINE, X CONTROL('P'),0, A_B_LINE, X 'J',0, A_FF_LINE, X 'K',0, A_BF_LINE, X 'Y',0, A_BF_LINE, X 'd',0, A_F_SCROLL, X CONTROL('D'),0, A_F_SCROLL, X 'u',0, A_B_SCROLL, X CONTROL('U'),0, A_B_SCROLL, X ' ',0, A_F_SCREEN, X 'f',0, A_F_SCREEN, X CONTROL('F'),0, A_F_SCREEN, X CONTROL('V'),0, A_F_SCREEN, X 'b',0, A_B_SCREEN, X CONTROL('B'),0, A_B_SCREEN, X ESC,'v',0, A_B_SCREEN, X 'z',0, A_F_WINDOW, X 'w',0, A_B_WINDOW, X 'F',0, A_F_FOREVER, X 'R',0, A_FREPAINT, X 'r',0, A_REPAINT, X CONTROL('R'),0, A_REPAINT, X CONTROL('L'),0, A_REPAINT, X 'g',0, A_GOLINE, X '<',0, A_GOLINE, X ESC,'<',0, A_GOLINE, X 'p',0, A_PERCENT, X '%',0, A_PERCENT, X '{',0, A_F_BRACKET|A_EXTRA, '{','}',0, X '}',0, A_B_BRACKET|A_EXTRA, '{','}',0, X '(',0, A_F_BRACKET|A_EXTRA, '(',')',0, X ')',0, A_B_BRACKET|A_EXTRA, '(',')',0, X '[',0, A_F_BRACKET|A_EXTRA, '[',']',0, X ']',0, A_B_BRACKET|A_EXTRA, '[',']',0, X ESC,CONTROL('F'),0, A_F_BRACKET, X ESC,CONTROL('B'),0, A_B_BRACKET, X 'G',0, A_GOEND, X ESC,'>',0, A_GOEND, X '>',0, A_GOEND, X 'P',0, A_GOPOS, X X '0',0, A_DIGIT, X '1',0, A_DIGIT, X '2',0, A_DIGIT, X '3',0, A_DIGIT, X '4',0, A_DIGIT, X '5',0, A_DIGIT, X '6',0, A_DIGIT, X '7',0, A_DIGIT, X '8',0, A_DIGIT, X '9',0, A_DIGIT, X X '=',0, A_STAT, X CONTROL('G'),0, A_STAT, X ':','f',0, A_STAT, X '/',0, A_F_SEARCH, X '?',0, A_B_SEARCH, X ESC,'/',0, A_F_SEARCH|A_EXTRA, '*',0, X ESC,'?',0, A_B_SEARCH|A_EXTRA, '*',0, X 'n',0, A_AGAIN_SEARCH, X ESC,'n',0, A_T_AGAIN_SEARCH, X 'N',0, A_REVERSE_SEARCH, X ESC,'N',0, A_T_REVERSE_SEARCH, X 'm',0, A_SETMARK, X '\'',0, A_GOMARK, X CONTROL('X'),CONTROL('X'),0, A_GOMARK, X 'E',0, A_EXAMINE, X ':','e',0, A_EXAMINE, X CONTROL('X'),CONTROL('V'),0, A_EXAMINE, X ':','n',0, A_NEXT_FILE, X ':','p',0, A_PREV_FILE, X ':','x',0, A_INDEX_FILE, X '-',0, A_OPT_TOGGLE, X ':','t',0, A_OPT_TOGGLE|A_EXTRA, 't',0, X 's',0, A_OPT_TOGGLE|A_EXTRA, 'o',0, X '_',0, A_DISP_OPTION, X '|',0, A_PIPE, X 'v',0, A_VISUAL, X '!',0, A_SHELL, X '+',0, A_FIRSTCMD, X X 'H',0, A_HELP, X 'h',0, A_HELP, X 'V',0, A_VERSION, X 'q',0, A_QUIT, X ':','q',0, A_QUIT, X ':','Q',0, A_QUIT, X 'Z','Z',0, A_QUIT, X ESC,ESC,0, A_QUIT, X}; X X/* X * Structure to support a list of command tables. X */ Xstruct tablelist X{ X struct tablelist *t_next; X char *t_start; X char *t_end; X}; X X/* X * Structure for the default command table. X */ Xstatic struct tablelist deftable = X { NULL, cmdtable, cmdtable+sizeof(cmdtable) }; X X/* X * List of tables; initially contains only the default table. X */ Xstatic struct tablelist *tables = &deftable; X Xstatic int cmd_search(); X Xextern int erase_char, kill_char; X X/* X * Decode a command character and return the associated action. X * The "extra" string, if any, is returned in sp. X */ X public int Xcmd_decode(cmd, sp) X char *cmd; X char **sp; X{ X register struct tablelist *t; X register int action; X X /* X * Search thru all the command tables. X * Stop when we find an action which is not A_INVALID. X */ X for (t = tables; t != NULL; t = t->t_next) X { X action = cmd_search(cmd, t->t_start, t->t_end, sp); X if (action != A_INVALID) X break; X } X return (action); X} X X/* X * Search a command table for the current command string (in cmd). X */ X static int Xcmd_search(cmd, table, endtable, sp) X char *cmd; X char *table; X char *endtable; X char **sp; X{ X register char *p; X register char *q; X register int a; X X for (p = table, q = cmd; p < endtable; p++, q++) X { X if (*p == *q) X { X /* X * Current characters match. X * If we're at the end of the string, we've found it. X * Return the action code, which is the character X * after the null at the end of the string X * in the command table. X */ X if (*p == '\0') X { X a = *++p & 0377; X /* X * Check for an "extra" string. X */ X if (a & A_EXTRA) X { X *sp = ++p; X a &= ~A_EXTRA; X } else X *sp = NULL; X return (a); X } X } else if (*q == '\0') X { X /* X * Hit the end of the user's command, X * but not the end of the string in the command table. X * The user's command is incomplete. X */ X return (A_PREFIX); X } else X { X /* X * Not a match. X * Skip ahead to the next command in the X * command table, and reset the pointer X * to the beginning of the user's command. X */ X while (*p++ != '\0') ; X if (*p & A_EXTRA) X while (*++p != '\0') ; X q = cmd-1; X } X } X /* X * No match found in the entire command table. X */ X return (A_INVALID); X} X X#if USERFILE X/* X * Set up a user command table, based on a "lesskey" file. X */ X public int Xadd_cmdtable(filename) X char *filename; X{ X register struct tablelist *t; X register POSITION len; X register long n; X register int f; X X /* X * Try to open the lesskey file. X * If we can't, return an error. X */ X f = open(filename, 0); X if (f < 0) X return (-1); X X /* X * Read the file into the user table. X * We first figure out the size of the file and allocate space for it. X * {{ Minimal error checking is done here. X * A garbage .less file will produce strange results. X * To avoid a large amount of error checking code here, we X * rely on the lesskey program to generate a good .less file. }} X */ X len = filesize(f); X if (len == NULL_POSITION || len < 3) X { X /* X * Bad file (valid file must have at least 3 chars). X */ X close(f); X return (-1); X } X if ((t = (struct tablelist *) X calloc(1, sizeof(struct tablelist))) == NULL) X { X close(f); X return (-1); X } X if ((t->t_start = (char *) calloc(len, sizeof(char))) == NULL) X { X free((char *)t); X close(f); X return (-1); X } X if (lseek(f, (offset_t)0, 0) == BAD_LSEEK) X { X free(t->t_start); X free((char *)t); X close(f); X return (-1); X } X n = read(f, t->t_start, (unsigned int) len); X close(f); X X /* X * In a valid lesskey file, the last byte or X * the second to the last byte must be zero. X */ X if (n != len || (t->t_start[n-1] != '\0' && t->t_start[n-2] != '\0')) X { X free(t->t_start); X free((char *)t); X return (-1); X } X t->t_end = t->t_start + n; X X /* X * Link it into the list of tables. X */ X t->t_next = tables; X tables = t; X return (0); X} X X/* X * Try to add the lesskey file "$HOME/.less" X */ X public void Xadd_hometable() X{ X char *filename; X X#if __MSDOS__ X filename = homefile("_less"); X#else X filename = homefile(".less"); X#endif X if (filename == NULL) X return; X /* X * Ignore errors. X */ X (void) add_cmdtable(filename); X free(filename); X} X#endif END_OF_FILE echo shar: Extracting \"help.c\" sed "s/^X//" >'help.c' <<'END_OF_FILE' 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#include "less.h" X X#if __MSDOS__ X#include X#include X#include X#include Xextern int output_mode; X#endif X Xextern char *progname; X X public void Xhelp() X{ X char *helpfile; X char *cmd; X X helpfile = find_helpfile(); X if (helpfile == NULL) X { X error("Cannot find help file", NULL_PARG); X return; X } X#if __MSDOS__ X putenv("LESS=-+v -+E -+s -mHPmHELP -- ?eEND -- Press g to see " X "it again:Press RETURN for more., or q when done "); X cmd = (char *) ecalloc(strlen(helpfile) + strlen(progname) + 50, X sizeof(char)); X if (output_mode == 0) X sprintf(cmd, "-%s %s", progname, helpfile); X else X sprintf(cmd, "-%s -qVW4,4,76,23,Help %s", progname, helpfile); X#else X cmd = (char *) ecalloc(strlen(helpfile) + strlen(progname) + 150, X sizeof(char)); X sprintf(cmd, X "-%s -m -H -+E -+s '-PmHELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done ' %s", X progname, helpfile); X#endif X free(helpfile); X lsystem(cmd); X error("End of help", NULL_PARG); X free(cmd); X} END_OF_FILE echo shar: Extracting \"input.c\" sed "s/^X//" >'input.c' <<'END_OF_FILE' X/* X * High level routines dealing with getting lines of input X * from the file being viewed. X * X * When we speak of "lines" here, we mean PRINTABLE lines; X * lines processed with respect to the screen width. X * We use the term "raw line" to refer to lines simply X * delimited by newlines; not processed with respect to screen width. X */ X X#include "less.h" X Xextern int squeeze; Xextern int chopline; Xextern int sigs; X X/* X * Get the next line. X * A "current" position is passed and a "new" position is returned. X * The current position is the position of the first character of X * a line. The new position is the position of the first character X * of the NEXT line. The line obtained is the line starting at curr_pos. X */ X public POSITION Xforw_line(curr_pos) X POSITION curr_pos; X{ X POSITION new_pos; X register int c; X int blankline; X int endline; X X if (curr_pos == NULL_POSITION || ch_seek(curr_pos)) X { X null_line(); X return (NULL_POSITION); X } X X prewind(); X plinenum(curr_pos); X (void) ch_seek(curr_pos); X X c = ch_forw_get(); X if (c == EOI) X { X null_line(); X return (NULL_POSITION); X } X blankline = (c == '\n' || c == '\r'); X X for (;;) X { X if (sigs) X { X null_line(); X return (NULL_POSITION); X } X if (c == '\n' || c == EOI) X { X /* X * End of the line. X */ X new_pos = ch_tell(); X endline = 1; X break; X } X X /* X * Append the char to the line and get the next char. X */ X if (pappend(c)) X { X /* X * The char won't fit in the line; the line X * is too long to print in the screen width. X * End the line here. X */ X if (chopline) X { X do X { X c = ch_forw_get(); X } while (c != '\n' && c != EOI); X new_pos = ch_tell(); X endline = 1; X } else X { X new_pos = ch_tell() - 1; X endline = 0; X } X break; X } X c = ch_forw_get(); X } X pdone(endline); X X if (squeeze && blankline) X { X /* X * This line is blank. X * Skip down to the last contiguous blank line X * and pretend it is the one which we are returning. X */ X while ((c = ch_forw_get()) == '\n' || c == '\r') X if (sigs) X { X null_line(); X return (NULL_POSITION); X } X if (c != EOI) X (void) ch_back_get(); X new_pos = ch_tell(); X } X X return (new_pos); X} X X/* X * Get the previous line. X * A "current" position is passed and a "new" position is returned. X * The current position is the position of the first character of X * a line. The new position is the position of the first character X * of the PREVIOUS line. The line obtained is the one starting at new_pos. X */ X public POSITION Xback_line(curr_pos) X POSITION curr_pos; X{ X POSITION new_pos, begin_new_pos; X int c; X int endline; X X if (curr_pos == NULL_POSITION || curr_pos <= ch_zero() || X ch_seek(curr_pos-1)) X { X null_line(); X return (NULL_POSITION); X } X X if (squeeze) X { X /* X * Find out if the "current" line was blank. X */ X (void) ch_forw_get(); /* Skip the newline */ X c = ch_forw_get(); /* First char of "current" line */ X (void) ch_back_get(); /* Restore our position */ X (void) ch_back_get(); X X if (c == '\n') X { X /* X * The "current" line was blank. X * Skip over any preceding blank lines, X * since we skipped them in forw_line(). X */ X while ((c = ch_back_get()) == '\n' || c == '\r') X if (sigs) X { X null_line(); X return (NULL_POSITION); X } X if (c == EOI) X { X null_line(); X return (NULL_POSITION); X } X (void) ch_forw_get(); X } X } X X /* X * Scan backwards until we hit the beginning of the line. X */ X for (;;) X { X if (sigs) X { X null_line(); X return (NULL_POSITION); 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 = ch_tell(); X break; X } X } X X /* X * Now scan forwards from the beginning of this line. X * We keep discarding "printable lines" (based on screen width) X * until we reach the curr_pos. X * X * {{ This algorithm is pretty inefficient if the lines X * are much longer than the screen width, X * but I don't know of any better way. }} X */ X if (ch_seek(new_pos)) X { X null_line(); X return (NULL_POSITION); X } X endline = 0; X loop: X begin_new_pos = new_pos; X prewind(); X plinenum(new_pos); X (void) ch_seek(new_pos); X X do X { X c = ch_forw_get(); X if (c == EOI || sigs) X { X null_line(); X return (NULL_POSITION); X } X new_pos++; X if (c == '\n') X { X endline = 1; X break; X } X if (pappend(c)) X { X /* X * Got a full printable line, but we haven't X * reached our curr_pos yet. Discard the line X * and start a new one. X */ X if (chopline) X { X endline = 1; X break; X } X pdone(0); X (void) ch_back_get(); X new_pos--; X goto loop; X } X } while (new_pos < curr_pos); X X pdone(endline); X X return (begin_new_pos); X} END_OF_FILE