[net.sources] less, part 1 of 2

mark@nsc-pdc.UUCP (Mark Nudelman) (07/10/85)

This is a new version of the program "less" which I posted 
about a month ago.  It is a pagination program similar to 
more or pg.  This distribution is in 2 parts to avoid
truncation problems in transmission; this is part 1.

This version has some bug fixes and some new features.
The major new feature is better handling of underlines
and backspaces.  It should also be somewhat more portable; 
in particular, I think all symbols are now unique in 6 
characters (yuch).

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, bug fixes 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.bsd41 
#	makefile.bsd42 
#	makefile.sys5 
#	makefile.xen 
#	ch.c 
#	command.c 
#	help.c 
#	input.c 
#	line.c 
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.2bsd:
	cp makefile.bsd42 makefile
   If your system is Berkeley 4.1bsd:
	cp makefile.bsd41 makefile
   If your system is Xenix 3.0:
	cp makefile.xen makefile
   Otherwise, edit the makefile to make the 
   system parameters match your system.

   There is one features which is selectable at compile time: shell escapes.  
   If you want to have shell escapes, edit the makefile appropriately.

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 [-cdtmMqQuU] [-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 possible only if a file, rather than standard input, is being read.
It 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 second line displayed
(but see the -t option, which changes this).
.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 to 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, if you like 
more-style prompting, 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
Normally,
.I less
prompts with a colon.
The -m command line option causes 
.I less
to prompt verbosely like 
.I more,
printing the file name and percent into the file.
.PP
The -M command line option causes 
.I less
to prompt even more verbosely than 
.I more.
.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, underlined text is displayed 
using the terminal's hardware underlining capability.
If the terminal does not provide this, underlined text is
displayed in
reverse video, bold, or some other distinctive video attribute.
(Underlining, by the way, is defined as overstriking an underscore character.
One character overstrikes another if a backspace is between the two.)
Overstrikes other than underlines are displayed with real backspaces.
If the -u command line option is given, 
underlining is treated as any other overstrike; no standout mode is used.
If the -U command line option is given,
backspaces are printed as the two character sequence "^H".
.PP
Normally,
.I less
will complain if the terminal is dumb; that is, lacks some important capability,
such as the ability to clear the screen or scroll backwards.
The -d flag suppresses this complaint 
(but does not otherwise change the behavior of the program on a dumb 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.bsd41
# 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 = 0

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 0


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

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


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar INSTALLATION less.l makefile.* *.c *.h *.awk > less.shar
SHAR_EOF
cat - << \SHAR_EOF > makefile.bsd42
# 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 = 0

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 1


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

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


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar INSTALLATION less.l makefile.* *.c *.h *.awk > less.shar
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
##########################################################################

# Define XENIX if running under XENIX 3.0
XENIX = 0

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 0


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

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


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar INSTALLATION less.l makefile.* *.c *.h *.awk > less.shar
SHAR_EOF
cat - << \SHAR_EOF > 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

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 0


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

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


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar INSTALLATION less.l makefile.* *.c *.h *.awk > less.shar
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;
extern int pipe;

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

#include "less.h"

extern int erase_char, kill_char;
extern int pr_type;
extern int new_file;
extern int sigs;
extern int sc_width, sc_height;
extern char *first_cmd;
extern char version[];

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();
	putc(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))
			{
				putc('^');
				cmd_col++;
				c = carat_char(c);
			}
			putc(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.
 */
	static void
cmd_exec()
{
	lower_left();
	flush();
}

/*
 * Display the appropriate prompt.
 */
	static void
prompt()
{
	int what;

	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.
	 */
	if (pr_type == PR_LONG)
	{
		what = MNAME|MOF|MBYTE|MPCT;
	} else if (new_file)
	{
		what = MNAME|MOF|MPCT;
		new_file = 0;
	} else if (pr_type == PR_MEDIUM)
	{
		what = MNAME|MPCT;
	} else /* (pr_type == PR_SHORT) */
	{
		putc(':');
		return;
	}
	so_enter();
	puts(eq_message(sc_width-1, what));
	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.
 */
	public void
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 */

		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 special case 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();
					putc('\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.
			 */
			lower_left();
			clear_eol();
			forward(sc_height - 1);
			break;

		case 'b':
			/*
			 * Backward one screen.
			 */
			lower_left();
			clear_eol();
			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;
			lower_left();
			clear_eol();
			forward(n);
			break;

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

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

		case 'u':
			/*
			 * Forward N lines 
			 * (default same as last 'd' or 'u' command).
			 */
			n = cmd_int();
			if (n > 0)
				scroll = n;
			lower_left();
			clear_eol();
			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(error_width(), MNAME|MOF|MBYTE|MPCT));
			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();
			putc(' ');	/* This looks nicer */
			cmd_col++;
			c = getcc();
			goto again;
			
#if SHELL_ESCAPE
		case '!':
			/*
			 * Shell escape.
			 */
			cmd_reset();
			mcc = '!';
			start_mcc();
			c = getcc();
			goto again;
#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_flag(c);
			break;

		default:
			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,d,t,m,M,u,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;
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');
	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);
	}

	/*
	 * 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);
}
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[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, especially involving long lines with underlining.
 * There are still some cases which will break it.
 *
 * There are four states:
 *	UL_NORMAL is the normal 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 ul_width, ue_width;
extern int sc_width, sc_height;

/*
 * Rewind the line buffer.
 */
	public void
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);

			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;
				curr++;
				column += ue_width;
				ul_state = UL_NORMAL;
			} 
			break;
		}
	}
	
	if (c == '\t') 
	{
		/*
		 * Expand a tab into spaces.
		 */
		do
		{
			NEW_COLUMN(column+1);
			*curr++ = ' ';
		} while ((column % 8) != 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);
}
SHAR_EOF

mark@nsc-pdc.UUCP (Mark Nudelman) (09/07/85)

