less (part 3 of 6)
Mark Nudelman
mark at unix386.Convergent.COM
Wed Mar 6 10:13:38 AEST 1991
#! /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 <whitespace> action [chars] <newline>
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> <action>
X * or
X * string <0> <action|A_EXTRA> chars <0>
X *
X * "string" is the command string.
X * "<0>" is one null byte.
X * "<action>" 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 <stdio.h>
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 * <c1><c2>...<cN><0><action>
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 <io.h>
X#include <stdlib.h>
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 <io.h>
X#include <dir.h>
X#include <string.h>
X#include <stdlib.h>
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
More information about the Alt.sources
mailing list