[net.sources.games] LESS for IBM PC

tomc@tekigm.UUCP (07/18/86)

------------------------------------------------------------------------
I got tired of the more utility for PC-DOS so I decided to rewrite the LESS
program so that I would run under PC-DOS. Below is the results. I hacked
the fix so please no comments on the style.

The compile the program you will need an Aztec C compiler (I have version 
3.20e). It uses specific function for screen control so if you want to use
the program with different compiler you will have to rewrite some of the code
in screen.c (This is where the terminal specific code is kept). The program
will compile with the large or small memory model however it will not make any
difference in the operation. The operation is the same as other version of LESS
so there is no manual in the distribution. If you do not have your own copy of
the manual remember the H key will give a list on all the commands.

Because I do not know how the shar a file I have combined the programs using
the program fpack that came accross the net not to long ago. The source for
fpack is included in the file and you can use VI to write it out and compile
it.

When you link the file use c.lib and s.lib.

I would be interested in any bugs and fixes that you come up with however I
probably will not support this version.

Have fun.
-------------------------- Cut here and run through FPACK -------------------
fpack:!@#$%^&*(): ch.c
/*
 * Low level character input from the input file.
 * We use these special purpose routines which optimize moving
 * both forward and backward from the current read pointer.
 */

#include "less.h"

public int file = -1;	/* File descriptor of the input file */

/*
 * Pool of buffers holding the most recently used blocks of the input file.
 */
#define BUFSIZ	1024
static struct buf {
	struct buf *next, *prev;
	long block;
	char data[BUFSIZ];
};
static struct buf *bufs = NULL;
public int nbufs;

/*
 * The buffer pool is kept as a doubly-linked circular list,
 * in order from most- to least-recently used.
 * The circular list is anchored by buf_anchor.
 */
static struct {
	struct buf *next, *prev;
} buf_anchor;
#define	END_OF_CHAIN	((struct buf *)&buf_anchor)
#define	buf_head	buf_anchor.next
#define	buf_tail	buf_anchor.prev

/*
 * If we fail to allocate enough memory for buffers, we try to limp
 * along with a minimum number of buffers.  
 */
#define	DEF_NBUFS	2	/* Minimum number of buffers */

extern int clean_data;
extern int pipe;

/*
 * Current position in file.
 * Stored as a block number and an offset into the block.
 */
static long ch_block = (long)0;
static int ch_offset = 0;

/*
 * Largest block number read if input is standard input (a pipe).
 */
static long last_piped_block;

/*
 * Get the character pointed to by the read pointer.
 * ch_get() is a macro which is more efficient to call
 * than fch_get (the function), in the usual case 
 * that the block desired is at the head of the chain.
 */
#define	ch_get()   ((buf_head->block == ch_block) ? \
			buf_head->data[ch_offset] : fch_get())
	static int
fch_get()
{
	register struct buf *bp;
	register int n;
	register int end;

	/*
	 * Look for a buffer holding the desired block.
	 */
	for (bp = buf_head;  bp != END_OF_CHAIN;  bp = bp->next)
		if (bp->block == ch_block)
			goto found;
	/*
	 * Block is not in a buffer.  
	 * Take the least recently used buffer 
	 * and read the desired block into it.
	 */
	bp = buf_tail;
	bp->block = ch_block;
	if (pipe)
	{
		/*
		 * The block requested should be one more than
		 * the last block read.
		 */
		if (ch_block != ++last_piped_block)
		{
			/* This "should not happen". */
			char message[80];
			sprintf(message, "Pipe error: last %ld, want %ld\n",
				last_piped_block-1, ch_block);
			error(message);
			quit();
		}
	} else
		lseek(file, ch_block * BUFSIZ, 0);

	/*
	 * Read the block.  This may take several reads if the input
	 * is coming from standard input, due to the nature of pipes.
	 */
	end = 0;
	while ((n = read(file, &bp->data[end], BUFSIZ-end)) > 0)
		if ((end += n) >= BUFSIZ)
			break;

	if (n < 0)
	{
		error("read error");
		quit();
	}

	/*
	 * Set an EOF marker in the buffered data itself.
	 * Then ensure the data is "clean": there are no 
	 * extra EOF chars in the data and that the "meta"
	 * bit (the 0200 bit) is reset in each char.
	 */
	if (end < BUFSIZ)
		bp->data[end] = EOF;

	if (!clean_data)
		while (--end >= 0)
		{
			bp->data[end] &= 0177;
			if (bp->data[end] == EOF)
				bp->data[end] = '@';
		}

    found:
	/* if (buf_head != bp) {this is guaranteed by the ch_get macro} */
	{
		/*
		 * Move the buffer to the head of the buffer chain.
		 * This orders the buffer chain, most- to least-recently used.
		 */
		bp->next->prev = bp->prev;
		bp->prev->next = bp->next;

		bp->next = buf_head;
		bp->prev = END_OF_CHAIN;
		buf_head->prev = bp;
		buf_head = bp;
	}
	return (bp->data[ch_offset]);
}

/*
 * Determine if a specific block is currently in one of the buffers.
 */
	static int
buffered(block)
	long block;
{
	register struct buf *bp;

	for (bp = buf_head;  bp != END_OF_CHAIN;  bp = bp->next)
		if (bp->block == block)
			return (1);
	return (0);
}

/*
 * Seek to a specified position in the file.
 * Return 0 if successful, non-zero if can't seek there.
 */
	public int
ch_seek(pos)
	register POSITION pos;
{
	long new_block;

	new_block = pos / BUFSIZ;
	if (!pipe || new_block == last_piped_block + 1 || buffered(new_block))
	{
		/*
		 * Set read pointer.
		 */
		ch_block = new_block;
		ch_offset = pos % BUFSIZ;
		return (0);
	}
	return (1);
}

/*
 * Seek to the end of the file.
 */
	public int
ch_end_seek()
{
	if (pipe)
	{
		/*
		 * Do it the slow way: read till end of data.
		 */
		while (ch_forw_get() != EOF)
			;
	} else
	{
	/*	(void) ch_seek((POSITION)(lseek(file, (off_t)0, 2))); remove cast */
		(void) ch_seek((POSITION)(lseek(file, (long)0, 2)));
	}
	return (0);
}

/*
 * Return the length of the file, if known.
 */
	public POSITION
ch_length()
{
	if (pipe)
		{
		return (NULL_POSITION);
		}
	/* return ((POSITION)(lseek(file, (off_t)0, 2))); remove cats */
	return ((POSITION)(lseek(file, (long)0, 2)));
}

/*
 * Return the current position in the file.
 */
	public POSITION
ch_tell()
{
	return (ch_block * BUFSIZ + ch_offset);
}

/*
 * Get the current char and post-increment the read pointer.
 */
	public int
ch_forw_get()
{
	register int c;

	c = ch_get();
	if (c != EOF && ++ch_offset >= BUFSIZ)
	{
		ch_offset = 0;
		ch_block ++;
	}
	return (c);
}

/*
 * Pre-decrement the read pointer and get the new current char.
 */
	public int
ch_back_get()
{
	register int c;

	if (--ch_offset < 0)
	{
		if (ch_block <= 0 || (pipe && !buffered(ch_block-1)))
		{
			ch_offset = 0;
			return (EOF);
		}
		ch_offset = BUFSIZ - 1;
		ch_block--;
	}
	c = ch_get();
	return (c);
}

/*
 * Initialize the buffer pool to all empty.
 * Caller suggests that we use want_nbufs buffers.
 */
ch_init(want_nbufs)
	int want_nbufs;
{
	register struct buf *bp;
	char *calloc();

	if (nbufs < want_nbufs)
	{
		/*
		 * We don't have enough buffers.  
		 * Free what we have (if any) and allocate some new ones.
		 */
		if (bufs != NULL)
			free((char *)bufs);
		bufs = (struct buf *) calloc(want_nbufs, sizeof(struct buf));
		nbufs = want_nbufs;
		if (bufs == NULL)
		{
			/*
			 * Couldn't get that many.
			 * Try for a small default number of buffers.
			 */
			char message[80];
			sprintf(message,
			  "Cannot allocate %d buffers.  Using %d buffers.", 
			  nbufs, DEF_NBUFS);
			error(message);
			bufs = (struct buf *) calloc(DEF_NBUFS, sizeof(struct buf));
			nbufs = DEF_NBUFS;
			if (bufs == NULL)
			{
				/*
				 * Couldn't even get the smaller number of bufs.
				 * Something is wrong here, don't continue.
				 */
				sprintf(message, 
				"Cannot even allocate %d buffers!  Quitting.\n",
				  DEF_NBUFS);
				error(message);
				quit();
				/*NOTREACHED*/
			}
		}
	}

	/*
	 * Initialize the buffers to empty.
	 * Set up the circular list.
	 */
	for (bp = &bufs[0];  bp < &bufs[nbufs];  bp++)
	{
		bp->next = bp + 1;
		bp->prev = bp - 1;
		bp->block = (long)(-1);
	}
	bufs[0].prev = bufs[nbufs-1].next = END_OF_CHAIN;
	buf_head = &bufs[0];
	buf_tail = &bufs[nbufs-1];
	last_piped_block = -1;
	(void) ch_seek((POSITION)0);	/* ? */
}
fpack:!@#$%^&*(): command.c
/*
 * User-level command processor.
 */

#include "less.h"

extern int erase_char, kill_char;
extern int pr_type;
extern int sigs;
extern int pipe;
extern int quit_at_eof;
extern int hit_eof;
extern int sc_width, sc_height;
extern char *first_cmd;
extern char version[];
extern char current_file[];
extern char *editor;

static char cmdbuf[90];		/* Buffer for holding a multi-char command */
static char *cp;		/* Pointer into cmdbuf */
static int cmd_col;		/* Current column of the multi-char command */
static char mcc;		/* The multi-char command letter (e.g. '/') */

/*
 * Reset command buffer (to empty).
 */
cmd_reset()
{
	cp = cmdbuf;
}

/*
 * Backspace in command buffer.
 */
	static int
cmd_erase()
{
	if (cp == cmdbuf)
		/*
		 * Backspace past beginning of the string:
		 * this usually means abort the command.
		 */
		return (1);

	if (control_char(*--cp))
	{
		/*
		 * Erase an extra character, for the carat.
		 */
		backspace();
		cmd_col--;
	}
	backspace();
	cmd_col--;
	return (0);
}

/*
 * Set up the display to start a new multi-character command.
 */
start_mcc()
{
	lower_left();
	clear_eol();
	putcb(mcc);
	cmd_col = 1;
}