A few days ago I asked if anyone wanted my latest copy of my
paginator, "less".  The response has been overwhelming, so
I am posting it here rather than mailing out copies.
As usual, please mail me any problems, complaints, suggestions
or praise.  Finally, many thanks to everyone who responded to
my request.  I did not realize the program was in such widespread
use.  My apologies if I don't answer everyone individually, but in
the 3 days since I posted the first message, I have had over 50 
replies (some of which have obviously incorrect reply paths...)
Anyway, here it is; enjoy!


Mark Nudelman			ihnp4!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.bsd41 
#	makefile.bsd42 
#	makefile.sys5 
#	makefile.xen 
#	funcs.h 
#	less.h 
#	position.h 
#	mkfuncs.awk 
#	main.c 
#	option.c 
#	prim.c 
#	ch.c 
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.2bsd:
	cp makefile.bsd42 makefile
   If your system is Berkeley 4.1bsd:
	cp makefile.bsd41 makefile
   If your system is Xenix 3.0:
	cp makefile.xen makefile
   Otherwise, edit the makefile to make the 
   system parameters match your system.

   These features are selectable at compile time:
	shell escapes (SHELL_ESCAPE)
	editor invocation (EDITOR)
   If you want to have either of these features, 
   edit the makefile appropriately.
   (If you do not include either SHELL_ESCAPE or EDITOR,
    you may wish to edit the manual page "less.l" to remove
    the references to the "!" and/or "v" commands.)

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	...!ihnp4!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 [-cdepstmMqQuU] [-b[fp]\fIn\fB] [-x\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.

.SH COMMANDS
.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 possible only if a file, rather than standard input, is being read.
It 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 second line displayed
(but see the -t option, which changes this).
.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 to 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
The following 
two 
commands may or may not be valid, depending on your particular installation.
.PP
.IP v
Invokes an editor to edit the current file being viewed.
The editor is taken from the environment variable EDITOR,
or defaults to "vi".
.PP
.IP "! shell-command"
Invokes a shell to run the shell-command given.
.PP
.SH OPTIONS
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, if you like 
more-style prompting, 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
.IP -s
The -s flag causes
consecutive blank lines to be squeezed into a single blank line.
This is useful when viewing
.I nroff
output.
.IP -t
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.
.IP -m
Normally,
.I less
prompts with a colon.
The -m command line option causes 
.I less
to prompt verbosely like 
.I more,
printing the file name and percent into the file.
.IP -M
The -M command line option causes 
.I less
to prompt even more verbosely than 
.I more.
.IP -q
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.
.IP -Q
Even if -q is given, 
.I 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.
.IP -e
Normally the only way to exit less is via the "q" command.
The -e command line option tells less to automatically exit
the second time it reaches end-of-file.
.IP -u
If the -u command line option is given, 
backspaces are treated as printable characters;
that is, they are sent to the terminal when they appear in the input.
.IP -U
If the -U command line option is given,
backspaces are printed as the two character sequence "^H".
If neither -u nor -U is given,
backspaces which appear adjacent to an underscore character
are treated specially:
the underlined text is displayed 
using the terminal's hardware underlining capability.
.IP -d
Normally,
.I less
will complain if the terminal is dumb; that is, lacks some important capability,
such as the ability to clear the screen or scroll backwards.
The -d flag suppresses this complaint 
(but does not otherwise change the behavior of the program on a dumb terminal).
.IP -p
Normally, 
.I less 
will repaint the screen by scrolling from the bottom of the screen.
If the -p flag is set, when
.I less 
needs to change the entire display, it will clear the screen
and paint from the top line down.
.IP -x
The -x\fIn\fR command line option sets tab stops every \fIn\fR positions.
The default for \fIn\fR is 8.
.IP -b
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.
.IP -c
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, causing an imperceptible speed improvement.
(However, if the data is not "clean", unpredicatable results may occur.)
.IP +
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.bsd41
# 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 = 0

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 0


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0

# EDITOR is 1 if you wish to allow editor invocation (the "v" command).
# (This is possible only if your system supplies the system() function.)
EDITOR = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

SRC1 =	main.c option.c prim.c ch.c 
SRC2 =	position.c input.c output.c screen.c \
	prompt.c line.c signal.c help.c ttyin.c command.c version.c
SRC =	$(SRC1) $(SRC2)
OBJ =	main.o option.o prim.o ch.o position.o input.o output.o screen.o \
	prompt.o line.o signal.o help.o ttyin.o command.o version.o


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" "-DEDITOR=$(EDITOR)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar INSTALLATION less.l makefile.* *.h *.awk $(SRC1) > less.shar.a
	shar $(SRC2) > less.shar.b
SHAR_EOF
cat - << \SHAR_EOF > makefile.bsd42
# 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 = 0

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 1


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0

# EDITOR is 1 if you wish to allow editor invocation (the "v" command).
# (This is possible only if your system supplies the system() function.)
EDITOR = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

SRC1 =	main.c option.c prim.c ch.c 
SRC2 =	position.c input.c output.c screen.c \
	prompt.c line.c signal.c help.c ttyin.c command.c version.c
SRC =	$(SRC1) $(SRC2)
OBJ =	main.o option.o prim.o ch.o position.o input.o output.o screen.o \
	prompt.o line.o signal.o help.o ttyin.o command.o version.o


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" "-DEDITOR=$(EDITOR)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar INSTALLATION less.l makefile.* *.h *.awk $(SRC1) > less.shar.a
	shar $(SRC2) > less.shar.b
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
##########################################################################

# Define XENIX if running under XENIX 3.0
XENIX = 0

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 0


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0

# EDITOR is 1 if you wish to allow editor invocation (the "v" command).
# (This is possible only if your system supplies the system() function.)
EDITOR = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

SRC1 =	main.c option.c prim.c ch.c 
SRC2 =	position.c input.c output.c screen.c \
	prompt.c line.c signal.c help.c ttyin.c command.c version.c
SRC =	$(SRC1) $(SRC2)
OBJ =	main.o option.o prim.o ch.o position.o input.o output.o screen.o \
	prompt.o line.o signal.o help.o ttyin.o command.o version.o


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" "-DEDITOR=$(EDITOR)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar INSTALLATION less.l makefile.* *.h *.awk $(SRC1) > less.shar.a
	shar $(SRC2) > less.shar.b
SHAR_EOF
cat - << \SHAR_EOF > 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

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 0


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0

# EDITOR is 1 if you wish to allow editor invocation (the "v" command).
# (This is possible only if your system supplies the system() function.)
EDITOR = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

SRC1 =	main.c option.c prim.c ch.c 
SRC2 =	position.c input.c output.c screen.c \
	prompt.c line.c signal.c help.c ttyin.c command.c version.c
SRC =	$(SRC1) $(SRC2)
OBJ =	main.o option.o prim.o ch.o position.o input.o output.o screen.o \
	prompt.o line.o signal.o help.o ttyin.o command.o version.o


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" "-DEDITOR=$(EDITOR)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar INSTALLATION less.l makefile.* *.h *.awk $(SRC1) > less.shar.a
	shar $(SRC2) > less.shar.b
SHAR_EOF
cat - << \SHAR_EOF > funcs.h
	public void edit ();
	public char * eq_message ();
	public void next_file ();
	public void prev_file ();
	public void quit ();
	public void init_option ();
	public void toggle_option ();
	public void scan_option ();
	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_end_seek ();
	public POSITION ch_length ();
	public POSITION ch_tell ();
	public int ch_forw_get ();
	public int ch_back_get ();
	public void ch_init ();
	public POSITION position ();
	public void add_forw_pos ();
	public void add_back_pos ();
	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 init ();
	public void deinit ();
	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 so_enter ();
	public void so_exit ();
	public void ul_enter ();
	public void ul_exit ();
	public void backspace ();
	public void putbs ();
	public char * eq_message ();
	public char * pr_string ();
	public void prewind ();
	public int pappend ();
	public void init_signals ();
	public void  psignals ();
	public void help ();
	public void open_getc ();
	public int getc ();
	public void commands ();
SHAR_EOF
cat - << \SHAR_EOF > less.h
/*
 * Standard include file for "less".
 */

/*
 * Language details.
 */
#if !VOID
#define	void  int
#endif
#define	public		/* PUBLIC FUNCTION */

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

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

/* 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 normal char; 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 special */
#define	UL_CHAR		'\201'	/* Enter underline mode */
#define	UE_CHAR		'\202'	/* Exit underline mode */

#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
cat - << \SHAR_EOF > 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.
 */
	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 %.*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.
 */
	public void
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.
 */
	public void
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.
 */
	static void
cat_file()
{
	register int c;

	while ((c = ch_forw_get()) != EOF)
		putc(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;
				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();
	deinit();
	flush();
	raw_mode(0);
	exit(0);
}
SHAR_EOF
cat - << \SHAR_EOF > 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 associated 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.
 */
	public void
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.
 */
	public void
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.
 */
	public void
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);
}
SHAR_EOF
cat - << \SHAR_EOF > 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.
 */
	static void
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))
			puts("...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)
			{
				if (quiet == NOT_QUIET)
					bell();
				else
					vbell();
				break;
			}
		}
		/*
		 * 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.
 */
	static void
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)
			{
				if (quiet == NOT_QUIET)
					bell();
				else
					vbell();
				break;
			}
		}
		/*
		 * 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();
	}
}

/*
 * 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)
	{
		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.
 */
	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, starting from a specified position.
 */
	static void
