[net.sources] less, a paginator

mark@nsc-pdc.UUCP (Mark Nudelman) (06/02/85)

This is a pagination program similar to more or pg.
It has been run on 4.1BSD, 4.2BSD and System V, 
but has not been tested on any other system.
To install, delete everything down to the cut line below, 
put the file in an empty directory, and run sh on the file.
Then read the file called INSTALLATION.
Problems, complaints, praise to the author.

	Mark Nudelman		nsc!nsc-pdc!mark
	National Semiconductor	tektronix!reed!nsc-pdc!mark


#!/bin/sh-----cut here-----cut here-----cut here-----cut here-----
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	INSTALLATION #	less.l #	makefile.bsd #	makefile.sys5 #	ch.c #	command.c #	help.c #	input.c #	line.c #	main.c #	output.c #	position.c #	prim.c #	screen.c #	signal.c #	ttyin.c #	version.c #	funcs.h #	less.h #	position.h #	mkfuncs.awk 
cat - << \SHAR_EOF > INSTALLATION
This is the distribution of "less", a paginator similar to "more" or "pg".
The manual page is in less.l.

INSTALLATION:

1. Move the distributed source to its own directory and 
   unpack it by running "sh" on the distribution file,
   if you have not already done so.

2. If your system is System V:
	cp makefile.sys5 makefile
   If your system is Berkeley 4.1bsd or 4.2bsd:
	cp makefile.bsd makefile
   Otherwise, edit the makefile to make the 
   system parameters match your system.

3. Type "make" and watch the fun.

4. If the make succeeds, it will generate a program "less"
   in your current directory.  Test the generated program.

5. When satisfied that it works, if you wish to install it
   in a public place, edit the makefile so that INSTALL_LESS
   and INSTALL_MAN are the proper filenames.
   Then type "make install".

If you have any problems building or running "less", 
you may mail to the author via USENET at:
	...!tektronix!reed!nsc-pdc!mark
  or	...!nsc!nsc-pdc!mark

Note to hackers: comments noting possible improvements are enclosed
in double curly brackets {{ like this }}.
SHAR_EOF
cat - << \SHAR_EOF > less.l
.TH LESS l
.SH NAME
less \- opposite of more
.SH SYNOPSIS
.B "less [-cdmtqQu] [-b[fp]\fIn\fB] [+\fIcommand\fB] [\fIname\fB] ..."
.SH DESCRIPTION
.I Less
is a program similar to 
.I more
(1), but which allows backwards movement
in the file as well as forward movement.
Also,
.I less
does not have to read the entire input file before starting,
so with large input files it starts up faster than text editors like
.I vi
(1).
.I Less
uses termcap, so it can run on a variety of terminals.
There is even limited support for hardcopy terminals.
(On a hardcopy terminal, lines which should be printed at the top
of the screen are prefixed with an up-arrow.)
.PP
Commands are based on both
.I more
and
.I vi.
Commands may be preceeded by a decimal number, 
called N in the descriptions below.
The number is used by some commands, as indicated.

.IP h
Help: display a summary of these commands.
If you forget all the other commands, remember this one.
.PP
.IP SPACE
Scroll forward one screen.
.PP
.IP f
Same as SPACE.
.PP
.IP b
Scroll backward one screen.
.PP
.IP RETURN
Scroll forward N lines, default 1.
.PP
.IP e
Same as RETURN.
.PP
.IP j
Also the same as RETURN.
.PP
.IP y
Scroll backward N lines, default 1.
.IP k
Same as y.
.PP
.IP d
Scroll forward N lines, default 10.
If N is specified, it becomes the new default for all d and u commands.
.PP
.IP u
Scroll backward N lines, default 10.
If N is specified, it becomes the new default for all d and u commands.
.PP
.IP r
Repaint the screen.
.PP
.IP R
Repaint the screen, discarding any buffered input.
Useful if the file is changing while it is being viewed.
.PP
.IP g
Go to line N in the file, default 1 (beginning of file).
(Warning: this may be slow if N is large.)
.PP
.IP G
Go to line N in the file, default the end of the file.
(Warning: this may be slow if standard input, 
rather than a file, is being read.)
.PP
.IP p
Go to a position N percent into the file.
N should be between 0 and 100.
(This is always fast, but not always useful.)
.PP
.IP /pattern
Search forward in the file for the N-th occurence of the pattern.
N defaults to 1.
The pattern is a regular expression, as recognized by
.I ed.
The search starts at the line immediately after the bottom line displayed.
.PP
.IP ?pattern
Search backward in the file for the N-th occurence of the pattern.
The search starts at the line immediately before the top line displayed.
.PP
.IP n
Repeat previous search, for N-th occurence of the last pattern.
.PP
.IP E [filename]
Examine a new file.
If the filename is missing, the "current" file (see the N and P commands
below) from the list of files in the command line is re-examined.
.PP
.IP N
Examine the next file (from the list of files given in the command line).
If a number N is specified (not be confused with the command N),
the N-th next file is examined.
.PP
.IP P
Examine the previous file.
If a number N is specified, the N-th previous file is examined.
.PP
.IP =
Prints the name of the file being viewed
and the byte offset of the bottom line being displayed.
If possible, it also prints the length of the file
and the percent of the file above the last displayed line.
.PP
.IP \-
Followed by one of the command line option letters (see below),
this will toggle the setting of that option
and print a message describing the new setting.
.PP
.IP v
Prints the version number of 
.I less 
being run.
.PP
.IP q
Exits
.I less.
.PP
Command line options are described below.
Options are also taken from the environment variable "LESS".
(The environment variable is parsed before the command line,
so command line options override the LESS environment variable.
Options may be changed while
.I less 
is running via the "\-" command.)
For example, to avoid typing "less -m ..." each time 
.I less 
is invoked, you might tell 
.I csh:
.sp
setenv LESS m
.sp
or if you use 
.I sh:
.sp
LESS=m; export LESS
.PP
Normally, forward searches start just after
the top displayed line (that is, at the second displayed line).
Thus forward searches include the currently displayed screen.
The -t command line option causes forward searches to start 
just after the bottom line displayed,
thus skipping the currently displayed screen.
.PP
The -m command line option causes 
.I less
to prompt verbosely like 
.I more,
printing the file name and percent into the file.
.PP
Normally, if an attempt is made to scroll past the end of the file
or before the beginning of the file, the terminal bell is rung to
indicate this fact.
The -q command line option tells
.I less
not to ring the bell at such times.
If the terminal has a "visual bell", it is used instead.
.PP
Even if -q is given, less will ring the bell on certain other errors,
such as typing an invalid character.
The -Q command line option tells
.I less
to be quiet all the time; that is, never ring the terminal bell.
If the terminal has a "visual bell", it is used instead.
.PP
Normally, backspaces in the text are printed as the 
two character sequence "^H".
If the -u command line option is given, 
real backspaces are sent to the terminal.
.PP
The -b\fIn\fR command line option tells
.I less
to use a non-standard buffer size.
There are two standard (default) buffer sizes,
one is used when a file is being read and the other
when a pipe (standard input) is being read.
The current defaults are 5 buffers for files and 12 for pipes.
(Buffers are 1024 bytes.)
The number \fIn\fR specifies a different number of buffers to use.
The -b may be followed by "f", in which case only 
the file default is changed, or by "p" in which case only the 
pipe default is changed.  Otherwise, both are changed..
.PP
Normally, when data is read by
.I less,
it is scanned to ensure that bit 7 (the high order bit) is turned off in
each byte read, and to ensure that there are no null (zero) bytes in
the data (null bytes are turned into "@" characters).
If the data is known to be "clean",
the -c command line option will tell 
.I less
to skip this checking.
(However, if the data is not "clean", unpredicatable results may occur.)
.PP
If a command line option begins with \fB+\fR,
the remainder of that option is taken to be an initial command to
.I less.
For example, +G tells
.I less
to start at the end of the file rather than the beginning,
and +/xyz tells it to start at the first occurence of "xyz" in the file.
As a special case, +<number> acts like +<number>g; 
that is, it starts the display at the specified line number
(however, see the caveat under the "g" command above).
.SH BUGS
When used on standard input (rather than a file), you can move
backwards only a finite amount, corresponding to that portion
of the file which is still buffered.
.PP
Searches will not find a string which has been split due
to line folding.
.sp
Tabs in the input are translated into the appropriate number of spaces, 
so a search pattern which includes a tab character will always fail.
Use the pattern "\ *" instead.
SHAR_EOF
cat - << \SHAR_EOF > makefile.bsd
# Makefile for "less"
#
# Invoked as:
#	make all
#   or	make install
# Plain "make" is equivalent to "make all".
#
# If you add or delete functions, remake funcs.h by doing:
#	make newfuncs
# This depends on the coding convention of function headers looking like:
#	" \t public <function-type> \n <function-name> ( ... ) "
#
# Also provided:
#	make lint	# Runs "lint" on all the sources.
#	make clean	# Removes "less" and the .o files.
#	make clobber	# Pretty much the same as make "clean".

