less beta release (part 3 of 5)

Mark Nudelman mark at unix386.Convergent.COM
Fri Sep 15 08:24:11 AEST 1989


#! /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 \"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 */
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;
X	long block;
X	int datasize;
X	unsigned char data[BUFSIZ];
X};
Xpublic int nbufs;
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 buf_anchor.
X */
X#define	END_OF_CHAIN	((struct buf *)&buf_anchor)
X#define	buf_head	buf_anchor.next
X#define	buf_tail	buf_anchor.prev
X
Xstatic struct {
X	struct buf *next, *prev;
X} buf_anchor = { END_OF_CHAIN, END_OF_CHAIN };
X
Xextern int seven_bit;
Xextern int ispipe;
Xextern int autobuf;
Xextern int cbufs;
Xextern int sigs;
X#if LOGFILE
Xextern int logfile;
X#endif
X
Xstatic int ch_addbuf();
X
X/*
X * Current position in file, as seen by external users of ch functions.
X * Stored as a block number and an offset into the block.
X */
Xstatic long ch_block;
Xstatic int ch_offset;
X
X/*
X * Real current position in file, as manipulated by system calls (lseek).
X */
Xstatic POSITION ch_fpos;
X
X/* 
X * Length of file.
X */
Xstatic POSITION ch_fsize;
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()   ((buf_head->block == ch_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 unsigned char *p;
X	POSITION pos;
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			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 (pos != ch_fpos)
X	{
X		/*
X		 * If input is a pipe, the data requested should 
X		 * be immediately after the last data read
X		 * (that is, there should be no need to seek).
X		 * If not, the 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");
X 			quit();
X 		}
X 		ch_fpos = pos;
X 	}
X
X	/*
X	 * Read the block.
X	 * If we read less than a full block, we just return the
X	 * partial block and pick up the rest next time.
X	 */
X	n = iread(file, &bp->data[bp->datasize], BUFSIZ - bp->datasize);
X	if (n == READ_INTR)
X		return (EOI);
X	if (n < 0)
X	{
X		error("read error");
X		quit();
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 read to end of file, set an EOI marker in 
X	 * the buffered data itself.
X	 * Then ensure the data is "clean": there are no 
X	 * extra EOI chars in the data.
X	 */
X	if (n == 0)
X	{
X		ch_fsize = pos;
X		bp->data[bp->datasize++] = EOI;
X	}
X
X	p = &bp->data[bp->datasize];
X	while (--n >= 0)
X	{
X		p--;
X		if (seven_bit)
X			*p &= 0177;
X		if (*p == EOI)
X			*p = '@';
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");
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	register int n;
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				n = bp->datasize;
X				if (bp->data[n-1] == EOI)
X					n--;
X				write(logfile, bp->data, n);
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
X	new_block = pos / BUFSIZ;
X	if (!ispipe || pos == ch_fpos || buffered(new_block))
X	{
X		/*
X		 * Set read pointer.
X		 */
X		ch_block = new_block;
X		ch_offset = pos % BUFSIZ;
X		return (0);
X	}
X	return (1);
X}
X
X/*
X * Seek to the end of the file.
X */
X	public int
Xch_end_seek()
X{
X	POSITION len;
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((POSITION)0) == 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	return (ch_fsize);
X}
X
X/*
X * Return the current position in the file.
X */
X	public POSITION
Xch_tell()
X{
X	return (ch_block * BUFSIZ + 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 && ++ch_offset >= BUFSIZ)
X	{
X		ch_offset = 0;
X		ch_block ++;
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	{
X		if (ch_block <= 0 || (ispipe && !buffered(ch_block-1)))
X		{
X			ch_offset = 0;
X			return (EOI);
X		}
X		ch_offset = BUFSIZ - 1;
X		ch_block--;
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 * keep==1 means keep the data in the current buffers;
X * otherwise discard the old data.
X */
X	public void
Xch_init(want_nbufs, keep)
X	int want_nbufs;
X	int keep;
X{
X	register struct buf *bp;
X	char message[80];
X
X	cbufs = nbufs;
X	if (nbufs < want_nbufs && ch_addbuf(want_nbufs - 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		sprintf(message, "Cannot allocate %d buffers",
X			want_nbufs - nbufs);
X		error(message);
X		if (nbufs == 0)
X			quit();
X		return;
X	}
X
X	if (keep)
X		return;
X
X	/*
X	 * We don't want to keep the old data,
X	 * so initialize all the buffers now.
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 = (ispipe) ? NULL_POSITION : filesize(file);
X
X	/*
X	 * Seek to the beginning of the file.
X	 */
X	if (!ispipe)
X		(void) lseek(file, (offset_t)0, 0);
X	ch_fpos = (POSITION) 0;
X 
X	(void) ch_seek((POSITION)0);
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	nbufs += nnew;
X	cbufs = nbufs;
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}
END_OF_FILE
echo shar: Extracting \"command.c\"
sed "s/^X//" >'command.c' <<'END_OF_FILE'
X/*
X * User-level command processor.
X */
X
X#include "less.h"
X#include "position.h"
X#include "cmd.h"
X
X#define	NO_MCA		0
X#define	MCA_DONE	1
X#define	MCA_MORE	2
X
Xextern int erase_char, kill_char;
Xextern int ispipe;
Xextern int sigs;
Xextern int quit_at_eof;
Xextern int hit_eof;
Xextern int sc_width;
Xextern int sc_height;
Xextern int swindow;
Xextern int curr_ac;
Xextern int ac;
Xextern int quitting;
Xextern int scroll;
Xextern int nohelp;
Xextern char *first_cmd;
Xextern char *every_first_cmd;
Xextern char version[];
Xextern POSITION initial_pos;
X#if EDITOR
Xextern char *editor;
Xextern char *editproto;
X#endif
Xextern int screen_trashed;	/* The screen has been overwritten */
X
Xstatic char cmdbuf[120];	/* Buffer for holding a multi-char command */
X#if SHELL_ESCAPE
Xstatic char *shellcmd = NULL;	/* For holding last shell command for "!!" */
X#endif
Xstatic char *cp;		/* Pointer into cmdbuf */
Xstatic int cmd_col;		/* Current column of the multi-char command */
Xstatic int mca;			/* The multicharacter command (action) */
Xstatic int search_type;		/* The previous type of search */
Xstatic int number;		/* The number typed by the user */
Xstatic char optchar;
X
X/*
X * Reset command buffer (to empty).
X */
X	static void
Xcmd_reset()
X{
X	cp = cmdbuf;
X}
X
X/*
X * Backspace in command buffer.
X */
X	static int
Xcmd_erase()
X{
X	if (cp == cmdbuf)
X		/*
X		 * Backspace past beginning of the string:
X		 * this usually means abort the command.
X		 */
X		return (1);
X
X	if (control_char(*--cp))
X	{
X		/*
X		 * Erase an extra character, for the carat.
X		 */
X		backspace();
X		cmd_col--;
X	}
X	backspace();
X	cmd_col--;
X	return (0);
X}
X
X/*
X * Set up the display to start a new multi-character command.
X */
X	static void
Xstart_mca(action, prompt)
X	int action;
X	char *prompt;
X{
X	lower_left();
X	clear_eol();
X	putstr(prompt);
X	/*
X	 * {{ Assumes that the prompt contains only simple printable
X	 *    characters, so can use strlen() to get the column. }}
X	 */
X	cmd_col = strlen(prompt);
X	mca = action;
X}
X
X/*
X * Set up the display to start a new search command.
X */
X	static void
Xsearch_mca()
X{
X	switch (search_type)
X	{
X	case SRCH_FORW:
X		start_mca(A_F_SEARCH, "/");
X		break;
X	case SRCH_BACK:
X		start_mca(A_B_SEARCH, "?");
X		break;
X	case SRCH_FILE:
X		start_mca(A_T_SEARCH, "@/");
X		break;
X	case SRCH_FORW|SRCH_NOMATCH:
X		start_mca(A_F_SEARCH, "! /");
X		break;
X	case SRCH_BACK|SRCH_NOMATCH:
X		start_mca(A_B_SEARCH, "! ?");
X		break;
X	case SRCH_FILE|SRCH_NOMATCH:
X		start_mca(A_T_SEARCH, "! @/");
X		break;
X	}
X}
X
X/*
X * Process a single character of a multi-character command, such as
X * a number, or the pattern of a search command.
X */
X	static int
Xcmd_char(c)
X	int c;
X{
X	if (c == erase_char)
X	{
X		if (cmd_erase())
X			return (1);
X	} else if (c == kill_char)
X	{
X		/* {{ Could do this faster, but who cares? }} */
X		while (cmd_erase() == 0)
X			;
X	} else if (cp >= &cmdbuf[sizeof(cmdbuf)-1])
X	{
X		/*
X		 * No room in the command buffer.
X		 */
X		bell();
X	} else if (cmd_col >= sc_width-3)
X	{
X		/*
X		 * No room on the screen.
X		 * {{ Could get fancy here; maybe shift the displayed
X		 *    line and make room for more chars, like ksh. }}
X		 */
X		bell();
X	} else
X	{
X		/*
X		 * Append the character to the string.
X		 */
X		*cp++ = c;
X		if (control_char(c))
X		{
X			putchr('^');
X			cmd_col++;
X			c = carat_char(c);
X		}
X		putchr(c);
X		cmd_col++;
X	}
X	return (0);
X}
X
X/*
X * Return the number currently in the command buffer.
X */
X	static int
Xcmd_int()
X{
X	*cp = '\0';
X	cp = cmdbuf;
X	return (atoi(cmdbuf));
X}
X
X/*
X * Move the cursor to lower left before executing a command.
X * This looks nicer if the command takes a long time before
X * updating the screen.
X */
X	static void
Xcmd_exec()
X{
X	lower_left();
X	flush();
X}
X
X/*
X * Display the appropriate prompt.
X */
X	static void
Xprompt()
X{
X	register char *p;
X
X	if (first_cmd != NULL && *first_cmd != '\0')
X	{
X		/*
X		 * No prompt necessary if commands are from first_cmd
X		 * rather than from the user.
X		 */
X		return;
X	}
X
X	/*
X	 * If nothing is displayed yet, display starting from initial_pos.
X	 */
X	if (position(TOP) == NULL_POSITION)
X		jump_loc(initial_pos);
X	else if (screen_trashed)
X		repaint();
X
X	/*
X	 * If the -E flag is set and we've hit EOF on the last file, quit.
X	 */
X	if (quit_at_eof == 2 && hit_eof && curr_ac + 1 >= ac)
X		quit();
X
X	/*
X	 * Select the proper prompt and display it.
X	 */
X	lower_left();
X	clear_eol();
X	p = pr_string();
X	lower_left();
X	clear_eol();
X	if (p == NULL)
X		putchr(':');
X	else
X	{
X		so_enter();
X		putstr(p);
X		so_exit();
X	}
X}
X
X/*
X * Get command character.
X * The character normally comes from the keyboard,
X * but may come from the "first_cmd" string.
X */
X	static int
Xgetcc()
X{
X	if (first_cmd == NULL)
X		return (getchr());
X
X	if (*first_cmd != '\0')
X		return (*first_cmd++);
X	/*
X	 * (*first_cmd == '\0') means we reached end of first_cmd input.
X	 */
X	first_cmd = NULL;
X	if (cp == cmdbuf || position(TOP) != NULL_POSITION)
X		return (getchr());
X	/*
X	 * Command is incomplete, so try to complete it.
X	 * There are only two cases:
X	 * 1. We have "/string" but no newline.  Add the \n.
X	 * 2. We have a number but no command.  Treat as #g.
X	 * (This is all pretty hokey.)
X	 */
X	if (mca != A_DIGIT)
X		/* Not a number; must be search string */
X		return ('\n'); 
X	else
X		/* A number; append a 'g' */
X		return ('g');
X}
X
X/*
X * Execute a multicharacter command.
X */
X	static void
Xexec_mca()
X{
X	register char *p;
X
X	*cp = '\0';
X	cmd_exec();
X	switch (mca)
X	{
X	case A_F_SEARCH:
X	case A_B_SEARCH:
X	case A_T_SEARCH:
X		search(search_type, cmdbuf, number);
X		/*
X		 * If this was a search from beginning of file, change
X		 * to a forward search, so that A_AGAIN_SEARCH will
X		 * search from where we are, not from beginning of file.
X		 */
X		if (SRCH_TYPE(search_type) == SRCH_FILE)
X			search_type = SRCH_FORW | SRCH_FLAG(search_type);
X		break;
X	case A_FIRSTCMD:
X		/*
X		 * Skip leading spaces or + signs in the string.
X		 */
X		for (p = cmdbuf;  *p == '+' || *p == ' ';  p++)
X			;
X		if (every_first_cmd != NULL)
X			free(every_first_cmd);
X		if (*p == '\0')
X			every_first_cmd = NULL;
X		else
X			every_first_cmd = save(p);
X		break;
X	case A_TOGGLE_OPTION:
X		toggle_option(optchar, cmdbuf, 1);
X		optchar = '\0';
X		break;
X	case A_EXAMINE:
X		/*
X		 * Ignore leading spaces and glob the filename.
X		 */
X		p = glob(skipsp(cmdbuf));
X		if (p == NULL)
X			break;
X		edit(p);
X		free(p);
X		break;
X#if SHELL_ESCAPE
X	case A_SHELL:
X		/*
X		 * !! just uses whatever is in shellcmd.
X		 * Otherwise, copy cmdbuf to shellcmd,
X		 * expanding any special characters ("%" or "#").
X		 */
X		if (*cmdbuf != '!')
X		{
X			if (shellcmd != NULL)
X				free(shellcmd);
X			shellcmd = fexpand(cmdbuf);
X			if (shellcmd == NULL)
X				break;
X		}
X
X		if (shellcmd == NULL)
X			lsystem("");
X		else
X			lsystem(shellcmd);
X		error("!done");
X		break;
X#endif
X	}
X}
X
X/*
X * Add a character to a multi-character command.
X */
X	static int
Xmca_char(c)
X	int c;
X{
X	char *p;
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		 */
X		return (NO_MCA);
X
X	case A_DIGIT:
X		/*
X		 * Entering digits of a number.
X		 * Terminated by a non-digit.
X		 */
X		if ((c < '0' || c > '9') &&
X			c != erase_char && c != kill_char)
X		{
X			/*
X			 * Not part of the number.
X			 * Treat as a normal command character.
X			 */
X			number = cmd_int();
X			mca = 0;
X			return (NO_MCA);
X		}
X		break;
X
X	case A_TOGGLE_OPTION:
X		/*
X		 * Special case for the TOGGLE_OPTION command.
X		 * If the option letter which was entered is a
X		 * single-char option, execute the command immediately,
X		 * so user doesn't have to hit RETURN.
X		 */
X		if (optchar == '\0' && c != erase_char && c != kill_char)
X		{
X			optchar = c;
X			if (single_char_option(c))
X			{
X				toggle_option(c, "", 1);
X				return (MCA_DONE);
X			}
X			/*
X			 * Display an option-specific prompt.
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_TOGGLE_OPTION, p);
X			return (MCA_MORE);
X		}
X		break;
X
X	case A_F_SEARCH:
X	case A_B_SEARCH:
X	case A_T_SEARCH:
X		/*
X		 * Special case for search commands.
X		 * A ! as the first character of the pattern
X		 * means invert the sense of the search.
X		 * Toggle the NOMATCH flag and get a new
X		 * character for the start of the pattern.
X		 */
X		if (cp == cmdbuf && c == '!')
X		{
X			search_type ^= SRCH_NOMATCH;
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	 * Need another character.
X	 */
X	return (MCA_MORE);
X}
X
X/*
X * Main command processor.
X * Accept and execute commands until a quit command, then return.
X */
X	public void
Xcommands()
X{
X	register int c;
X	register int action;
X
X	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();
X		}
X			
X		/*
X		 * Display prompt and accept a character.
X		 */
X		cmd_reset();
X		prompt();
X		noprefix();
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		switch (action = cmd_decode(c))
X		{
X		case A_DIGIT:
X			/*
X			 * First digit of a number.
X			 */
X			start_mca(A_DIGIT, ":");
X			goto again;
X
X		case A_F_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, 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, 1);
X			break;
X
X		case A_F_LINE:
X			/*
X			 * Forward N (default 1) line.
X			 */
X			if (number <= 0)
X				number = 1;
X			cmd_exec();
X			forward(number, 0);
X			break;
X
X		case A_B_LINE:
X			/*
X			 * Backward N (default 1) line.
X			 */
X			if (number <= 0)
X				number = 1;
X			cmd_exec();
X			backward(number, 0);
X			break;
X
X		case A_F_SCROLL:
X			/*
X			 * Forward N lines 
X			 * (default same as last 'd' or 'u' command).
X			 */
X			if (number > 0)
X				scroll = number;
X			cmd_exec();
X			forward(scroll, 0);
X			break;
X
X		case A_B_SCROLL:
X			/*
X			 * Forward N lines 
X			 * (default same as last 'd' or 'u' command).
X			 */
X			if (number > 0)
X				scroll = number;
X			cmd_exec();
X			backward(scroll, 0);
X			break;
X
X		case A_FREPAINT:
X			/*
X			 * Flush buffers, then repaint screen.
X			 * Don't flush the buffers on a pipe!
X			 */
X			if (!ispipe)
X			{
X				ch_init(0, 0);
X				clr_linenum();
X			}
X			/* FALLTHRU */
X		case A_REPAINT:
X			/*
X			 * Repaint screen.
X			 */
X			cmd_exec();
X			repaint();
X			break;
X
X		case A_GOLINE:
X			/*
X			 * Go to line N, default beginning of file.
X			 */
X			if (number <= 0)
X				number = 1;
X			cmd_exec();
X			jump_back(number);
X			break;
X
X		case A_PERCENT:
X			/*
X			 * Go to a specified percentage into the file.
X			 */
X			if (number < 0)
X				number = 0;
X			if (number > 100)
X				number = 100;
X			cmd_exec();
X			jump_percent(number);
X			break;
X
X		case A_GOEND:
X			/*
X			 * Go to line N, default end of file.
X			 */
X			cmd_exec();
X			if (number <= 0)
X				jump_forw();
X			else
X				jump_back(number);
X			break;
X
X		case A_STAT:
X			/*
X			 * Print file name, etc.
X			 */
X			cmd_exec();
X			error(eq_message());
X			break;
X			
X		case A_VERSION:
X			/*
X			 * Print version number, without the "@(#)".
X			 */
X			cmd_exec();
X			error(version+4);
X			break;
X
X		case A_QUIT:
X			/*
X			 * Exit.
X			 */
X			quit();
X
X		case A_T_SEARCH:
X			search_type = SRCH_FILE;
X			goto do_search;
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_REVERSE_SEARCH:
X			/*
X			 * Repeat previous search, in reverse direction.
X			 */
X			c = SRCH_FLAG(search_type);
X			if (SRCH_TYPE(search_type) == SRCH_BACK)
X				search_type = SRCH_FORW;
X			else
X				search_type = SRCH_BACK;
X			search_type |= c;
X			/*FALLTHRU*/
X		case A_AGAIN_SEARCH:
X			/*
X			 * Repeat previous search.
X			 */
X			if (number <= 0)
X				number = 1;
X			search_mca();
X			cmd_exec();
X			search(search_type, (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			cmd_reset();
X			start_mca(A_EXAMINE, "Examine: ");
X			c = getcc();
X			goto again;
X			
X		case A_VISUAL:
X			/*
X			 * Invoke an editor on the input file.
X			 */
X#if EDITOR
X		{
X			if (ispipe)
X			{
X				error("Cannot edit standard input");
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			 * Throw away any buffered data, 
X			 * since the file was edited.
X			 */
X			ch_init(0, 0);
X			clr_linenum();
X			break;
X		}
X#else
X			error("Command not available");
X			break;
X#endif
X
X		case A_NEXT_FILE:
X			/*
X			 * Examine next file.
X			 */
X			if (number <= 0)
X				number = 1;
X			next_file(number);
X			break;
X
X		case A_PREV_FILE:
X			/*
X			 * Examine previous file.
X			 */
X			if (number <= 0)
X				number = 1;
X			prev_file(number);
X			break;
X
X		case A_TOGGLE_OPTION:
X			/*
X			 * Toggle a flag setting.
X			 */
X			cmd_reset();
X			start_mca(A_TOGGLE_OPTION, "-");
X			c = getcc();
X			goto again;
X
X		case A_DISP_OPTION:
X			/*
X			 * Report a flag setting.
X			 */
X			cmd_reset();
X			start_mca(A_DISP_OPTION, "_");
X			c = getcc();
X			if (c == erase_char || c == kill_char)
X				break;
X			toggle_option(c, "", 0);
X			break;
X
X		case A_FIRSTCMD:
X			/*
X			 * Set an initial command for new files.
X			 */
X			cmd_reset();
X			start_mca(A_FIRSTCMD, "+");
X			c = getcc();
X			goto again;
X
X		case A_SHELL:
X			/*
X			 * Shell escape.
X			 */
X#if SHELL_ESCAPE
X			cmd_reset();
X			start_mca(A_SHELL, "!");
X			c = getcc();
X			goto again;
X#else
X			error("Command not available");
X			break;
X#endif
X
X		case A_SETMARK:
X			/*
X			 * Set a mark.
X			 */
X			start_mca(A_SETMARK, "mark: ");
X			c = getcc();
X			if (c == erase_char || c == kill_char)
X				break;
X			setmark(c);
X			break;
X
X		case A_GOMARK:
X			/*
X			 * Go to a mark.
X			 */
X			start_mca(A_GOMARK, "goto mark: ");
X			c = getcc();
X			if (c == erase_char || c == kill_char)
X				break;
X			gomark(c);
X			break;
X
X		case A_ANYBRAC:
X			/*
X			 * Find matching bracket.
X			 */
X	/* TEMP */
X	if (number > 0) 
X	{
X		error("% is now used for bracket matching; use p for percent");
X		break;
X	}
X	/* end TEMP */
X			cmd_exec();
X			match_brac('\0', 1);
X			break;
X
X		case A_BRACLP:
X			/*
X			 * Go to matching left parenthesis.
X			 */
X			cmd_exec();
X			match_brac('(', number);
X			break;
X
X		case A_BRACRP:
X			/*
X			 * Go to matching right parenthesis.
X			 */
X			cmd_exec();
X			match_brac(')', number);
X			break;
X
X		case A_BRACLS:
X			/*
X			 * Go to matching left square bracket.
X			 */
X			cmd_exec();
X			match_brac('[', number);
X			break;
X
X		case A_BRACRS:
X			/*
X			 * Go to matching right square bracket.
X			 */
X			cmd_exec();
X			match_brac(']', number);
X			break;
X
X		case A_BRACLC:
X			/*
X			 * Go to matching left curly bracket.
X			 */
X			cmd_exec();
X			match_brac('{', number);
X			break;
X
X		case A_BRACRC:
X			/*
X			 * Go to matching right curly bracket.
X			 */
X			cmd_exec();
X			match_brac('}', number);
X			break;
X
X		case A_PREFIX:
X			/*
X			 * The command is incomplete (more chars are needed).
X			 * Display the current char, so the user knows
X			 * what's going on, and get another character.
X			 */
X			if (mca != A_PREFIX)
X				start_mca(A_PREFIX, "* ");
X			if (c == '\33')
X			{
X				/*
X				 * ESCAPE is special: we print "ESC"
X				 * rather than "^[".  We don't have
X				 * to worry about backspacing over it,
X				 * because prefix chars are never erased.
X				 */
X				putstr("ESC");
X			} else if (control_char(c))
X			{
X				putchr('^');
X				putchr(carat_char(c));
X			} else
X				putchr(c);
X			c = getcc();
X			goto again;
X
X		default:
X			if (action & A_1_TOGGLE_OPTION)
X			{
X				cmd_reset();
X				start_mca(A_TOGGLE_OPTION, "-");
X				c = action & ~A_1_TOGGLE_OPTION;
X				goto again;
X			}
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 *
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
X#define	ESC	CONTROL('[')
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	'\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	'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	'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_ANYBRAC,
X	'{',0,				A_BRACLC,
X	'}',0,				A_BRACRC,
X	'(',0,				A_BRACLP,
X	')',0,				A_BRACRP,
X	'[',0,				A_BRACLS,
X	']',0,				A_BRACRS,
X	'G',0,				A_GOEND,
X	ESC,'>',0,			A_GOEND,
X	'>',0,				A_GOEND,
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_T_SEARCH,
X	'n',0,				A_AGAIN_SEARCH,
X	ESC,'n',0,			A_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	':','n',0,			A_NEXT_FILE,
X	':','p',0,			A_PREV_FILE,
X	'-',0,				A_TOGGLE_OPTION,
X	':','t','a',0,			A_1_TOGGLE_OPTION|('t'),
X	's',0,				A_1_TOGGLE_OPTION|('l'),
X	'_',0,				A_DISP_OPTION,
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};
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
X/*
X * Buffer to hold current command string.
X */
Xstatic char kbuf[MAX_CMDLEN+1];
Xstatic char *kp = kbuf;
X
Xstatic int cmd_search();
X
X/*
X * Decode a command character and return the associated action.
X */
X	public int
Xcmd_decode(c)
X	int c;
X{
X	register struct tablelist *t;
X	register int action;
X
X	/*
X	 * Append the new command character to the command string in kbuf.
X	 */
X	*kp++ = c;
X	*kp = '\0';
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(t->t_start, t->t_end);
X		if (action != A_INVALID)
X			break;
X	}
X
X	if (action != A_PREFIX)
X		/*
X		 * This is not a prefix character.
X		 */
X		noprefix();
X
X	return (action);
X}
X
X/*
X * Indicate that we're not in a prefix command
X * by resetting the command buffer pointer.
X */
X	public void
Xnoprefix()
X{
X	kp = kbuf;
X}
X
X/*
X * Search a command table for the current command string (in kbuf).
X */
X	static int
Xcmd_search(table, endtable)
X	char *table;
X	char *endtable;
X{
X	register char *p;
X	register char *q;
X
X	for (p = table, q = kbuf;  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				return (p[1] & 0377);
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 user's command.
X			 */
X			while (*p++ != '\0') ;
X			q = kbuf-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, len);
X	close(f);
X
X	if (n != len || t->t_start[n-2] != '\0')
X	{
X		/*
X		 * Several error cases are lumped together here:
X		 * - Cannot read user file (n < 0).
X		 * - User file is too short (a valid file must
X		 *   have at least 3 chars: one char command string,
X		 *   the terminating null byte, and the action byte).
X		 * - The final entry in the user file is bad (it
X		 *   doesn't have a null byte in the proper place).
X		 * Many other error cases are not caught, such as
X		 * invalid format in any except the last entry,
X		 * invalid action codes, command strings too long, etc.
X		 */
X		free(t->t_start);
X		free((char *)t);
X		return (-1);
X	}
X	t->t_end = t->t_start + n;
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 *homedir;
X	char *filename;
X	extern char *getenv();
X
X	homedir = getenv("HOME");
X	if (homedir == NULL)
X		return;
X	filename = (char *) calloc(strlen(homedir)+7, sizeof(char));
X	if (filename == NULL)
X		return;
X	sprintf(filename, "%s/.less", homedir);
X
X	/*
X	 * Ignore errors.
X	 */
X	(void) add_cmdtable(filename);
X
X	free(filename);
X}
X#endif
END_OF_FILE
echo shar: Extracting \"help.c\"
sed "s/^X//" >'help.c' <<'END_OF_FILE'
X#include  "less.h"
X
X/*
X * Display some help.
X * Just invoke another "less" to display the help file.
X *
X * {{ This makes this function very simple, and makes changing the
X *    help file very easy, but it may present difficulties on
X *    (non-Unix) systems which do not supply the "system()" function. }}
X */
X
X	public void
Xhelp()
X{
X	char cmd[sizeof(HELPFILE)+150];
X
X	sprintf(cmd, 
X	 "-less -m -H -+E -+s '-PmHELP -- ?eEND -- Press g to see it again:Press RETURN for more., or q when done ' %s",
X	 HELPFILE);
X	lsystem(cmd);
X	error("End of help");
X}
END_OF_FILE
echo shar: Extracting \"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 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
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');
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			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			new_pos = ch_tell() - 1;
X			break;
X		}
X		c = ch_forw_get();
X	}
X	(void) pappend('\0');
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')
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
X	if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 ||
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')
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    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			break;
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			(void) pappend('\0');
X			(void) ch_back_get();
X			new_pos--;
X			goto loop;
X		}
X	} while (new_pos < curr_pos);
X
X	(void) pappend('\0');
X
X	return (begin_new_pos);
X}
END_OF_FILE
echo shar: Extracting \"line.c\"
sed "s/^X//" >'line.c' <<'END_OF_FILE'
X/*
X * Routines to manipulate the "line buffer".
X * The line buffer holds a line of output as it is being built
X * in preparation for output to the screen.
X * We keep track of the PRINTABLE length of the line as it is being built.
X */
X
X#include "less.h"
X
Xstatic char linebuf[1024];	/* Buffer which holds the current output line */
Xstatic char xlinebuf[1024];	/* Extension of linebuf to hold high bits */
Xstatic int curr;		/* Index into linebuf */
Xstatic int column;		/* Printable length, accounting for
X				   backspaces, etc. */
Xstatic int is_null_line;	/* */
X
X/*
X * A ridiculously complex state machine takes care of backspaces 
X * when in BS_SPECIAL mode.  The complexity arises from the attempt
X * to deal with all cases, especially involving long lines with underlining,
X * boldfacing or whatever.  There are still some cases which will break it.
X *
X * There are four states:
X *	LN_NORMAL is the normal state (not in underline mode).
X *	LN_UNDERLINE means we are in underline mode.  We expect to get
X *		either a sequence like "_\bX" or "X\b_" to continue
X *		underline mode, or anything else to end underline mode.
X *	LN_BOLDFACE means we are in boldface mode.  We expect to get sequences
X *		like "X\bX\b...X\bX" to continue boldface mode, or anything
X *		else to end boldface mode.
X *	LN_UL_X means we are one character after LN_UNDERLINE
X *		(we have gotten the '_' in "_\bX" or the 'X' in "X\b_").
X *	LN_UL_XB means we are one character after LN_UL_X 
X *		(we have gotten the backspace in "_\bX" or "X\b_";
X *		we expect one more ordinary character, 
X *		which will put us back in state LN_UNDERLINE).
X *	LN_BO_X means we are one character after LN_BOLDFACE
X *		(we have gotten the 'X' in "X\bX").
X *	LN_BO_XB means we are one character after LN_BO_X
X *		(we have gotten the backspace in "X\bX";
X *		we expect one more 'X' which will put us back
X *		in LN_BOLDFACE).
X */
Xstatic int ln_state;		/* Currently in normal/underline/bold/etc mode? */
X#define	LN_NORMAL	0	/* Not in underline, boldface or whatever mode */
X#define	LN_UNDERLINE	1	/* In underline, need next char */
X#define	LN_UL_X		2	/* In underline, got char, need \b */
X#define	LN_UL_XB	3	/* In underline, got char & \b, need one more */
X#define	LN_BOLDFACE	4	/* In boldface, need next char */
X#define	LN_BO_X		5	/* In boldface, got char, need \b */
X#define	LN_BO_XB	6	/* In boldface, got char & \b, need same char */
X
Xextern int bs_mode;
Xextern int tabstop;
Xextern int linenums;
Xextern int sendctl;
Xextern int twiddle;
Xextern int auto_wrap, ignaw;
Xextern int bo_width, be_width;
Xextern int ul_width, ue_width;
Xextern int sc_width, sc_height;
X
X/*
X * Macros to manipulate the characters in the line buffer.
X * We encapsulate the manipulation here because of the complexity
X * of the "extended characters".  Each character is stored with
X * its low-order 8 bits in linebuf and the higher-order bits in
X * xlinebuf.  Normal characters are only 8 bits, but special
X * marker characters like BE_CHAR, etc. are wider than 8 bits.
X * In addition, the CARATBIT bit may be or-ed into any character
X * to cause it to be prefixed with a carat when printed.
X *
X *	STOC		Store a character near the current char position.
X *	MOVC		Move a character from one position to another.
X *	INC		Increment (or decrement) the current char position.
X *	READC		Read a character from near the current char position.
X *	SPACEUSED 	Returns the amount of space used in the buffer.
X *	SPACELEFT	Returns the amount of space remaining in the buffer.
X */
X#define	STOC(i,c)		{ linebuf[curr+(i)] = (c); \
X				  xlinebuf[curr+(i)] = ((c)>>8); }
X#define	MOVC(i,j)		{ linebuf[curr+(i)] = linebuf[curr+(j)]; \
X				  xlinebuf[curr+(i)] = xlinebuf[curr+(j)]; }
X#define	INC(i)			{ curr += (i); }
X#define	READC(i)		((linebuf[curr+(i)]&0377) | ((xlinebuf[curr+(i)]&0377)<<8))
X#define	SPACEUSED		(curr)
X#define	SPACELEFT		(sizeof(linebuf) - curr)
X
X
X/*
X * Rewind the line buffer.
X */
X	public void
Xprewind()
X{
X	curr = 0;
X	column = 0;
X	ln_state = LN_NORMAL;
X	is_null_line = 0;
X}
X
X/*
X * Insert the line number (of the given position) into the line buffer.
X */
X	public void
Xplinenum(pos)
X	POSITION pos;
X{
X	register int lno;
X	register int i;
X	register int n;
X	char buf[10];
X
X	/*
X	 * We display the line number at the start of each line
X	 * only if the -N option is set.
X	 */
X	if (linenums != 2)
X		return;
X
X	/*
X	 * Get the line number and put it in the current line.
X	 * {{ Note: since find_linenum calls forw_raw_line,
X	 *    it may seek in the input file, requiring the caller 
X	 *    of plinenum to re-seek if necessary. }}
X	 */
X	lno = find_linenum(pos);
X
X	sprintf(buf, "%6d", lno);
X	n = strlen(buf);
X	column += n;
X	for (i = 0;  i < n;  i++)
X	{
X		STOC(0, buf[i]);
X		INC(1);
X	}
X
X	/*
X	 * Append enough spaces to bring us to the next tab stop.
X	 * {{ We could avoid this at the cost of adding some
X	 *    complication to the tab stop logic in pappend(). }}
X	 */
X	do
X	{
X		STOC(0, ' ');
X		INC(1);
X		column++;
X	} while (column % tabstop);
X}
X
X/*
X * Append a character to the line buffer.
X * Expand tabs into spaces, handle underlining, boldfacing, etc.
X * Returns 0 if ok, 1 if couldn't fit in buffer.
X */
X
X#define	NEW_COLUMN(newcol)	if ((newcol) + ((ln_state)?ue_width:0) > sc_width) \
X					return (1); else column = (newcol)
X	public int
Xpappend(c)
X	int c;
X{
X	if (c == '\0')
X	{
X		/*
X		 * Terminate any special modes, if necessary.
X		 * Append a '\0' to the end of the line.
X		 */
X		switch (ln_state)
X		{
X		case LN_UL_X:
X			MOVC(0, -1);
X			STOC(-1, UE_CHAR);
X			INC(1);
X			break;
X		case LN_BO_X:
X			MOVC(0, -1);
X			STOC(-1, BE_CHAR);
X			INC(1);
X			break;
X		case LN_UL_XB:
X		case LN_UNDERLINE:
X			STOC(0, UE_CHAR);
X			INC(1);
X			break;
X		case LN_BO_XB:
X		case LN_BOLDFACE:
X			STOC(0, BE_CHAR);
X			INC(1);
X			break;
X		}
X		ln_state = LN_NORMAL;
X
X		if (column < sc_width || !auto_wrap || ignaw)
X		{
X			STOC(0, '\n');
X			INC(1);
X		}
X		STOC(0, '\0');
X		return (0);
X	}
X
X	if (SPACELEFT < 12)
X		/*
X		 * Almost out of room in the line buffer.
X		 * Don't take any chances.
X		 * {{ Linebuf is supposed to be big enough that this
X		 *    will never happen, but may need to be made 
X		 *    bigger for wide screens or lots of backspaces. }}
X		 */
X		return (1);
X
X	if (bs_mode == BS_SPECIAL)
X	{
X		/*
X		 * Advance the state machine.
X		 */
X		switch (ln_state)
X		{
X		case LN_NORMAL:
X			if (SPACEUSED <= 1 || READC(-1) != '\b')
X				break;
X
X			if (c == READC(-2))
X				goto enter_boldface;
X			if (c == '_' || READC(-2) == '_')
X				goto enter_underline;
X			INC(-2);
X			break;
X
Xenter_boldface:
X			/*
X			 * We have "X\bX" (including the current char).
X			 * Switch into boldface mode.
X			 */
X			if (column + bo_width + be_width + 1 >= sc_width)
X				/*
X				 * Not enough room left on the screen to 
X				 * enter and exit boldface mode.
X				 */
X				return (1);
X
X			if (bo_width > 0 && SPACEUSED > 2 && READC(-3) == ' ')
X			{
X				/*
X				 * Special case for magic cookie terminals:
X				 * if the previous char was a space, replace 
X				 * it with the "enter boldface" sequence.
X				 */
X				STOC(-3, BO_CHAR);
X				column += bo_width-1;
X			} else
X			{
X				MOVC(-1, -2);
X				STOC(-2, BO_CHAR);
X				column += bo_width;
X				INC(1);
X			}
X			goto ln_bo_xb_case;
X
Xenter_underline:
X			/*
X			 * We have either "_\bX" or "X\b_" (including
X			 * the current char).  Switch into underline mode.
X			 */
X			if (column + ul_width + ue_width + 1 >= sc_width)
X				/*
X				 * Not enough room left on the screen to 
X				 * enter and exit underline mode.
X				 */
X				return (1);
X
X			if (ul_width > 0 && SPACEUSED > 2 && READC(-3) == ' ')
X			{
X				/*
X				 * Special case for magic cookie terminals:
X				 * if the previous char was a space, replace 
X				 * it with the "enter underline" sequence.
X				 */
X				STOC(-3, UL_CHAR);
X				column += ul_width-1;
X			} else
X			{
X				MOVC(-1, -2);
X				STOC(-2, UL_CHAR);
X				column += ul_width;
X				INC(1);
X			}
X			goto ln_ul_xb_case;
X			/*NOTREACHED*/
X		case LN_UL_XB:
X			/*
X			 * Termination of a sequence "_\bX" or "X\b_".
X			 */
X			if (c != '_' && READC(-2) != '_' && c == READC(-2))
X			{
X				/*
X				 * We seem to have run on from underlining
X				 * into boldfacing - this is a nasty fix, but
X				 * until this whole routine is rewritten as a
X				 * real DFA, ...  well ...
X				 */
X				MOVC(0, -2);
X				STOC(-2, UE_CHAR);
X				STOC(-1, BO_CHAR);
X				INC(2); /* char & non-existent backspace */
X				ln_state = LN_BO_XB;
X				goto ln_bo_xb_case;
X			}
Xln_ul_xb_case:
X			if (c == '_')
X				c = READC(-2);
X			INC(-2);
X			ln_state = LN_UNDERLINE;
X			break;
X		case LN_BO_XB:
X			/*
X			 * Termination of a sequence "X\bX".
X			 */
X			if (c != READC(-2) && (c == '_' || READC(-2) == '_'))
X			{
X				/*
X				 * We seem to have run on from
X				 * boldfacing into underlining.
X				 */
X				MOVC(0, -2);
X				STOC(-2, BE_CHAR);
X				STOC(-1, UL_CHAR);
X				INC(2); /* char & non-existent backspace */
X				ln_state = LN_UL_XB;
X				goto ln_ul_xb_case;
X			}
Xln_bo_xb_case:
X			INC(-2);
X			ln_state = LN_BOLDFACE;
X			break;
X		case LN_UNDERLINE:
X			if (column + ue_width + bo_width + 1 + be_width >= sc_width)
X				/*
X				 * We have just barely enough room to 
X				 * exit underline mode and handle a possible
X				 * underline/boldface run on mixup.
X				 */
X				return (1);
X			ln_state = LN_UL_X;
X			break;
X		case LN_BOLDFACE:
X			if (c == '\b')
X			{
X				ln_state = LN_BO_XB;
X				break;
X			}
X			if (column + be_width + ul_width + 1 + ue_width >= sc_width)
X				/*
X				 * We have just barely enough room to 
X				 * exit underline mode and handle a possible
X				 * underline/boldface run on mixup.
X				 */
X				return (1);
X			ln_state = LN_BO_X;
X			break;
X		case LN_UL_X:
X			if (c == '\b')
X			{
X				ln_state = LN_UL_XB;
X			} else
X			{
X				/*
X				 * Exit underline mode.
X				 * We have to shuffle the chars a bit
X				 * to make this work.
X				 */
X				MOVC(0, -1);
X				STOC(-1, UE_CHAR);
X				column += ue_width;
X				if (ue_width > 0 && READC(0) == ' ')
X				{
X					/*
X					 * Another special case for magic
X					 * cookie terminals: if the next
X					 * char is a space, replace it
X					 * with the "exit underline" sequence.
X					 */
X					column--;
X				} else
X				{
X					INC(1);
X				}
X				ln_state = LN_NORMAL;
X			} 
X			break;
X		case LN_BO_X:
X			if (c == '\b')
X			{
X				ln_state = LN_BO_XB;
X			} else
X			{
X				/*
X				 * Exit boldface mode.
X				 * We have to shuffle the chars a bit
X				 * to make this work.
X				 */
X				MOVC(0, -1);
X				STOC(-1, BE_CHAR);
X				column += be_width;
X				if (be_width > 0 && READC(0) == ' ')
X				{
X					/*
X					 * Another special case for magic
X					 * cookie terminals: if the next
X					 * char is a space, replace it
X					 * with the "exit boldface" sequence.
X					 */
X					column--;
X				} else
X				{
X					INC(1);
X				}
X				ln_state = LN_NORMAL;
X			} 
X			break;
X		}
X	}
X	
X	if (c == '\t') 
X	{
X		/*
X		 * Expand a tab into spaces.
X		 */
X		do
X		{
X			STOC(0, ' ');
X			INC(1);
X			NEW_COLUMN(column+1);
X		} while ((column % tabstop) != 0);
X		return (0);
X	}
X
X	if (c == '\b')
X	{
X		if (bs_mode == BS_CONTROL)
X		{
X			/*
X			 * Treat backspace as a control char: output "^H".
X			 */
X			NEW_COLUMN(column+2);
X			STOC(0, ('H' | CARATBIT));
X			INC(1);
X		} else
X		{
X			/*
X			 * Output a real backspace.
X			 */
X			column--;
X			STOC(0, '\b');
X			INC(1);
X		}
X		return (0);
X	} 
X
X	if (control_char(c) && !sendctl)
X	{
X		/*
X		 * Put a "^X" into the buffer.
X		 * The CARATBIT bit is used to tell put_line() to prefix
X		 * the char with a ^.  We don't actually put the ^
X		 * in the buffer because we sometimes need to move
X		 * chars around, and such movement might separate 
X		 * the ^ from its following character.
X		 */
X		NEW_COLUMN(column+2);
X		STOC(0, (carat_char(c) | CARATBIT));
X		INC(1);
X		return (0);
X	}
X
X	/*
X	 * Ordinary character.  Just put it in the buffer.
X	 */
X	NEW_COLUMN(column+1);
X	STOC(0, c);
X	INC(1);
X	return (0);
X}
X
X/*
X * Get a character from the current line.
X */
X	public int
Xgline(i)
X	int i;
X{
X	if (is_null_line)
X	{
X		/*
X		 * If there is no current line, we pretend the line is
X		 * either "~" or "", depending on the "twiddle" flag.
X		 */
X		if (twiddle)
X			switch (i)
X			{
X			case 0:  return ('~');
X			case 1:  return ('\n');
X			}
X		return (0);
X	}
X
X	return ((linebuf[i]&0377) | ((xlinebuf[i]&0377)<<8));
X}
X
X/*
X * Indicate that there is no current line.
X */
X	public void
Xnull_line()
X{
X	is_null_line = 1;
X}
X
X/*
X * Analogous to forw_line(), but deals with "raw lines":
X * lines which are not split for screen width.
X * {{ This is supposed to be more efficient than forw_line(). }}
X */
X	public POSITION
Xforw_raw_line(curr_pos, linep)
X	POSITION curr_pos;
X	char **linep;
X{
X	register char *p;
X	register int c;
X	POSITION new_pos;
X
X	if (curr_pos == NULL_POSITION || ch_seek(curr_pos) ||
X		(c = ch_forw_get()) == EOI)
X		return (NULL_POSITION);
X
X	p = linebuf;
X
X	for (;;)
X	{
X		if (c == '\n' || c == EOI)
X		{
X			new_pos = ch_tell();
X			break;
X		}
X		if (p >= &linebuf[sizeof(linebuf)-1])
X		{
X			/*
X			 * Overflowed the input buffer.
X			 * Pretend the line ended here.
X			 * {{ The line buffer is supposed to be big
X			 *    enough that this never happens. }}
X			 */
X			new_pos = ch_tell() - 1;
X			break;
X		}
X		*p++ = c;
X		c = ch_forw_get();
X	}
X	*p = '\0';
X	if (linep != NULL)
X		*linep = linebuf;
X	return (new_pos);
X}
X
X/*
X * Analogous to back_line(), but deals with "raw lines".
X * {{ This is supposed to be more efficient than back_line(). }}
X */
X	public POSITION
Xback_raw_line(curr_pos, linep)
X	POSITION curr_pos;
X	char **linep;
X{
X	register char *p;
X	register int c;
X	POSITION new_pos;
X
X	if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 ||
X		ch_seek(curr_pos-1))
X		return (NULL_POSITION);
X
X	p = &linebuf[sizeof(linebuf)];
X	*--p = '\0';
X
X	for (;;)
X	{
X		c = ch_back_get();
X		if (c == '\n')
X		{
X			/*
X			 * This is the newline ending the previous line.
X			 * We have hit the beginning of the line.
X			 */
X			new_pos = ch_tell() + 1;
X			break;
X		}
X		if (c == EOI)
X		{
X			/*
X			 * We have hit the beginning of the file.
X			 * This must be the first line in the file.
X			 * This must, of course, be the beginning of the line.
X			 */
X			new_pos = (POSITION)0;
X			break;
X		}
X		if (p <= linebuf)
X		{
X			/*
X			 * Overflowed the input buffer.
X			 * Pretend the line ended here.
X			 */
X			new_pos = ch_tell() + 1;
X			break;
X		}
X		*--p = c;
X	}
X	if (linep != NULL)
X		*linep = p;
X	return (new_pos);
X}
END_OF_FILE



More information about the Alt.sources mailing list