prepaint(pos)	
	POSITION pos;
{
	hit_eof = 0;
	forw(sc_height-1, pos, 0);
}

/*
 * Repaint the screen.
 */
	public void
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.
 */
	public void
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.
 */
	public void
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.
 */
	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_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 == '?').
 */
	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 = 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), 1);
	} else if (lines_from_top < 0 && -lines_from_top < sc_height)
	{
		/*
		 * Scroll backwards.
		 */
		clear_eol();
		back(-lines_from_top, position(TOP), 1);
	} 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
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;
extern int pipe;

/*
 * 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 %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)));
	}
	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_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.
 */
	public void
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);	/* ? */
}
SHAR_EOF

jimb@amdcad.UUCP (Jim Budler) (09/19/85)

I made a few changes to version 40, which I called 40a and 40b.

40a :  Allows a terminal without reverse scroll or insert line
	to use less with the penalty of repainting the screen
	after every upward scroll command. i.e. if you go up one
	page the screen is repainted, but if you go up one line
	it is also repainted.  It still works the same way it 
	did when used with a smarter terminal.

40b :  Used execl() instead of system() to call the editor.  Added
	some signal handling around the fork() call to allow all this
	to work properly if you enter the editor, then stop the editor.

	One bad hack:  I #defined vfork() fork() near the beginning of
	command.c.  I should have done it in the Makefile.
----------------------< cut here >----------------------------------
*** /tmp/,RCSt1011254	Wed Sep 18 15:37:20 1985
--- command.c	Wed Sep 18 13:52:56 1985
***************
*** 3,8
   */
  
  #include "less.h"
  
  extern int erase_char, kill_char;
  extern int pr_type;

--- 3,11 -----
   */
  
  #include "less.h"
+ #include <sys/wait.h>
+ #include <signal.h>
+ #define fork() vfork()
  
  extern int erase_char, kill_char;
  extern int pr_type;
***************
*** 204,209
  	register int n;
  	register int scroll = 10;
  	register int last_mcc = 0;
  
  	mcc = 0;
  

--- 207,213 -----
  	register int n;
  	register int scroll = 10;
  	register int last_mcc = 0;
+ 	int id;
  
  	mcc = 0;
  
***************
*** 509,516
  			clear_eol();
  			flush();
  			raw_mode(0);
! 			sprintf(cmdbuf, "%s %s", editor, current_file);
! 			system(cmdbuf);
  			raw_mode(1);
  			first_cmd = "R";
  			break;

--- 513,531 -----
  			clear_eol();
  			flush();
  			raw_mode(0);
! 			if ( fork() == 0 ) {
! 				execl(editor, editor, current_file, 0 );
! 				write ( 2, "exec failed\n", 12);
! 				_exit(1);
! 			}
! 			signal(SIGINT,SIG_IGN);
! 			signal(SIGQUIT,SIG_IGN);
! #ifdef SIGTSTP
! 			signal(SIGTSTP,SIG_DFL);
! #endif
! 
! 			wait(0);
! 			init_signals();
  			raw_mode(1);
  			first_cmd = "R";
  			break;