##########################################################################

# System-specific parameters

# TERMIO is 1 if your system has /usr/include/termio.h.
# This is normally the case for System 5.
# If TERMIO is 0 your system must have /usr/include/sgtty.h.
# This is normally the case for BSD.
TERMIO = 0

# off_t is the type which lseek() returns.
# It is also the type of lseek()'s second argument.
off_t = long

# REGCMP is 1 if your system has the regcmp() function.
# This is normally the case for System 5.
# RECOMP is 1 if your system has the re_comp() function.
# This is normally the case for BSD.
# If neither is 1, pattern matching is supported, but without metacharacters.
REGCMP = 0
RECOMP = 1

# LIBS is the list of libraries needed.
LIBS = -ltermcap

# INSTALL_LESS is a list of the public versions of less.
# INSTALL_MAN is a list of the public versions of the manual page.
INSTALL_LESS =	/usr/local/less
INSTALL_MAN =	/usr/man/manl/less.l

# OPTIM is passed to the compiler and the loader.
# It is normally "-O" but may be, for example, "-g".
OPTIM = -O

##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-Doff_t=$(off_t)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)"

CFLAGS = $(OPTIM) $(DEFS)

##########################################################################

SRC =	main.c prim.c ch.c position.c input.c output.c screen.c \
	line.c signal.c help.c ttyin.c command.c version.c
OBJ =	main.o prim.o ch.o position.o input.o output.o screen.o \
	line.o signal.o help.o ttyin.o command.o version.o

##########################################################################

all: less

less: $(OBJ)
	cc $(OPTIM) -o less $(OBJ) $(LIBS)

install: install_man install_less

install_man: less.l
	for f in $(INSTALL_MAN); do  rm -f $$f; cp less.l $$f;  done
	touch install_man
	
install_less: less
	for f in $(INSTALL_LESS); do  rm -f $$f; cp less $$f;  done
	touch install_less

$(OBJ): less.h funcs.h

lint:
	lint -h $(DEFS) $(SRC)

newfuncs:
	mv funcs.h funcs.h.OLD
	awk -f mkfuncs.awk $(SRC) >funcs.h

clean:
	rm -f $(OBJ) less

clobber:
	rm -f *.o less install_less install_man
SHAR_EOF
cat - << \SHAR_EOF > makefile.sys5
# Makefile for "less"
#
# Invoked as:
#	make all
#   or	make install
# Plain "make" is equivalent to "make all".
#
# If you add or delete functions, remake funcs.h by doing:
#	make newfuncs
# This depends on the coding convention of function headers looking like:
#	" \t public <function-type> \n <function-name> ( ... ) "
#
# Also provided:
#	make lint	# Runs "lint" on all the sources.
#	make clean	# Removes "less" and the .o files.
#	make clobber	# Pretty much the same as make "clean".

##########################################################################

# System-specific parameters

# TERMIO is 1 if your system has /usr/include/termio.h.
# This is normally the case for System 5.
# If TERMIO is 0 your system must have /usr/include/sgtty.h.
# This is normally the case for BSD.
TERMIO = 1

# off_t is the type which lseek() returns.
# It is also the type of lseek()'s second argument.
off_t = long

# REGCMP is 1 if your system has the regcmp() function.
# This is normally the case for System 5.
# RECOMP is 1 if your system has the re_comp() function.
# This is normally the case for BSD.
# If neither is 1, pattern matching is supported, but without metacharacters.
REGCMP = 1
RECOMP = 0

# LIBS is the list of libraries needed.
LIBS = -lcurses -lPW

# INSTALL_LESS is a list of the public versions of less.
# INSTALL_MAN is a list of the public versions of the manual page.
INSTALL_LESS =	/usr/lbin/less
INSTALL_MAN =	/usr/man/manl/less.l

# OPTIM is passed to the compiler and the loader.
# It is normally "-O" but may be, for example, "-g".
OPTIM = -O

##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-Doff_t=$(off_t)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)"

CFLAGS = $(OPTIM) $(DEFS)

##########################################################################

SRC =	main.c prim.c ch.c position.c input.c output.c screen.c \
	line.c signal.c help.c ttyin.c command.c version.c
OBJ =	main.o prim.o ch.o position.o input.o output.o screen.o \
	line.o signal.o help.o ttyin.o command.o version.o

##########################################################################

all: less

less: $(OBJ)
	cc $(OPTIM) -o less $(OBJ) $(LIBS)

install: install_man install_less