/*
 * Process a single character of a multi-character command, such as
 * a number, or the pattern of a search command.
 */
	static int
cmd_char(c)
	int c;
{
	if (c == erase_char)
	{
		if (cmd_erase())
			return (1);
	} else if (c == kill_char)
	{
		/* {{ Could do this faster, but who cares? }} */
		while (cmd_erase() == 0)
			;
	} else
	{
		/*
		 * Append the character to the string,
		 * if there is room in the buffer and on the screen.
		 */
		if (cp < &cmdbuf[sizeof(cmdbuf)-1] && cmd_col < sc_width-3)
		{
			*cp++ = c;
			if (control_char(c))
			{
				putcb('^');
				cmd_col++;
				c = carat_char(c);
			}
			putcb(c);
			cmd_col++;
		} else
			bell();
	}
	return (0);
}

/*
 * Return the number currently in the command buffer.
 */
	static int
cmd_int()
{
	*cp = '\0';
	return (atoi(cmdbuf));
}

/*
 * Move the cursor to lower left before executing a command.
 * This looks nicer if the command takes a long time before
 * updating the screen.
 */
cmd_exec()
{
	lower_left();
	flush();
}

/*
 * Display the appropriate prompt.
 */
prompt()
{
	register char *p;
	char *pr_string();

	if (first_cmd != NULL && *first_cmd != '\0')
		/*
		 * No prompt necessary if commands are from first_cmd
		 * rather than from the user.
		 */
		return;

	/*
	 * Select the proper prompt and display it.
	 */
	p = pr_string();
	if (p == NULL)
		putcb(':');
	else
	{
		so_enter();
		putsb(p);
		so_exit();
	}
}

/*
 * Get command character.
 * The character normally comes from the keyboard,
 * but may come from the "first_cmd" string.
 */
	static int
getcc()
{
	if (first_cmd == NULL)
		return (getc());

	if (*first_cmd == '\0')
	{
		/*
		 * Reached end of first_cmd input.
		 */
		first_cmd = NULL;
		if (cp > cmdbuf) 
		{
			/*
			 * Command is incomplete, so try to complete it.
			 * There are only two cases:
			 * 1. We have "/string" but no newline.  Add the \n.
			 * 2. We have a number but no command.  Treat as #g.
			 * (This is all pretty hokey.)
			 */
			if (mcc != ':')
				return ('\n'); 
			else
				return ('g');
		}
		return (getc());
	}
	return (*first_cmd++);
}

/*
 * Main command processor.
 * Accept and execute commands until a quit command, then return.
 */
commands()
{
	register int c;
	register int n;
	register int scroll = 10;
	register int last_mcc = 0;

	mcc = 0;

	for (;;)
	{
		/*
		 * Display prompt and accept a character.
		 */
		psignals();	/* See if any signals need processing */

		if (quit_at_eof && hit_eof > 1)
			/*
			 * After hitting end-of-file for the second time,
			 * automatically advance to the next file.
			 * If there are no more files, quit.
			 */
			next_file(1);

		cmd_reset();
		lower_left();
		clear_eol();
		prompt();
		c = getcc();

	again:
		if (sigs)
			continue;

		if (mcc)
		{
			/*
			 * We are in a multi-character command.  
			 * All chars until newline go into the command buffer.
			 * (Note that mcc == ':' is a speciverscase that
			 *  means a number is being entered.)
			 */
			if (mcc != ':' && (c == '\n' || c == '\r'))
			{
				/*
				 * Execute the command.
				 */
				*cp = '\0';
				cmd_exec();
				if (mcc == 'E')
				{
					char *p;
					/*
					 * Ignore leading spaces 
					 * in the filename.
					 */
					for (p = cmdbuf;  *p == ' ';  p++) ;
					edit(p);
#if SHELL_ESCAPE
				} else if (mcc == '!')
				{
					lower_left();
					putcb('\n');
					flush();
					raw_mode(0);
					system(cmdbuf);
					raw_mode(1);
					error("!done");
					first_cmd = "r";	/* Repaint */
#endif
				} else
					search(mcc, cmdbuf, n);
				mcc = 0;
			} else
			{
				if (mcc == ':' && (c < '0' || c > '9') &&
					c != erase_char && c != kill_char)
				{
					/*
					 * This is not part of the number
					 * we were entering.  Process
					 * it as a regular character.
					 */
					mcc = 0;
					goto again;
				}

				/*
				 * Append the char to the command buffer.
				 */
				if (cmd_char(c))
				{
					/* Abort the multi-char command. */
					mcc = 0;
					continue;
				}
				c = getcc();
				goto again;
			}
		} else switch (c)
		{
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			/*
			 * First digit of a number.
			 */
			mcc = ':';
			start_mcc();
			goto again;

		case 'f':
		case ' ':
			/*
			 * Forward one screen.
			 */
			forward(sc_height - 1);
			break;

		case 'b':
			/*
			 * Backward one screen.
			 */
			backward(sc_height - 1);
			break;

		case 'e':
		case 'j':
		case '\r':
		case '\n':
			/*
			 * Forward N (default 1) line.
			 */
			n = cmd_int();
			if (n <= 0)
				n = 1;
			forward(n);
			break;

		case 'y':
		case 'k':
			/*
			 * Backward N (default 1) line.
			 */
			n = cmd_int();
			if (n <= 0)
				n = 1;
			backward(n);
			break;

		case 'd':
			/*
			 * Forward N lines 
			 * (default same as last 'd' or 'u' command).
			 */
			n = cmd_int();
			if (n > 0)
				scroll = n;
			forward(scroll);
			break;

		case 'u':
			/*
			 * Forward N lines 
			 * (default same as last 'd' or 'u' command).
			 */
			n = cmd_int();
			if (n > 0)
				scroll = n;
			backward(scroll);
			break;

		case 'R':
			/*
			 * Flush buffers, then repaint screen.
			 */
			ch_init(0);
			/* Fall thru */
		case 'r':
			/*
			 * Repaint screen.
			 */
			repaint();
			break;

		case 'g':
			/*
			 * Go to line N, default beginning of file.
			 */
			n = cmd_int();
			if (n <= 0)
				n = 1;
			cmd_exec();
			jump_back(n);
			break;

		case 'p':
			/*
			 * Go to a specified percentage into the file.
			 */
			n = cmd_int();
			if (n < 0)
				n = 0;
			if (n > 100)
				n = 100;
			cmd_exec();
			jump_percent(n);
			break;

		case 'G':
			/*
			 * Go to line N, default end of file.
			 */
			n = cmd_int();
			cmd_exec();
			if (n <= 0)
				jump_forw();
			else
				jump_back(n);
			break;

		case '=':
			/*
			 * Print file name, etc.
			 */
			error(eq_message());
			break;
			
		case 'V':
			/*
			 * Print version number, without the "@(#)".
			 */
			error(version+4);
			break;

		case 'q':
			/*
			 * Exit.
			 */
			return;

		case '/':
		case '?':
			/*
			 * Search for a pattern.
			 * Accept chars of the pattern until \n.
			 */
			n = cmd_int();
			if (n <= 0)
				n = 1;
			mcc = last_mcc = c;
			start_mcc();
			c = getcc();
			goto again;

		case 'n':
			/*
			 * Repeat previous search.
			 */
			n = cmd_int();
			if (n <= 0)
				n = 1;
			mcc = last_mcc;
			start_mcc();
			cmd_exec();
			search(mcc, (char *)NULL, n);
			mcc = 0;
			break;

		case 'h':
			/*
			 * Help.
			 */
			help();
			repaint();
			break;

		case 'E':
			/*
			 * Edit a new file.  Get the filename.
			 */
			cmd_reset();
			mcc = 'E';
			start_mcc();
			putsb("dit: ");	/* This looks nicer */
			cmd_col += 5;
			c = getcc();
			goto again;
			
#if SHELL_ESCAPE
		case '!':
			/*
			 * Shell escape.
			 */
			cmd_reset();
			mcc = '!';
			start_mcc();
			c = getcc();
			goto again;
#endif

#if EDITOR
		case 'v':
			if (pipe)
			{
				error("Cannot edit standard input");
				break;
			}
			lower_left();
			clear_eol();
			flush();
			raw_mode(0);
			sprintf(cmdbuf, "%s %s", editor, current_file);
			system(cmdbuf);
			raw_mode(1);
			first_cmd = "R";
			break;
#endif

		case 'N':
			/*
			 * Examine next file.
			 */
			n = cmd_int();
			if (n <= 0)
				n = 1;
			next_file(n);
			break;

		case 'P':
			/*
			 * Examine previous file.
			 */
			n = cmd_int();
			if (n <= 0)
				n = 1;
			prev_file(n);
			break;

		case '-':
			/*
			 * Toggle a flag setting.
			 */
			mcc = '-';
			start_mcc();
			c = getcc();
			mcc = 0;
			if (c == erase_char || c == kill_char)
				/* Abort the "-" command. */
				break;
			toggle_option(c);
			break;

		default:
			bell();
			break;
		}
	}
}
fpack:!@#$%^&*(): fpack.c
/*MANUAL.TH FPACK 1WI "December 15, 1985" "Wang Institute" "Unix User's Manual"
*/

/*MANUAL.SH NAME
fpack \- pack and unpack ascii files with simple archiving scheme
*/

/*MANUAL.SH USAGE
.B fpack
[files]
*/

/*MANUAL.SH DESCRIPTION
.I fpack
is a simple plain text file archiving scheme to either
reduce the number of files or to package them together.
It is designed to be portable to systems between which
files may be transferred, such as between UNIX and MSDOS.
It can save space on systems that use disk blocks for files that occupy
a small part of a block.
One of the program's requirements is that it does not alter the format
of its input, so files like documents or human readable data files
are not converted to a special format.
.PP
Files are delimited by a special string at the start of a line:
.br
	fpack:!@#$%^&*():	<filename>
.br
*/

/*MANUAL.SH NOTES
.PP
Text outside file delimiters in an archive will be ignored.
.PP
If a file does not end with a newline character,
one will be silently added.
.PP
If a file to be unpacked exists,
then it will not be overwritten.
Instead, the contents of the file(s) being unpacked will be discarded.
*/

/*MANUAL.SH EXAMPLES
.nf
Pack up some C source files.
	fpack a.c b.c c.c > archive
Unpack all files.
	fpack < archive
.fi
*/

/*MANUAL.SH "SEE ALSO"
shar(1), sh(1) make a more flexible archiving scheme for UNIX.
*/

/*MANUAL.SH AUTHOR
Gary Perlman
*/


#include <stdio.h>

#ifndef lint
static	char	sccsid[] = "@(#)fpack.c 1.1 (WangInst) 12/15/85";
#endif