*** /tmp/,RCSt1011265	Wed Sep 18 15:38:21 1985
--- prim.c	Thu Sep 12 14:50:24 1985
***************
*** 6,11
  #include "position.h"
  
  public int hit_eof;
  
  extern int quiet;
  extern int top_search;

--- 6,12 -----
  #include "position.h"
  
  public int hit_eof;
+ extern int noaddline;
  
  extern int quiet;
  extern int top_search;
***************
*** 116,124
  		 * Display the line on the screen.
  		 */
  		add_back_pos(pos);
! 		home();
! 		add_line();
! 		put_line();
  	}
  }
  

--- 117,131 -----
  		 * Display the line on the screen.
  		 */
  		add_back_pos(pos);
! 		if ( noaddline == 0 ) {
! 			home();
! 			add_line();
! 			put_line();
! 		}
! 	}
! 	if ( noaddline == 1) {
! 		clear();
! 		repaint();
  	}
  }
  
*** /tmp/,RCSt1011277	Wed Sep 18 15:40:09 1985
--- screen.c	Thu Sep 12 15:17:11 1985
***************
*** 39,44
  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 */

--- 39,45 -----
  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 noaddline = 0;		/* Terminal doesn't reverse scroll */
***************
*** 41,46
  
  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 */

--- 42,48 -----
  
  public int auto_wrap;		/* Terminal does \r\n when write past margin */
  public int ignaw;		/* Terminal ignores \n immediately after wrap */
+ public int noaddline = 0;		/* Terminal doesn't reverse scroll */
  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 */
***************
*** 323,328
  	{
  		cannot("scroll backwards");
  		sc_addline = "";
  	}
  
  	if (dumb || tgetflag("bs"))

--- 325,331 -----
  	{
  		cannot("scroll backwards");
  		sc_addline = "";
+ 		noaddline = 1;
  	}
  
  	if (dumb || tgetflag("bs"))
*** /tmp/,RCSt1011289	Wed Sep 18 15:41:31 1985
--- version.c	Wed Sep 18 14:59:07 1985
***************
*** 69,74
   *	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
   *	-----------------------------------------------------------------
   */
  

--- 69,76 -----
   *	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
+  *      v40a: Added repaint to reverse scrolling for 'dumb' 9/12/85 jcb
+  *      v40b: Replaced system() with fork() and execl() for editor 9/18/85 jcb
   *	-----------------------------------------------------------------
   */
  
***************
*** 72,75
   *	-----------------------------------------------------------------
   */
  
! char version[] = "@(#) less  version 40";

--- 74,77 -----
   *	-----------------------------------------------------------------
   */
  
! char version[] = "@(#) less  version 40b";
-- 
 Jim Budler
 Advanced Micro Devices, Inc.
 (408) 749-5806
 UUCPnet: {ucbvax,decwrl,ihnp4,allegra,intelca}!amdcad!jimb
 Compuserve:	72415,1200

"... Don't sue me, I'm just the piano player!...."

mark@nsc-pdc.UUCP (Mark Nudelman) (11/19/85)

This is the latest version of less, a paginator similar to more or pg.
Summarized below are the improvements over the last posted version
(version 40, later patched to 41).  This distribution is in two parts.

Comments, suggestions, bugs may be sent to me.  However I will be off
the net for at least several weeks starting November 25;  after that
I will have a new net address.  Hopefully any mail sent to my old
address during that time will reach me when I return to the net.

Enjoy!

Mark Nudelman			for now:   ihnp4!nsc!nsc-pdc!mark
National Semiconductor		           tektronix!reed!nsc-pdc!mark