install_man: less.l
	for f in $(INSTALL_MAN); do  rm -f $$f; cp less.l $$f;  done
	touch install_man
	
install_less: less
	for f in $(INSTALL_LESS); do  rm -f $$f; cp less $$f;  done
	touch install_less

$(OBJ): less.h funcs.h

lint:
	lint -h $(DEFS) $(SRC)

newfuncs:
	mv funcs.h funcs.h.OLD
	awk -f mkfuncs.awk $(SRC) >funcs.h

clean:
	rm -f $(OBJ) less

clobber:
	rm -f *.o less install_less install_man
SHAR_EOF
cat - << \SHAR_EOF > 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;

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

/*
 * 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 %d, want %d\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_seek_end()
{
	if (pipe)
	{
		/*
		 * Do it the slow way: read till end of data.
		 */
		while (ch_get_forw() != EOF)
			;
	} else
	{
		(void) ch_seek((POSITION)(lseek(file, (off_t)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)));
}

/*
 * 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_get_forw()
{
	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_get_back()
{
	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.
 */
	public void
ch_init(want_nbufs)
	int want_nbufs;
{
	register struct buf *bp;

	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);	/* ? */
}
SHAR_EOF
cat - << \SHAR_EOF > command.c
/*
 * User-level command processor.
 */

#include "less.h"

extern int erase_char, kill_char;
extern int eq_prompt;
extern int new_file;
extern int signals;
extern char *first_cmd;
extern char version[];

static char cmdbuf[90];
static char *cp;
static int cmd_col;
static int slash;
static int need_colon;

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

/*
 * 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);
}

/*
 * Display the colon prompt.
 */
	static void
colon()
{
	putc(':');
	need_colon = 0;
}

/*
 * Accept 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 (need_colon)
	{
		lower_left();
		clear_eol();
		lower_left();
		colon();
	}

	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 < screen_width-3)
		{
			*cp++ = c;
			if (control_char(c))
			{
				putc('^');
				cmd_col++;
				c = carat_char(c);
			}
			putc(c);
			cmd_col++;
		} else
			bell();
	}
	return (0);
}

/*
 * Return the number entered before the current command.
 */
	static int
cmd_int()
{
	*cp = '\0';
	return (atoi(cmdbuf));
}

/*
 * Output a carriage return before executing a command.
 * This looks nicer if the command takes a long time before
 * updating the screen.
 */
	static void
cmd_exec()
{
	putc('\r');
	flush();
}

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

	if (eq_prompt || new_file)
	{
		/*
		 * Prompt with verbose prompt.
		 */
		enter_standout();
		puts(eq_message(screen_width-1));
		exit_standout();
		new_file = 0;
		need_colon = 1;
	} else
	{
		/*
		 * Prompt with short prompt.
		 */
		colon();
	}
}

/*
 * 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.
			 */
			if (slash)
				return ('\n'); 
			else
				/* This is pretty hokey. */
				return ('g');
		}
		return (getc());
	}
	return (*first_cmd++);
}

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

	slash = 0;

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

		cmd_reset();
		lower_left();
		clear_eol();
		lower_left();	/* Most terminals don't need this */
		prompt();
		c = getcc();

	again:
		if (signals)
			continue;

		if (slash)
		{
			/*
			 * We are in a search (or other multi-character) 
			 * command.  All chars until newline are part of 
			 * the "pattern".
			 */
			if (c == '\n' || c == '\r')
			{
				/*
				 * Execute the command.
				 */
				*cp = '\0';
				cmd_exec();
				if (slash == 'E')
				{
					char *p;
					/*
					 * Ignore leading spaces 
					 * in the filename.
					 */
					for (p = cmdbuf;  *p == ' ';  p++) ;
					edit(p);
				} else
					search(slash, cmdbuf, n);
				slash = 0;
			} else
			{
				/*
				 * Append the char to the pattern being 
				 * built in the command buffer.
				 */
				if (cmd_char(c))
				{
					slash = 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':
			/*
			 * One digit of a number.
			 */
		case_cmd_char:
			if (cmd_char(c))
				continue;
			c = getcc();
			goto again;

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

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

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

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

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

		case 'u':
			/*
			 * Forward N lines 
			 * (default same as last 'd' or 'u' command).
			 */
			lower_left();
			clear_eol();
			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.
			 */
			cmd_exec();
			n = cmd_int();
			if (n <= 0)
				n = 1;
			jump_back(n);
			break;

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

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

		case '=':
			/*
			 * Print file name, etc.
			 */
			error(eq_message(error_width()));
			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.
			 */
			last_slash = slash = c;
			n = cmd_int();
			if (n <= 0)
				n = 1;
			cmd_reset();
			lower_left();
			putc(c);
			clear_eol();
			c = getcc();
			need_colon = 0;
			goto again;

		case 'n':
			/*
			 * Repeat previous search.
			 */
			lower_left();
			putc(last_slash);
			clear_eol();
			cmd_exec();
			n = cmd_int();
			if (n <= 0)
				n = 1;
			search(last_slash, (char *)NULL, n);
			break;

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

		case 'E':
			/*
			 * Edit a new file.  Get the filename.
			 */
			cmd_reset();
			lower_left();
			clear_eol();
			colon();
			putc('E');
			putc(' ');
			slash = 'E';
			c = getcc();
			goto again;
			
		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.
			 */
			lower_left();
			clear_eol();
			colon();
			putc('-');
			c = getcc();
			if (c == erase_char || c == kill_char)
				/* Abort the "-" command. */
				break;
			toggle_flag(c);
			break;

		default:
			/*
			 * Might be erase or line-kill character.
			 * Otherwise it is an error.
			 */
			if (c == erase_char || c == kill_char)
				goto case_cmd_char;
			bell();
			break;
		}
	}
}
SHAR_EOF
cat - << \SHAR_EOF > help.c
#include  "less.h"

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

	static void
help1()
{
	puts("R              Repaint screen, discarding buffered input.\n");
	puts("p           *  Position to N percent into the file.\n");
	puts("-x             Toggle a flag (x may be b,c,m,t,u,q,Q).\n");
	puts("E [file]       Examine a new file.\n");
	puts("N              Examine the next file (from the command line).\n");
	puts("P              Examine the previous file (from the command line).\n");
	puts("v              Print version number.\n");
	error("");
}

	public void
help()
{
	register int i;

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

		switch (i)
		{
		case 0:		help0();	break;
		case 1:		help1();	break;
		}
	}
}
SHAR_EOF
cat - << \SHAR_EOF > 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;