#define	MAGIC	"fpack:!@#$%^&*(): " /* default file delimiter */

#ifdef	VERBOSE
#define	blabber(fun,file) fprintf (stderr, "fpack: %s '%s'\n", fun, file)
#else
#define	blabber(fun,file)
#endif

typedef	int       Status;      /* return/exit status of functions */
#define	SUCCESS   ((Status) 0)
#define	FAILURE   ((Status) 1)

/*FUNCTION main */
main (argc, argv)
int 	argc;     /* argument count */
char	**argv;   /* argument vector */
	{
	Status	result = SUCCESS;

	if (argc == 1) /* no files, unpack stdin */
		result = funpack ();
	else
		{
		while (--argc)
			if (fpack (*++argv) == FAILURE)
				result = FAILURE;
		printf ("%s\n", MAGIC); /* end of files */
		}

	exit (result);
	}

/*FUNCTION fpack:	pack files for later extraction by funpack */
Status
fpack (file)
char	*file;
	{
	FILE	*ioptr;
	char	line[BUFSIZ];
	char	*ptr;
	
	if (ioptr = fopen (file, "r"))
		{
		blabber ("packing", file);
		printf ("%s%s%s\n", MAGIC, file);
		while (fgets (line, BUFSIZ, ioptr))
			fputs (line, stdout);
		for (ptr = line; *ptr; ptr++);
		if (ptr > line && *(ptr-1) != '\n') /* incomplete last line */
			putc ('\n', stdout);
		fclose (ioptr);
		return (SUCCESS);
		}
	fprintf (stderr, "fpack: Can't open '%s' for reading\n", file);
	return (FAILURE);
	}

/*FUNCTION funpack:	unpack and create files packed by fpack */
Status
funpack ()
	{
	FILE	*ioptr = NULL;
	char	line[BUFSIZ];
	int 	maglen = strlen (MAGIC);
	char	*ptr;

	while (gets (line))
		{
		if (!strncmp (MAGIC, line, maglen))
			{
			if (ioptr)
				{
				fclose (ioptr);
				ioptr = NULL;
				}
			ptr = line + maglen;
			if (*ptr == '\0') /* done */
				ioptr = NULL;
			else if (access (ptr, 4) == 0) /* file exists */
				{
				fprintf (stderr, "fpack: '%s' exists (not unpacked)\n", ptr);
				ioptr = NULL;
				}
			else if ((ioptr = fopen (ptr, "w")) == NULL)
				{
				fprintf (stderr, "fpack: Can't create '%s'\n", ptr);
				return (FAILURE);
				}
			if (ioptr != NULL)
				blabber ("unpacking", ptr);
			}
		else if (ioptr != NULL)
			{
			fputs (line, ioptr);
			putc ('\n', ioptr);
			}
		}
	return (SUCCESS);
	}

/*FUNCTION access:	determine if a file exists */
#ifdef MSDOS /* fake a version of the access(2) function */
access (file, mode)
char	*file;
int 	mode;     /* this is ignored in MSDOS version */
	{
	FILE	*ioptr;
	if (ioptr = fopen (file, "r"))
		fclose (ioptr);
	return (ioptr == NULL);
	}
#endif
fpack:!@#$%^&*(): funcs.h
/* define the function declarations for other than int returning functions */
extern char * eq_message ();
extern POSITION ch_length ();
extern POSITION ch_tell ();
extern POSITION position ();
extern POSITION forw_line ();
extern POSITION back_line ();
extern char * eq_message ();
extern char * pr_string ();
extern POSITION lseek();
fpack:!@#$%^&*(): help.c
#include  "less.h"

/*
 * Display some help.
 * Help is in two pages.
 */
help0()
{
	putsb("f, SPACE       Forward one screen.\n");
	putsb("b              Backward one screen.\n");
	putsb("e, j, CR    *  Forward N lines, default 1.\n");
	putsb("y, k        *  Backward N lines, default 1.\n");
	putsb("d           *  Forward N lines, default 10 or last N to d or u command.\n");
	putsb("u           *  Backward N lines, default 10 or last N to d or u command.\n");
	putsb("r              Repaint screen.\n");
	putsb("g           *  Go to line N, default 1.\n");
	putsb("G           *  Like g, but default is last line in file.\n");
	putsb("=              Print current file name\n");
	putsb("/pattern    *  Search forward for N-th occurence of pattern.\n");
	putsb("?pattern    *  Search backward for N-th occurence of pattern.\n");
	putsb("n           *  Repeat previous search (for N-th occurence).\n");
	putsb("q              Exit.\n");
	error("More help...");
}

help1()
{
	char message[100];
	extern char all_options[];

	putsb("R              Repaint screen, discarding buffered input.\n");
	putsb("p           *  Position to N percent into the file.\n");
	sprintf(message, 
	     "-X             Toggle a flag (X may be one of \"%s\").\n", 
				all_options);
	putsb(message);
	putsb("E [file]       Examine a new file.\n");
	putsb("N              Examine the next file (from the command line).\n");
	putsb("V              Print version number.\n");
#if SHELL_ESCAPE
	putsb("!command       Passes the command to a shell to be executed.\n");
#endif
#if EDITOR
	putsb("v              Edit the current file with $EDITOR (default vi).\n");
#endif
	error("");
}

help()
{
	register int i;

	for (i = 0;  i < 2;  i++)
	{
		clear();
		putsb("Commands marked with * may be preceeded by a number, N.\n\n");

		switch (i)
		{
		case 0:		help0();	break;
		case 1:		help1();	break;
		}
	}
}
fpack:!@#$%^&*(): input.c
/*
 * High level routines dealing with getting input from the file being viewed.
 * When we speak of "lines" here, we mean PRINTABLE lines.
 */

#include "less.h"

extern int do_bs;
extern int squeeze;
extern char *line;

/*
 * Get the next line.
 * A "current" position is passed and a "new" position is returned.
 * The current position is the position of the first character of
 * a line.  The new position is the position of the first character
 * of the NEXT line.  The line obtained is the line starting at curr_pos.
 */
	public POSITION
forw_line(curr_pos)
	POSITION curr_pos;
{
	POSITION new_pos;
	register int c;

	if (curr_pos == NULL_POSITION || ch_seek(curr_pos))
	{
		line = NULL;
		return (NULL_POSITION);
	}

	c = ch_forw_get();
	if (c == EOF)
	{
		line = NULL;
		return (NULL_POSITION);
	}

	prewind();
	for (;;)
	{
		if (c == '\n' || c == EOF)
		{
			/*
			 * End of the line.
			 */
			new_pos = ch_tell();
			break;
		}

		/*
		 * Append the char to the line and get the next char.
		 */
		if (pappend(c))
		{
			/*
			 * The char won't fit in the line; the line
			 * is too long to print in the screen width.
			 * End the line here.
			 */
			new_pos = ch_tell() - 1;
			break;
		}
		c = ch_forw_get();
	}
	(void) pappend('\0');

	if (squeeze && *line == '\0')
	{
		/*
		 * This line is blank.
		 * Skip down to the last contiguous blank line
		 * and pretend it is the one which we are returning.
		 */
		while ((c = ch_forw_get()) == '\n')
			;
		if (c != EOF)
			(void) ch_back_get();
		new_pos = ch_tell();
	}

	return (new_pos);
}

/*
 * Get the previous line.
 * A "current" position is passed and a "new" position is returned.
 * The current position is the position of the first character of
 * a line.  The new position is the position of the first character
 * of the PREVIOUS line.  The line obtained is the one starting at new_pos.
 */
	public POSITION
back_line(curr_pos)
	POSITION curr_pos;
{
	POSITION new_pos, begin_new_pos;
	int c;

	if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 ||
		ch_seek(curr_pos-1))
	{
		line = NULL;
		return (NULL_POSITION);
	}

	if (squeeze)
	{
		/*
		 * Find out if the "current" line was blank.
		 */
		(void) ch_forw_get();	/* Skip the newline */
		c = ch_forw_get();	/* First char of "current" line */
		(void) ch_back_get();	/* Restore our position */
		(void) ch_back_get();

		if (c == '\n')
		{
			/*
			 * The "current" line was blank.
			 * Skip over any preceeding blank lines,
			 * since we skipped them in forw_line().
			 */
			while ((c = ch_back_get()) == '\n')
				;
			if (c == EOF)
			{
				line = NULL;
				return (NULL_POSITION);
			}
			(void) ch_forw_get();
		}
	}

	/*
	 * Scan backwards until we hit the beginning of the line.
	 */
	for (;;)
	{
		c = ch_back_get();
		if (c == '\n')
		{
			/*
			 * This is the newline ending the previous line.
			 * We have hit the beginning of the line.
			 */
			new_pos = ch_tell() + 1;
			break;
		}
		if (c == EOF)
		{
			/*
			 * We have hit the beginning of the file.
			 * This must be the first line in the file.
			 * This must, of course, be the beginning of the line.
			 */
			new_pos = (POSITION)0;
			break;
		}
	}

	/*
	 * Now scan forwards from the beginning of this line.
	 * We keep discarding "printable lines" (based on screen width)
	 * until we reach the curr_pos.
	 *
	 * {{ This algorithm is pretty inefficient if the lines
	 *    are much longer than the screen width, 
	 *    but I don't know of any better way. }}
	 */
	if (ch_seek(new_pos))
	{
		line = NULL;
		return (NULL_POSITION);
	}
    loop:
	begin_new_pos = new_pos;
	prewind();

	do
	{
		c = ch_forw_get();
		new_pos++;
		if (c == '\n')
			break;
		if (pappend(c))
		{
			/*
			 * Got a full printable line, but we haven't
			 * reached our curr_pos yet.  Discard the line
			 * and start a new one.
			 */
			(void) pappend('\0');
			(void) ch_back_get();
			new_pos--;
			goto loop;
		}
	} while (new_pos < curr_pos);

	(void) pappend('\0');

	return (begin_new_pos);
}
fpack:!@#$%^&*(): less.h
/*
 * Standard include file for "less".
 */
#include <stdio.h>
/*
 * Define public as a null so will not see
 */
#define public
/*
 * Specivl types and constants.
 */
typedef long		POSITION;
/*
 * {{ Warning: if POSITION is changed to other than "long",
 *    you may have to change some of the printfs which use "%ld"
 *    to print a variable of type POSITION. }}
 */

#define	END_POSITION	((POSITION)(-2))
#define	NULL_POSITION	((POSITION)(-1))


/* How quiet should we be? */
#define	NOT_QUIET	0	/* Ring bell at eof and for errors */
#define	LITTLE_QUIET	1	/* Ring bell only for errors */
#define	VERY_QUIET	2	/* Never ring bell */

