v16i032: Less, a pager that's more than more, Part03/04

Rich Salz rsalz at uunet.uu.net
Sat Sep 17 04:34:14 AEST 1988


Submitted-by: ctnews!UNIX386!mark
Posting-number: Volume 16, Issue 32
Archive-name: less5/part03

#! /bin/sh
# This is a shell archive.
# Remove anything before this line, then unpack it
# by saving it into a file and typing "sh file".

echo shar: Extracting \"main.c\"
sed "s/^X//" >'main.c' <<'END_OF_FILE'
X/*
X * Entry point, initialization, miscellaneous routines.
X */
X
X#include "less.h"
X#include "position.h"
X
Xpublic int	ispipe;
Xpublic char *	first_cmd;
Xpublic char *	every_first_cmd;
Xpublic int	new_file;
Xpublic int	is_tty;
Xpublic char 	*current_file;
Xpublic char 	*previous_file;
Xpublic POSITION	prev_pos;
Xpublic int	any_display;
Xpublic int	scroll;
Xpublic int	ac;
Xpublic char **	av;
Xpublic int 	curr_ac;
Xpublic int	quitting;
X
Xextern int	file;
Xextern int	quit_at_eof;
Xextern int	cbufs;
Xextern int	errmsgs;
X
X#if LOGFILE
Xpublic int	logfile = -1;
Xpublic int	force_logfile = 0;
Xpublic char *	namelogfile = NULL;
X#endif
X
X#if EDITOR
Xpublic char *	editor;
X#endif
X
X#if TAGS
Xextern char *	tagfile;
Xextern char *	tagpattern;
Xextern int	tagoption;
X#endif
X
X
X/*
X * Edit a new file.
X * Filename "-" means standard input.
X * No filename means the "current" file, from the command line.
X */
X	public void
Xedit(filename)
X	register char *filename;
X{
X	register int f;
X	register char *m;
X	POSITION initial_pos;
X	char message[100];
X	static int didpipe;
X
X	initial_pos = NULL_POSITION;
X	if (filename == NULL || *filename == '\0')
X	{
X		if (curr_ac >= ac)
X		{
X			error("No current file");
X			return;
X		}
X		filename = save(av[curr_ac]);
X	} else if (strcmp(filename, "#") == 0)
X	{
X		if (*previous_file == '\0')
X		{
X			error("no previous file");
X			return;
X		}
X		filename = save(previous_file);
X		initial_pos = prev_pos;
X	} else
X		filename = save(filename);
X
X	if (strcmp(filename, "-") == 0)
X	{
X		/* 
X		 * Use standard input.
X		 */
X		if (didpipe)
X		{
X			error("Can view standard input only once");
X			return;
X		}
X		f = 0;
X	} else if ((m = bad_file(filename, message, sizeof(message))) != NULL)
X	{
X		error(m);
X		free(filename);
X		return;
X	} else if ((f = open(filename, 0)) < 0)
X	{
X		error(errno_message(filename, message, sizeof(message)));
X		free(filename);
X		return;
X	}
X
X	if (isatty(f))
X	{
X		/*
X		 * Not really necessary to call this an error,
X		 * but if the control terminal (for commands)
X		 * and the input file (for data) are the same,
X		 * we get weird results at best.
X		 */
X		error("Can't take input from a terminal");
X		if (f > 0)
X			close(f);
X		free(filename);
X		return;
X	}
X
X#if LOGFILE
X	if (f == 0 && namelogfile != NULL && is_tty)
X		use_logfile();
X#endif
X
X	/*
X	 * We are now committed to using the new file.
X	 * Close the current input file and set up to use the new one.
X	 */
X	if (file > 0)
X		close(file);
X	new_file = 1;
X	if (previous_file != NULL)
X		free(previous_file);
X	previous_file = current_file;
X	current_file = filename;
X	prev_pos = position(TOP);
X	ispipe = (f == 0);
X	if (ispipe)
X		didpipe = 1;
X	file = f;
X	ch_init(cbufs, 0);
X	init_mark();
X
X	if (every_first_cmd != NULL)
X		first_cmd = every_first_cmd;
X
X	if (is_tty)
X	{
X		int no_display = !any_display;
X		any_display = 1;
X		if (no_display && errmsgs > 0)
X		{
X			/*
X			 * We displayed some messages on error output
X			 * (file descriptor 2; see error() function).
X			 * Before erasing the screen contents,
X			 * display the file name and wait for a keystroke.
X			 */
X			error(filename);
X		}
X		/*
X		 * Indicate there is nothing displayed yet.
X		 */
X		pos_clear();
X		if (initial_pos != NULL_POSITION)
X			jump_loc(initial_pos);
X		clr_linenum();
X	}
X}
X
X/*
X * Edit the next file in the command line list.
X */
X	public void
Xnext_file(n)
X	int n;
X{
X	if (curr_ac + n >= ac)
X	{
X		if (quit_at_eof)
X			quit();
X		error("No (N-th) next file");
X	} else
X		edit(av[curr_ac += n]);
X}
X
X/*
X * Edit the previous file in the command line list.
X */
X	public void
Xprev_file(n)
X	int n;
X{
X	if (curr_ac - n < 0)
X		error("No (N-th) previous file");
X	else
X		edit(av[curr_ac -= n]);
X}
X
X/*
X * Copy a file directly to standard output.
X * Used if standard output is not a tty.
X */
X	static void
Xcat_file()
X{
X	register int c;
X
X	while ((c = ch_forw_get()) != EOI)
X		putchr(c);
X	flush();
X}
X
X#if LOGFILE
X
Xuse_logfile()
X{
X	int exists;
X	int answer;
X	char message[100];
X
X	/*
X	 * If he asked for a log file and we have opened standard input,
X	 * create the log file.  
X	 * We take care not to blindly overwrite an existing file.
X	 */
X	end_logfile();
X
X	/*
X	 * {{ We could use access() here. }}
X	 */
X	exists = open(namelogfile, 0);
X	close(exists);
X	exists = (exists >= 0);
X
X	if (exists && !force_logfile)
X	{
X		static char w[] = "WARNING: log file exists: ";
X		strcpy(message, w);
X		strtcpy(message+sizeof(w)-1, namelogfile,
X			sizeof(message)-sizeof(w));
X		error(message);
X		answer = 'X';	/* Ask the user what to do */
X	} else
X		answer = 'O';	/* Create the log file */
X
Xloop:
X	switch (answer)
X	{
X	case 'O': case 'o':
X		logfile = creat(namelogfile, 0644);
X		break;
X	case 'A': case 'a':
X		logfile = open(namelogfile, 1);
X		if (lseek(logfile, (offset_t)0, 2) < 0)
X		{
X			close(logfile);
X			logfile = -1;
X		}
X		break;
X	case 'D': case 'd':
X		answer = 0;	/* Don't print an error message */
X		break;
X	case 'q':
X		quit();
X	default:
X		putstr("\n  Overwrite, Append, or Don't log? ");
X		answer = getchr();
X		putstr("\n");
X		flush();
X		goto loop;
X	}
X
X	if (logfile < 0 && answer != 0)
X	{
X		sprintf(message, "Cannot write to \"%s\"", 
X			namelogfile);
X		error(message);
X	}
X}
X
X#endif
X
X/*
X * Entry point.
X */
Xmain(argc, argv)
X	int argc;
X	char *argv[];
X{
X	char *getenv();
X
X
X	/*
X	 * Process command line arguments and LESS environment arguments.
X	 * Command line arguments override environment arguments.
X	 */
X	init_prompt();
X	init_option();
X	scan_option(getenv("LESS"));
X	argv++;
X	while ( (--argc > 0) && 
X		(argv[0][0] == '-' || argv[0][0] == '+') && 
X		argv[0][1] != '\0')
X		scan_option(*argv++);
X
X#if EDITOR
X	editor = getenv("EDITOR");
X	if (editor == NULL || *editor == '\0')
X		editor = EDIT_PGM;
X#endif
X
X	/*
X	 * Set up list of files to be examined.
X	 */
X	ac = argc;
X	av = argv;
X	curr_ac = 0;
X
X	/*
X	 * Set up terminal, etc.
X	 */
X	is_tty = isatty(1);
X	if (!is_tty)
X	{
X		/*
X		 * Output is not a tty.
X		 * Just copy the input file(s) to output.
X		 */
X		if (ac < 1)
X		{
X			edit("-");
X			cat_file();
X		} else
X		{
X			do
X			{
X				edit((char *)NULL);
X				if (file >= 0)
X					cat_file();
X			} while (++curr_ac < ac);
X		}
X		exit(0);
X	}
X
X	raw_mode(1);
X	get_term();
X	open_getchr();
X	init();
X	init_cmd();
X
X	init_signals(1);
X
X	/*
X	 * Select the first file to examine.
X	 */
X#if TAGS
X	if (tagoption)
X	{
X		/*
X		 * A -t option was given.
X		 * Verify that no filenames were also given.
X		 * Edit the file selected by the "tags" search,
X		 * and search for the proper line in the file.
X		 */
X		if (ac > 0)
X		{
X			error("No filenames allowed with -t option");
X			quit();
X		}
X		if (tagfile == NULL)
X			quit();
X		edit(tagfile);
X		if (file < 0)
X			quit();
X		if (tagsearch())
X			quit();
X	} else
X#endif
X	if (ac < 1)
X		edit("-");	/* Standard input */
X	else 
X	{
X		/*
X		 * Try all the files named as command arguments.
X		 * We are simply looking for one which can be
X		 * opened without error.
X		 */
X		do
X		{
X			edit((char *)NULL);
X		} while (file < 0 && ++curr_ac < ac);
X	}
X
X	if (file >= 0)
X		commands();
X	quit();
X	/*NOTREACHED*/
X}
X
X/*
X * Copy a string, truncating to the specified length if necessary.
X * Unlike strncpy(), the resulting string is guaranteed to be null-terminated.
X */
X	public void
Xstrtcpy(to, from, len)
X	char *to;
X	char *from;
X	unsigned int len;
X{
X	strncpy(to, from, len);
X	to[len-1] = '\0';
X}
X
X/*
X * Copy a string to a "safe" place
X * (that is, to a buffer allocated by calloc).
X */
X	public char *
Xsave(s)
X	char *s;
X{
X	register char *p;
X
X	p = calloc(strlen(s)+1, sizeof(char));
X	if (p == NULL)
X	{
X		error("cannot allocate memory");
X		quit();
X	}
X	strcpy(p, s);
X	return (p);
X}
X
X/*
X * Exit the program.
X */
X	public void
Xquit()
X{
X	/*
X	 * Put cursor at bottom left corner, clear the line,
X	 * reset the terminal modes, and exit.
X	 */
X	quitting = 1;
X#if LOGFILE
X	end_logfile();
X#endif
X	lower_left();
X	clear_eol();
X	deinit();
X	flush();
X	raw_mode(0);
X	exit(0);
X}
END_OF_FILE
echo shar: Extracting \"option.c\"
sed "s/^X//" >'option.c' <<'END_OF_FILE'
X/*
X * Process command line options.
X * Each option is a single letter which controls a program variable.
X * The options have defaults which may be changed via
X * the command line option, or toggled via the "-" command.
X */
X
X#include "less.h"
X
X#define	toupper(c)	((c)-'a'+'A')
X
X#define	END_OPTION_STRING	('$')
X
X/*
X * Types of options.
X */
X#define	BOOL		01	/* Boolean option: 0 or 1 */
X#define	TRIPLE		02	/* Triple-valued option: 0, 1 or 2 */
X#define	NUMBER		04	/* Numeric option */
X#define	REPAINT		040	/* Repaint screen after toggling option */
X#define	NO_TOGGLE	0100	/* Option cannot be toggled with "-" cmd */
X
X/*
X * Variables controlled by command line options.
X */
Xpublic int clean_data;		/* Can we assume the data is "clean"? 
X				   (That is, free of nulls, etc) */
Xpublic int quiet;		/* Should we suppress the audible bell? */
Xpublic int how_search;		/* Where should forward searches start? */
Xpublic int top_scroll;		/* Repaint screen from top?
X				   (alternative is scroll from bottom) */
Xpublic int pr_type;		/* Type of prompt (short, medium, long) */
Xpublic int bs_mode;		/* How to process backspaces */
Xpublic int know_dumb;		/* Don't complain about dumb terminals */
Xpublic int quit_at_eof;		/* Quit after hitting end of file twice */
Xpublic int squeeze;		/* Squeeze multiple blank lines into one */
Xpublic int tabstop;		/* Tab settings */
Xpublic int back_scroll;		/* Repaint screen on backwards movement */
Xpublic int twiddle;		/* Display "~" for lines after EOF */
Xpublic int caseless;		/* Do "caseless" searches */
Xpublic int linenums;		/* Use line numbers */
Xpublic int cbufs;		/* Current number of buffers */
Xpublic int autobuf;
Xpublic int plusoption;
X
Xextern char *prproto[];
Xextern char *eqproto;
Xextern int nbufs;
Xextern int sc_window;
Xextern int ispipe;
Xextern char *first_cmd;
Xextern char *every_first_cmd;
X#if LOGFILE
Xextern char *namelogfile;
Xextern int force_logfile;
Xextern int logfile;
X#endif
X#if TAGS
Xextern char *tagfile;
Xextern char *tagpattern;
Xpublic int tagoption = 0;
X#endif
X
Xstatic char *opt_P();
X
Xstatic struct option
X{
X	char oletter;		/* The controlling letter (a-z) */
X	char otype;		/* Type of the option */
X	int odefault;		/* Default value */
X	int *ovar;		/* Pointer to the associated variable */
X	char *odesc[3];		/* Description of each value */
X} option[] =
X{
X	{ 'a', TRIPLE, 0, &how_search,
X		{ "Forward search starts at second REAL line displayed",
X		  "Forward search starts at bottom of screen",
X		  "Forward search starts at second SCREEN line displayed"
X		}
X	},
X	{ 'b', NUMBER, 10, &cbufs,
X		{ "%d buffers",
X		  NULL, NULL
X		}
X	},
X	{ 'B', BOOL, 1, &autobuf,
X		{ "Don't automatically allocate buffers",
X		  "Automatically allocate buffers when needed",
X		  NULL
X		}
X	},
X	{ 'c', TRIPLE, 0, &top_scroll,
X		{ "Repaint by scrolling from bottom of screen",
X		  "Repaint by clearing each line",
X		  "Repaint by painting from top of screen"
X		}
X	},
X	{ 'd', BOOL|NO_TOGGLE, 0, &know_dumb,
X		{ NULL, NULL, NULL}
X	},
X	{ 'e', TRIPLE, 0, &quit_at_eof,
X		{ "Don't quit at end-of-file",
X		  "Quit at end-of-file",
X		  "Quit immediately at end-of-file"
X		}
X	},
X	{ 'h', NUMBER, -1, &back_scroll,
X		{ "Backwards scroll limit is %d lines",
X		  NULL, NULL
X		}
X	},
X	{ 'i', BOOL, 0, &caseless,
X		{ "Case is significant in searches",
X		  "Ignore case in searches",
X		  NULL
X		}
X	},
X	{ 'm', TRIPLE, 0, &pr_type,
X		{ "Short prompt",
X		  "Medium prompt",
X		  "Long prompt"
X		}
X	},
X	{ 'n', BOOL, 1, &linenums,
X		{ "Don't use line numbers",
X		  "Use line numbers",
X		  NULL
X		}
X	},
X	{ 'q', TRIPLE, 0, &quiet,
X		{ "Ring the bell for errors AND at eof/bof",
X		  "Ring the bell for errors but not at eof/bof",
X		  "Never ring the bell"
X		}
X	},
X	{ 's', BOOL|REPAINT, 0, &squeeze,
X		{ "Don't squeeze multiple blank lines",
X		  "Squeeze multiple blank lines",
X		  NULL
X		}
X	},
X	{ 'u', TRIPLE|REPAINT, 0, &bs_mode,
X		{ "Underlined text displayed in underline mode",
X		  "Backspaces cause overstrike",
X		  "Backspaces print as ^H"
X		}
X	},
X	{ 'w', BOOL|REPAINT, 1, &twiddle,
X		{ "Display nothing for lines after end-of-file",
X		  "Display ~ for lines after end-of-file",
X		  NULL
X		}
X	},
X	{ 'x', NUMBER|REPAINT, 8, &tabstop,
X		{ "Tab stops every %d spaces", 
X		  NULL, NULL 
X		}
X	},
X	{ 'z', NUMBER|REPAINT, -1, &sc_window,
X		{ "Scroll window size is %d lines",
X		  NULL, NULL
X		}
X	},
X	{ '\0' }
X};
X
X/*
X * Initialize each option to its default value.
X */
X	public void
Xinit_option()
X{
X	register struct option *o;
X
X	first_cmd = every_first_cmd = NULL;
X
X	for (o = option;  o->oletter != '\0';  o++)
X	{
X		/*
X		 * Set each variable to its default.
X		 */
X		*(o->ovar) = o->odefault;
X	}
X}
X
X/*
X * Toggle command line flags from within the program.
X * Used by the "-" and "_" commands.
X * If do_toggle is zero, just report the current setting, without changing it.
X */
X	public void
Xtoggle_option(s, do_toggle)
X	char *s;
X	int do_toggle;
X{
X	int c;
X	register struct option *o;
X	char *msg;
X	int n;
X	int dorepaint;
X	char message[100];
X
X	c = *s++;
X
X	switch (c)
X	{
X	case 'P':
X		/*
X		 * Special case for -P.
X		 */
X		if (*s == '\0')
X			error(prproto[pr_type]);
X		else
X			(void) opt_P(s);
X		return;
X#if TAGS
X	case 't':
X		/*
X		 * Special case for -t.
X		 */
X		if (*s == '\0')
X		{
X			error("no tag");
X			return;
X		}
X		findtag(s);
X		if (tagfile != NULL)
X		{
X			edit(tagfile);
X			(void) tagsearch();
X		}
X		return;
X#endif
X#if LOGFILE
X	case 'L':
X		/*
X		 * Special case for -l and -L.
X		 */
X		force_logfile = 1;
X		goto case_l;
X	case 'l':
X		force_logfile = 0;
X	case_l:
X		if (*s == '\0')
X		{
X			if (logfile < 0)
X				error("no log file");
X			else
X			{
X				sprintf(message, "log file \"%s\"",
X					namelogfile);
X				error(message);
X			}
X			return;
X		}
X		if (!ispipe)
X		{
X			error("input is not a pipe");
X			return;
X		}
X		if (logfile >= 0)
X		{
X			error("log file is already in use");
X			return;
X		}
X		namelogfile = save(s);
X		use_logfile();
X		sync_logfile();
X		return;
X#endif
X	}
X
X	msg = NULL;
X	for (o = option;  o->oletter != '\0';  o++)
X	{
X		if (o->otype & NO_TOGGLE)
X			continue;
X		dorepaint = (o->otype & REPAINT);
X		if ((o->otype & BOOL) && (o->oletter == c))
X		{
X			/*
X			 * Boolean option: 
X			 * just toggle it.
X			 */
X			if (do_toggle)
X				*(o->ovar) = ! *(o->ovar);
X		} else if ((o->otype & TRIPLE) && (o->oletter == c))
X		{
X			/*
X			 * Triple-valued option with lower case letter:
X			 * make it 1 unless already 1, then make it 0.
X			 */
X			if (do_toggle)
X				*(o->ovar) = (*(o->ovar) == 1) ? 0 : 1;
X		} else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c))
X		{
X			/*
X			 * Triple-valued option with upper case letter:
X			 * make it 2 unless already 2, then make it 0.
X			 */
X			if (do_toggle)
X				*(o->ovar) = (*(o->ovar) == 2) ? 0 : 2;
X		} else if ((o->otype & NUMBER) && (o->oletter == c))
X		{
X			n = getnum(&s, '\0');
X			if (n < 0)
X			{
X				/*
X				 * No number; just a query.
X				 * No need to repaint screen.
X				 */
X				dorepaint = 0;
X			} else
X			{
X				/*
X				 * Number follows the option letter.
X				 * Set the variable to that number.
X				 */
X				if (do_toggle)
X					*(o->ovar) = n;
X			}
X
X			/*
X			 * Special case for -b.
X			 * Call ch_init to set new number of buffers.
X			 */
X			if (o->ovar == &cbufs)
X				ch_init(cbufs, 1);
X
X			sprintf(message, o->odesc[0], 
X				(o->ovar == &back_scroll) ? 
X				get_back_scroll() : *(o->ovar));
X			msg = message;
X		} else
X			continue;
X
X		/*
X		 * Print a message describing the new setting.
X		 */
X		if (msg == NULL)
X			msg = o->odesc[*(o->ovar)];
X		error(msg);
X
X		if (do_toggle && dorepaint)
X			repaint();
X		return;
X	}
X
X	if (control_char(c))
X		sprintf(message, "-^%c", carat_char(c));
X	else
X		sprintf(message, "-%c", c);
X	strcat(message, ": no such flag.");
X	error(message);
X}
X
X/*
X * Determine if an option is a single character option (BOOL or TRIPLE),
X * or if it a multi-character option (NUMBER).
X */
X	public int
Xsingle_char_option(c)
X	int c;
X{
X	register struct option *o;
X
X	if (c == 'P')
X		return (0);
X#if TAGS
X	if (c == 't')
X		return (0);
X#endif
X#if LOGFILE
X	if (c == 'l' || c == 'L')
X		return (0);
X#endif
X	for (o = option;  o->oletter != '\0';  o++)
X		if (o->oletter == c)
X			return (o->otype & (BOOL|TRIPLE));
X	return (1);
X}
X
X/*
X * Scan to end of string or to an END_OPTION_STRING character.
X * In the latter case, replace the char with a null char.
X * Return a pointer to the remainder of the string, if any.
X */
X	static char *
Xoptstring(s, c)
X	char *s;
X	int c;
X{
X	register char *p;
X	char message[80];
X
X	if (*s == '\0')
X	{
X		sprintf(message, "string is required after -%c", c);
X		error(message);
X		exit(1);
X	}
X	for (p = s;  *p != '\0';  p++)
X		if (*p == END_OPTION_STRING)
X		{
X			*p = '\0';
X			return (p+1);
X		}
X	return (p);
X}
X
X/* 
X * Scan an argument (either from command line or from LESS environment 
X * variable) and process it.
X */
X	public void
Xscan_option(s)
X	char *s;
X{
X	register struct option *o;
X	register int c;
X	int set_default;
X	char message[80];
X
X	if (s == NULL)
X		return;
X
X	set_default = 0;
X    next:
X	if (*s == '\0')
X		return;
X	switch (c = *s++)
X	{
X	case ' ':
X	case '\t':
X	case END_OPTION_STRING:
X		goto next;
X	case '-':
X		if (set_default = (*s == '+'))
X			s++;
X		goto next;
X	case '+':
X		plusoption = 1;
X		if (*s == '+')
X			every_first_cmd = save(++s);
X		first_cmd = s;
X		s = optstring(s, c);
X		goto next;
X#if LOGFILE
X	case 'L':
X		force_logfile = 1;
X		/* FALLTHRU */
X	case 'l':
X		namelogfile = s;
X		s = optstring(s, c);
X		goto next;
X#endif
X#if TAGS
X	case 't':
X	{
X		char *p;
X		tagoption = 1;
X		p = s;
X		s = optstring(s, c);
X		findtag(p);
X		goto next;
X	}
X#endif
X	case 'P':
X		s = opt_P(s);
X		goto next;
X	case '0':  case '1':  case '2':  case '3':  case '4':
X	case '5':  case '6':  case '7':  case '8':  case '9':
X		/*
X		 * Handle special "more" compatibility form "-number"
X		 * (instead of -znumber) to set the scrolling window size.
X		 */
X		s--;
X		c = 'z';
X		break;
X	}
X
X	for (o = option;  o->oletter != '\0';  o++)
X	{
X		if ((o->otype & BOOL) && (o->oletter == c))
X		{
X			if (set_default)
X				*(o->ovar) = o->odefault;
X			else
X				*(o->ovar) = ! o->odefault;
X			goto next;
X		} else if ((o->otype & TRIPLE) && (o->oletter == c))
X		{
X			if (set_default)
X				*(o->ovar) = o->odefault;
X			else
X				*(o->ovar) = (o->odefault == 1) ? 0 : 1;
X			goto next;
X		} else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c))
X		{
X			if (set_default)
X				*(o->ovar) = o->odefault;
X			else
X				*(o->ovar) = (o->odefault == 2) ? 0 : 2;
X			goto next;
X		} else if ((o->otype & NUMBER) && (o->oletter == c))
X		{
X			*(o->ovar) = getnum(&s, c);
X			goto next;
X		}
X	}
X
X	sprintf(message, "\"-%c\": invalid flag", c);
X	error(message);
X	exit(1);
X}
X
X/*
X * Special case for -P.
X */
X	static char *
Xopt_P(s)
X	register char *s;
X{
X	register char *es;
X	register char **proto;
X
X	es = optstring(s, 'P');
X
X	/*
X	 * Figure out which prototype string should be changed.
X	 */
X	switch (*s)
X	{
X	case 'm':  proto = &prproto[PR_MEDIUM];	s++;	break;
X	case 'M':  proto = &prproto[PR_LONG];	s++;	break;
X	case '=':  proto = &eqproto;		s++;	break;
X	default:   proto = &prproto[pr_type];		break;
X	}
X
X	free(*proto);
X	*proto = save(s);
X
X	return (es);
X}
X
X/*
X * Translate a string into a number.
X * Like atoi(), but takes a pointer to a char *, and updates
X * the char * to point after the translated number.
X */
X	public int
Xgetnum(sp, c)
X	char **sp;
X	int c;
X{
X	register char *s;
X	register int n;
X	char message[80];
X
X	s = *sp;
X	if (*s < '0' || *s > '9')
X	{
X		if (c == '\0')
X			return (-1);
X		sprintf(message, "number is required after -%c", c);
X		error(message);
X		exit(1);
X	}
X
X	n = 0;
X	while (*s >= '0' && *s <= '9')
X		n = 10 * n + *s++ - '0';
X	*sp = s;
X	return (n);
X}
END_OF_FILE
echo shar: Extracting \"prim.c\"
sed "s/^X//" >'prim.c' <<'END_OF_FILE'
X/*
X * Primitives for displaying the file on the screen.
X */
X
X#include "less.h"
X#include "position.h"
X
Xpublic int hit_eof;	/* Keeps track of how many times we hit end of file */
Xpublic int screen_trashed;
X
Xstatic int squished;
X
Xextern int quiet;
Xextern int sigs;
Xextern int how_search;
Xextern int top_scroll;
Xextern int back_scroll;
Xextern int sc_width, sc_height;
Xextern int quit_at_eof;
Xextern int caseless;
Xextern int linenums;
Xextern int plusoption;
Xextern char *line;
Xextern char *first_cmd;
X#if TAGS
Xextern int tagoption;
X#endif
X
X/*
X * Sound the bell to indicate he is trying to move past end of file.
X */
X	static void
Xeof_bell()
X{
X	if (quiet == NOT_QUIET)
X		bell();
X	else
X		vbell();
X}
X
X/*
X * Check to see if the end of file is currently "displayed".
X */
X	static void
Xeof_check()
X{
X	POSITION pos;
X
X	if (sigs)
X		return;
X	/*
X	 * If the bottom line is empty, we are at EOF.
X	 * If the bottom line ends at the file length,
X	 * we must be just at EOF.
X	 */
X	pos = position(BOTTOM_PLUS_ONE);
X	if (pos == NULL_POSITION || pos == ch_length())
X		hit_eof++;
X}
X
X/*
X * If the screen is "squished", repaint it.
X * "Squished" means the first displayed line is not at the top
X * of the screen; this can happen when we display a short file
X * for the first time.
X */
X	static void
Xsquish_check()
X{
X	if (!squished)
X		return;
X	squished = 0;
X	repaint();
X}
X
X/*
X * Display n lines, scrolling forward, 
X * starting at position pos in the input file.
X * "force" means display the n lines even if we hit end of file.
X * "only_last" means display only the last screenful if n > screen size.
X */
X	static void
Xforw(n, pos, force, only_last)
X	register int n;
X	POSITION pos;
X	int force;
X	int only_last;
X{
X	int eof = 0;
X	int nlines = 0;
X	int do_repaint;
X	static int first_time = 1;
X
X	squish_check();
X
X	/*
X	 * do_repaint tells us not to display anything till the end, 
X	 * then just repaint the entire screen.
X	 */
X	do_repaint = (only_last && n > sc_height-1);
X
X	if (!do_repaint)
X	{
X		if (top_scroll && n >= sc_height - 1)
X		{
X			/*
X			 * Start a new screen.
X			 * {{ This is not really desirable if we happen
X			 *    to hit eof in the middle of this screen,
X			 *    but we don't yet know if that will happen. }}
X			 */
X			if (top_scroll == 2)
X				clear();
X			home();
X			force = 1;
X		} else
X		{
X			lower_left();
X			clear_eol();
X		}
X
X		if (pos != position(BOTTOM_PLUS_ONE))
X		{
X			/*
X			 * This is not contiguous with what is
X			 * currently displayed.  Clear the screen image 
X			 * (position table) and start a new screen.
X			 */
X			pos_clear();
X			add_forw_pos(pos);
X			force = 1;
X			if (top_scroll)
X			{
X				if (top_scroll == 2)
X					clear();
X				home();
X			} else if (!first_time)
X			{
X				putstr("...skipping...\n");
X			}
X		}
X	}
X
X	while (--n >= 0)
X	{
X		/*
X		 * Read the next line of input.
X		 */
X		pos = forw_line(pos);
X		if (pos == NULL_POSITION)
X		{
X			/*
X			 * End of file: stop here unless the top line 
X			 * is still empty, or "force" is true.
X			 */
X			eof = 1;
X			if (!force && position(TOP) != NULL_POSITION)
X				break;
X			line = NULL;
X		}
X		/*
X		 * Add the position of the next line to the position table.
X		 * Display the current line on the screen.
X		 */
X		add_forw_pos(pos);
X		nlines++;
X		if (do_repaint)
X			continue;
X		/*
X		 * If this is the first screen displayed and
X		 * we hit an early EOF (i.e. before the requested
X		 * number of lines), we "squish" the display down
X		 * at the bottom of the screen.
X		 * But don't do this if a + option or a -t option
X		 * was given.  These options can cause us to
X		 * start the display after the beginning of the file,
X		 * and it is not appropriate to squish in that case.
X		 */
X		if (first_time && line == NULL && !top_scroll && 
X#if TAGS
X		    !tagoption &&
X#endif
X		    !plusoption)
X		{
X			squished = 1;
X			continue;
X		}
X		if (top_scroll == 1)
X			clear_eol();
X		put_line();
X	}
X
X	if (eof && !sigs)
X		hit_eof++;
X	else
X		eof_check();
X	if (nlines == 0)
X		eof_bell();
X	else if (do_repaint)
X		repaint();
X	first_time = 0;
X	(void) currline(BOTTOM);
X}
X
X/*
X * Display n lines, scrolling backward.
X */
X	static void
Xback(n, pos, force, only_last)
X	register int n;
X	POSITION pos;
X	int force;
X	int only_last;
X{
X	int nlines = 0;
X	int do_repaint;
X
X	squish_check();
X	do_repaint = (n > get_back_scroll() || (only_last && n > sc_height-1));
X	hit_eof = 0;
X	while (--n >= 0)
X	{
X		/*
X		 * Get the previous line of input.
X		 */
X		pos = back_line(pos);
X		if (pos == NULL_POSITION)
X		{
X			/*
X			 * Beginning of file: stop here unless "force" is true.
X			 */
X			if (!force)
X				break;
X			line = NULL;
X		}
X		/*
X		 * Add the position of the previous line to the position table.
X		 * Display the line on the screen.
X		 */
X		add_back_pos(pos);
X		nlines++;
X		if (!do_repaint)
X		{
X			home();
X			add_line();
X			put_line();
X		}
X	}
X
X	eof_check();
X	if (nlines == 0)
X		eof_bell();
X	else if (do_repaint)
X		repaint();
X	(void) currline(BOTTOM);
X}
X
X/*
X * Display n more lines, forward.
X * Start just after the line currently displayed at the bottom of the screen.
X */
X	public void
Xforward(n, only_last)
X	int n;
X	int only_last;
X{
X	POSITION pos;
X
X	if (quit_at_eof && hit_eof)
X	{
X		/*
X		 * If the -e flag is set and we're trying to go
X		 * forward from end-of-file, go on to the next file.
X		 */
X		next_file(1);
X		return;
X	}
X
X	pos = position(BOTTOM_PLUS_ONE);
X	if (pos == NULL_POSITION)
X	{
X		eof_bell();
X		hit_eof++;
X		return;
X	}
X	forw(n, pos, 0, only_last);
X}
X
X/*
X * Display n more lines, backward.
X * Start just before the line currently displayed at the top of the screen.
X */
X	public void
Xbackward(n, only_last)
X	int n;
X	int only_last;
X{
X	POSITION pos;
X
X	pos = position(TOP);
X	if (pos == NULL_POSITION)
X	{
X		/* 
X		 * This will almost never happen,
X		 * because the top line is almost never empty. 
X		 */
X		eof_bell();
X		return;   
X	}
X	back(n, pos, 0, only_last);
X}
X
X/*
X * Repaint the screen, starting from a specified position.
X */
X	static void
Xprepaint(pos)	
X	POSITION pos;
X{
X	hit_eof = 0;
X	forw(sc_height-1, pos, 1, 0);
X	screen_trashed = 0;
X}
X
X/*
X * Repaint the screen.
X */
X	public void
Xrepaint()
X{
X	/*
X	 * Start at the line currently at the top of the screen
X	 * and redisplay the screen.
X	 */
X	prepaint(position(TOP));
X}
X
X/*
X * Jump to the end of the file.
X * It is more convenient to paint the screen backward,
X * from the end of the file toward the beginning.
X */
X	public void
Xjump_forw()
X{
X	POSITION pos;
X
X	if (ch_end_seek())
X	{
X		error("Cannot seek to end of file");
X		return;
X	}
X	lastmark();
X	pos = ch_tell();
X	clear();
X	pos_clear();
X	add_back_pos(pos);
X	back(sc_height - 1, pos, 0, 0);
X}
X
X/*
X * Jump to line n in the file.
X */
X	public void
Xjump_back(n)
X	register int n;
X{
X	register int c;
X	int nlines;
X
X	/*
X	 * This is done the slow way, by starting at the beginning
X	 * of the file and counting newlines.
X	 *
X	 * {{ Now that we have line numbering (in linenum.c),
X	 *    we could improve on this by starting at the
X	 *    nearest known line rather than at the beginning. }}
X	 */
X	if (ch_seek((POSITION)0))
X	{
X		/* 
X		 * Probably a pipe with beginning of file no longer buffered. 
X		 * If he wants to go to line 1, we do the best we can, 
X		 * by going to the first line which is still buffered.
X		 */
X		if (n <= 1 && ch_beg_seek() == 0)
X			jump_loc(ch_tell());
X		error("Cannot get to beginning of file");
X		return;
X	}
X
X	/*
X	 * Start counting lines.
X	 */
X	for (nlines = 1;  nlines < n;  nlines++)
X	{
X		while ((c = ch_forw_get()) != '\n')
X			if (c == EOI)
X			{
X				char message[40];
X				sprintf(message, "File has only %d lines", 
X					nlines-1);
X				error(message);
X				return;
X			}
X	}
X
X	jump_loc(ch_tell());
X}
X
X/*
X * Jump to a specified percentage into the file.
X * This is a poor compensation for not being able to
X * quickly jump to a specific line number.
X */
X	public void
Xjump_percent(percent)
X	int percent;
X{
X	POSITION pos, len;
X	register int c;
X
X	/*
X	 * Determine the position in the file
X	 * (the specified percentage of the file's length).
X	 */
X	if ((len = ch_length()) == NULL_POSITION)
X	{
X		error("Don't know length of file");
X		return;
X	}
X	pos = (percent * len) / 100;
X
X	/*
X	 * Back up to the beginning of the line.
X	 */
X	if (ch_seek(pos) == 0)
X	{
X		while ((c = ch_back_get()) != '\n' && c != EOI)
X			;
X		if (c == '\n')
X			(void) ch_forw_get();
X		pos = ch_tell();
X	}
X	jump_loc(pos);
X}
X
X/*
X * Jump to a specified position in the file.
X */
X	public void
Xjump_loc(pos)
X	POSITION pos;
X{
X	register int nline;
X	POSITION tpos;
X
X	if ((nline = onscreen(pos)) >= 0)
X	{
X		/*
X		 * The line is currently displayed.  
X		 * Just scroll there.
X		 */
X		forw(nline, position(BOTTOM_PLUS_ONE), 1, 0);
X		return;
X	}
X
X	/*
X	 * Line is not on screen.
X	 * Seek to the desired location.
X	 */
X	if (ch_seek(pos))
X	{
X		error("Cannot seek to that position");
X		return;
X	}
X
X	/*
X	 * See if the desired line is BEFORE the currently
X	 * displayed screen.  If so, then move forward far
X	 * enough so the line we're on will be at the bottom
X	 * of the screen, in order to be able to call back()
X	 * to make the screen scroll backwards & put the line
X	 * at the top of the screen.
X	 * {{ This seems inefficient, but it's not so bad,
X	 *    since we can never move forward more than a
X	 *    screenful before we stop to redraw the screen. }}
X	 */
X	tpos = position(TOP);
X	if (tpos != NULL_POSITION && pos < tpos)
X	{
X		POSITION npos = pos;
X		/*
X		 * Note that we can't forw_line() past tpos here,
X		 * so there should be no EOI at this stage.
X		 */
X		for (nline = 0;  npos < tpos && nline < sc_height - 1;  nline++)
X			npos = forw_line(npos);
X
X		if (npos < tpos)
X		{
X			/*
X			 * More than a screenful back.
X			 */
X			lastmark();
X			clear();
X			pos_clear();
X			add_back_pos(npos);
X		}
X
X		/*
X		 * Note that back() will repaint() if nline > back_scroll.
X		 */
X		back(nline, npos, 1, 0);
X		return;
X	}
X	/*
X	 * Remember where we were; clear and paint the screen.
X	 */
X  	lastmark();
X  	prepaint(pos);
X}
X
X/*
X * The table of marks.
X * A mark is simply a position in the file.
X */
X#define	NMARKS		(27)		/* 26 for a-z plus one for quote */
X#define	LASTMARK	(NMARKS-1)	/* For quote */
Xstatic POSITION marks[NMARKS];
X
X/*
X * Initialize the mark table to show no marks are set.
X */
X	public void
Xinit_mark()
X{
X	int i;
X
X	for (i = 0;  i < NMARKS;  i++)
X		marks[i] = NULL_POSITION;
X}
X
X/*
X * See if a mark letter is valid (between a and z).
X */
X	static int
Xbadmark(c)
X	int c;
X{
X	if (c < 'a' || c > 'z')
X	{
X		error("Choose a letter between 'a' and 'z'");
X		return (1);
X	}
X	return (0);
X}
X
X/*
X * Set a mark.
X */
X	public void
Xsetmark(c)
X	int c;
X{
X	if (badmark(c))
X		return;
X	marks[c-'a'] = position(TOP);
X}
X
X	public void
Xlastmark()
X{
X	marks[LASTMARK] = position(TOP);
X}
X
X/*
X * Go to a previously set mark.
X */
X	public void
Xgomark(c)
X	int c;
X{
X	POSITION pos;
X
X	if (c == '\'')
X		pos = marks[LASTMARK];
X	else if (badmark(c))
X		return;
X	else 
X		pos = marks[c-'a'];
X
X	if (pos == NULL_POSITION)
X		error("mark not set");
X	else
X		jump_loc(pos);
X}
X
X/*
X * Get the backwards scroll limit.
X * Must call this function instead of just using the value of
X * back_scroll, because the default case depends on sc_height and
X * top_scroll, as well as back_scroll.
X */
X	public int
Xget_back_scroll()
X{
X	if (back_scroll >= 0)
X		return (back_scroll);
X	if (top_scroll)
X		return (sc_height - 2);
X	return (sc_height - 1);
X}
X
X/*
X * Search for the n-th occurence of a specified pattern, 
X * either forward or backward.
X */
X	public void
Xsearch(search_forward, pattern, n, wantmatch)
X	register int search_forward;
X	register char *pattern;
X	register int n;
X	int wantmatch;
X{
X	POSITION pos, linepos;
X	register char *p;
X	register char *q;
X	int linenum;
X	int linematch;
X#if RECOMP
X	char *re_comp();
X	char *errmsg;
X#else
X#if REGCMP
X	char *regcmp();
X	static char *cpattern = NULL;
X#else
X	static char lpbuf[100];
X	static char *last_pattern = NULL;
X#endif
X#endif
X
X	if (caseless && pattern != NULL)
X	{
X		/*
X		 * For a caseless search, convert any uppercase
X		 * in the pattern to lowercase.
X		 */
X		for (p = pattern;  *p != '\0';  p++)
X			if (*p >= 'A' && *p <= 'Z')
X				*p += 'a' - 'A';
X	}
X#if RECOMP
X
X	/*
X	 * (re_comp handles a null pattern internally, 
X	 *  so there is no need to check for a null pattern here.)
X	 */
X	if ((errmsg = re_comp(pattern)) != NULL)
X	{
X		error(errmsg);
X		return;
X	}
X#else
X#if REGCMP
X	if (pattern == NULL || *pattern == '\0')
X	{
X		/*
X		 * A null pattern means use the previous pattern.
X		 * The compiled previous pattern is in cpattern, so just use it.
X		 */
X		if (cpattern == NULL)
X		{
X			error("No previous regular expression");
X			return;
X		}
X	} else
X	{
X		/*
X		 * Otherwise compile the given pattern.
X		 */
X		char *s;
X		if ((s = regcmp(pattern, 0)) == NULL)
X		{
X			error("Invalid pattern");
X			return;
X		}
X		if (cpattern != NULL)
X			free(cpattern);
X		cpattern = s;
X	}
X#else
X	if (pattern == NULL || *pattern == '\0')
X	{
X		/*
X		 * Null pattern means use the previous pattern.
X		 */
X		if (last_pattern == NULL)
X		{
X			error("No previous regular expression");
X			return;
X		}
X		pattern = last_pattern;
X	} else
X	{
X		strcpy(lpbuf, pattern);
X		last_pattern = lpbuf;
X	}
X#endif
X#endif
X
X	/*
X	 * Figure out where to start the search.
X	 */
X
X	if (position(TOP) == NULL_POSITION)
X	{
X		/*
X		 * Nothing is currently displayed.
X		 * Start at the beginning of the file.
X		 * (This case is mainly for first_cmd searches,
X		 * for example, "+/xyz" on the command line.)
X		 */
X		pos = (POSITION)0;
X	} else if (!search_forward)
X	{
X		/*
X		 * Backward search: start just before the top line
X		 * displayed on the screen.
X		 */
X		pos = position(TOP);
X	} else if (how_search == 0)
X	{
X		/*
X		 * Start at the second real line displayed on the screen.
X		 */
X		pos = position(TOP);
X		do
X			pos = forw_raw_line(pos);
X		while (pos < position(TOP+1));
X	} else if (how_search == 1)
X	{
X		/*
X		 * Start just after the bottom line displayed on the screen.
X		 */
X		pos = position(BOTTOM_PLUS_ONE);
X	} else
X	{
X		/*
X		 * Start at the second screen line displayed on the screen.
X		 */
X		pos = position(TOP_PLUS_ONE);
X	}
X
X	if (pos == NULL_POSITION)
X	{
X		/*
X		 * Can't find anyplace to start searching from.
X		 */
X		error("Nothing to search");
X		return;
X	}
X
X	linenum = find_linenum(pos);
X	for (;;)
X	{
X		/*
X		 * Get lines until we find a matching one or 
X		 * until we hit end-of-file (or beginning-of-file 
X		 * if we're going backwards).
X		 */
X		if (sigs)
X			/*
X			 * A signal aborts the search.
X			 */
X			return;
X
X		if (search_forward)
X		{
X			/*
X			 * Read the next line, and save the 
X			 * starting position of that line in linepos.
X			 */
X			linepos = pos;
X			pos = forw_raw_line(pos);
X			if (linenum != 0)
X				linenum++;
X		} else
X		{
X			/*
X			 * Read the previous line and save the
X			 * starting position of that line in linepos.
X			 */
X			pos = back_raw_line(pos);
X			linepos = pos;
X			if (linenum != 0)
X				linenum--;
X		}
X
X		if (pos == NULL_POSITION)
X		{
X			/*
X			 * We hit EOF/BOF without a match.
X			 */
X			error("Pattern not found");
X			return;
X		}
X
X		/*
X		 * If we're using line numbers, we might as well
X		 * remember the information we have now (the position
X		 * and line number of the current line).
X		 */
X		if (linenums)
X			add_lnum(linenum, pos);
X
X		if (caseless)
X		{
X			/*
X			 * If this is a caseless search, convert 
X			 * uppercase in the input line to lowercase.
X			 * While we're at it, remove any backspaces
X			 * along with the preceeding char.
X			 * This allows us to match text which is 
X			 * underlined or overstruck.
X			 */
X			for (p = q = line;  *p != '\0';  p++, q++)
X			{
X				if (*p >= 'A' && *p <= 'Z')
X					/* Convert uppercase to lowercase. */
X					*q = *p + 'a' - 'A';
X				else if (q > line && *p == '\b')
X					/* Delete BS and preceeding char. */
X					q -= 2;
X				else
X					/* Otherwise, just copy. */
X					*q = *p;
X			}
X		}
X
X		/*
X		 * Test the next line to see if we have a match.
X		 * This is done in a variety of ways, depending
X		 * on what pattern matching functions are available.
X		 */
X#if REGCMP
X		linematch = (regex(cpattern, line) != NULL);
X#else
X#if RECOMP
X		linematch = (re_exec(line) == 1);
X#else
X		linematch = match(pattern, line);
X#endif
X#endif
X		/*
X		 * We are successful if wantmatch and linematch are
X		 * both true (want a match and got it),
X		 * or both false (want a non-match and got it).
X		 */
X		if (((wantmatch && linematch) || (!wantmatch && !linematch)) &&
X		      --n <= 0)
X			/*
X			 * Found the line.
X			 */
X			break;
X	}
X
X	jump_loc(linepos);
X}
X
X#if (!REGCMP) && (!RECOMP)
X/*
X * We have neither regcmp() nor re_comp().
X * We use this function to do simple pattern matching.
X * It supports no metacharacters like *, etc.
X */
X	static int
Xmatch(pattern, buf)
X	char *pattern, *buf;
X{
X	register char *pp, *lp;
X
X	for ( ;  *buf != '\0';  buf++)
X	{
X		for (pp = pattern, lp = buf;  *pp == *lp;  pp++, lp++)
X			if (*pp == '\0' || *lp == '\0')
X				break;
X		if (*pp == '\0')
X			return (1);
X	}
X	return (0);
X}
X#endif
END_OF_FILE
echo shar: Extracting \"ch.c\"
sed "s/^X//" >'ch.c' <<'END_OF_FILE'
X/*
X * Low level character input from the input file.
X * We use these special purpose routines which optimize moving
X * both forward and backward from the current read pointer.
X */
X
X#include "less.h"
X
Xpublic int file = -1;		/* File descriptor of the input file */
X
X/*
X * Pool of buffers holding the most recently used blocks of the input file.
X */
X#define BUFSIZ	1024
Xstruct buf {
X	struct buf *next, *prev;
X	long block;
X	int datasize;
X	char data[BUFSIZ];
X};
Xpublic int nbufs;
X
X/*
X * The buffer pool is kept as a doubly-linked circular list,
X * in order from most- to least-recently used.
X * The circular list is anchored by buf_anchor.
X */
X#define	END_OF_CHAIN	((struct buf *)&buf_anchor)
X#define	buf_head	buf_anchor.next
X#define	buf_tail	buf_anchor.prev
X
Xstatic struct {
X	struct buf *next, *prev;
X} buf_anchor = { END_OF_CHAIN, END_OF_CHAIN };
X
Xextern int clean_data;
Xextern int ispipe;
Xextern int autobuf;
Xextern int cbufs;
Xextern int sigs;
X#if LOGFILE
Xextern int logfile;
X#endif
X
X/*
X * Current position in file.
X * Stored as a block number and an offset into the block.
X */
Xstatic long ch_block;
Xstatic int ch_offset;
X
X/* 
X * Length of file, needed if input is a pipe.
X */
Xstatic POSITION ch_fsize;
X
X/*
X * Number of bytes read, if input is standard input (a pipe).
X */
Xstatic POSITION last_piped_pos;
X
X/*
X * Get the character pointed to by the read pointer.
X * ch_get() is a macro which is more efficient to call
X * than fch_get (the function), in the usual case 
X * that the block desired is at the head of the chain.
X */
X#define	ch_get()   ((buf_head->block == ch_block && \
X		     ch_offset < buf_head->datasize) ? \
X			buf_head->data[ch_offset] : fch_get())
X	static int
Xfch_get()
X{
X	register struct buf *bp;
X	register int n;
X	register char *p;
X	POSITION pos;
X
X	/*
X	 * Look for a buffer holding the desired block.
X	 */
X	for (bp = buf_head;  bp != END_OF_CHAIN;  bp = bp->next)
X		if (bp->block == ch_block)
X		{
X			if (ch_offset >= bp->datasize)
X				/*
X				 * Need more data in this buffer.
X				 */
X				goto read_more;
X			/*
X			 * On a pipe, we don't sort the buffers LRU
X			 * because this can cause gaps in the buffers.
X			 * For example, suppose we've got 12 1K buffers,
X			 * and a 15K input stream.  If we read the first 12K
X			 * sequentially, then jump to line 1, then jump to
X			 * the end, the buffers have blocks 0,4,5,6,..,14.
X			 * If we then jump to line 1 again and try to
X			 * read sequentially, we're out of luck when we
X			 * get to block 1 (we'd get the "pipe error" below).
X			 * To avoid this, we only sort buffers on a pipe
X			 * when we actually READ the data, not when we
X			 * find it already buffered.
X			 */
X			if (ispipe)
X				return (bp->data[ch_offset]);
X			goto found;
X		}
X	/*
X	 * Block is not in a buffer.  
X	 * Take the least recently used buffer 
X	 * and read the desired block into it.
X	 * If the LRU buffer has data in it, 
X	 * and autobuf is true, and input is a pipe, 
X	 * then try to allocate a new buffer first.
X	 */
X	if (autobuf && ispipe && buf_tail->block != (long)(-1))
X		(void) ch_addbuf(1);
X	bp = buf_tail;
X	bp->block = ch_block;
X	bp->datasize = 0;
X
X    read_more:
X	pos = (ch_block * BUFSIZ) + bp->datasize;
X	if (ispipe)
X	{
X		/*
X		 * The data requested should be immediately after
X		 * the last data read from the pipe.
X		 */
X		if (pos != last_piped_pos)
X		{
X			error("pipe error");
X			quit();
X		}
X	} else
X		lseek(file, pos, 0);
X
X	/*
X	 * Read the block.
X	 * If we read less than a full block, we just return the
X	 * partial block and pick up the rest next time.
X	 */
X	n = iread(file, &bp->data[bp->datasize], BUFSIZ - bp->datasize);
X	if (n == READ_INTR)
X		return (EOI);
X	if (n < 0)
X	{
X		error("read error");
X		quit();
X	}
X	if (ispipe)
X		last_piped_pos += n;
X
X#if LOGFILE
X	/*
X	 * If we have a log file, write the new data to it.
X	 */
X	if (logfile >= 0 && n > 0)
X		write(logfile, &bp->data[bp->datasize], n);
X#endif
X
X	bp->datasize += n;
X
X	/*
X	 * Set an EOI marker in the buffered data itself.
X	 * Then ensure the data is "clean": there are no 
X	 * extra EOI chars in the data and that the "meta"
X	 * bit (the 0200 bit) is reset in each char.
X	 */
X	if (n == 0)
X	{
X		ch_fsize = pos;
X		bp->data[bp->datasize++] = EOI;
X	}
X
X	if (!clean_data)
X	{
X		p = &bp->data[bp->datasize];
X		while (--n >= 0)
X		{
X			*--p &= 0177;
X			if (*p == EOI)
X				*p = '@';
X		}
X	}
X
X    found:
X	if (buf_head != bp)
X	{
X		/*
X		 * Move the buffer to the head of the buffer chain.
X		 * This orders the buffer chain, most- to least-recently used.
X		 */
X		bp->next->prev = bp->prev;
X		bp->prev->next = bp->next;
X
X		bp->next = buf_head;
X		bp->prev = END_OF_CHAIN;
X		buf_head->prev = bp;
X		buf_head = bp;
X	}
X
X	if (ch_offset >= bp->datasize)
X		/*
X		 * After all that, we still don't have enough data.
X		 * Go back and try again.
X		 */
X		goto read_more;
X
X	return (bp->data[ch_offset]);
X}
X
X#if LOGFILE
X/*
X * Close the logfile.
X * If we haven't read all of standard input into it, do that now.
X */
X	public void
Xend_logfile()
X{
X	static int tried = 0;
X
X	if (logfile < 0)
X		return;
X	if (!tried && ch_fsize == NULL_POSITION)
X	{
X		tried = 1;
X		ierror("finishing logfile");
X		while (ch_forw_get() != EOI)
X			if (sigs)
X				break;
X	}
X	close(logfile);
X	logfile = -1;
X}
X
X/*
X * Start a log file AFTER less has already been running.
X * Invoked from the - command; see toggle_option().
X * Write all the existing buffered data to the log file.
X */
X	public void
Xsync_logfile()
X{
X	register struct buf *bp;
X	register int n;
X	long block;
X	long last_block;
X
X	last_block = (last_piped_pos + BUFSIZ - 1) / BUFSIZ;
X	for (block = 0;  block <= last_block;  block++)
X		for (bp = buf_head;  bp != END_OF_CHAIN;  bp = bp->next)
X			if (bp->block == block)
X			{
X				n = bp->datasize;
X				if (bp->data[n-1] == EOI)
X					n--;
X				write(logfile, bp->data, n);
X				break;
X			}
X}
X
X#endif
X
X/*
X * Determine if a specific block is currently in one of the buffers.
X */
X	static int
Xbuffered(block)
X	long block;
X{
X	register struct buf *bp;
X
X	for (bp = buf_head;  bp != END_OF_CHAIN;  bp = bp->next)
X		if (bp->block == block)
X			return (1);
X	return (0);
X}
X
X/*
X * Seek to a specified position in the file.
X * Return 0 if successful, non-zero if can't seek there.
X */
X	public int
Xch_seek(pos)
X	register POSITION pos;
X{
X	long new_block;
X
X	new_block = pos / BUFSIZ;
X	if (!ispipe || pos == last_piped_pos || buffered(new_block))
X	{
X		/*
X		 * Set read pointer.
X		 */
X		ch_block = new_block;
X		ch_offset = pos % BUFSIZ;
X		return (0);
X	}
X	return (1);
X}
X
X/*
X * Seek to the end of the file.
X */
X	public int
Xch_end_seek()
X{
X	if (!ispipe)
X		return (ch_seek(ch_length()));
X
X	/*
X	 * Do it the slow way: read till end of data.
X	 */
X	while (ch_forw_get() != EOI)
X		if (sigs)
X			return (1);
X	return (0);
X}
X
X/*
X * Seek to the beginning of the file, or as close to it as we can get.
X * We may not be able to seek there if input is a pipe and the
X * beginning of the pipe is no longer buffered.
X */
X	public int
Xch_beg_seek()
X{
X	register struct buf *bp, *firstbp;
X
X	/*
X	 * Try a plain ch_seek first.
X	 */
X	if (ch_seek((POSITION)0) == 0)
X		return (0);
X
X	/*
X	 * Can't get to position 0.
X	 * Look thru the buffers for the one closest to position 0.
X	 */
X	firstbp = bp = buf_head;
X	if (bp == END_OF_CHAIN)
X		return (1);
X	while ((bp = bp->next) != END_OF_CHAIN)
X		if (bp->block < firstbp->block)
X			firstbp = bp;
X	ch_block = firstbp->block;
X	ch_offset = 0;
X	return (0);
X}
X
X/*
X * Return the length of the file, if known.
X */
X	public POSITION
Xch_length()
X{
X	if (ispipe)
X		return (ch_fsize);
X	return ((POSITION)(lseek(file, (offset_t)0, 2)));
X}
X
X/*
X * Return the current position in the file.
X */
X	public POSITION
Xch_tell()
X{
X	return (ch_block * BUFSIZ + ch_offset);
X}
X
X/*
X * Get the current char and post-increment the read pointer.
X */
X	public int
Xch_forw_get()
X{
X	register int c;
X
X	c = ch_get();
X	if (c != EOI && ++ch_offset >= BUFSIZ)
X	{
X		ch_offset = 0;
X		ch_block ++;
X	}
X	return (c);
X}
X
X/*
X * Pre-decrement the read pointer and get the new current char.
X */
X	public int
Xch_back_get()
X{
X	if (--ch_offset < 0)
X	{
X		if (ch_block <= 0 || (ispipe && !buffered(ch_block-1)))
X		{
X			ch_offset = 0;
X			return (EOI);
X		}
X		ch_offset = BUFSIZ - 1;
X		ch_block--;
X	}
X	return (ch_get());
X}
X
X/*
X * Allocate buffers.
X * Caller wants us to have a total of at least want_nbufs buffers.
X * keep==1 means keep the data in the current buffers;
X * otherwise discard the old data.
X */
X	public void
Xch_init(want_nbufs, keep)
X	int want_nbufs;
X	int keep;
X{
X	register struct buf *bp;
X	char message[80];
X
X	cbufs = nbufs;
X	if (nbufs < want_nbufs && ch_addbuf(want_nbufs - nbufs))
X	{
X		/*
X		 * Cannot allocate enough buffers.
X		 * If we don't have ANY, then quit.
X		 * Otherwise, just report the error and return.
X		 */
X		sprintf(message, "cannot allocate %d buffers",
X			want_nbufs - nbufs);
X		error(message);
X		if (nbufs == 0)
X			quit();
X		return;
X	}
X
X	if (keep)
X		return;
X
X	/*
X	 * We don't want to keep the old data,
X	 * so initialize all the buffers now.
X	 */
X	for (bp = buf_head;  bp != END_OF_CHAIN;  bp = bp->next)
X		bp->block = (long)(-1);
X	last_piped_pos = (POSITION)0;
X	ch_fsize = NULL_POSITION;
X	(void) ch_seek((POSITION)0);
X}
X
X/*
X * Allocate some new buffers.
X * The buffers are added to the tail of the buffer chain.
X */
X	static int
Xch_addbuf(nnew)
X	int nnew;
X{
X	register struct buf *bp;
X	register struct buf *newbufs;
X
X	/*
X	 * We don't have enough buffers.  
X	 * Allocate some new ones.
X	 */
X	newbufs = (struct buf *) calloc(nnew, sizeof(struct buf));
X	if (newbufs == NULL)
X		return (1);
X
X	/*
X	 * Initialize the new buffers and link them together.
X	 * Link them all onto the tail of the buffer list.
X	 */
X	nbufs += nnew;
X	cbufs = nbufs;
X	for (bp = &newbufs[0];  bp < &newbufs[nnew];  bp++)
X	{
X		bp->next = bp + 1;
X		bp->prev = bp - 1;
X		bp->block = (long)(-1);
X	}
X	newbufs[nnew-1].next = END_OF_CHAIN;
X	newbufs[0].prev = buf_tail;
X	buf_tail->next = &newbufs[0];
X	buf_tail = &newbufs[nnew-1];
X	return (0);
X}
END_OF_FILE
echo shar: Extracting \"position.c\"
sed "s/^X//" >'position.c' <<'END_OF_FILE'
X/*
X * Routines dealing with the "position" table.
X * This is a table which tells the position (in the input file) of the
X * first char on each currently displayed line.
X *
X * {{ The position table is scrolled by moving all the entries.
X *    Would be better to have a circular table 
X *    and just change a couple of pointers. }}
X */
X
X#include "less.h"
X#include "position.h"
X
X#define	NPOS	100		/* {{ sc_height must be less than NPOS }} */
Xstatic POSITION table[NPOS];	/* The position table */
X
Xextern int sc_width, sc_height;
X
X/*
X * Return the starting file position of a line displayed on the screen.
X * The line may be specified as a line number relative to the top
X * of the screen, but is usually one of these special cases:
X *	the top (first) line on the screen
X *	the second line on the screen
X *	the bottom line on the screen
X *	the line after the bottom line on the screen
X */
X	public POSITION
Xposition(where)
X	int where;
X{
X	switch (where)
X	{
X	case BOTTOM:
X		where = sc_height - 2;
X		break;
X	case BOTTOM_PLUS_ONE:
X		where = sc_height - 1;
X		break;
X	case MIDDLE:
X		where = sc_height / 2;
X	}
X	return (table[where]);
X}
X
X/*
X * Add a new file position to the bottom of the position table.
X */
X	public void
Xadd_forw_pos(pos)
X	POSITION pos;
X{
X	register int i;
X
X	/*
X	 * Scroll the position table up.
X	 */
X	for (i = 1;  i < sc_height;  i++)
X		table[i-1] = table[i];
X	table[sc_height - 1] = pos;
X}
X
X/*
X * Add a new file position to the top of the position table.
X */
X	public void
Xadd_back_pos(pos)
X	POSITION pos;
X{
X	register int i;
X
X	/*
X	 * Scroll the position table down.
X	 */
X	for (i = sc_height - 1;  i > 0;  i--)
X		table[i] = table[i-1];
X	table[0] = pos;
X}
X
X/*
X * Initialize the position table, done whenever we clear the screen.
X */
X	public void
Xpos_clear()
X{
X	register int i;
X
X	for (i = 0;  i < sc_height;  i++)
X		table[i] = NULL_POSITION;
X}
X
X/*
X * See if the byte at a specified position is currently on the screen.
X * Check the position table to see if the position falls within its range.
X * Return the position table entry if found, -1 if not.
X */
X	public int
Xonscreen(pos)
X	POSITION pos;
X{
X	register int i;
X
X	if (pos < table[0])
X		return (-1);
X	for (i = 1;  i < sc_height;  i++)
X		if (pos < table[i])
X			return (i-1);
X	return (-1);
X}
END_OF_FILE
echo shar: Extracting \"input.c\"
sed "s/^X//" >'input.c' <<'END_OF_FILE'
X/*
X * High level routines dealing with getting lines of input 
X * from the file being viewed.
X *
X * When we speak of "lines" here, we mean PRINTABLE lines;
X * lines processed with respect to the screen width.
X * We use the term "raw line" to refer to lines simply
X * delimited by newlines; not processed with respect to screen width.
X */
X
X#include "less.h"
X
Xextern int squeeze;
Xextern int sigs;
Xextern char *line;
X
X/*
X * Get the next line.
X * A "current" position is passed and a "new" position is returned.
X * The current position is the position of the first character of
X * a line.  The new position is the position of the first character
X * of the NEXT line.  The line obtained is the line starting at curr_pos.
X */
X	public POSITION
Xforw_line(curr_pos)
X	POSITION curr_pos;
X{
X	POSITION new_pos;
X	register int c;
X
X	if (curr_pos == NULL_POSITION || ch_seek(curr_pos))
X		return (NULL_POSITION);
X
X	c = ch_forw_get();
X	if (c == EOI)
X		return (NULL_POSITION);
X
X	prewind();
X	for (;;)
X	{
X		if (sigs)
X			return (NULL_POSITION);
X		if (c == '\n' || c == EOI)
X		{
X			/*
X			 * End of the line.
X			 */
X			new_pos = ch_tell();
X			break;
X		}
X
X		/*
X		 * Append the char to the line and get the next char.
X		 */
X		if (pappend(c))
X		{
X			/*
X			 * The char won't fit in the line; the line
X			 * is too long to print in the screen width.
X			 * End the line here.
X			 */
X			new_pos = ch_tell() - 1;
X			break;
X		}
X		c = ch_forw_get();
X	}
X	(void) pappend('\0');
X
X	if (squeeze && *line == '\0')
X	{
X		/*
X		 * This line is blank.
X		 * Skip down to the last contiguous blank line
X		 * and pretend it is the one which we are returning.
X		 */
X		while ((c = ch_forw_get()) == '\n')
X			if (sigs)
X				return (NULL_POSITION);
X		if (c != EOI)
X			(void) ch_back_get();
X		new_pos = ch_tell();
X	}
X
X	return (new_pos);
X}
X
X/*
X * Get the previous line.
X * A "current" position is passed and a "new" position is returned.
X * The current position is the position of the first character of
X * a line.  The new position is the position of the first character
X * of the PREVIOUS line.  The line obtained is the one starting at new_pos.
X */
X	public POSITION
Xback_line(curr_pos)
X	POSITION curr_pos;
X{
X	POSITION new_pos, begin_new_pos;
X	int c;
X
X	if (curr_pos == NULL_POSITION || curr_pos <= (POSITION)0 ||
X		ch_seek(curr_pos-1))
X		return (NULL_POSITION);
X
X	if (squeeze)
X	{
X		/*
X		 * Find out if the "current" line was blank.
X		 */
X		(void) ch_forw_get();	/* Skip the newline */
X		c = ch_forw_get();	/* First char of "current" line */
X		(void) ch_back_get();	/* Restore our position */
X		(void) ch_back_get();
X
X		if (c == '\n')
X		{
X			/*
X			 * The "current" line was blank.
X			 * Skip over any preceeding blank lines,
X			 * since we skipped them in forw_line().
X			 */
X			while ((c = ch_back_get()) == '\n')
X				if (sigs)
X					return (NULL_POSITION);
X			if (c == EOI)
X				return (NULL_POSITION);
X			(void) ch_forw_get();
X		}
X	}
X
X	/*
X	 * Scan backwards until we hit the beginning of the line.
X	 */
X	for (;;)
X	{
X		if (sigs)
X			return (NULL_POSITION);
X		c = ch_back_get();
X		if (c == '\n')
X		{
X			/*
X			 * This is the newline ending the previous line.
X			 * We have hit the beginning of the line.
X			 */
X			new_pos = ch_tell() + 1;
X			break;
X		}
X		if (c == EOI)
X		{
X			/*
X			 * We have hit the beginning of the file.
X			 * This must be the first line in the file.
X			 * This must, of course, be the beginning of the line.
X			 */
X			new_pos = ch_tell();
X			break;
X		}
X	}
X
X	/*
X	 * Now scan forwards from the beginning of this line.
X	 * We keep discarding "printable lines" (based on screen width)
X	 * until we reach the curr_pos.
X	 *
X	 * {{ This algorithm is pretty inefficient if the lines
X	 *    are much longer than the screen width, 
X	 *    but I don't know of any better way. }}
X	 */
X	if (ch_seek(new_pos))
X		return (NULL_POSITION);
X    loop:
X	begin_new_pos = new_pos;
X	prewind();
X
X	do
X	{
X		c = ch_forw_get();
X		if (c == EOI || sigs)
X			return (NULL_POSITION);
X		new_pos++;
X		if (c == '\n')
X			break;
X		if (pappend(c))
X		{
X			/*
X			 * Got a full printable line, but we haven't
X			 * reached our curr_pos yet.  Discard the line
X			 * and start a new one.
X			 */
X			(void) pappend('\0');
X			(void) ch_back_get();
X			new_pos--;
X			goto loop;
X		}
X	} while (new_pos < curr_pos);
X
X	(void) pappend('\0');
X
X	return (begin_new_pos);
X}
END_OF_FILE
echo shar: Extracting \"linenum.c\"
sed "s/^X//" >'linenum.c' <<'END_OF_FILE'
X/*
X * Code to handle displaying line numbers.
X *
X * Finding the line number of a given file position is rather tricky.
X * We don't want to just start at the beginning of the file and
X * count newlines, because that is slow for large files (and also
X * wouldn't work if we couldn't get to the start of the file; e.g.
X * if input is a long pipe).
X *
X * So we use the function add_lnum to cache line numbers.
X * We try to be very clever and keep only the more interesting
X * line numbers when we run out of space in our table.  A line
X * number is more interesting than another when it is far from
X * other line numbers.   For example, we'd rather keep lines
X * 100,200,300 than 100,101,300.  200 is more interesting than
X * 101 because 101 can be derived very cheaply from 100, while
X * 200 is more expensive to derive from 100.
X *
X * The function currline() returns the line number of a given
X * position in the file.  As a side effect, it calls add_lnum
X * to cache the line number.  Therefore currline is occasionally
X * called to make sure we cache line numbers often enough.
X */
X
X#include "less.h"
X#include "position.h"
X
X/*
X * Structure to keep track of a line number and the associated file position.
X * A doubly-linked circular list of line numbers is kept ordered by line number.
X */
Xstruct linenum
X{
X	struct linenum *next;		/* Link to next in the list */
X	struct linenum *prev;		/* Line to previous in the list */
X	POSITION pos;			/* File position */
X	POSITION gap;			/* Gap between prev and next */
X	int line;			/* Line number */
X};
X/*
X * "gap" needs some explanation: the gap of any particular line number
X * is the distance between the previous one and the next one in the list.
X * ("Distance" means difference in file position.)  In other words, the
X * gap of a line number is the gap which would be introduced if this
X * line number were deleted.  It is used to decide which one to replace
X * when we have a new one to insert and the table is full.
X */
X
X#define	NPOOL	50			/* Size of line number pool */
X
X#define	LONGTIME	(2)		/* In seconds */
X
Xpublic int lnloop = 0;			/* Are we in the line num loop? */
X
Xstatic struct linenum anchor;		/* Anchor of the list */
Xstatic struct linenum *freelist;	/* Anchor of the unused entries */
Xstatic struct linenum pool[NPOOL];	/* The pool itself */
Xstatic struct linenum *spare;		/* We always keep one spare entry */
X
Xextern int linenums;
Xextern int sigs;
X
X/*
X * Initialize the line number structures.
X */
X	public void
Xclr_linenum()
X{
X	register struct linenum *p;
X
X	/*
X	 * Put all the entries on the free list.
X	 * Leave one for the "spare".
X	 */
X	for (p = pool;  p < &pool[NPOOL-2];  p++)
X		p->next = p+1;
X	pool[NPOOL-2].next = NULL;
X	freelist = pool;
X
X	spare = &pool[NPOOL-1];
X
X	/*
X	 * Initialize the anchor.
X	 */
X	anchor.next = anchor.prev = &anchor;
X	anchor.gap = 0;
X	anchor.pos = (POSITION)0;
X	anchor.line = 1;
X}
X
X/*
X * Calculate the gap for an entry.
X */
X	static void
Xcalcgap(p)
X	register struct linenum *p;
X{
X	/*
X	 * Don't bother to compute a gap for the anchor.
X	 * Also don't compute a gap for the last one in the list.
X	 * The gap for that last one should be considered infinite,
X	 * but we never look at it anyway.
X	 */
X	if (p == &anchor || p->next == &anchor)
X		return;
X	p->gap = p->next->pos - p->prev->pos;
X}
X
X/*
X * Add a new line number to the cache.
X * The specified position (pos) should be the file position of the
X * FIRST character in the specified line.
X */
X	public void
Xadd_lnum(line, pos)
X	int line;
X	POSITION pos;
X{
X	register struct linenum *p;
X	register struct linenum *new;
X	register struct linenum *nextp;
X	register struct linenum *prevp;
X	register POSITION mingap;
X
X	/*
X	 * Find the proper place in the list for the new one.
X	 * The entries are sorted by position.
X	 */
X	for (p = anchor.next;  p != &anchor && p->pos < pos;  p = p->next)
X		if (p->line == line)
X			/* We already have this one. */
X			return;
X	nextp = p;
X	prevp = p->prev;
X
X	if (freelist != NULL)
X	{
X		/*
X		 * We still have free (unused) entries.
X		 * Use one of them.
X		 */
X		new = freelist;
X		freelist = freelist->next;
X	} else
X	{
X		/*
X		 * No free entries.
X		 * Use the "spare" entry.
X		 */
X		new = spare;
X		spare = NULL;
X	}
X
X	/*
X	 * Fill in the fields of the new entry,
X	 * and insert it into the proper place in the list.
X	 */
X	new->next = nextp;
X	new->prev = prevp;
X	new->pos = pos;
X	new->line = line;
X
X	nextp->prev = new;
X	prevp->next = new;
X
X	/*
X	 * Recalculate gaps for the new entry and the neighboring entries.
X	 */
X	calcgap(new);
X	calcgap(nextp);
X	calcgap(prevp);
X
X	if (spare == NULL)
X	{
X		/*
X		 * We have used the spare entry.
X		 * Scan the list to find the one with the smallest
X		 * gap, take it out and make it the spare.
X		 * We should never remove the last one, so stop when
X		 * we get to p->next == &anchor.  This also avoids
X		 * looking at the gap of the last one, which is
X		 * not computed by calcgap.
X		 */
X		mingap = anchor.next->gap;
X		for (p = anchor.next;  p->next != &anchor;  p = p->next)
X		{
X			if (p->gap <= mingap)
X			{
X				spare = p;
X				mingap = p->gap;
X			}
X		}
X		spare->next->prev = spare->prev;
X		spare->prev->next = spare->next;
X	}
X}
X
X/*
X * If we get stuck in a long loop trying to figure out the
X * line number, print a message to tell the user what we're doing.
X */
X	static void
Xlongloopmessage()
X{
X	ierror("Calculating line numbers");
X	/*
X	 * Set the lnloop flag here, so if the user interrupts while
X	 * we are calculating line numbers, the signal handler will 
X	 * turn off line numbers (linenums=0).
X	 */
X	lnloop = 1;
X}
X
X/*
X * Find the line number associated with a given position.
X * Return 0 if we can't figure it out.
X */
X	public int
Xfind_linenum(pos)
X	POSITION pos;
X{
X	register struct linenum *p;
X	register int lno;
X	register int loopcount;
X	POSITION cpos;
X#if GET_TIME
X	long startime;
X#endif
X
X	if (!linenums)
X		/*
X		 * We're not using line numbers.
X		 */
X		return (0);
X	if (pos == NULL_POSITION)
X		/*
X		 * Caller doesn't know what he's talking about.
X		 */
X		return (0);
X	if (pos == (POSITION)0)
X		/*
X		 * Beginning of file is always line number 1.
X		 */
X		return (1);
X
X	/*
X	 * Find the entry nearest to the position we want.
X	 */
X	for (p = anchor.next;  p != &anchor && p->pos < pos;  p = p->next)
X		continue;
X	if (p->pos == pos)
X		/* Found it exactly. */
X		return (p->line);
X
X	/*
X	 * This is the (possibly) time-consuming part.
X	 * We start at the line we just found and start
X	 * reading the file forward or backward till we
X	 * get to the place we want.
X	 *
X	 * First decide whether we should go forward from the 
X	 * previous one or backwards from the next one.
X	 * The decision is based on which way involves 
X	 * traversing fewer bytes in the file.
X	 */
X	flush();
X#if GET_TIME
X	startime = get_time();
X#endif
X	if (p == &anchor || pos - p->prev->pos < p->pos - pos)
X	{
X		/*
X		 * Go forward.
X		 */
X		p = p->prev;
X		if (ch_seek(p->pos))
X			return (0);
X		loopcount = 0;
X		for (lno = p->line, cpos = p->pos;  cpos < pos;  lno++)
X		{
X			/*
X			 * Allow a signal to abort this loop.
X			 */
X			cpos = forw_raw_line(cpos);
X			if (sigs || cpos == NULL_POSITION)
X				return (0);
X#if GET_TIME
X			if (loopcount >= 0 && ++loopcount > 100)
X			{
X				loopcount = 0;
X				if (get_time() >= startime + LONGTIME)
X				{
X					longloopmessage();
X					loopcount = -1;
X				}
X			}
X#else
X			if (loopcount >= 0 && ++loopcount > LONGLOOP)
X			{
X				longloopmessage();
X				loopcount = -1;
X			}
X#endif
X		}
X		lnloop = 0;
X		/*
X		 * If the given position is not at the start of a line,
X		 * make sure we return the correct line number.
X		 */
X		if (cpos > pos)
X			lno--;
X	} else
X	{
X		/*
X		 * Go backward.
X		 */
X		if (ch_seek(p->pos))
X			return (0);
X		loopcount = 0;
X		for (lno = p->line, cpos = p->pos;  cpos > pos;  lno--)
X		{
X			/*
X			 * Allow a signal to abort this loop.
X			 */
X			cpos = back_raw_line(cpos);
X			if (sigs || cpos == NULL_POSITION)
X				return (0);
X#if GET_TIME
X			if (loopcount >= 0 && ++loopcount > 100)
X			{
X				loopcount = 0;
X				if (get_time() >= startime + LONGTIME)
X				{
X					longloopmessage();
X					loopcount = -1;
X				}
X			}
X#else
X			if (loopcount >= 0 && ++loopcount > LONGLOOP)
X			{
X				longloopmessage();
X				loopcount = -1;
X			}
X#endif
X		}
X		lnloop = 0;
X	}
X
X	/*
X	 * We might as well cache it.
X	 */
X	add_lnum(lno, cpos);
X	return (lno);
X}
X
X/*
X * Return the line number of the "current" line.
X * The argument "where" tells which line is to be considered
X * the "current" line (e.g. TOP, BOTTOM, MIDDLE, etc).
X */
X	public int
Xcurrline(where)
X	int where;
X{
X	POSITION pos;
X
X	pos = position(where);
X	if (pos == NULL_POSITION)
X		pos = ch_length();
X	return (find_linenum(pos));
X}
X
X#if DEBUG_STUFF
Xdebug()
X{
X	register struct linenum *p;
X	char buf[20];
X
X	lower_left();
X	clear_eol();
X	for (p = anchor.next;  p != &anchor;  p = p->next)
X	{
X		sprintf(buf, "%d-%d ", p->line, p->pos);
X		putstr(buf);
X	}
X	putstr("\n");
X	error("DEBUG");
X}
X#endif /*DEBUG_STUFF*/
END_OF_FILE


-- 
Please send comp.sources.unix-related mail to rsalz at uunet.uu.net.



More information about the Comp.sources.unix mailing list