/*
 * 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_get_forw();
	if (c == EOF)
	{
		line = NULL;
		return (NULL_POSITION);
	}

	prewind();
	for (;;)
	{
		if (c == '\n' || c == EOF)
		{
			/*
			 * End of the line.
			 */
			new_pos = ch_tell();
			break;
		}
		if (plong())
		{
			/*
			 * 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;
		}
		/*
		 * Append the char to the line and get the next char.
		 */
		pappend(c);
		c = ch_get_forw();
	}
	pappend('\0');
	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, start_new_pos;
	int c;

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

	/*
	 * Scan backwards until we hit the beginning of the line.
	 */
	for (;;)
	{
		c = ch_get_back();
		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:
	start_new_pos = new_pos;
	prewind();

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

	pappend('\0');
	return (start_new_pos);
}
SHAR_EOF
cat - << \SHAR_EOF > 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[200];	/* Buffer which holds the current output line */
static char *curr;		/* Pointer into linebuf */
static int length;		/* Printable length, accounting for
				   backspaces, etc. */
public char *line;		/* Pointer to the current line.
				   Usually points to linebuf. */

extern int do_bs;		/* Process backspaces */

/*
 * OVERFLOW means we ran out of space in the linebuf.
 * {{ Linebuf is supposed to be big enough that this never happens,
 *    but may need to be made larger for very wide screens, 
 *    or lots of backspaces. }}
 */
#define	OVERFLOW	(curr >= &linebuf[sizeof(linebuf)-1])

/*
 * Rewind the line buffer.
 */
	public void
prewind()
{
	line = curr = linebuf;
	length = 0;
}

/*
 * Append a character to the line buffer and keep 
 * track of the PRINTABLE length of the buffer.  
 * This is normally the same as the actual length 
 * of the buffer, except for backspaces 
 * (which are the only control characters put into the line buffer,
 *  and only if do_bs is true).
 */
	static void
append(c)
	int c;
{
	if (OVERFLOW && c != '\0')
		return;
	if (do_bs && c == '\b')
	{
		*curr++ = '\b';
		length--;
	} else if (c == '\0')
	{
		*curr++ = '\0';
	} else if (control_char(c))
	{
		*curr++ = '^';
		*curr++ = carat_char(c);
		length += 2;
	} else
	{
		*curr++ = c;
		length++;
	}
}

/*
 * Append a character to the line buffer.
 * Expand tabs into spaces.
 */
	public void
pappend(c)
	int c;
{
	if (c != '\t')
		append(c);
	else do
		append(' ');
	while ((length % 8) != 0 && !OVERFLOW);
}

/*
 * Determine whether there is room for another character in the line buffer.
 * The length limit is determined by the printable length of the string
 * being too long for the screen.
 */
	public int
plong()
{
	return (length >= screen_width);
}
SHAR_EOF
cat - << \SHAR_EOF > main.c
/*
 * Entry point, initialization, miscellaneous routines.
 */

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

public int 	screen_height;
public int	screen_width;
public int	pipe;
public jmp_buf	main_loop;
public char *	first_cmd;
public int	new_file;

/*
 * 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 search_from_top;	/* Should forward searches start at the top 
				   of the screen? (alternative is bottom) */
public int eq_prompt;		/* Do we prompt with the "equals" message?
				   (that is, the message normally printed
				   by the "=" command) */
public int do_bs;		/* Process backspaces? */
public int know_dumb;		/* Don't complain about dumb terminals */

/*
 * Defaults for command line options.
 */
#define	NBUFS_PIPED		12	/* # buffers if input is a pipe */
#define	NBUFS_NON_PIPED		5	/* # buffers if input is a file */
#define	CLEAN_DATA		0	/* Don't assume data is clean */
#define	SEARCH_FROM_TOP		1	/* Start forw search from top of screen */
#define	EQ_PROMPT		0	/* Don't prompt verbosely */
#define	DO_BS			0	/* Don't output backspaces */
#define	KNOW_DUMB		0	/* Warn me if I have a dumb terminal */

extern int nbufs;
extern int signals;

static char current_file[128];

static int ac;
static char **av;
static int curr_ac;

/*
 * Edit a new file.
 * Filename "-" means standard input.
 * No filename means the "current" file, from the command line.
 */
	public void
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 %.60s", 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 (first_cmd == NULL)
		/* Display the first screen. */
		jump_back(1);
	else
		/* Indicate there is nothing yet on the screen. */
		pos_clear();
}

/*
 * Construct a message suitable for printing by the "=" command.
 * Also used by verbose prompting (-m) 
 * and as the first prompt of a new file.
 * Truncate the file name if it is too long to print.
 */
	public char *
eq_message(width)
	int width;
{
	POSITION pos, len;
	int namelen, tlen;
	int i;
	char buf2[200];
	static char buffer[200];

	/*
	 * Build the message in "buffer", 
	 * temporarily ignoring the width limit.
	 */
	strcpy(buffer, current_file);
	namelen = strlen(buffer);
	
	if (ac > 1)
	{
		sprintf(buf2, " (file %d of %d)", curr_ac+1, ac);
		strcat(buffer, buf2);
	}

	pos = position(BOTTOM_PLUS_ONE);
	if (pos == NULL_POSITION)
		sprintf(buf2, " at bottom");
	else
		sprintf(buf2, " at byte %d", pos);
	strcat(buffer, buf2);

	if ((len = ch_length()) > 0)
	{
		sprintf(buf2, "/%d", len);
		strcat(buffer, buf2);
		if (pos != NULL_POSITION)
		{
			sprintf(buf2, " (%d%%)", (100 * pos) / len);
			strcat(buffer, buf2);
		}
	}

	/*
	 * If we have exceeded the width limit,
	 * shift the last part of the message to the left,
	 * overwriting the tail of the filename,
	 * and append ".." to the end of the filename.
	 */
	if ((tlen = strlen(buffer)) > width)
	{
		int shift = tlen - width;
		for (i = namelen+1;  i <= tlen;  i++)
			buffer[i-shift] = buffer[i];
		buffer[namelen-shift] = buffer[namelen-shift-1] = '.';
	}

	return (buffer);
}

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

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

/*
 * Toggle command line flags from within the program.
 * Used by the "-" command.
 */
	public void