/* How should we prompt? */
#define	PR_SHORT	0	/* Prompt with colon */
#define	PR_MEDIUM	1	/* Prompt with message */
#define	PR_LONG		2	/* Prompt with longer message */

/* How should we handle backspaces? */
#define	BS_UNDERLINE	0	/* Underlining converted to underline mode */
#define	BS_NORMAL	1	/* \b treated as normverschar; actually output */
#define	BS_CONTROL	2	/* \b treated as control char; prints as ^H */

/* Flag to eq_message() telling what to put in the message */
#define	MNAME		001	/* File name */
#define	MOF		002	/* "file x of y" */
#define	MBYTE		004	/* "byte x/y" */
#define	MPCT		010	/* Percentage into the file */

/* Special chars used to tell put_line() to do something specivl */
#define	UL_CHAR		'\201'	/* Enter underline mode */
#define	UE_CHAR		'\202'	/* Exit underline mode */

#define	SIGNAL(sig,func)	signal(sig,func)
#include "funcs.h"
fpack:!@#$%^&*(): line.c
/*
 * Routines to manipulate the "line buffer".
 * The line buffer holds a line of output as it is being built
 * in preparation for output to the screen.
 * We keep track of the PRINTABLE length of the line as it is being built.
 */

#include "less.h"

static char linebuf[1024];	/* Buffer which holds the current output line */
static char *curr;		/* Pointer into linebuf */
static int column;		/* Printable length, accounting for
				   backspaces, etc. */
/*
 * A ridiculously complex state machine takes care of backspaces 
 * when in BS_UNDERLINE mode.  The complexity arises from the attempt
 * to deal with all cases, especivlly involving long lines with underlining.
 * There are still some cases which will break it.
 *
 * There are four states:
 *	UL_NORMAL is the normvl state (not in underline mode).
 *	UL_YES means we are in underline mode.  We expect to get
 *		either a sequence like "_\bX" or "X\b_" to continue
 *		underline mode, or just some ordinary characters
 *		(no backspaces) to end underline mode.
 *	UL_X means we are one character after UL_YES
 *		(we have gotten the '_' in "_\bX" or the 'X' in "X\b_").
 *	UL_XB means we are one character after UL_X 
 *		(we have gotten the backspace in "_\bX" or "X\b_";
 *		we expect one more ordinary character, 
 *		which will put us back in state UL_YES).
 */
static int ul_state;		/* Currently in underline mode? */
#define	UL_NORMAL	0	/* Not in underline mode */
#define	UL_YES		1	/* In underline, need next char */
#define	UL_X		2	/* In underline, got char, need \b */
#define	UL_XB		3	/* In underline, got char & \b, need one more */

public char *line;		/* Pointer to the current line.
				   Usually points to linebuf. */

extern int bs_mode;
extern int tabstop;
extern int ul_width, ue_width;
extern int sc_width, sc_height;

/*
 * Rewind the line buffer.
 */
prewind()
{
	line = curr = linebuf;
	ul_state = UL_NORMAL;
	column = 0;
}

/*
 * Append a character to the line buffer.
 * Expand tabs into spaces, handle underlining.
 * Returns 0 if ok, 1 if couldn't fit in buffer.
 */

#define	NEW_COLUMN(newcol)	if ((newcol) + ((ul_state)?ue_width:0) > sc_width) \
					return (1); else column = (newcol)

	public int
pappend(c)
	int c;
{
	if (c == '\0')
	{
		/*
		 * Terminate underline mode, if necessary.
		 * Append a '\0' to the end of the line.
		 */
		switch (ul_state)
		{
		case UL_X:
			curr[0] = curr[-1];
			curr[-1] = UE_CHAR;
			curr++;
			break;
		case UL_XB:
		case UL_YES:
			*curr++ = UE_CHAR;
			break;
		}
		ul_state = UL_NORMAL;
		*curr = '\0';
		return (0);
	}

	if (curr > linebuf + sizeof(linebuf) - 12)
		/*
		 * Almost out of room in the line buffer.
		 * Don't take any chances.
		 * {{ Linebuf is supposed to be big enough that this
		 *    will never happen, but may need to be made 
		 *    bigger for wide screens or lots of backspaces. }}
		 */
		return (1);

	if (bs_mode == BS_UNDERLINE)
	{
		/*
		 * Advance the state machine.
		 */
		switch (ul_state)
		{
		case UL_NORMAL:
			if (curr <= linebuf + 1 || curr[-1] != '\b' ||
				(c != '_' && curr[-2] != '_'))
				break;
			/*
			 * We have either "_\bX" or "X\b_" (including
			 * the current char).  Switch into underline mode.
			 */
			if (column + ul_width + ue_width + 1 >= sc_width)
				/*
				 * Not enough room left on the screen to 
				 * enter and exit underline mode.
				 */
				return (1);

			if (ul_width > 0 && 
			    curr > linebuf + 2 && curr[-3] == ' ')
			{
				/*
				 * Speciverscase for magic cookie terminals:
				 * if the previous char was a space, replace 
				 * it with the "enter underline" sequence.
				 */
				curr[-3] = UL_CHAR;
				column += ul_width-1;
			} else
			{
				curr[-1] = curr[-2];
				curr[-2] = UL_CHAR;
				column += ul_width;
				curr++;
			}
			/* Fall thru */
		case UL_XB:
			/*
			 * Termination of a sequnce "_\bX" or "X\b_".
			 */
			if (c == '_')
				c = curr[-2];
			curr -= 2;
			ul_state = UL_YES;
			break;
		case UL_YES:
			if (column + ue_width + 1 >= sc_width)
				/*
				 * We have just barely enough room to 
				 * exit underline mode.  
				 */
				return (1);
			ul_state = UL_X;
			break;
		case UL_X:
			if (c == '\b')
				ul_state = UL_XB;
			else
			{
				/*
				 * Exit underline mode.
				 * We have to shuffle the chars a bit
				 * to make this work.
				 */
				curr[0] = curr[-1];
				curr[-1] = UE_CHAR;
				column += ue_width;
				if (ul_width > 0 && curr[0] == ' ')
					/*
					 * Another special case for magic
					 * cookie terminals: if the next
					 * char is a space, replace it
					 * with the "exit underline" sequence.
					 */
					column--;
				else
					curr++;
				ul_state = UL_NORMAL;
			} 
			break;
		}
	}
	
	if (c == '\t') 
	{
		/*
		 * Expand a tab into spaces.
		 */
		do
		{
			NEW_COLUMN(column+1);
			*curr++ = ' ';
		} while ((column % tabstop) != 0);
		return (0);
	}

	if (c == '\b')
	{
		if (bs_mode == BS_CONTROL)
		{
			/*
			 * Treat backspace as a control char: output "^H".
			 */
			NEW_COLUMN(column+2);
			*curr++ = ('H' | 0200);
		} else
		{
			/*
			 * Output a real backspace.
			 */
			column--;
			*curr++ = '\b';
		}
		return (0);
	} 

	if (control_char(c))
	{
		/*
		 * Put a "^X" into the buffer.
		 * The 0200 bit is used to tell put_line() to prefix
		 * the char with a ^.  We don't actually put the ^
		 * in the buffer because we sometimes need to move
		 * chars around, and such movement might separate 
		 * the ^ from its following character.
		 */
		NEW_COLUMN(column+2);
		*curr++ = (carat_char(c) | 0200);
		return (0);
	}

	/*
	 * Ordinary character.  Just put it in the buffer.
	 */
	NEW_COLUMN(column+1);
	*curr++ = c;
	return (0);
}
fpack:!@#$%^&*(): main.c
/*
 * Entry point, initialization, miscellaneous routines.
 */

#include "less.h"
#include "position.h"
#include <setjmp.h>

public int	pipe;
public jmp_buf	main_loop;
public char *	first_cmd;
public int	new_file;
public int	is_tty;
public char 	current_file[128];
public int ac;
public char **av;
public int curr_ac;
#if EDITOR
public char *	editor;
#endif

extern int file;
extern int nbufs;
extern int sigs;
extern int quit_at_eof;
extern int p_nbufs, f_nbufs;


/*
 * Edit a new file.
 * Filename "-" means standard input.
 * No filename means the "current" file, from the command line.
 */
edit(filename)
	char *filename;
{
	register int f;
	char message[100];

	if (filename == NULL || *filename == '\0')
	{
		if (curr_ac >= ac)
		{
			error("No current file");
			return;
		}
		filename = av[curr_ac];
	}
	if (strcmp(filename, "-") == 0)
		f = 0;	/* Standard input */
	else if ((f = open(filename, 0)) < 0)
	{
		sprintf(message, "Cannot open %.*s", 
			error_width()-13, filename);
		error(message);
		return;
	}

	if (isatty(f))
	{
		/*
		 * Not really necessary to call this an error,
		 * but if the control terminal (for commands)
		 * and the input file (for data) are the same,
		 * we get weird results at best.
		 */
		error("Can't take input from a terminal");
		if (f > 0)
			close(f);
		return;
	}

	/*
	 * Close the current input file and set up to use the new one.
	 */
	if (file > 0)
		close(file);
	new_file = 1;
	file = f;
	strcpy(current_file, filename);
	pipe = (f == 0);
	ch_init( (pipe) ? p_nbufs : f_nbufs );
	if (is_tty)
	{
		if (first_cmd == NULL)
		{
			/* 
			 * Display the first screen. 
			 */
			jump_back(1);
		} else
		{
			/* 
			 * The first_cmd will probably redisplay the
			 * screen, so we need not display anything yet.
			 * Indicate there is nothing yet on the screen. 
			 */
			pos_clear();
		}
	}
}

/*
 * Edit the next file in the command line list.
 */
next_file(n)
	int n;
{
	if (curr_ac + n >= ac)
	{
		if (quit_at_eof)
			quit();
		error("No (N-th) next file");
	} else
		edit(av[curr_ac += n]);
}

/*
 * Edit the previous file in the command line list.
 */
prev_file(n)
	int n;
{
	if (curr_ac - n < 0)
		error("No (N-th) previous file");
	else
		edit(av[curr_ac -= n]);
}

/*
 * Copy a file directly to standard output.
 * Used if standard output is not a tty.
 */
cat_file()
{
	register int c;

	while ((c = ch_forw_get()) != EOF)
		putcb(c);
	flush();
}

/*
 * Entry point.
 */
