v07i083: New release of LESS, Part02/03

sources-request at mirror.TMC.COM sources-request at mirror.TMC.COM
Sat Dec 6 08:20:51 AEST 1986


Submitted by: rgb at nscpdc.uucp (Robert Bond)
Mod.sources: Volume 7, Issue 83
Archive-name: less3/Part02

 ---- cut here ---- cut here ---- cut here ---- cut here ---- cut here ----
: This is a shell archive.
: Unpack by running /bin/sh.
echo less.nro
cat >less.nro <<'_SHAR_EOF_'
.TH LESS l
.SH NAME
less \- opposite of more
.SH SYNOPSIS
.B "less [-cdepstwmMqQuU] [-h\fIN\fB] [-b[fp]\fIN\fB] [-x\fIN\fB] [-[z]\fIN\fB]"
.br
.B "     [-P[mM]\fIstring\fB] [-l\fIlogfile\fB] [+\fIcmd\fB]  [\fIfilename\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
In the following descriptions, ^X means control-X.
.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 window (see option \-z below).
If N is more than the screen size, only the final screenful is displayed.
.PP
.IP "f or ^F"
Same as SPACE.
.PP
.IP "b or ^B"
Scroll backward N lines, default one window (see option \-z below).
If N is more than the screen size, only the final screenful is displayed.
.PP
.IP RETURN
Scroll forward N lines, default 1.
The entire N lines are displayed, even if N is more than the screen size.
.PP
.IP "e or ^E"
Same as RETURN.
.PP
.IP "j or ^J"
Also the same as RETURN.
.PP
.IP "y or ^Y"
Scroll backward N lines, default 1.
The entire N lines are displayed, even if N is more than the screen size.
.IP "k or ^K"
Same as y.
.PP
.IP "d or ^D"
Scroll forward N lines, default 10.
If N is specified, it becomes the new default for 
subsequent d and u commands.
.PP
.IP "u or ^U"
Scroll backward N lines, default 10.
If N is specified, it becomes the new default for 
subsequent d and u commands.
.PP
.IP "r or ^R or ^L"
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 "'"
(Single quote.)
Followed by any lowercase letter, returns to the position which
was previously marked with that letter.
Followed by another single quote, returns to the postion at
which the last "large" movement command was executed.
All marks are lost when a new file is examined.
.PP
.IP /pattern
Search forward in the file for the N-th line containing 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 line containing the pattern.
The search starts at the line immediately before the top line displayed.
.PP
.IP n
Repeat previous search, for N-th line containing 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 "= or ^G"
Prints some information about the file being viewed,
including its name
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 +cmd
Causes the specified cmd to be executed each time a new file is examined.
For example, +G causes 
.I less
to initially display each file starting at the end 
rather than the beginning.
.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.
Most options may be changed while
.I less 
is running, via the "\-" command.
.PP
Options are also taken from the environment variable "LESS".
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
.sp
The environment variable is parsed before the command line,
so command line options override the LESS environment variable.
A dollar sign ($) may be used to signal the end of an option string.
This is important only for options like \-P which take a
following string.
.IP \-s
The \-s option 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 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 option causes 
.I less
to prompt verbosely (like 
.I more),
with the percent into the file.
.IP \-M
The \-M option causes 
.I less
to prompt even more verbosely than 
.I more.
.IP \-P
The \-P option provides a way to tailor the three prompt
styles to your own preference.
You would normally put this option in your LESS environment
variable, rather than type it in with each less command.
Such an option must either be the last option in the LESS variable,
or be terminated by a dollar sign.
\-P followed by a string changes the default (short) prompt to that string.
\-Pm changes the medium (\-m) prompt to the string, and
\-PM changes the long (\-M) prompt.
The string consists of a sequence of letters which are replaced
with certain predefined strings, as follows:
.br
	F	file name
.br
	f	file name, only once
.br
	O	file n of n
.br
	o	file n of n, only once
.br
	b	byte offset
.br
	p	percent into file
.br
	P	percent if known, else byte offset