toggle_flag(c)
	int c;
{
	register char *s;
	char message[100];

	switch (c)
	{
	case 'c':
		if (clean_data = !clean_data)
			s = "Assume data is clean";
		else
			s = "Don't assume data is clean";
		break;
	case 'q':
		quiet = (quiet == LITTLE_QUIET) ? NOT_QUIET : LITTLE_QUIET;
		goto qflag;
	case 'Q':
		quiet = (quiet == VERY_QUIET) ? NOT_QUIET : VERY_QUIET;
	qflag:
		switch (quiet)
		{
		case NOT_QUIET:
			s = "Ring the bell for errors AND at eof/bof";
			break;
		case LITTLE_QUIET:
			s = "Ring the bell for errors but not at eof/bof";
			break;
		case VERY_QUIET:
			s = "Never ring the bell";
			break;
		}
		break;
	case 'm':
		if (eq_prompt = !eq_prompt)
			s = "Verbose prompt (the = message)";
		else
			s = "Short prompt (a colon)";
		break;
	case 't':
		if (search_from_top = !search_from_top)
			s = "Forward search starts from top of screen";
		else
			s = "Forward search starts from bottom of screen";
		break;
	case 'u':
		if (do_bs = !do_bs)
			s = "Backspace causes overstrike";
		else
			s = "Backspace prints as ^H";
		break;
	case 'b':
		sprintf(message, "Currently using %d buffers", nbufs);
		s = message;
		break;
	default:
		if (control_char(c))
			sprintf(message, "\"-^%c\"", carat_char(c));
		else
			sprintf(message, "\"-%c\"", c);
		strcat(message, ": no such flag.  Use one of \"bcmtuqQ\"");
		s = message;
		break;
	}
	error(s);
}

/*
 * Scan an argument (either from command line or from LESS environment 
 * variable) and process it.
 */
	static void
arg_scan(s)
	register char *s;
{
	register int n;
	char bflag;

	if (s == NULL)
		return;

	while (*s != '\0') switch (*s++)
	{
	case 'b':
		bflag = '\0';
		if (*s == 'f' || *s == 'p')
		{
			bflag = *s;
			++s;
		}

		if (*s < '0' || *s > '9')
		{
			printf("-b requires number: \"-b<n>\"\n");
			exit(1);
			/*NOTREACHED*/
		}

		n = 0;
		while (*s >= '0' && *s <= '9')
			n = 10 * n + *s++ - '0';
		if (n <= 0 || n > 200)
		{
			printf("invalid number (%d) for -b\n", n);
			exit(1);
			/*NOTREACHED*/
		}

		switch (bflag)
		{
		case 'p':
			p_nbufs = n;
			break;
		case 'f':
			f_nbufs = n;
			break;
		case '\0':
			p_nbufs = f_nbufs = n;
			break;
		}
		break;
	case 'c':
		clean_data = !CLEAN_DATA;
		break;
	case 'd':
		know_dumb = !KNOW_DUMB;
		break;
	case 'q':
		quiet = LITTLE_QUIET;
		break;
	case 'Q':
		quiet = VERY_QUIET;
		break;
	case 'm':
		eq_prompt = !EQ_PROMPT;
		break;
	case 't':
		search_from_top = !SEARCH_FROM_TOP;
		break;
	case 'u':
		do_bs = !DO_BS;
		break;
	case '+':
		first_cmd = s;
		return;
	case '-':
	case ' ':
	case '\t':
		break;
	default:
		printf("\"-%c\": invalid flag\n", *--s);
		exit(1);
		/*NOTREACHED*/
	}
}

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

	/*
	 * Initialize command line flags to their defaults.
	 */
	p_nbufs = NBUFS_PIPED;
	f_nbufs = NBUFS_NON_PIPED;
	clean_data = CLEAN_DATA;
	quiet = NOT_QUIET;
	search_from_top = SEARCH_FROM_TOP;
	eq_prompt = EQ_PROMPT;
	do_bs = DO_BS;
	first_cmd = NULL;
	know_dumb = KNOW_DUMB;

	/*
	 * Process command line arguments and LESS environment arguments.
	 * Command line arguments override environment arguments.
	 */
	arg_scan(getenv("LESS"));
	argc--;
	argv++;
	while (**argv == '-' || **argv == '+')
	{
		arg_scan(*argv++);
		if (--argc <= 0)
			break;
	}

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

	/*
	 * Set up terminal, etc.
	 */
	raw_mode(1);
	get_term();
	start_getc();

	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;
				putc('\n');  flush();
			} while (++curr_ac < ac);
		}
	}
	if (file >= 0)
		commands();
	quit();
}

/*
 * Exit the program.
 */
	public void
quit()
{
	/*
	 * Put cursor at bottom left corner, clear the line,
	 * reset the terminal modes, and exit.
	 */
	lower_left();
	clear_eol();
	lower_left();
	flush();
	raw_mode(0);
	exit(0);
}
SHAR_EOF
cat - << \SHAR_EOF > output.c
/*
 * High level routines dealing with the output to the screen.
 */

#include "less.h"

extern int signals;
	
/*
 * Display the line which is in the line buffer.
 */
	public void
put_line()
{
	register char *p;
	register int column;
	extern int auto_wrap, ignaw;

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

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

	column = 0;
	for (p = line;  *p != '\0';  p++)
	{
		putc(*p);
		if (*p == '\b')
			column--;
		else
			column++;
	}
	if (column < screen_width || !auto_wrap || ignaw)
		putc('\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.
 */
	public void
flush()
{
	write(1, obuf, ob-obuf);
	ob = obuf;
}

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

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

/*
 * Output a string.
 */
	public void
puts(s)
	register char *s;
{
	while (*s != '\0')
		putc(*s++);
}

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

static char return_to_continue[] = "  (press RETURN)";

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

	lower_left();
	clear_eol();
	enter_standout();
	puts(s);
	puts(return_to_continue);
	exit_standout();

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

	public int
error_width()
{
	/*
	 * Allow 2 extra spaces at each end for terminals that eat spaces 
	 * to enter/exit standout mode.
	 * Also don't use the last position, because some terminals
	 * will scroll if you write in the last char of the last line.
	 *
	 * {{ Could use termcap "sg" to determine how many spaces
	 *    the terminal eats for enter/exit standout.
	 *    Maybe something in termcap also tells whether terminal
	 *    scrolls if you write in lower right corner?
	 *    Probably not worth it to gain the (max) 5 extra spaces. }}
	 */
	return (screen_width - sizeof(return_to_continue) - 5);
}
SHAR_EOF
cat - << \SHAR_EOF > 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		/* {{ screen_height must be less than NPOS }} */
static POSITION table[NPOS];	/* The position table */


/*
 * 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;

	switch (where)
	{
	case TOP:
		n = 0;
		break;
	case TOP_PLUS_ONE:
		n = 1;
		break;
	case BOTTOM:
		n = screen_height - 2;
		break;
	case BOTTOM_PLUS_ONE:
		n = screen_height - 1;
		break;
	}
	return (table[n]);
}

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

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

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

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

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

	for (i = 0;  i < screen_height;  i++)
		table[i] = NULL_POSITION;
}
SHAR_EOF
cat - << \SHAR_EOF > prim.c
/*
 * Primitives for displaying the file on the screen.
 */

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

extern int quiet;
extern int search_from_top;
extern int signals;

/*
 * Display n lines, scrolling forward, 
 * starting at position pos in the input file.
 */
	static void
forw(n, pos, force)
	register int n;
	POSITION pos;
	int force;
{
	while (--n >= 0)
	{
		/*
		 * Read the next line of input.
		 */
		pos = forw_line(pos);
		if (line == NULL)
		{
			/*
			 * End of file: stop here unless the top line 
			 * is still empty, or "force" is true.
			 */
			if (!force && position(TOP) != NULL_POSITION)
			{
				if (quiet == NOT_QUIET)
					bell();
				else
					vbell();
				break;
			}
			pos = NULL_POSITION;
		}
		/*
		 * Add the position of the next line to the position table.
		 * Display the current line on the screen.
		 */
		add_pos_forw(pos);
		lower_left();
		put_line();
	}
}

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

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

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

/*
 * Display n more lines, backward.
 * Start just before the line currently displayed at the top of the screen.
 */
	public void
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.
 */
	public void
repaint()
{
	POSITION pos;

	/*
	 * Start at the line currently at the top of the screen
	 * and redisplay the screen.
	 */
	pos = position(TOP);
	clear();
	pos_clear();
	add_pos_forw(pos);
	forw(screen_height - 1, pos, 0);
}

/*
 * 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.
 */
	public void
jump_forw()
{
	POSITION pos;

	if (ch_seek_end())
	{
		/* Currently never happens, see ch_seek_end(). */
		error("Cannot get to end of file");
		return;
	}
	pos = ch_tell();
	clear();
	pos_clear();
	add_pos_back(pos);
	back(screen_height - 1, pos, 0);
}

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

	/*
	 * 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_get_forw()) != '\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. }}
	 */
	pos = ch_tell();
	clear();
	pos_clear();
	add_pos_forw(pos);
	forw(screen_height - 1, pos, 0);
}

/*
 * 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.
 */
	public void
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_get_back()) != '\n' && c != EOF)
		;
	if (c == '\n')
		(void) ch_get_forw();
	pos = ch_tell();

	/*
	 * Clear and paint the screen.
	 */
	clear();
	pos_clear();
	add_pos_forw(pos);
	forw(screen_height - 1, pos, 0);
}