Summary of changes:

	* New commands (m and ') to mark a line and return
	  to a previously marked line, as in vi.

	* A new command line option, -hN, specfies that backwards 
	  scrolls of more than N lines should be displayed in a 
	  forwards direction instead.  For terminals which cannot 
	  scroll backwards, -h0 is assumed to force ALL scrolls
	  to be in a forwards direction.

	* A new syntax for the +cmd command line option: if
	  2 pluses are given (i.e. ++cmd), the cmd applies to
	  ALL files viewed, not just the first one.

	* A new command line option, -w, suppresses the tildes
	  which represent lines after end-of-file.

	* Less now remembers the length of standard input
	  (a pipe) but ONLY after you have reached end-of-file.

	* The 'f' and 'b' commands now take as an argument the
	  number of lines to move.  If more than a screenful is 
	  specified, only the last screenful is displayed.

	* The end of file indicator (END) is now more reliable.

	* Other bug fixes.

#!/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.bsd41
#	makefile.bsd42
#	makefile.sys5
#	makefile.xen
#	funcs.h
#	less.h
#	position.h
#	mkfuncs.awk
#	main.c
#	option.c
#	prim.c
echo shar: extracting INSTALLATION
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.2bsd:
	cp makefile.bsd42 makefile
   If your system is Berkeley 4.1bsd:
	cp makefile.bsd41 makefile
   If your system is Xenix 3.0:
	cp makefile.xen makefile
   Otherwise, edit the makefile to make the 
   system parameters match your system.

   These features are selectable at compile time:
	shell escapes (SHELL_ESCAPE)
	editor invocation (EDITOR)
	alternate error message handling (ONLY_RETURN)
   If you want to have any of these features, 
   edit the makefile appropriately.
   (If you do not include either SHELL_ESCAPE or EDITOR,
    you may wish to edit the manual page "less.l" to remove
    the references to the "!" and/or "v" commands.)

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	...!ihnp4!nsc!nsc-pdc!mark

Note to hackers: comments noting possible improvements are enclosed
in double curly brackets {{ like this }}.
SHAR_EOF
echo shar: extracting less.l
cat - << \SHAR_EOF > less.l
.TH LESS l
.SH NAME
less \- opposite of more
.SH SYNOPSIS
.B "less [-cdepstwmMqQuU] [-h\fIn\fB] [-b[fp]\fIn\fB] [-x\fIn\fB] [+\fIcmd\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.

.SH COMMANDS
.IP h
Help: display a summary of these commands.
If you forget all the other commands, remember this one.
.PP
.IP SPACE
Scroll forward N lines, default one screen.
If N is more than the screen size, only one screenful is displayed.
.PP
.IP f
Same as SPACE.
.PP
.IP b
Scroll backward N lines, default one screen.
If N is more than the screen size, only one screenful is displayed.
.PP
.IP RETURN
Scroll forward N lines, default 1.
If N is more than the screen size, the entire N lines are displayed.
.PP
.IP e
Same as RETURN.
.PP
.IP j
Also the same as RETURN.
.PP
.IP y
Scroll backward N lines, default 1.
If N is more than the screen size, the entire N lines are displayed.
.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 possible if standard input is being read,
but only if
.I less
has already read to the end of the file.
It is always fast, but not always useful.)
.PP
.IP %
Same as p.
.PP
.IP m
Followed by any lowercase letter, marks the current position with that letter.
.PP
.IP "'"
Followed by any lowercase letter, returns to the position which
was previously marked with that letter.
All marks are lost when a new file is examined.
.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 second line displayed
(but see the -t option, which changes this).
.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 to 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
The following 
two 
commands may or may not be valid, depending on your particular installation.
.PP
.IP v
Invokes an editor to edit the current file being viewed.
The editor is taken from the environment variable EDITOR,
or defaults to "vi".
.PP
.IP "! shell-command"
Invokes a shell to run the shell-command given.
.PP
.SH OPTIONS
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, if you like 
more-style prompting, 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
.IP -s
The -s flag causes
consecutive blank lines to be squeezed into a single blank line.
This is useful when viewing
.I nroff
output.
.IP -t
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.
.IP -m
Normally,
.I less
prompts with a colon.
The -m command line option causes 
.I less
to prompt verbosely like 
.I more,
printing the file name and percent into the file.
.IP -M
The -M command line option causes 
.I less
to prompt even more verbosely than 
.I more.
.IP -q
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.
.IP -Q
Even if -q is given, 
.I 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.
.IP -e
Normally the only way to exit less is via the "q" command.
The -e command line option tells less to automatically exit
the second time it reaches end-of-file.
.IP -u
If the -u command line option is given, 
backspaces are treated as printable characters;
that is, they are sent to the terminal when they appear in the input.
.IP -U
If the -U command line option is given,
backspaces are printed as the two character sequence "^H".
If neither -u nor -U is given,
backspaces which appear adjacent to an underscore character
are treated specially:
the underlined text is displayed 
using the terminal's hardware underlining capability.
.IP -w
Normally,
.I less
uses a tilde character to represent lines past the end of the file.
The -w option causes blank lines to be used instead.
.IP -d
Normally,
.I less
will complain if the terminal is dumb; that is, lacks some important capability,
such as the ability to clear the screen or scroll backwards.
The -d flag suppresses this complaint 
(but does not otherwise change the behavior of the program on a dumb terminal).
.IP -p
Normally, 
.I less 
will repaint the screen by scrolling from the bottom of the screen.
If the -p flag is set, when
.I less 
needs to change the entire display, it will clear the screen
and paint from the top line down.
.IP -h
Normally,
.I less
will scroll backwards when backwards movement is necessary.
The -h option specifies a maximum number of lines to scroll backwards.
If it is necessary to move backwards more than this many lines,
the screen is repainted in a forward direction.
(If the terminal does not have the ability to scroll
backwards, -h0 is implied.)
.IP -x
The -x\fIn\fR command line option sets tab stops every \fIn\fR positions.
The default for \fIn\fR is 8.
.IP -b
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.
.IP -c
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, causing an imperceptible speed improvement.
(However, if the data is not "clean", unpredicatable results may occur.)
.IP +
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).
If the option starts with \fB++\fR, the initial command applies to
every file being viewed, not just the first one.

.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.
SHAR_EOF
echo shar: extracting makefile.bsd41
cat - << \SHAR_EOF > makefile.bsd41
# 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 = 0

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 0


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0

# EDITOR is 1 if you wish to allow editor invocation (the "v" command).
# (This is possible only if your system supplies the system() function.)
# EDIT_PGM is the name of the (default) editor to be invoked.
EDITOR = 0
EDIT_PGM = /usr/ucb/vi

# ONLY_RETURN is 1 if you want RETURN to be the only input which
# will continue past an error message.
# Otherwise, any key will continue past an error message.
ONLY_RETURN = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

SRC1 =	main.c option.c prim.c 
SRC2 =	ch.c position.c input.c output.c screen.c \
	prompt.c line.c signal.c help.c ttyin.c command.c version.c
SRC =	$(SRC1) $(SRC2)
OBJ =	main.o option.o prim.o ch.o position.o input.o output.o screen.o \
	prompt.o line.o signal.o help.o ttyin.o command.o version.o


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \
	"-DEDITOR=$(EDITOR)" "-DEDIT_PGM=\"$(EDIT_PGM)\"" \
	"-DONLY_RETURN=$(ONLY_RETURN)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar -v INSTALLATION less.l makefile.* *.h *.awk $(SRC1) > less.shar.a
	shar -v $(SRC2) > less.shar.b
SHAR_EOF
echo shar: extracting makefile.bsd42
cat - << \SHAR_EOF > makefile.bsd42
# 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 = 0

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 1


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0

# EDITOR is 1 if you wish to allow editor invocation (the "v" command).
# (This is possible only if your system supplies the system() function.)
# EDIT_PGM is the name of the (default) editor to be invoked.
EDITOR = 0
EDIT_PGM = /usr/ucb/vi

# ONLY_RETURN is 1 if you want RETURN to be the only input which
# will continue past an error message.
# Otherwise, any key will continue past an error message.
ONLY_RETURN = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

SRC1 =	main.c option.c prim.c
SRC2 =	ch.c position.c input.c output.c screen.c \
	prompt.c line.c signal.c help.c ttyin.c command.c version.c