.br
Angle brackets, < and >, may be used to surround a
literal string to be included in the prompt.
The defaults are "fo" for the short prompt,
"foP" for the medium prompt, and
"Fobp" for the long prompt.
.br
Example: Setting your LESS variable to "PmFOP$PMFObp"
would change the medium and long prompts to always 
include the file name and "file n of n" message.
.br
Another example: Setting your LESS variable to 
.br
"mPm<--Less-->FoPe"
would change the medium prompt to the string "--Less--" followed
by the file name and percent into the file.
It also selects the medium
prompt as the default prompt (because of the first "m").
.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 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 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 option tells less to automatically exit
the second time it reaches end-of-file.
.IP \-u
If the \-u 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 option is given,
backspaces are printed as the two character sequence "^H".
.sp
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.
Also, backspaces which appear between two identical characters
are treated specially: 
the overstruck text is printed 
using the terminal's hardware boldface capability.
Other backspaces are deleted, along with the preceeding character.
.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 option 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 option 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 \-[z]
When given a backwards or forwards window command,
.I less
will by
default scroll backwards or forwards one screenful of lines. 
The \-z\fIn\fR option changes the default scrolling window size 
to \fIn\fR lines.
If \fIn\fR is greater than the screen size, 
the scrolling window size will be set to one screenful.  
Note that the "z" is optional for compatibility with
.I more.
.IP -x
The -x\fIn\fR option sets tab stops every \fIn\fR positions.
The default for \fIn\fR is 8.
.IP -l
The -l option, followed immediately by a filename,
will cause 
.I less
to copy its input to the named file as it is being viewed.
This applies only when the input file is a pipe,
not an ordinary file.
.IP -b
The -b\fIn\fR 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 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.
The + command described previously
may also be used to set (or change) an initial command for every file.

.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.
The -b option may be used to expand the buffer space.
_SHAR_EOF_

echo main.c
cat >main.c <<'_SHAR_EOF_'
/*
 * 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	any_display;
public int	ac;
public char **	av;
public int 	curr_ac;
#if LOGFILE
public int	logfile = -1;
public char *	namelogfile = NULL;
#endif
#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;
extern int errmsgs;


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

	if (filename == NULL || *filename == '\0')
	{
		if (curr_ac >= ac)
		{
			error("No current file");
			return;
		}
		filename = av[curr_ac];
	}
	if (strcmp(filename, "-") == 0)
	{
		/* 
		 * Use standard input.
		 */
		if (didpipe)
		{
			error("Can view standard input only once");
			return;
		}
		f = 0;
	} else if ((f = open(filename, 0)) < 0)
	{
		static char co[] = "Cannot open ";
		strcpy(message, co);
		strtcpy(message+sizeof(co)-1, filename, 
			sizeof(message)-sizeof(co));
		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;
	}

#if LOGFILE
	/*
	 * If he asked for a log file and we have opened standard input,
	 * create the log file.  
	 * We take care not to blindly overwrite an existing file.
	 */
	end_logfile();
	if (f == 0 && namelogfile != NULL && is_tty)
	{
		int exists;
		int answer;

		/*
		 * {{ We could use access() here. }}
		 */
		exists = open(namelogfile, 0);
		close(exists);
		exists = (exists >= 0);

		if (exists)
		{
			static char w[] = "WARNING: log file exists: ";
			strcpy(message, w);
			strtcpy(message+sizeof(w)-1, namelogfile,
				sizeof(message)-sizeof(w));
			error(message);
			answer = 'X';	/* Ask the user what to do */
		} else
			answer = 'O';	/* Create the log file */

	loop:
		switch (answer)
		{
		case 'O': case 'o':
			logfile = creat(namelogfile, 0644);
			break;
		case 'A': case 'a':
			logfile = open(namelogfile, 1);
			if (lseek(logfile, (off_t)0, 2) < 0)
			{
				close(logfile);
				logfile = -1;
			}
			break;
		case 'D': case 'd':
			answer = 0;	/* Don't print an error message */
			break;
		case 'q':
			quit();
		default:
			puts("\n  Overwrite, Append, or Don't log? ");
			answer = getc();
			puts("\n");
			flush();
			goto loop;
		}

		if (logfile < 0 && answer != 0)
		{
			sprintf(message, "Cannot write to \"%s\"", 
				namelogfile);
			error(message);
		}
	}