/*
 * Search for the n-th occurence of a specified pattern, 
 * either forward (direction == '/'), or backwards (direction == '?').
 */
	public void
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 = screen_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 (search_from_top)
	{
		/*
		 * 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 = screen_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 (signals)
			/*
			 * 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 < screen_height)
	{
		/*
		 * Scroll forward.
		 */
		clear_eol();
		forw(lines_from_top-1, position(BOTTOM_PLUS_ONE), 1);
	} else if (lines_from_top < 0 && -lines_from_top < screen_height)
	{
		/*
		 * Scroll backwards.
		 */
		clear_eol();
		back(-lines_from_top, position(TOP), 1);
	} else
	{
		/*
		 * Clear and paint screen.
		 */
		clear();
		pos_clear();
		add_pos_forw(pos);
		forw(screen_height - 1, pos, 1);
	}
}

#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
SHAR_EOF
cat - << \SHAR_EOF > screen.c
/*
 * Routines which deal with the characteristics of the terminal.
 * Uses termcap to be as terminal-independent as possible.
 *
 * {{ Someday this should be rewritten to use curses. }}
 */

#include "less.h"

#if TERMIO
#include <termio.h>
#else
#include <sgtty.h>
#endif

/*
 * Strings passed to tputs() to do various terminal functions.
 */
static char
	*sc_pad,		/* Pad string */
	*sc_home,		/* Cursor home */
	*sc_addline,		/* Add line, scroll down following lines */
	*sc_lower_left,		/* Cursor to last line, first column */
	*sc_move,		/* General cursor positioning */
	*sc_clear,		/* Clear screen */
	*sc_clear_eol,		/* Clear to end of line */
	*sc_enter_standout,	/* Enter standout (highlighted) mode */
	*sc_exit_standout,	/* Exit standout mode */
	*sc_visual_bell,	/* Visual bell (flash screen) sequence */
	*sc_backspace;		/* Backspace cursor */
static int dumb;
static int hard;

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 */

/*
 * 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*/ short ospeed;	/* Terminal output baud rate */
/*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 */
char *tgetstr();
char *tgoto();

/*
 * 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.
 */
	public void
raw_mode(on)
	int on;
{
#if TERMIO
	struct termio s;
	static struct termio save_term;

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

		/*
		 * Save modes and set certain variables dependent on modes.
		 */
		save_term = s;
		ospeed = s.c_cflag & CBAUD;
		erase_char = s.c_cc[VERASE];
		kill_char = s.c_cc[VKILL];

		/*
		 * Set the modes to the way we want them.
		 */
		s.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
		s.c_oflag |=  (OPOST|ONLCR|TAB3);
		s.c_oflag &= ~(OCRNL|ONOCR|ONLRET);
		s.c_cc[VMIN] = 1;
		s.c_cc[VTIME] = 0;
	} else
	{
		/*
		 * Restore saved modes.
		 */
		s = save_term;
	}
	ioctl(2, TCSETAW, &s);
#else
	struct sgttyb s;
	static struct sgttyb save_term;

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

		/*
		 * Save modes and set certain variables dependent on modes.
		 */
		save_term = s;
		ospeed = s.sg_ospeed;
		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|XTABS);
	} else
	{
		/*
		 * Restore saved modes.
		 */
		s = save_term;
	}
	ioctl(2, TIOCSETP, &s);
#endif
}