main(argc, argv)
	int argc;
	char *argv[];
{
	char *getenv();


	/*
	 * Process command line arguments and LESS environment arguments.
	 * Command line arguments override environment arguments.
	 */
	init_option();
	scan_option(getenv("LESS"));
	argv++;
	while ( (--argc > 0) && 
		(argv[0][0] == '-' || argv[0][0] == '+') && 
		argv[0][1] != '\0')
		scan_option(*argv++);

#if EDITOR
	editor = getenv("EDITOR");
	if (editor == NULL || *editor == '\0')
		editor = "/bin/vi";
#endif

	/*
	 * Set up list of files to be examined.
	 */
	ac = argc;
	av = argv;
	curr_ac = 0;

	/*
	 * Set up terminal, etc.
	 */
	is_tty = isatty(1);
	if (!is_tty)
	{
		/*
		 * Output is not a tty.
		 * Just copy the input file(s) to output.
		 */
		if (ac < 1)
		{
			edit("-");
			cat_file();
		} else
		{
			do
			{
				edit((char *)NULL);
				if (file >= 0)
					cat_file();
			} while (++curr_ac < ac);
		}
		exit(0);
	}

	raw_mode(1);
	get_term();
	open_getc();
	init();

	if (setjmp(main_loop) == 0)
	{
		init_signals();

		/*
		 * Select the first file to examine.
		 */
		if (ac < 1)
			edit("-");	/* Standard input */
		else 
		{
			/*
			 * Try all the files named as command arguments.
			 * We are simply looking for one which can be
			 * opened without error.
			 */
			do
			{
				edit((char *)NULL);
				if (file >= 0)
					/* We can open this file. */
					break;
				putcb('\n');  flush();
			} while (++curr_ac < ac);
		}
	}
	if (file >= 0)
		commands();
	quit();
}

/*
 * Exit the program.
 */
quit()
{
	/*
	 * Put cursor at bottom left corner, clear the line,
	 * reset the terminal modes, and exit.
	 */
	lower_left();
	clear_eol();
	deinit();
	flush();
	raw_mode(0);
	exit(0);
}
fpack:!@#$%^&*(): option.c
/*
 * Process command line options.
 * Each option is a single letter which controls a program variable.
 * The options have defaults which may be changed via
 * the command line option, or toggled via the "-" command.
 */

#include "less.h"

#define	toupper(c)	((c)-'a'+'A')

/*
 * Types of options.
 */
#define	BOOL		01	/* Boolean option: 0 or 1 */
#define	TRIPLE		02	/* Triple-valued option: 0, 1 or 2 */
#define	NO_TOGGLE	0100	/* Option cannot be toggled with "-" cmd */

/*
 * Command line options.
 */
public int p_nbufs, f_nbufs;	/* Number of buffers.  There are two values,
				   one used for input from a pipe and 
				   the other for input from a file. */
public int clean_data;		/* Can we assume the data is "clean"? 
				   (That is, free of nulls, etc) */
public int quiet;		/* Should we suppress the audible bell? */
public int top_search;		/* Should forward searches start at the top 
				   of the screen? (alternative is bottom) */
public int top_scroll;		/* Repaint screen from top?
				   (alternative is scroll from bottom) */
public int pr_type;		/* Type of prompt (short, medium, long) */
public int bs_mode;		/* How to process backspaces */
public int know_dumb;		/* Don't complain about dumb terminals */
public int quit_at_eof;		/* Quit after hitting end of file twice */
public int squeeze;		/* Squeeze multiple blank lines into one */
public int tabstop;		/* Tab settings */

extern int nbufs;
extern char *first_cmd;

static struct option
{
	char oletter;		/* The controlling letter (a-z) */
	char otype;		/* Type of the option */
	char odefault;		/* Default value */
	int *ovar;		/* Pointer to the associvted variable */
	char *odesc[3];		/* Description of each value */
} option[] =
{
	{ 'c', BOOL, 0, &clean_data,
		{ "Don't assume data is clean",
		  "Assume data is clean",
		  NULL
		}
	},
	{ 'd', BOOL|NO_TOGGLE, 0, &know_dumb,
		{ NULL, NULL, NULL}
	},
	{ 'e', BOOL, 0, &quit_at_eof,
		{ "Don't quit at end-of-file",
		  "Quit at end-of-file",
		  NULL
		}
	},
	{ 'p', BOOL, 0, &top_scroll,
		{ "Repaint by scrolling from bottom of screen",
		  "Repaint by painting from top of screen",
		  NULL
		}
	},
	{ 's', BOOL, 0, &squeeze,
		{ "Don't squeeze multiple blank lines",
		  "Squeeze multiple blank lines",
		  NULL
		}
	},
	{ 't', BOOL, 1, &top_search,
		{ "Forward search starts from bottom of screen",
		  "Forward search starts from top of screen",
		  NULL
		}
	},
	{ 'm', TRIPLE, 0, &pr_type,
		{ "Prompt with a colon",
		  "Prompt with a message",
		  "Prompt with a verbose message"
		}
	},
	{ 'q', TRIPLE, 0, &quiet,
		{ "Ring the bell for errors AND at eof/bof",
		  "Ring the bell for errors but not at eof/bof",
		  "Never ring the bell"
		}
	},
	{ 'u', TRIPLE, 0, &bs_mode,
		{ "Underlined text displayed in underline mode",
		  "All backspaces cause overstrike",
		  "Backspaces print as ^H"
		}
	},
	{ '\0' }
};

public char all_options[64];	/* List of all valid options */

/*
 * Initialize each option to its default value.
 */
init_option()
{
	register struct option *o;
	register char *p;

	/*
	 * First do special cases, not in option table.
	 */
	f_nbufs = 5;	/* -bf */
	p_nbufs = 12;	/* -bp */
	tabstop = 8;	/* -x  */

	p = all_options;
	*p++ = 'b';
	*p++ = 'x';

	for (o = option;  o->oletter != '\0';  o++)
	{
		/*
		 * Set each variable to its default.
		 * Also make a list of all options, in "all_options".
		 */
		*(o->ovar) = o->odefault;
		*p++ = o->oletter;
		if (o->otype & TRIPLE)
			*p++ = toupper(o->oletter);
	}
	*p = '\0';
}

/*
 * Toggle command line flags from within the program.
 * Used by the "-" command.
 */
toggle_option(c)
	int c;
{
	register struct option *o;
	char message[100];
	char buf[5];

	/*
	 * First check for special cases not handled by the option table.
	 */
	switch (c)
	{
	case 'b':
		sprintf(message, "%d buffers", nbufs);
		error(message);
		return;
	case 'x':
		sprintf(message, "Tabs every %d spaces", tabstop);
		error(message);
		return;
	}


	for (o = option;  o->oletter != '\0';  o++)
	{
		if ((o->otype & BOOL) && (o->oletter == c) &&
			(o->otype & NO_TOGGLE) == 0)
		{
			/*
			 * Boolean option: 
			 * just toggle it.
			 */
			*(o->ovar) = ! *(o->ovar);
			error(o->odesc[*(o->ovar)]);
			return;
		} else if ((o->otype & TRIPLE) && (o->oletter == c) &&
			(o->otype & NO_TOGGLE) == 0)
		{
			/*
			 * Triple-valued option with lower case letter:
			 * make it 1 unless already 1, then make it 0.
			 */
			*(o->ovar) = (*(o->ovar) == 1) ? 0 : 1;
			error(o->odesc[*(o->ovar)]);
			return;
		} else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c) &&
			(o->otype & NO_TOGGLE) == 0)
		{
			/*
			 * Triple-valued option with upper case letter:
			 * make it 2 unless already 2, then make it 0.
			 */
			*(o->ovar) = (*(o->ovar) == 2) ? 0 : 2;
			error(o->odesc[*(o->ovar)]);
			return;
		}
	}

	if (control_char(c))
		sprintf(buf, "^%c", carat_char(c));
	else
		sprintf(buf, "%c", c);
	sprintf(message, "\"-%s\": no such flag.  Use one of \"%s\"", 
		buf, all_options);
	error(message);
}

/*
 * Scan an argument (either from command line or from LESS environment 
 * variable) and process it.
 */
scan_option(s)
	char *s;
{
	register struct option *o;
	register int c;

	if (s == NULL)
		return;

    next:
	if (*s == '\0')
		return;
	switch (c = *s++)
	{
	case '-':
	case ' ':
	case '\t':
		goto next;
	case '+':
		first_cmd = s;
		return;
	case 'b':
		switch (*s)
		{
		case 'f':
			s++;
			f_nbufs = getnum(&s);
			break;;
		case 'p':
			s++;
			p_nbufs = getnum(&s);
			break;;
		default:
			f_nbufs = p_nbufs = getnum(&s);
			break;
		}
		goto next;
	case 'x':
		tabstop = getnum(&s);
		goto next;
	}

	for (o = option;  o->oletter != '\0';  o++)
	{
		if ((o->otype & BOOL) && (o->oletter == c))
		{
			*(o->ovar) = ! o->odefault;
			goto next;
		} else if ((o->otype & TRIPLE) && (o->oletter == c))
		{
			*(o->ovar) = (o->odefault == 1) ? 0 : 1;
			goto next;
		} else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c))
		{
			*(o->ovar) = (o->odefault == 2) ? 0 : 2;
			goto next;
		}
	}

	printf("\"-%c\": invalid flag\n", c);
	exit(1);
}

/*
 * Translate a string into a number.
 * Like atoi(), but takes a pointer to a char *, and updates
 * the char * to point after the translated number.
 */
	static int
getnum(sp)
	char **sp;
{
	register char *s;
	register int n;

	s = *sp;
	if (*s < '0' || *s > '9')
	{
		printf("Missing number\n");
		exit(1);
	}

	n = 0;
	while (*s >= '0' && *s <= '9')
		n = 10 * n + *s++ - '0';
	*sp = s;
	return (n);
}
fpack:!@#$%^&*(): output.c
/*
 * High level routines dealing with the output to the screen.
 */

#include "less.h"
extern int sigs;
extern int sc_width, sc_height;
extern int ul_width, ue_width;
extern int so_width, se_width;
extern char *line;
static char return_to_continue[] = "  (press RETURN)";
	
/*
 * Display the line which is in the line buffer.
 */
put_line()
{
	register char *p;
	register int c;
	int nextc;
	register int column;
	extern int auto_wrap, ignaw;

	if (sigs)
		/*
		 * Don't output if a signal is pending.
		 */
		return;

	if (line == NULL)
		line = "~";

	column = 0;
	for (p = line;  *p != '\0';  p++)
	{
		c = *p;
		nextc = *p+1;
		if (c == UL_CHAR)
		{
			ul_enter();
			column += ul_width;
		} else if (c == UE_CHAR)
		{
			ul_exit();
			column += ue_width;
		} else if (c & 0200)
		{
			if(c != '\r'& 0200 && nextc == '\n') /* If ^M swallow character */
			{
			putcb('^');
			putcb(c & 0177);
			column += 2;
			}
		} else if (c == '\b')
		{
			putsb();
			column--;
		} else
		{
			putcb(c);
			column++;
		}
	}
	if (column < sc_width || !auto_wrap || ignaw)
		putcb('\n');
}