#endif

	/*
	 * We are now committed to using the new file.
	 * Close the current input file and set up to use the new one.
	 */
	if (file > 0)
		close(file);
	new_file = 1;
	strtcpy(current_file, filename, sizeof(current_file));
	ispipe = (f == 0);
	if (ispipe)
		didpipe = 1;
	file = f;
	ch_init( (ispipe) ? p_nbufs : f_nbufs );
	init_mark();

	if (every_first_cmd != NULL)
		first_cmd = every_first_cmd;

	if (is_tty)
	{
		int no_display = !any_display;
		any_display = 1;
		if (no_display && errmsgs > 0)
		{
			/*
			 * We displayed some messages on error output
			 * (file descriptor 2; see error() function).
			 * Before erasing the screen contents,
			 * display the file name and wait for a keystroke.
			 */
			error(filename);
		}
		/*
		 * Indicate there is nothing displayed yet.
		 */
		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 (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);
		} while (file < 0 && ++curr_ac < ac);
	}

	if (file >= 0)
		commands();
	quit();
}

/*
 * Copy a string, truncating to the specified length if necessary.
 * Unlike strncpy(), the resulting string is guaranteed to be null-terminated.
 */
strtcpy(to, from, len)
	char *to;
	char *from;
	int len;
{
	strncpy(to, from, len);
	to[len-1] = '\0';
}

/*
 * Exit the program.
 */
	public void
quit()
{
	/*
	 * Put cursor at bottom left corner, clear the line,
	 * reset the terminal modes, and exit.
	 */
#if LOGFILE
	end_logfile();
#endif
	lower_left();
	clear_eol();
	deinit();
	flush();
	raw_mode(0);
	exit(0);
}
_SHAR_EOF_

echo option.c
cat >option.c <<'_SHAR_EOF_'
/*
 * 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')

#define	END_OPTION_STRING	('$')

/*
 * 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	REPAINT		040	/* Repaint screen after toggling 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 char *prproto[];
extern int nbufs;
extern int sc_window;
extern char *first_cmd;
extern char *every_first_cmd;
#if LOGFILE
extern char *namelogfile;
#endif

#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|REPAINT, 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|REPAINT, 1, &twiddle,
		{ "Display nothing for lines after end-of-file",
		  "Display ~ for lines after end-of-file",
		  NULL
		}
	},
	{ 'm', TRIPLE, 0, &pr_type,
		{ "Short prompt",
		  "Medium prompt",
		  "Long prompt"
		}
	},
	{ '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|REPAINT, 0, &bs_mode,
		{ "Underlined text displayed in underline mode",
		  "Backspaces cause overstrike",
		  "Backspaces print as ^H"
		}
	},
	{ 'z', NUMBER, 24, &sc_window,
		{ "Scroll window size is %d lines",
		  NULL, NULL
		}
	},
	{ '\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 & NO_TOGGLE)
			continue;
		if ((o->otype & BOOL) && (o->oletter == c))
		{
			/*
			 * Boolean option: 
			 * just toggle it.
			 */
			*(o->ovar) = ! *(o->ovar);
		} else if ((o->otype & TRIPLE) && (o->oletter == c))
		{
			/*
			 * Triple-valued option with lower case letter:
			 * make it 1 unless already 1, then make it 0.
			 */
			*(o->ovar) = (*(o->ovar) == 1) ? 0 : 1;
		} else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c))
		{
			/*
			 * Triple-valued option with upper case letter:
			 * make it 2 unless already 2, then make it 0.
			 */
			*(o->ovar) = (*(o->ovar) == 2) ? 0 : 2;
		} else if ((o->otype & NUMBER) && (o->oletter == c))
		{
			sprintf(message, o->odesc[0], 
				(o->ovar == &back_scroll) ? 
				get_back_scroll() : *(o->ovar));
			error(message);
			return;
		} else
			continue;

		if (o->otype & REPAINT)
			repaint();
		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 to end of string or to an END_OPTION_STRING character.
 * In the latter case, replace the char with a null char.
 * Return a pointer to the remainder of the string, if any.
 */
	static char *
optstring(s)
	char *s;
{
	register char *p;

	for (p = s;  *p != '\0';  p++)
		if (*p == END_OPTION_STRING)
		{
			*p = '\0';
			return (p+1);
		}
	return (p);
}