static int cannot_flag = 0;

	static void
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);
	cannot_flag = 1;
}

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

	char *getenv();

	/*
	 * Find out what kind of terminal this is.
	 */
	if (tgetent(termbuf, getenv("TERM")) <= 0)
		dumb = 1;

	/*
	 * Get size of the screen.
	 */
	if (dumb || (screen_height = tgetnum("li")) < 0 || tgetflag("hc"))
	{
		/* Oh no, this is a hardcopy terminal. */
		hard = 1;
		screen_height = 24;
	}
	if (dumb || (screen_width = tgetnum("co")) < 0)
		screen_width = 80;

	auto_wrap = tgetflag("am");
	ignaw = tgetflag("xn");

	/*
	 * Get various string-valued capabilities.
	 */
	sp = sbuf;

	sc_pad = (dumb) ? NULL : tgetstr("pc", &sp);
	if (sc_pad != NULL)
		PC = *sc_pad;

	sc_clear_eol = (dumb) ? NULL : tgetstr("ce", &sp);
	if (hard || sc_clear_eol == NULL || *sc_clear_eol == '\0')
	{
		cannot("clear to end of line");
		sc_clear_eol = "";
	}

	sc_clear = (dumb) ? NULL : tgetstr("cl", &sp);
	if (hard || sc_clear == NULL || *sc_clear == '\0')
	{
		cannot("clear screen");
		sc_clear = "\n\n";
	}

	sc_move = (dumb) ? NULL : tgetstr("cm", &sp);
	if (hard || sc_move == NULL || *sc_move == '\0')
	{
		/*
		 * This is not an error here, because we don't 
		 * always need sc_move.
		 * We need it only if we don't have home or lower-left.
		 */
		sc_move = "";
	}

	sc_enter_standout = (dumb) ? NULL : tgetstr("so", &sp);
	if (hard || sc_enter_standout == NULL)
		sc_enter_standout = "";

	sc_exit_standout = (dumb) ? NULL : tgetstr("se", &sp);
	if (hard || sc_exit_standout == NULL)
		sc_exit_standout = "";

	sc_visual_bell = (dumb) ? NULL : tgetstr("vb", &sp);
	if (hard || sc_visual_bell == NULL)
		sc_visual_bell = "";

	sc_home = (dumb) ? NULL : tgetstr("ho", &sp);
	if (hard || sc_home == NULL || *sc_home == '\0')
	{
		if (*sc_move == '\0')
		{
			cannot("home cursor");
			/*
			 * This last resort for sc_home is supposed to
			 * suggest moving to the top of the "virtual screen".
			 */
			sc_home = "|\b^";		
		} else
		{
			/* 
			 * No "home" string,
			 * but we can use "move(0,0)".
			 */
			strcpy(sp, tgoto(sc_move, 0, 0));
			sc_home = sp;
			sp += strlen(sp) + 1;
		}
	}

	sc_lower_left = (dumb) ? NULL : tgetstr("ll", &sp);
	if (hard || sc_lower_left == NULL || *sc_lower_left == '\0')
	{
		if (*sc_move == '\0')
		{
			cannot("move cursor to lower left of screen");
			sc_lower_left = "\r";
		} else
		{
			/*
			 * No "lower-left" string, 
			 * but we can use "move(0,last-line)".
			 */
			strcpy(sp, tgoto(sc_move, 0, screen_height-1));
			sc_lower_left = sp;
			sp += strlen(sp) + 1;
		}
	}

	/*
	 * To add a line at top of screen and scroll the display down,
	 * we use "al" (add line) or "sr" (scroll reverse).
	 */
	if (dumb)
		sc_addline = NULL;
	else if ((sc_addline = tgetstr("al", &sp)) == NULL || 
		 *sc_addline == '\0')
		sc_addline = tgetstr("sr", &sp);

	if (hard || sc_addline == NULL || *sc_addline == '\0')
	{
		cannot("scroll backwards");
		sc_addline = "";
	}

	if (dumb || tgetflag("bs"))
		sc_backspace = "\b";
	else
	{
		sc_backspace = tgetstr("bc", &sp);
		if (sc_backspace == NULL || *sc_backspace == '\0')
			sc_backspace = "\b";
	}

	if (cannot_flag)
		/* Give him time to read all the "cannot" messages. */
		error("");
}


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


/*
 * Home cursor (move to upper left corner of screen).
 */
	public void
home()
{
	tputs(sc_home, 1, putc);
}

/*
 * Add a blank line (called with cursor at home).
 * Should scroll the display down.
 */
	public void
add_line()
{
	tputs(sc_addline, screen_height, putc);
}

/*
 * Move cursor to lower left corner of screen.
 */
	public void
lower_left()
{
	tputs(sc_lower_left, 1, putc);
}

/*
 * Ring the terminal bell.
 */
	public void
bell()
{
	if (quiet == VERY_QUIET)
		vbell();
	else
		putc('\7');
}

/*
 * Output the "visual bell", if there is one.
 */
	public void
vbell()
{
	if (*sc_visual_bell == '\0')
		return;
	tputs(sc_visual_bell, screen_height, putc);
}

/*
 * Clear the screen.
 */
	public void
clear()
{
	tputs(sc_clear, screen_height, putc);
}

/*
 * Clear from the cursor to the end of the cursor's line.
 */
	public void
clear_eol()
{
	tputs(sc_clear_eol, 1, putc);
}

/*
 * Begin "standout" (bold, underline, or whatever).
 */
	public void
enter_standout()
{
	tputs(sc_enter_standout, 1, putc);
}

/*
 * End "standout".
 */
	public void
exit_standout()
{
	tputs(sc_exit_standout, 1, putc);
}

/*
 * Erase the character to the left of the cursor 
 * and move the cursor left.
 */
	public void
backspace()
{
	/* 
	 * Assume we can erase a character by overstriking with a space.
	 */
	tputs(sc_backspace, 1, putc);
	putc(' ');
	tputs(sc_backspace, 1, putc);
}
SHAR_EOF
cat - << \SHAR_EOF > 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;

public int signals;
#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);
	signals |= S_INTERRUPT;
	if (reading)
		psignals();
}

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

/*
 * "Continue signal handler.
 */
	static HANDLER
cont()
{
	SIGNAL(SIGTSTP, stop);
	SIGNAL(SIGCONT, cont);
	raw_mode(1);
	first_cmd = "r";	/* Repaint when we get back to main cmd loop */
	longjmp(main_loop, 1);
}
#endif


	public void
init_signals()
{
	(void) SIGNAL(SIGINT, interrupt);
#ifdef SIGTSTP
	(void) SIGNAL(SIGTSTP, stop);
	(void) SIGNAL(SIGCONT, cont);
#endif
}

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

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

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

#ifdef SIGTSTP
	if (tsignals & S_STOP)
	{
		lower_left();
		clear_eol();
		flush();
		raw_mode(0);
		SIGNAL(SIGTSTP, SIG_DFL);
		kill(getpid(), SIGTSTP);
		return;
		/*
		 * The "continue" signal handler cont() will gain
		 * control when we are resumed.  Cont() will longjmp
		 * back to the main command loop.
		 */
	}
#endif
	if (tsignals & S_INTERRUPT)
	{
		bell();
		/*
		 * {{ You may prefer this to the bell(): }}
		 * error("Interrupt");
		 */
	}

	longjmp(main_loop, 1);
}
SHAR_EOF
cat - << \SHAR_EOF > 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. }}
 */
public int reading;

static int tty;

/*
 * Open keyboard for input.
 * (Just use file descriptor 2.)
 */
	public void
start_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);
}
SHAR_EOF
cat - << \SHAR_EOF > 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.
 *
 *	-----------------------------------------------------------------
 *
 *	usage:  less [-qQmtcu] [-b[fp]<n>] [+<cmd>] [file-name] ...
 *
 *	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
 *	-----------------------------------------------------------------
 */