SRC =	$(SRC1) $(SRC2)
OBJ =	main.o option.o prim.o ch.o position.o input.o output.o screen.o \
	prompt.o line.o signal.o help.o ttyin.o command.o version.o


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \
	"-DEDITOR=$(EDITOR)" "-DEDIT_PGM=\"$(EDIT_PGM)\"" \
	"-DONLY_RETURN=$(ONLY_RETURN)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar -v INSTALLATION less.l makefile.* *.h *.awk $(SRC1) > less.shar.a
	shar -v $(SRC2) > less.shar.b
SHAR_EOF
echo shar: extracting makefile.sys5
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
##########################################################################

# Define XENIX if running under XENIX 3.0
XENIX = 0

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 0


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0

# EDITOR is 1 if you wish to allow editor invocation (the "v" command).
# (This is possible only if your system supplies the system() function.)
# EDIT_PGM is the name of the (default) editor to be invoked.
EDITOR = 0
EDIT_PGM = /usr/ucb/vi

# ONLY_RETURN is 1 if you want RETURN to be the only input which
# will continue past an error message.
# Otherwise, any key will continue past an error message.
ONLY_RETURN = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

SRC1 =	main.c option.c prim.c 
SRC2 =	ch.c position.c input.c output.c screen.c \
	prompt.c line.c signal.c help.c ttyin.c command.c version.c
SRC =	$(SRC1) $(SRC2)
OBJ =	main.o option.o prim.o ch.o position.o input.o output.o screen.o \
	prompt.o line.o signal.o help.o ttyin.o command.o version.o


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \
	"-DEDITOR=$(EDITOR)" "-DEDIT_PGM=\"$(EDIT_PGM)\"" \
	"-DONLY_RETURN=$(ONLY_RETURN)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar -v INSTALLATION less.l makefile.* *.h *.awk $(SRC1) > less.shar.a
	shar -v $(SRC2) > less.shar.b
SHAR_EOF
echo shar: extracting makefile.xen
cat - << \SHAR_EOF > 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

# VOID is 1 if your C compiler supports the "void" type,
# 0 if it does not.
VOID = 1

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

# 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

# SIGSETMASK is 1 if your system has the sigsetmask() call.
# This is normally the case only for BSD 4.2,
# not for BSD 4.1 or System 5.
SIGSETMASK = 0


##########################################################################
# Optional and semi-optional features
##########################################################################

# 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

# SHELL_ESCAPE is 1 if you wish to allow shell escapes.
# (This is possible only if your system supplies the system() function.)
SHELL_ESCAPE = 0

# EDITOR is 1 if you wish to allow editor invocation (the "v" command).
# (This is possible only if your system supplies the system() function.)
# EDIT_PGM is the name of the (default) editor to be invoked.
EDITOR = 0
EDIT_PGM = /usr/ucb/vi

# ONLY_RETURN is 1 if you want RETURN to be the only input which
# will continue past an error message.
# Otherwise, any key will continue past an error message.
ONLY_RETURN = 0


##########################################################################
# Compilation environment.
##########################################################################

# 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


##########################################################################
# Files
##########################################################################

SRC1 =	main.c option.c prim.c 
SRC2 =	ch.c position.c input.c output.c screen.c \
	prompt.c line.c signal.c help.c ttyin.c command.c version.c
SRC =	$(SRC1) $(SRC2)
OBJ =	main.o option.o prim.o ch.o position.o input.o output.o screen.o \
	prompt.o line.o signal.o help.o ttyin.o command.o version.o


##########################################################################
# Rules
##########################################################################

DEFS =	"-DTERMIO=$(TERMIO)" \
	"-DSIGSETMASK=$(SIGSETMASK)" \
	"-Doff_t=$(off_t)" "-DVOID=$(VOID)" \
	"-DREGCMP=$(REGCMP)" "-DRECOMP=$(RECOMP)" \
	"-DSHELL_ESCAPE=$(SHELL_ESCAPE)" \
	"-DEDITOR=$(EDITOR)" "-DEDIT_PGM=\"$(EDIT_PGM)\"" \
	"-DONLY_RETURN=$(ONLY_RETURN)" \
	"-DXENIX=$(XENIX)"

