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