char version[] = "@(#) less  version 23";
SHAR_EOF
cat - << \SHAR_EOF > funcs.h
	public void edit ();
	public char * eq_message ();
	public void next_file ();
	public void prev_file ();
	public void toggle_flag ();
	public void quit ();
	public void forward ();
	public void backward ();
	public void repaint ();
	public void jump_forw ();
	public void jump_back ();
	public void jump_percent ();
	public void search ();
	public int ch_seek ();
	public int ch_seek_end ();
	public POSITION ch_length ();
	public POSITION ch_tell ();
	public int ch_get_forw ();
	public int ch_get_back ();
	public void ch_init ();
	public POSITION position ();
	public void add_pos_forw ();
	public void add_pos_back ();
	public void pos_clear ();
	public POSITION forw_line ();
	public POSITION back_line ();
	public void put_line ();
	public int control_char ();
	public int carat_char ();
	public void flush ();
	public void dropout ();
	public void putc ();
	public void puts ();
	public void error ();
	public int error_width ();
	public void raw_mode ();
	public void get_term ();
	public void home ();
	public void add_line ();
	public void lower_left ();
	public void bell ();
	public void vbell ();
	public void clear ();
	public void clear_eol ();
	public void enter_standout ();
	public void exit_standout ();
	public void backspace ();
	public void prewind ();
	public void pappend ();
	public int plong ();
	public void init_signals ();
	public void  psignals ();
	public void help ();
	public void start_getc ();
	public int getc ();
	public void commands ();
SHAR_EOF
cat - << \SHAR_EOF > less.h
/*
 * Standard include file for "less".
 */

#define	public		/* PUBLIC FUNCTION */
typedef long		POSITION;
#define	END_POSITION	((POSITION)(-2))
#define	NULL_POSITION	((POSITION)(-1))

#define	EOF		(0)
#define	NULL		(0)

#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 */

extern int file;
extern int pipe;
extern char *line;
extern int screen_height, screen_width;

#define	SIGNAL(sig,func)	signal(sig,func)

off_t lseek();

#include "funcs.h"
SHAR_EOF
cat - << \SHAR_EOF > 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
SHAR_EOF
cat - << \SHAR_EOF > mkfuncs.awk
BEGIN { FS="("; state = 0 }

/^	public/ { ftype = $0; state = 1 }

{ if (state == 1)
	state = 2
  else if (state == 2)
	{ print ftype,$1,"();"; state = 0 }
}
SHAR_EOF

frodo@wcom.UUCP (Jim Scardelis) (06/05/85)

> This is a pagination program similar to more or pg.
> It has been run on 4.1BSD, 4.2BSD and System V, 
> but has not been tested on any other system.
> To install, delete everything down to the cut line below, 
> put the file in an empty directory, and run sh on the file.
> Then read the file called INSTALLATION.
> Problems, complaints, praise to the author.
> 
> 	Mark Nudelman		nsc!nsc-pdc!mark
> 	National Semiconductor	tektronix!reed!nsc-pdc!mark
> 

Attached is a shar archive containing a diff file "screen.diff" to be
applied to screen.c, and "makefile.xen" for compiling less under
Xenix 3.0 on an IBM PC/AT.



# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by frodo on Wed Jun  5 00:08:07 EDT 1985
# Contents:  screen.diff makefile.xen
 
echo x - screen.diff
sed 's/^@//' > "screen.diff" <<'@//E*O*F screen.diff//'
8a9,12
> #if XENIX
> #include <sys/types.h>
> #include <sys/ioctl.h>
> #endif
@//E*O*F screen.diff//
chmod u=rw,g=r,o=r screen.diff
 
echo x - makefile.xen
sed 's/^@//' > "makefile.xen" <<'@//E*O*F makefile.xen//'
# Makefile for "less"
#
# Invoked as:
#	make all
#   or	make install
# Plain "make" is equivalent to "make all".
#
# If you add or delete functions, remake funcs.h by doing:
#	make newfuncs
# This depends on the coding convention of function headers looking like:
#	" \t public <function-type> \n <function-name> ( ... ) "
#
# Also provided:
#	make lint	# Runs "lint" on all the sources.
#	make clean	# Removes "less" and the .o files.
#	make clobber	# Pretty much the same as make "clean".

##########################################################################

# System-specific parameters

# Define XENIX if running under XENIX 3.0
XENIX = 1

# TERMIO is 1 if your system has /usr/include/termio.h.
# This is normally the case for System 5.
# If TERMIO is 0 your system must have /usr/include/sgtty.h.
# This is normally the case for BSD.
TERMIO = 1

# off_t is the type which lseek() returns.
# It is also the type of lseek()'s second argument.
off_t = long

# REGCMP is 1 if your system has the regcmp() function.
# This is normally the case for System 5.
# RECOMP is 1 if your system has the re_comp() function.
# This is normally the case for BSD.
# If neither is 1, pattern matching is supported, but without metacharacters.
REGCMP = 1
RECOMP = 0

# LIBS is the list of libraries needed.
LIBS = -lcurses -ltermlib

# INSTALL_LESS is a list of the public versions of less.
# INSTALL_MAN is a list of the public versions of the manual page.
INSTALL_LESS =	/usr/lbin/less
INSTALL_MAN =	/usr/man/manl/less.l

# OPTIM is passed to the compiler and the loader.
# It is normally "-O" but may be, for example, "-g".
OPTIM = -O

##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-Doff_t=$(off_t)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)"\
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)

##########################################################################

SRC =	main.c prim.c ch.c position.c input.c output.c screen.c \
	line.c signal.c help.c ttyin.c command.c version.c
OBJ =	main.o prim.o ch.o position.o input.o output.o screen.o \
	line.o signal.o help.o ttyin.o command.o version.o

##########################################################################

all: less

less: $(OBJ)
	cc $(OPTIM) -o less $(OBJ) $(LIBS)

install: install_man install_less

install_man: less.l
	for f in $(INSTALL_MAN); do  rm -f $$f; cp less.l $$f;  done
	touch install_man
	
install_less: less
	for f in $(INSTALL_LESS); do  rm -f $$f; cp less $$f;  done
	touch install_less

$(OBJ): less.h funcs.h

lint:
	lint -h $(DEFS) $(SRC)

newfuncs:
	mv funcs.h funcs.h.OLD
	awk -f mkfuncs.awk $(SRC) >funcs.h

clean:
	rm -f $(OBJ) less

clobber:
	rm -f *.o less install_less install_man
@//E*O*F makefile.xen//
chmod u=rw,g=r,o=r makefile.xen
 
exit 0
-- 

uucp: {vax135|ihnp4}!timeinc!wcom!frodo		
ARPA: 1891@NJIT-EIES.MAILNET@MIT-MULTICS.ARPA
"The opinions expressed herein are those of my computer, and not necessarily
      those of myself, Warner Computer Systems, or any other computer or
        company along the line. "