CFLAGS = $(OPTIM) $(DEFS)


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 -hp $(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:
	shar -v INSTALLATION less.l makefile.* *.h *.awk $(SRC1) > less.shar.a
	shar -v $(SRC2) > less.shar.b
SHAR_EOF
echo shar: extracting funcs.h
cat - << \SHAR_EOF > funcs.h
	public void edit ();
	public void next_file ();
	public void prev_file ();
	public void quit ();
	public void init_option ();
	public void toggle_option ();
	public void scan_option ();
	public void forward ();
	public void backward ();
	public void repaint ();
	public void jump_forw ();
	public void jump_back ();
	public void jump_percent ();
	public void jump_loc ();
	public void init_mark ();
	public void setmark ();
	public void gomark ();
	public void search ();
	public int ch_seek ();
	public int ch_end_seek ();
	public POSITION ch_length ();
	public POSITION ch_tell ();
	public int ch_forw_get ();
	public int ch_back_get ();
	public void ch_init ();
	public POSITION position ();
	public void add_forw_pos ();
	public void add_back_pos ();
	public void pos_clear ();
	public int onscreen ();
	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 init ();
	public void deinit ();
	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 so_enter ();
	public void so_exit ();
	public void ul_enter ();
	public void ul_exit ();
	public void backspace ();
	public void putbs ();
	public char * eq_message ();
	public char * pr_string ();
	public void prewind ();
	public int pappend ();
	public POSITION forw_raw_line ();
	public POSITION back_raw_line ();
	public void init_signals ();
	public void  psignals ();
	public void lsystem ();
	public void help ();
	public void open_getc ();
	public int getc ();
	public void commands ();
SHAR_EOF
echo shar: extracting less.h
cat - << \SHAR_EOF > less.h
/*
 * Standard include file for "less".
 */

/*
 * Language details.
 */
#if !VOID
#define	void  int
#endif
#define	public		/* PUBLIC FUNCTION */

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

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

/* 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 normal char; 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 special */
#define	UL_CHAR		'\201'	/* Enter underline mode */
#define	UE_CHAR		'\202'	/* Exit underline mode */

#define	CONTROL(c)		((c)&037)
#define	SIGNAL(sig,func)	signal(sig,func)

off_t lseek();

#include "funcs.h"
SHAR_EOF
echo shar: extracting position.h
cat - << \SHAR_EOF > position.h
/*
 * Include file for interfacing to position.c modules.
 */
#define	TOP		0
#define	TOP_PLUS_ONE	1
#define	BOTTOM		-1
#define	BOTTOM_PLUS_ONE	-2
SHAR_EOF
echo shar: extracting mkfuncs.awk
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
echo shar: extracting main.c
cat - << \SHAR_EOF > main.c
/*
 * Entry point, initialization, miscellaneous routines.
 */

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

public int	ispipe;
public jmp_buf	main_loop;
public char *	first_cmd;
public char *	every_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;
extern int back_scroll;
extern int top_scroll;
extern int sc_height;


/*
 * 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];
	static int any_edited = 0;
	static int hold_scroll = 0;

	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);
		if (any_edited)
			error(message);
		else
		{
			puts(message);
			hold_scroll = 1;
		}
		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;
	strcpy(current_file, filename);
	ispipe = (f == 0);
	file = f;
	ch_init( (ispipe) ? p_nbufs : f_nbufs );
	init_mark();
	if (every_first_cmd != NULL)
		first_cmd = every_first_cmd;
	if (is_tty)
	{
		any_edited = 1;
		if (hold_scroll)
		{
			/*
			 * Before erasing the screen contents,
			 * display the file name and ask for a keystroke.
			 */
			error(filename);
			hold_scroll = 0;
		}
		if (first_cmd == NULL || *first_cmd == '\0')
		{
			/* 
			 * Display the first screen. 
			 */
			jump_back(1);
		} else
		{
			/* 
			 * The first_cmd will hopefully 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.
 */
	public void
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.
 */
	public void
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.
 */
	static void
cat_file()
{
	register int c;

	while ((c = ch_forw_get()) != EOF)
		putc(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 = EDIT_PGM;
#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 (back_scroll < 0)
	{
		/* {{ KLUDGE }} */
		back_scroll = sc_height-1;
		if (top_scroll)
			back_scroll--;
	}

	if (setjmp(main_loop))
		quit();
	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();
	deinit();
	flush();
	raw_mode(0);
	exit(0);
}
SHAR_EOF
echo shar: extracting option.c
cat - << \SHAR_EOF > 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	NUMBER		04	/* Numeric option */
#define	NO_TOGGLE	0100	/* Option cannot be toggled with "-" cmd */

/*
 * Variables controlled by 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 */
public int back_scroll;		/* Repaint screen on backwards movement */
public int twiddle;		/* Display "~" for lines after EOF */

extern int nbufs;
extern char *first_cmd;
extern char *every_first_cmd;

#define	DEF_F_NBUFS	5	/* Default for f_nbufs */
#define	DEF_P_NBUFS	12	/* Default for p_nbufs */

static struct option
{
	char oletter;		/* The controlling letter (a-z) */
	char otype;		/* Type of the option */
	int odefault;		/* Default value */
	int *ovar;		/* Pointer to the associated 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
		}
	},
	{ 'h', NUMBER, -1, &back_scroll,
		{ "Backwards scroll limit is %d lines",
		  NULL, NULL
		}
	},
	{ 'p', BOOL, 0, &top_scroll,
		{ "Repaint by scrolling from bottom of screen",
		  "Repaint by painting from top of screen",
		  NULL
		}
	},
	{ 'x', NUMBER, 8, &tabstop,
		{ "Tab stops every %d spaces", 
		  NULL, 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
		}
	},
	{ 'w', BOOL, 1, &twiddle,
		{ "Display nothing for lines after end-of-file",
		  "Display ~ for lines after end-of-file",
		  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.
 */
	public void
init_option()
{
	register struct option *o;
	register char *p;

	/*
	 * First do special cases, not in option table.
	 */
	first_cmd = every_first_cmd = NULL;
	f_nbufs = DEF_F_NBUFS;		/* -bf */
	p_nbufs = DEF_P_NBUFS;		/* -bp */

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

	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.
 */
	public void
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;
	}


	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;
		} else if ((o->otype & NUMBER) && (o->oletter == c) &&
			(o->otype & NO_TOGGLE) == 0)
		{
			sprintf(message, o->odesc[0], *(o->ovar));
			error(message);
			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.
 */
	public void
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 '+':
		if (*s == '+')
			every_first_cmd = ++s;
		first_cmd = s;
		return;
	case 'b':
		switch (*s)
		{
		case 'f':
			s++;
			f_nbufs = getnum(&s, 'b');
			break;
		case 'p':
			s++;
			p_nbufs = getnum(&s, 'b');
			break;
		default:
			f_nbufs = p_nbufs = getnum(&s, 'b');
			break;
		}
		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;
		} else if ((o->otype & NUMBER) && (o->oletter == c))
		{
			*(o->ovar) = getnum(&s, c);
			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, c)
	char **sp;
	int c;
{
	register char *s;
	register int n;

	s = *sp;
	if (*s < '0' || *s > '9')
	{
		printf("number is required after -%c\n", c);
		exit(1);
	}

	n = 0;
	while (*s >= '0' && *s <= '9')
		n = 10 * n + *s++ - '0';
	*sp = s;
	return (n);
}
SHAR_EOF
echo shar: extracting prim.c
cat - << \SHAR_EOF > prim.c
/*
 * Primitives for displaying the file on the screen.
 */

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

public int hit_eof;	/* Keeps track of how many times we hit end of file */

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

/*
 * Sound the bell to indicate he is trying to move past end of file.
 */
	static void
eof_bell()
{
	if (quiet == NOT_QUIET)
		bell();
	else
		vbell();
}

/*
 * Check to see if the end of file is currently "displayed".
 */
	static void
eof_check()
{
	POSITION pos;

	/*
	 * If the bottom line is empty, we are at EOF.
	 * If the bottom line ends at the file length,
	 * we must be just at EOF.
	 */
	pos = position(BOTTOM_PLUS_ONE);
	if (pos == NULL_POSITION || pos == ch_length())
		hit_eof++;
}

/*
 * Display n lines, scrolling forward, 
 * starting at position pos in the input file.
 * "force" means display the n lines even if we hit end of file.
 * "only_last" means display only the last screenful if n > screen size.
 */
	static void
forw(n, pos, force, only_last)
	register int n;
	POSITION pos;
	int force;
	int only_last;
{
	int eof = 0;
	int nlines = 0;
	int repaint_flag;

	/*
	 * repaint_flag tells us not to display anything till the end, 
	 * then just repaint the entire screen.
	 */
	repaint_flag = (only_last && n > sc_height-1);

	if (!repaint_flag)
	{
		if (top_scroll && n >= sc_height - 1)
		{
			/*
			 * Start a new screen.
			 * {{ This is not really desirable if we happen
			 *    to hit eof in the middle of this screen,
			 *    but we don't know if that will happen now. }}
			 */
			clear();
			home();
			force = 1;
		} else
		{
			lower_left();
			clear_eol();
		}

		if (pos != position(BOTTOM_PLUS_ONE))
		{
			/*
			 * This is not contiguous with what is
			 * currently displayed.  Clear the screen image 
			 * (position table) and start a new screen.
			 */
			pos_clear();
			add_forw_pos(pos);
			force = 1;
			if (top_scroll)
			{
				clear();
				home();
			} else
			{
				puts("...skipping...\n");
			}
		}
	}

	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)
				break;
			line = NULL;
		}
		/*
		 * Add the position of the next line to the position table.
		 * Display the current line on the screen.
		 */
		add_forw_pos(pos);
		nlines++;
		if (!repaint_flag)
			put_line();
	}

	if (eof)
		hit_eof++;
	else
		eof_check();
	if (nlines == 0)
		eof_bell();
	else if (repaint_flag)
		repaint();
}