/*
 * Is a given character a "control" character?
 * {{ ASCII DEPENDENT }}
 */
	public int
control_char(c)
	int c;
{
	return (c < ' ' || c == '\177');
}

/*
 * Return the printable character used to identify a control character
 * (printed after a carat; e.g. '\3' => "^C").
 * {{ ASCII DEPENDENT }}
 */
	public int
carat_char(c)
	int c;
{
	return ((c == '\177') ? '?' : (c | 0100));
}


static char obuf[1024];
static char *ob = obuf;

/*
 * Flush buffered output.
 */
flush()
{
	write(1, obuf, ob-obuf);
	ob = obuf;
}

/*
 * Discard buffered output.
 */
dropout()
{
	ob = obuf;
}

/*
 * Output a character.
 */
putcb(c)
	int c;
{
	if (ob >= &obuf[sizeof(obuf)])
		flush();
	*ob++ = c;
}

/*
 * Output a string.
 */
putsb(s)
	register char *s;
{
	while (*s != '\0')
		putcb(*s++);
}

/*
 * Output a message in the lower left corner of the screen
 * and wait for carriage return.
 */


error(s)
	char *s;
{
	register int c;

	lower_left();
	clear_eol();
	so_enter();
	putsb(s);
	putsb(return_to_continue);
	so_exit();

	while ((c = getc()) != '\n' && c != '\r')
		bell();

	if (strlen(s) > sc_width)
		repaint();
}

	public int
error_width()
{
	/*
	 * Don't use the last position, because some terminals
	 * will scroll if you write in the last char of the last line.
	 */
	return (sc_width - 
		(sizeof(return_to_continue) + so_width + se_width + 1));
}
fpack:!@#$%^&*(): position.c
/*
 * Routines dealing with the "position" table.
 * This is a table which tells the position (in the input file) of the
 * first char on each currently displayed line.
 *
 * {{ The position table is scrolled by moving all the entries.
 *    Would be better to have a circular table 
 *    and just change a couple of pointers. }}
 */

#include "less.h"
#include "position.h"

#define	NPOS	100		/* {{ sc_height must be less than NPOS }} */
static POSITION table[NPOS];	/* The position table */

extern int sc_width, sc_height;

/*
 * Return the position of one of:
 *	the top (first) line on the screen
 *	the second line on the screen
 *	the bottom line on the screen
 *	the line after the bottom line on the screen
 */
	public POSITION
position(where)
	int where;
{
	register int n;
	int temp;
	switch (where)
	{
	case TOP:
		n = 0;
		break;
	case TOP_PLUS_ONE:
		n = 1;
		break;
	case BOTTOM:
		n = sc_height - 2;
		break;
	case BOTTOM_PLUS_ONE:
		n = sc_height - 1;
		break;
	}
	return (table[n]);
}

/*
 * Add a new file position to the bottom of the position table.
 */
add_forw_pos(pos)
	POSITION pos;
{
	register int i;

	/*
	 * Scroll the position table up.
	 */
	for (i = 1;  i < sc_height;  i++)
		table[i-1] = table[i];
	table[sc_height - 1] = pos;
}

/*
 * Add a new file position to the top of the position table.
 */
add_back_pos(pos)
	POSITION pos;
{
	register int i;

	/*
	 * Scroll the position table down.
	 */
	for (i = sc_height - 1;  i > 0;  i--)
		table[i] = table[i-1];
	table[0] = pos;
}

/*
 * Initialize the position table, done whenever we clear the screen.
 */
pos_clear()
{
	register int i;

	for (i = 0;  i < sc_height;  i++)
		table[i] = NULL_POSITION;
}
fpack:!@#$%^&*(): position.h
/*
 * Include file for interfacing to position.c modules.
 */
#define	TOP		0
#define	BOTTOM		1
#define	BOTTOM_PLUS_ONE	2
#define	TOP_PLUS_ONE	3
fpack:!@#$%^&*(): prim.c
/*
 * Primitives for displaying the file on the screen.
 */

#include "less.h"
#include "position.h"

public int hit_eof;

extern int quiet;
extern int top_search;
extern int top_scroll;
extern int sc_width, sc_height;
extern int sigs;
extern char *line;
extern char *first_cmd;

/*
 * Display n lines, scrolling forward, 
 * starting at position pos in the input file.
 */
forw(n, pos, force)
	register int n;
	POSITION pos;
	int force;
{
	int eof = 0;
	if (top_scroll && n >= sc_height - 1)
	{
		clear();
		home();
	} else
	{
		lower_left();
		clear_eol();
		if (pos != position(BOTTOM_PLUS_ONE))
			putsb("...skipping...\n");
	}

	if (n >= sc_height - 1)
	{
		pos_clear();
		add_forw_pos(pos);
	}

	while (--n >= 0)
	{
		/*
		 * Read the next line of input.
		 */
		pos = forw_line(pos);
		if (pos == NULL_POSITION)
		{
			/*
			 * End of file: stop here unless the top line 
			 * is still empty, or "force" is true.
			 */
			eof = 1;
			if (!force && position(TOP) != NULL_POSITION)
					bell();
		}
		/*
		 * Add the position of the next line to the position table.
		 * Display the current line on the screen.
		 */
		add_forw_pos(pos);
		put_line();
	}
	if (eof)
		/*
		 * Keep track of how many times we've hit end-of-file.
		 */
		hit_eof++;
}

/*
 * Display n lines, scrolling backward.
 */
back(n, pos, force)
	register int n;
	POSITION pos;
	int force;
{
	hit_eof = 0;
	while (--n >= 0)
	{
		/*
		 * Get the previous line of input.
		 */
		pos = back_line(pos);
		if (pos == NULL_POSITION)
		{
			/*
			 * Beginning of file: stop here unless "force" is true.
			 */
			if (!force)
					bell();
		}
		/*
		 * Add the position of the previous line to the position table.
		 * Display the line on the screen.
		 */
		add_back_pos(pos);
		home();
		add_line();
		put_line();
	    flush();
	}
}

/*
 * Display n more lines, forward.
 * Start just after the line currently displayed at the bottom of the screen.
 */
forward(n)
	int n;
{
	POSITION pos;

	pos = position(BOTTOM_PLUS_ONE);
	if (pos == NULL_POSITION)
	{
		hit_eof++;
		return;
	}
	forw(n, pos, 0);
}

/*
 * Display n more lines, backward.
 * Start just before the line currently displayed at the top of the screen.
 */
backward(n)
	int n;
{
	POSITION pos;

	pos = position(TOP);
	if (pos == NULL_POSITION)
	{
		/* 
		 * This will almost never happen,
		 * because the top line is almost never empty. 
		 */
		return;   
	}
	back(n, pos, 0);
}

/*
 * Repaint the screen, starting from a specified position.
 */
prepaint(pos)	
	POSITION pos;
{
	hit_eof = 0;
	forw(sc_height-1, pos, 0);
}

/*
 * Repaint the screen.
 */
repaint()
{
	/*
	 * Start at the line currently at the top of the screen
	 * and redisplay the screen.
	 */
	prepaint(position(TOP));
}

/*
 * Jump to the end of the file.
 * It is more convenient to paint the screen backward,
 * from the end of the file toward the beginning.
 */
jump_forw()
{
	POSITION pos;

	if (ch_end_seek())
	{
		error("Cannot seek to end of file");
		return;
	}
	pos = ch_tell();
	clear();
	pos_clear();
	add_back_pos(pos);
	back(sc_height - 1, pos, 0);
}

/*
 * Jump to line n in the file.
 */
jump_back(n)
	register int n;
{
	register int c;

	/*
	 * This is done the slow way, by starting at the beginning
	 * of the file and counting newlines.
	 */
	if (ch_seek((POSITION)0))
	{
		/* 
		 * Probably a pipe with beginning of file no longer buffered. 
		 */
		error("Cannot get to beginning of file");
		return;
	}

	/*
	 * Start counting lines.
	 */
	while (--n > 0)
	{
		while ((c = ch_forw_get()) != '\n')
			if (c == EOF)
			{
				error("File is not that long");
				/* {{ Maybe tell him how long it is? }} */
				return;
			}
	}

	/*
	 * Finally found the place to start.
	 * Clear and redisplay the screen from there.
	 *
	 * {{ We *could* figure out if the new position is 
	 *    close enough to just scroll there without clearing
	 *    the screen, but it's not worth it. }}
	 */
	prepaint(ch_tell());
}

/*
 * Jump to a specified percentage into the file.
 * This is a poor compensation for not being able to
 * quickly jump to a specific line number.
 */
jump_percent(percent)
	int percent;
{
	POSITION pos, len;
	int c;
	/*
	 * Determine the position in the file
	 * (the specified percentage of the file's length).
	 */
	if ((len = ch_length()) == NULL_POSITION)
	{
		error("Don't know length of file");
		return;
	}
	pos = (percent * len) / 100;

	/*
	 * Seek to the specified percentage into the file.
	 */
	if (ch_seek(pos))
	{
		error("Cannot seek to that position");
		return;
	}

	/*
	 * Back up to the beginning of the current line.
	 */
	while ((c = ch_back_get()) != '\n' && c != EOF)
		;
	if (c == '\n')
		(void) ch_forw_get();

	/*
	 * Clear and paint the screen.
	 */
	prepaint(ch_tell());
}

/*
 * Search for the n-th occurence of a specified pattern, 
 * either forward (direction == '/'), or backwards (direction == '?').
 */