/* 
 * 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;
	char message[80];

	if (s == NULL)
		return;

    next:
	if (*s == '\0')
		return;
	switch (c = *s++)
	{
	case '-':
	case ' ':
	case '\t':
	case END_OPTION_STRING:
		goto next;
	case '+':
		if (*s == '+')
			every_first_cmd = ++s;
		first_cmd = s;
		s = optstring(s);
		goto next;
	case 'P':
		switch (*s)
		{
		case 'm':  prproto[PR_MEDIUM] = ++s;  break;
		case 'M':  prproto[PR_LONG] = ++s;    break;
		default:   prproto[PR_SHORT] = s;     break;
		}
		s = optstring(s);
		goto next;
#if LOGFILE
	case 'l':
		namelogfile = s;
		s = optstring(s);
		goto next;
#endif
	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;
	case '0':  case '1':  case '2':  case '3':  case '4':
	case '5':  case '6':  case '7':  case '8':  case '9':
		{
			/*
			 * Handle special "more" compatibility form "-number"
			 * to set the scrolling window size.
			 */
			s--;
			sc_window = 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;
		} else if ((o->otype & NUMBER) && (o->oletter == c))
		{
			*(o->ovar) = getnum(&s, c);
			goto next;
		}
	}

	sprintf(message, "\"-%c\": invalid flag", c);
	error(message);
	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;
	char message[80];

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

	n = 0;
	while (*s >= '0' && *s <= '9')
		n = 10 * n + *s++ - '0';
	*sp = s;
	return (n);
}
_SHAR_EOF_

echo prim.c
cat >prim.c <<'_SHAR_EOF_'
/*
 * 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;
	static int first_time = 1;

	/*
	 * 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 yet know if that will happen. }}
			 */
			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 if (!first_time)
			{
				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 || 
			(first_time && line == NULL && !top_scroll))
			continue;
		put_line();
	}

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

/*
 * 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 > get_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;
	}
	lastmark();
	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;
	int nlines;

	/*
	 * 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. 
		 * If he wants to go to line 1, we do the best we can, 
		 * by going to the first line which is still buffered.
		 */
		if (n <= 1 && ch_beg_seek() == 0)
			jump_loc(ch_tell());
		error("Cannot get to beginning of file");
		return;
	}

	/*
	 * Start counting lines.
	 */
	for (nlines = 1;  nlines < n;  nlines++)
	{
		while ((c = ch_forw_get()) != '\n')
			if (c == EOF)
			{
				char message[40];
				sprintf(message, "File has only %d lines", 
					nlines-1);
				error(message);
				return;
			}
	}

	jump_loc(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;
	register 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;

	/*
	 * Back up to the beginning of the line.
	 */
	if (ch_seek(pos) == 0)
	{
		while ((c = ch_back_get()) != '\n' && c != EOF)
			;
		if (c == '\n')
			(void) ch_forw_get();
		pos = ch_tell();
	}
	jump_loc(pos);
}

/*
 * Jump to a specified position in the file.
 */
	public void