/*
 * Display n lines, scrolling backward.
 */
	static void
back(n, pos, force, only_last)
	register int n;
	POSITION pos;
	int force;
	int only_last;
{
	int nlines = 0;
	int repaint_flag;

	repaint_flag = (n > back_scroll || (only_last && n > sc_height-1));
	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)
				break;
			line = NULL;
		}
		/*
		 * Add the position of the previous line to the position table.
		 * Display the line on the screen.
		 */
		add_back_pos(pos);
		nlines++;
		if (!repaint_flag)
		{
			home();
			add_line();
			put_line();
		}
	}

	eof_check();
	if (nlines == 0)
		eof_bell();
	else if (repaint_flag)
		repaint();
}

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

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

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

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

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

/*
 * Repaint the screen.
 */
	public void
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.
 */
	public void
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, 0);
}

/*
 * Jump to line n in the file.
 */
	public void
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.
 */
	public void
jump_percent(percent)
	int percent;
{
	POSITION pos, len;

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

	public void
jump_loc(pos)
	POSITION pos;
{
	register int c;
	register int nline;
	POSITION tpos;

	/*
	 * See if the desired line is BEFORE the currently
	 * displayed screen.  If so, see if it is close enough 
	 * to scroll backwards to it.
	 */
	tpos = position(TOP);
	if (pos < tpos)
	{
		for (nline = 1;  nline <= back_scroll;  nline++)
		{
			tpos = back_line(tpos);
			if (tpos == NULL_POSITION || tpos <= pos)
			{
				back(nline, position(TOP), 1, 0);
				return;
			}
		}
	} else if ((nline = onscreen(pos)) >= 0)
	{
		/*
		 * The line is currently displayed.  
		 * Just scroll there.
		 */
		forw(nline, position(BOTTOM_PLUS_ONE), 1, 0);
		return;
	}

	/*
	 * Line is not on screen.
	 * Back up to the beginning of the current line.
	 */
	if (ch_seek(pos))
	{
		error("Cannot seek to that position");
		return;
	}
	while ((c = ch_back_get()) != '\n' && c != EOF)
		;
	if (c == '\n')
		(void) ch_forw_get();

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

/*
 * The table of marks.
 * A mark is simply a position in the file.
 */
static POSITION marks[26];

/*
 * Initialize the mark table to show no marks are set.
 */
	public void
init_mark()
{
	int i;

	for (i = 0;  i < 26;  i++)
		marks[i] = NULL_POSITION;
}

/*
 * See if a mark letter is valid (between a and z).
 */
	static int
badmark(c)
	int c;
{
	if (c < 'a' || c > 'z')
	{
		error("Choose a letter between 'a' and 'z'");
		return (1);
	}
	return (0);
}

/*
 * Set a mark.
 */
	public void
setmark(c)
	int c;
{
	if (badmark(c))
		return;
	marks[c-'a'] = position(TOP);
}

/*
 * Go to a previously set mark.
 */
	public void
gomark(c)
	int c;
{
	POSITION pos;

	if (badmark(c))
		return;
	if ((pos = marks[c-'a']) == NULL_POSITION)
		error("mark not set");
	else
		jump_loc(pos);
}

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

#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;
	} else if (!search_forward)
	{
		/*
		 * Backward search: start just before the top line
		 * displayed on the screen.
		 */
		pos = position(TOP);
	} else if (top_search)
	{
		/*
		 * Forward search and "start from top".
		 * Start at the second line displayed on the screen.
		 */
		pos = position(TOP_PLUS_ONE);
	} else
	{
		/*
		 * Forward search but don't "start from top".
		 * Start just after the bottom line displayed on the screen.
		 */
		pos = position(BOTTOM_PLUS_ONE);
	}

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

	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 save the 
			 * starting position of that line in linepos.
			 */
			linepos = pos;
			pos = forw_raw_line(pos);
		} else
		{
			/*
			 * Read the previous line and save the
			 * starting position of that line in linepos.
			 */
			pos = back_raw_line(pos);
			linepos = pos;
		}

		if (pos == NULL_POSITION)
		{
			/*
			 * 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;
	}
	jump_loc(linepos);
}

#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