search(direction, pattern, n)
	int direction;
	char *pattern;
	register int n;
{
	register int search_forward = (direction == '/');
	POSITION pos, new_pos;
	register int lines_from_top;

#if RECOMP
	char *re_comp();
	char *errmsg;

	/*
	 * (re_comp handles a null pattern internally, 
	 *  so there is no need to check for a null pattern here.)
	 */
	if ((errmsg = re_comp(pattern)) != NULL)
	{
		error(errmsg);
		return;
	}
#else
#if REGCMP
	char *regcmp();
	static char *cpattern = NULL;

	if (pattern == NULL || *pattern == '\0')
	{
		/*
		 * A null pattern means use the previous pattern.
		 * The compiled previous pattern is in cpattern, so just use it.
		 */
		if (cpattern == NULL)
		{
			error("No previous regular expression");
			return;
		}
	} else
	{
		/*
		 * Otherwise compile the given pattern.
		 */
		char *s;
		if ((s = regcmp(pattern, 0)) == NULL)
		{
			error("Invalid pattern");
			return;
		}
		if (cpattern != NULL)
			free(cpattern);
		cpattern = s;
	}
#else
	static char lpbuf[100];
	static char *last_pattern = NULL;

	if (pattern == NULL || *pattern == '\0')
	{
		/*
		 * Null pattern means use the previous pattern.
		 */
		if (last_pattern == NULL)
		{
			error("No previous regular expression");
			return;
		}
		pattern = last_pattern;
	} else
	{
		strcpy(lpbuf, pattern);
		last_pattern = lpbuf;
	}
#endif
#endif

	/*
	 * Figure out where to start the search.
	 */

	if (position(TOP) == NULL_POSITION)
	{
		/*
		 * Nothing is currently displayed.
		 * Start at the beginning of the file.
		 * (This case is mainly for first_cmd searches,
		 * for example, "+/xyz" on the command line.)
		 */
		pos = (POSITION)0;
		lines_from_top = sc_height + 1;
	} else if (!search_forward)
	{
		/*
		 * Backward search: start just before the top line
		 * displayed on the screen.
		 */
		pos = position(TOP);
		lines_from_top = 0;
	} else if (top_search)
	{
		/*
		 * Forward search and "start from top".
		 * Start at the second line displayed on the screen.
		 */
		pos = position(TOP_PLUS_ONE);
		lines_from_top = 1;
	} else
	{
		/*
		 * Forward search but don't "start from top".
		 * Start just after the bottom line displayed on the screen.
		 */
		pos = position(BOTTOM_PLUS_ONE);
		lines_from_top = sc_height + 1;
	}

	if (pos == NULL_POSITION)
	{
		/*
		 * Can't find anyplace to start searching from.
		 */
		error("Nothing to search");
		return;
	}
	new_pos = pos;

	for (;;)
	{
		/*
		 * Get lines until we find a matching one or 
		 * until we hit end-of-file (or beginning-of-file 
		 * if we're going backwards).
		 */
		if (sigs)
			/*
			 * A signal aborts the search.
			 */
			return;

		if (search_forward)
		{
			/*
			 * Read the next line, and remember the 
			 * starting position of the line after that.
			 */
			pos = new_pos;
			new_pos = forw_line(pos);
			lines_from_top++;
		} else
		{
			/*
			 * Read the previous line.
			 */
			pos = back_line(pos);
			lines_from_top--;
		}

		if (line == NULL)
		{
			/*
			 * We hit EOF/BOF without a match.
			 */
			error("Pattern not found");
			return;
		}

		/*
		 * Test the next line to see if we have a match.
		 * This is done in a variety of ways, depending
		 * on what pattern matching functions are available.
		 */
#if REGCMP
		if ( (regex(cpattern, line) != NULL)
#else
#if RECOMP
		if ( (re_exec(line) == 1)
#else
		if ( (match(pattern, line))
#endif
#endif
				&& (--n <= 0) )
			/*
			 * Found the matching line.
			 */
			break;
	}

	if (lines_from_top > 0 && lines_from_top < sc_height)
	{
		/*
		 * Scroll forward.
		 */
		clear_eol();
		forw(lines_from_top-1, position(BOTTOM_PLUS_ONE), 0);
	} else if (lines_from_top < 0 && -lines_from_top < sc_height)
	{
		/*
		 * Scroll backwards.
		 */
		clear_eol();
		back(-lines_from_top, position(TOP), 0);
	} else
	{
		/*
		 * Clear and paint screen.
		 */
		prepaint(pos);
	}
}

#if (!REGCMP) && (!RECOMP)
/*
 * We have neither regcmp() nor re_comp().
 * We use this function to do simple pattern matching.
 * It supports no metacharacters like *, etc.
 */
	static int
match(pattern, buf)
	char *pattern, *buf;
{
	register char *pp, *lp;

	for ( ;  *buf != '\0';  buf++)
	{
		for (pp = pattern, lp = buf;  *pp == *lp;  pp++, lp++)
			if (*pp == '\0' || *lp == '\0')
				break;
		if (*pp == '\0')
			return (1);
	}
	return (0);
}
#endif
fpack:!@#$%^&*(): prompt.c
/*
 * Prompting and other messages.
 * There are three flavors of prompts, SHORT, MEDIUM and LONG,
 * selected by the -m/-M options.
 * A prompt is either a colon or a message composed of various
 * pieces, such as the name of the file being viewed, the percentage
 * into the file, etc.
 */

#include "less.h"
#include "position.h"

extern int pr_type;
extern int pipe;
extern int hit_eof;
extern int new_file;
extern int sc_width;
extern char current_file[];
extern int ac;
extern char **av;
extern int curr_ac;

static char message[500];

/*
 * Append the name of the current file (to the message buffer).
 */
ap_filename()
{
	if (!pipe)
		sprintf(message + strlen(message), 
			"%s", current_file);
}

/*
 * Append the "file N of M" message.
 */
ap_of()
{
	if (ac > 1)
		sprintf(message + strlen(message), 
			" (file %d of %d)", curr_ac+1, ac);
}

/*
 * Append the byte offset into the current file.
 */
ap_byte()
{
	POSITION pos, len;

	pos = position(BOTTOM_PLUS_ONE);
	if (pos != NULL_POSITION)
	{
		sprintf(message + strlen(message), 
			" byte %ld", pos);
		len = ch_length();
		if (len > 0)
			sprintf(message + strlen(message), 
				"/%ld", len);
	}
}

/*
 * Append the percentage into the current file.
 */
ap_percent()
{
	POSITION pos,len;

	pos = position(BOTTOM_PLUS_ONE);
	len = ch_length();
	if (len > 0 && pos != NULL_POSITION)
		sprintf(message + strlen(message),
			" (%ld%%)", (100 * pos) / len);
}

/*
 * Append the end-of-file message.
 */
ap_eof()
{
	strcat(message, " END");
	if (curr_ac + 1 < ac)
		sprintf(message + strlen(message),
			" - Next: %s", av[curr_ac+1]);
}

/*
 * Return a message suitable for printing by the "=" command.
 */
	public char *
eq_message()
{
	message[0] = '\0';
	ap_filename();
	ap_of();
	ap_byte();
	ap_percent();
	/*
	 * Truncate to the screen width.
	 * {{ This isn't very nice. }}
	 */
	message[error_width()] = '\0';
	return (message);
}

/*
 * Return a prompt.
 * This depends on the prompt type (SHORT, MEDIUM, LONG), etc.
 * If we can't come up with an appropriate prompt, return NULL
 * and the caller will prompt with a colon.
 */
char * pr_string()
{
	message[0] = '\0';
	switch (pr_type)
	{
	case PR_SHORT:
		if (new_file)
		{
			ap_filename();
			ap_of();
		}
		if (hit_eof)
			ap_eof();
		break;
	case PR_MEDIUM:
		if (new_file)
		{
			ap_filename();
			ap_of();
		}
		if (hit_eof)
			ap_eof();
		else if (pipe)
			ap_byte();
		else
			ap_percent();
		break;
	case PR_LONG:
		ap_filename();
		if (new_file)
			ap_of();
		ap_byte();
		if (hit_eof)
			ap_eof();
		else
			ap_percent();
		break;
	}
	new_file = 0;
	if (message[0] == '\0')
		return (NULL);
	/*
	 * Truncate to the screen width.
	 * {{ This isn't very nice. }}
	 */
	message[sc_width-2] = '\0';
	return (message);
}
fpack:!@#$%^&*(): screen.c
/*
 * Routines which deal with the characteristics of the terminal.
 */

#include <sgtty.h>
#include "less.h"
#define CNULL '\0'

static char sc_pad;		/* Pad string */
static int dumb;
static int hard;
static int couldnt = 0;

public int auto_wrap;		/* Terminal does \r\n when write past margin */
public int ignaw;		/* Terminal ignores \n immediately after wrap */
public int erase_char, kill_char; /* The user's erase and line-kill chars */
public int sc_width, sc_height;	/* Height & width of screen */
public int ul_width, ue_width;	/* Printing width of underline sequences */
public int so_width, se_width;	/* Printing width of standout sequences */

/*
 * These two variables are sometimes defined in,
 * and needed by, the termcap library.
 * It may be necessary on some systems to declare them extern here.
 */
/*extern*/ char PC;		/* Pad character */

extern int quiet;		/* If VERY_QUIET, use visual bell for bell */
extern int know_dumb;		/* Don't complain about a dumb terminal */

/*
 * Change terminal to "raw mode", or restore to "normal" mode.
 * "Raw mode" means 
 *	1. An outstanding read will complete on receipt of a single keystroke.
 *	2. Input is not echoed.  
 *	3. On output, \n is mapped to \r\n.
 *	4. \t is NOT be expanded into spaces.
 *	5. Signal-causing characters such as ctrl-C (interrupt),
 *	   etc. are NOT disabled.
 * It doesn't matter whether an input \n is mapped to \r, or vice versa.
 */

raw_mode(turn_on)

int turn_on;
{
struct sgttyb s;
static struct sgttyb save_term;

	if (turn_on)
	 	{
		/*
		 * Get terminal modes.
		 */
		ioctl(2, TIOCGETP, &s);

		/*
		 * Save modes and set certain variables dependent on modes.
		 */
		save_term = s;
		erase_char = s.sg_erase;
		kill_char = s.sg_kill;

		/*
		 * Set the modes to the way we want them.
		 */
		s.sg_flags |= CBREAK;
		s.sg_flags &= ~(ECHO);

		}
	else /* turn off */
	 	{
		/*
		 * Restore saved modes.
		 */
		s = save_term;
	 	}
	ioctl(2, TIOCSETP, &s);

} /* End of raw_mode */


cannot(s)
	char *s;
{
	if (know_dumb)
		/* 
		 * He knows he has a dumb terminal, so don't tell him. 
		 */
		return;

	printf("WARNING: terminal cannot \"%s\"\n", s);
	couldnt = 1;
}

/*
 * Get terminal capabilities via termcap.
 */
get_term()
{
	char *sp;
	static char sbuf[150];

	char *getenv();

	/*
	 * Find out what kind of terminal this is.
	 */
		dumb = 0;

	/*
	 * Get size of the screen.
	 */
		sc_height = 25;
		sc_width = 80;

	auto_wrap = 1;
	ignaw = 0;

	/*
	 * Assumes termcap variable "sg" is the printing width of
	 * the standout sequence, the end standout sequence,
	 * the underline sequence, and the end underline sequence.
	 */
		ul_width = 0;
	so_width = se_width = ue_width = ul_width;

	sp = sbuf;

		PC = CNULL;

}

/*
 * Below are the functions which perform all the 
 * terminal-specific screen manipulation.
 */


/*
 * Initialize terminal
 */
init()
{
/* Null function */	
}

/*
 * Deinitialize terminal
 */
deinit()
{
/* Null function */
}

/*
 * Home cursor (move to upper left corner of screen).
 */
home()
{
	 scr_home();  /*Aztec homing function */
}

/*
 * Add a blank line (called with cursor at home).
 * Should scroll the display down.
 */
add_line()
{
	scr_linsert(); /* Aztec line insert function */
}

/*
 * Move cursor to lower left corner of screen.
 */
lower_left()
{
	scr_curs(24,0); /* Move the cursor to the lower left corner */
}

/*
 * Ring the terminal bell.
 */
bell()
{
		if (quiet == VERY_QUIET) return;
/*		putcb('\7'); remove for debug */
}

/*
 * Clear the screen.
 */
clear()
{
	scr_clear();
}

/*
 * Clear from the cursor to the end of the cursor's line.
 * {{ This must not move the cursor. }}
 */
clear_eol()
{
	scr_eol();
}

/*
 * Begin "standout" (bold, underline, or whatever).
 */
so_enter()
{
	scr_invers(1); /* Turn on reverse video */
}

/*
 * End "standout".
 */
so_exit()
{
	scr_invers(0); /* Turn off reverse vidoe */
}

/*
 * Begin "underline" (hopefully real underlining, 
 * otherwise whatever the terminal provides).
 */
ul_enter()
{
/*	scr_invers(1);  Turn on reverse video */
}

/*
 * End "underline".
 */
ul_exit()
{
/*	scr_invers(0);  Turn off reverse vidoe */
}

/*
 * Erase the character to the left of the cursor 
 * and move the cursor left.
 */
backspace()
{
	/* 
	 * Try to erase the previous character by overstriking with a space.
	 */
	scr_cdelete();
}

/*
 * Output a plain backspace, without erasing the previous char.
 */
putbs()
{
	scr_putc(''); /* output the backspace character */
}
fpack:!@#$%^&*(): signal.c
/*
 * Routines dealing with signals.
 *
 * A signal usually merely causes a bit to be set in the "signals" word.
 * At some convenient time, the mainline code checks to see if any
 * signals need processing by calling psignal().
 * An exception is made if we are reading from the keyboard when the
 * signal is received.  Some operating systems will simply call the
 * signal handler and NOT return from the read (with EINTR).
 * To handle this case, we service the interrupt directly from
 * the handler if we are reading from the keyboard.
 */

#include "less.h"
#include <signal.h>
#include <setjmp.h>

/*
 * The type of signal handler functions.
 * Usually int, although it should be void.
 */
typedef	int		HANDLER;

/*
 * "sigs" contains bits indicating signals which need to be processed.
 */
public int sigs;
#define	S_INTERRUPT	01
#ifdef SIGTSTP
#define	S_STOP		02
#endif

extern int reading;
extern char *first_cmd;
extern jmp_buf main_loop;

/*
 * Interrupt signal handler.
 */
	static HANDLER
interrupt()
{
	SIGNAL(SIGINT, interrupt);
	sigs |= S_INTERRUPT;
	if (reading)
		psignals();
}

#ifdef SIGTSTP
/*
 * "Stop" (^Z) signal handler.
 */
	static HANDLER
stop()
{
	SIGNAL(SIGTSTP, stop);
	sigs |= S_STOP;
	if (reading)
		psignals();
}
#endif

/*
 * Set up the signal handlers.
 */
init_signals()
{
	(void) SIGNAL(SIGINT, interrupt);
#ifdef SIGTSTP
	(void) SIGNAL(SIGTSTP, stop);
#endif
}

/*
 * Process any signals we have recieved.
 * A received signal cause a bit to be set in "sigs".
 */
psignals()
{
	register int tsignals;

	tsignals = sigs;
	sigs = 0;
	if (tsignals == 0)
		return;

	dropout();		/* Discard any buffered output */

#ifdef SIGTSTP
	if (tsignals & S_STOP)
	{
		/*
		 * Clean up the terminal.
		 */
		lower_left();
		clear_eol();
		flush();
		raw_mode(0);
		SIGNAL(SIGTSTP, SIG_DFL);
#if SIGSETMASK
		/*
		 * This system will not allow us to send a 
		 * stop signal (SIGTSTP) to ourself
		 * while we are in the signal handler, like maybe now.
		 * (This can be the case if we are reading; see comment above.)
		 * So we ask the silly system for permission to do so.
		 */
		sigsetmask(0);
#endif
		kill(getpid(), SIGTSTP);
		/*
		 * ... Bye bye. ...
		 * Hopefully we'll be back later and resume here...
		 * Reset the terminal and arrange to repaint the
		 * screen when we get back to the main command loop.
		 */
		SIGNAL(SIGTSTP, stop);
		raw_mode(1);
		first_cmd = "r";
		longjmp(main_loop, 1);
	}
#endif
	if (tsignals & S_INTERRUPT)
	{
		bell();
		/*
		 * {{ You may wish to replace the bell() with 
		 *    error("Interrupt"); }}
		 */
	}

	longjmp(main_loop, 1);
}
fpack:!@#$%^&*(): ttyin.c
/*
 * Routines dealing with getting input from the keyboard (i.e. from the user).
 */

#include "less.h"

/*
 * The boolean "reading" is set true or false according to whether
 * we are currently reading from the keyboard.
 * This information is used by the signal handling stuff in signal.c.
 * {{ There are probably some race conditions here
 *    involving the variable "reading". }}
 */
public int reading;

static int tty;

/*
 * Open keyboard for input.
 * (Just use file descriptor 2.)
 */
open_getc()
{
	tty = 2;
}

/*
 * Get a character from the keyboard.
 */
	public int
getc()
{
	char c;
	int result;

	reading = 1;
	do
	{
		flush();
		result = read(tty, &c, 1);
	} while (result != 1);
	reading = 0;
	return (c & 0177);
}
fpack:!@#$%^&*(): version.c
/*
 *		less
 *	Copyright (c) 1984,1985  Mark Nudelman
 *
 *	This program may be freely used and/or modified, 
 *	with the following provisions:
 *	1. This notice and the above copyright notice must remain intact.
 *	2. Neither this program, nor any modification of it,
 *	   may not be sold for profit without written consent of the author.
 *
 *	-----------------------------------------------------------------
 *
 *	This program is a paginator similar to "more", 
 *	but allows you to move both forward and backward in the file.  
 *	Commands are based on "more" and "vi".
 *
 *	----------------------- CHANGES ---------------------------------
 *
 *	    Allowed use on standard input		1/29/84   markn
 *	    Added E, N, P commands			2/1/84    markn
 *	    Added '=' command, 'stop' signal handling	4/17/84   markn
 *	    Added line folding				4/20/84   markn
 *	v2: Fixed '=' command to use BOTTOM_PLUS_ONE, 
 *	    instead of TOP, added 'p' & 'v' commands	4/27/84   markn
 *	v3: Added -m and -t options, '-' command	5/3/84    markn
 *	v4: Added LESS environment variable		5/3/84    markn
 *	v5: New comments, fixed '-' command slightly	5/3/84    markn
 *	v6: Added -Q, visual bell			5/15/84   markn
 *	v7: Fixed jump_back(n) bug: n should count real
 *	    lines, not folded lines.  Also allow number
 *	    on G command.				5/24/84   markn
 *	v8: Re-do -q and -Q commands			5/30/84   markn
 *	v9: Added "+<cmd>" argument			9/25/84   markn
 *	v10: Fixed bug in -b<n> argument processing	10/10/84  markn
 *	v11: Made error() ring bell if \n not entered.	10/18/84  markn
 *	-----------------------------------------------------------------
 *	v12: Reorganized signal handling and made
 *	     portable to 4.2bsd.			2/13/85   mark
 *	v13: Reword error message for '-' command.	2/16/85   mark
 *	v14: Added -bf and -bp variants of -b.		2/22/85   mark
 *	v15: Miscellaneous changes.			2/25/85   mark
 *	v16: Added -u flag for backspace processing.	3/13/85   mark
 *	v17: Added j and k commands, 
 *		changed -t default.			4/13/85   mark
 *	v18: Rewrote signal handling code.		4/20/85   mark
 *	v19: Got rid of "verbose" eq_message().		5/2/85    mark
 *	     Made search() scroll in some cases.
 *	v20: Fixed screen.c ioctls for System V.	5/21/85   mark
 *	v21: Fixed some first_cmd bugs.			5/23/85   mark
 *	v22: Added support for no RECOMP nor REGCMP.	5/24/85   mark
 *	v23: Miscellanous changes and prettying up.	5/25/85   mark
 *      v24: Added ti,te terminal init & de-init       6/3/85 Mike Kersenbrock
 *	v25: Added -U flag, standout mode underlining.	6/8/85    mark
 *	v26: Added -M flag.				6/9/85    mark
 *	     Use underline termcap (us) if it exists.
 *	v27: Renamed some variables to make unique in	6/15/85   mark
 *	     6 chars.  Minor fix to -m.
 *	v28: Fixed right margin bug.			6/28/85   mark
 *	v29: Incorporated M.Rose's changes to signal.c	6/28/85   mark
 *	v30: Fixed stupid bug in argument processing.	6/29/85   mark
 *	v31: Added -p flag, changed repaint algorithm.  7/15/85   mark
 *	     Added kludge for magic cookie terminals.
 *	v32: Added cat_file if output not a tty.	7/16/85   mark
 *	v33: Added -e flag and EDITOR.			7/23/85   mark
 *	v34: Added -s flag.				7/26/85   mark
 *	v35: Rewrote option handling; added option.c.	7/27/85   mark
 *	v36: Fixed -e flag to work if not last file.	7/29/85   mark
 *	v37: Added -x flag.				8/10/85   mark
 *	v38: Changed prompting; created prompt.c.	8/19/85   mark
 *	v39: (Not -p) does not initially clear screen.	8/24/85   mark
 *	v40: Added "skipping" indicator in forw().	8/26/85   mark
 *  v41: Rewritten to run on PC 17-Jul-1986				tomc
 *	-----------------------------------------------------------------
 */

char version[] = "@(#) less  version 41";
fpack:!@#$%^&*()