jump_loc(pos)
	POSITION pos;
{
	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.
	 * {{ This can be expensive if he has specified a very
	 *    large back_scroll count.  Perhaps we should put
	 *    some sanity limit on the loop count here. }}
	 */
	tpos = position(TOP);
	if (tpos != NULL_POSITION && pos < tpos)
	{
		int bs = get_back_scroll();
		for (nline = 1;  nline <= bs;  nline++)
		{
			tpos = back_line(tpos);
			if (tpos == NULL_POSITION)
				break;
			if (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.
	 * Remember where we were; clear and paint the screen.
	 */
	if (ch_seek(pos))
	{
		error("Cannot seek to that position");
		return;
	}
	lastmark();
	prepaint(pos);
}

/*
 * The table of marks.
 * A mark is simply a position in the file.
 */
#define	NMARKS		(27)		/* 26 for a-z plus one for quote */
#define	LASTMARK	(NMARKS-1)	/* For quote */
static POSITION marks[NMARKS];

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

	for (i = 0;  i < NMARKS;  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);
}

	public void
lastmark()
{
	marks[LASTMARK] = position(TOP);
}

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

	if (c == '\'')
		pos = marks[LASTMARK];
	else if (badmark(c))
		return;
	else 
		pos = marks[c-'a'];

	if (pos == NULL_POSITION)
		error("mark not set");
	else
		jump_loc(pos);
}

/*
 * Get the backwards scroll limit.
 * Must call this function instead of just using the value of
 * back_scroll, because the default case depends on sc_height and
 * top_scroll, as well as back_scroll.
 */
	public int
get_back_scroll()
{
	if (back_scroll < 0)
		return (sc_height - 1 - top_scroll);
	return (back_scroll);
}

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

echo ch.c
cat >ch.c <<'_SHAR_EOF_'
/*
 * 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
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 ispipe;
extern int sigs;

#if LOGFILE
extern int logfile;
#endif

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

/* 
 * Length of file, needed if input is a pipe.
 */
static POSITION ch_fsize;

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

	/*
	 * 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;
	pos = ch_block * BUFSIZ;
	if (ispipe)
	{
		/*
		 * 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",
				(long)last_piped_block-1, (long)ch_block);
			error(message);
			quit();
		}
	} else
		lseek(file, pos, 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();
	}

#if LOGFILE
	/*
	 * If we have a log file, write this block to it.
	 */
	if (logfile >= 0 && end > 0)
		write(logfile, bp->data, end);
#endif

	/*
	 * 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)
	{
		ch_fsize = pos + end;
		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]);
}

#if LOGFILE
/*
 * Close the logfile.
 * If we haven't read all of standard input into it, do that now.
 */
	public void
end_logfile()
{
	static int tried;

	if (logfile < 0)
		return;
	if (!tried && ch_fsize == NULL_POSITION)
	{
		tried = 1;
		lower_left();
		clear_eol();
		so_enter();
		puts("finishing logfile... (interrupt to abort)");
		so_exit();
		flush();
		while (sigs == 0 && ch_forw_get() != EOF)
			;
	}
	close(logfile);
	logfile = -1;
}
#endif

/*
 * 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 (!ispipe || 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 (ispipe)
	{
		/*
		 * 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);
}

/*
 * Seek to the beginning of the file, or as close to it as we can get.
 * We may not be able to seek there if input is a pipe and the
 * beginning of the pipe is no longer buffered.
 */
	public int
ch_beg_seek()
{
	register struct buf *bp, *firstbp;

	/*
	 * Try a plain ch_seek first.
	 */
	if (ch_seek((POSITION)0) == 0)
		return (0);

	/*
	 * Can't get to position 0.
	 * Look thru the buffers for the one closest to position 0.
	 */
	firstbp = bp = buf_head;
	if (bp == END_OF_CHAIN)
		return (1);
	while ((bp = bp->next) != END_OF_CHAIN)
		if (bp->block < firstbp->block)
			firstbp = bp;
	ch_block = firstbp->block;
	ch_offset = 0;
	return (0);
}

/*
 * Return the length of the file, if known.
 */
	public POSITION
ch_length()
{
	if (ispipe)
		return (ch_fsize);
	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 || (ispipe && !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.",
				  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;
	ch_fsize = NULL_POSITION;
	(void) ch_seek((POSITION)0);
}
_SHAR_EOF_

echo position.c
cat >position.c <<'_SHAR_EOF_'
/*
 * Routines dealing with the "position" table.
 * This is a table which tells the position (in the input file) of the
 * first char on each currently displayed line.
 *
 * {{ The position table is scrolled by moving all the entries.
 *    Would be better to have a circular table 
 *    and just change a couple of pointers. }}
 */

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

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

extern int sc_width, sc_height;

/*
 * Return the starting file position of a line displayed on the screen.
 * The line may be specified as a line number relative to the top
 * of the screen, but is usually one of these special cases:
 *	the top (first) line on the screen
 *	the second line on the screen
 *	the bottom line on the screen
 *	the line after the bottom line on the screen
 */
	public POSITION
position(where)
	int where;
{
	switch (where)
	{
	case BOTTOM:
		where = sc_height - 2;
		break;
	case BOTTOM_PLUS_ONE:
		where = sc_height - 1;
		break;
	}
	return (table[where]);
}

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

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

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

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

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

	for (i = 0;  i < sc_height;  i++)
		table[i] = NULL_POSITION;
}

/*
 * See if the byte at a specified position is currently on the screen.
 * Check the position table to see if the position falls within its range.
 * Return the position table entry if found, -1 if not.
 */
	public int
onscreen(pos)
	POSITION pos;
{
	register int i;

	if (pos < table[0])
		return (-1);
	for (i = 1;  i < sc_height;  i++)
		if (pos < table[i])
			return (i-1);
	return (-1);
}
_SHAR_EOF_

echo input.c
cat >input.c <<'_SHAR_EOF_'
/*
 * High level routines dealing with getting lines of input 
 * from the file being viewed.
 *
 * When we speak of "lines" here, we mean PRINTABLE lines;
 * lines processed with respect to the screen width.
 * We use the term "raw line" to refer to lines simply
 * delimited by newlines; not processed with respect to screen width.
 */

#include "less.h"

extern int squeeze;
extern char *line;

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

	if (curr_pos == NULL_POSITION || ch_seek(curr_pos))
		return (NULL_POSITION);

	c = ch_forw_get();
	if (c == EOF)
		return (NULL_POSITION);

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

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

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

	return (new_pos);
}

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

	if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 ||
		ch_seek(curr_pos-1))
		return (NULL_POSITION);

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

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

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

echo output.c
cat >output.c <<'_SHAR_EOF_'
/*
 * High level routines dealing with the output to the screen.
 */

#include "less.h"

public int errmsgs;	/* Count of messages displayed by error() */

extern int sigs;
extern int sc_width, sc_height;
extern int ul_width, ue_width;
extern int so_width, se_width;
extern int bo_width, be_width;
extern int tabstop;
extern int twiddle;
extern int any_display;
extern char *line;
extern char *first_cmd;

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

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

	if (line == NULL)
		line = (twiddle) ? "~" : "";

	column = 0;
	for (p = line;  *p != '\0';  p++)
	{
		switch (c = *p)
		{
		case UL_CHAR:
			ul_enter();
			column += ul_width;
			break;
		case UE_CHAR:
			ul_exit();
			column += ue_width;
			break;
		case BO_CHAR:
			bo_enter();
			column += bo_width;
			break;
		case BE_CHAR:
			bo_exit();
			column += be_width;
			break;
		case '\t':
			do
			{
				putc(' ');
				column++;
			} while ((column % tabstop) != 0);
			break;
		case '\b':
			putbs();
			column--;
			break;
		default:
			if (c & 0200)
			{
				putc('^');
				putc(c & 0177);
				column += 2;
			} else
			{
				putc(c);
				column++;
			}
		}
	}
	if (column < sc_width || !auto_wrap || ignaw)
		putc('\n');
}

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

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


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

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

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

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

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

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

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

	public void
error(s)
	char *s;
{
	register int c;
	static char buf[2];

	errmsgs++;
	if (!any_display)
	{
		/*
		 * Nothing has been displayed yet.
		 * Output this message on error output (file
		 * descriptor 2) and don't wait for a keystroke
		 * to continue.
		 *
		 * This has the desirable effect of producing all
		 * error messages on error output if standard output
		 * is directed to a file.  It also does the same if
		 * we never produce any real output; for example, if
		 * the input file(s) cannot be opened.  If we do
		 * eventually produce output, code in edit() makes
		 * sure these messages can be seen before they are
		 * overwritten or scrolled away.
		 */
		write(2, s, strlen(s));
		write(2, "\n", 1);
		return;
	}

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

#if ONLY_RETURN
	while ((c = getc()) != '\n' && c != '\r')
		bell();
#else
	c = getc();
	if (c != '\n' && c != '\r' && c != ' ')
	{
		buf[0] = c;
		first_cmd = buf;
	}
#endif

	if (strlen(s) + sizeof(return_to_continue) + 
		so_width + se_width + 1 > sc_width)
		/*
		 * Printing the message has probably scrolled the screen.
		 * {{ Unless the terminal doesn't have auto margins,
		 *    in which case we just hammered on the right margin. }}
		 */
		repaint();
}

#ifdef notdef
	public int
error_width()
{
	/*
	 * Don't use the last position, because some terminals
	 * will scroll if you write in the last char of the last line.
	 */
	return (sc_width - 
		(sizeof(return_to_continue) + so_width + se_width + 1));
}
#endif
_SHAR_EOF_



More information about the Mod.sources mailing list