#ifndef NO_IDENT
static char *Id = "$Id: browse.c,v 1.22 1995/10/27 13:31:04 tom Exp $";
#endif

/*
 * Title:	BROWSE.c (BROWSE main program)
 * Author:	T. Dickey
 *		(This is a completely re-written version of a program
 *		titled BROWSE, which was written by L.Seeton, 06-Nov-1983).
 * Created:	16 Apr 1984
 * Last update:
 *		27 Oct 1995, release-version
 *		22 Oct 1995, DEC-C clean-compile
 *		28 May 1995, use stdarg instead of VARARGS hack.
 *		27 May 1995, prototyped
 *		18 Feb 1995	port to AXP (renamed 'alarm').
 *		24 Feb 1989	disable /COMMAND when invoked from FLIST.
 *				added LOGFILE/LOGARGS hooks to integrate with
 *				FLIST's /COMMAND and /LOG options.
 *		22 Aug 1988	Forgot to check for no file at all!
 *		07 Oct 1985	Added 80/132-column switch 'G'.
 *		17 Aug 1985	Corrected case of overstrike of text on space.
 *		15 Aug 1985	Permit value on /SQUEEZE
 *		29 Jul 1985	Interpret VT100 keypad.
 *		18 Jul 1985	Gave up on using 'rmscc' (file attributes) to
 *				set implicit value for /JOIN, since we have
 *				often files from tape w/o carriage-control.
 *		03 Jul 1985	Use 'scanint' instead of 'sscanf' to bypass bug
 *				in CC2.0
 *		15 Jun 1985	Added '&' in 'opts[]' declaration to make
 *				CC2.0 happy.
 *		27 Apr 1985	Broke out read-command with 'edtcmd' stuff.
 *		12 Apr 1985	Added 'gotraw' call to make buffer-input faster.
 *				Use VARARGS in 'warn'.
 *		10 Apr 1985	Added 'clrwarn()' call so that if we get an
 *				error (e.g., in /COMMAND) which does not normally
 *				halt operation, then FLIST won't have its
 *				refresh-disable set.
 *		06 Apr 1985	redid search-abort as CTRL/X-ast.  Added
 *				/COMMAND-option (given support in 'getraw').
 *		28 Mar 1985	added 'status' argument to 'error'.
 *		26 Mar 1985	RELEASE-2: v923
 *		20 Mar 1985	Changed to a conventional (VMS-style) help-display.
 *		14 Mar 1985	renamed the release-version of this to BROWSE
 *		02 Mar 1985	changed /OVER to 2-state toggle, with an
 *				argument which permits any state to be set.
 *				Permit highlighting of space overstruck by a
 *				nonnull/nonblank.
 *		18 Feb 1985	fixed 'offset[0]' latching; indexed files do
 *				not necessarily start with rfa=0.
 *		15 Feb 1985	alter /JOIN to look for trailing LF (not CR).
 *		13 Feb 1985	use 'calloc' for static buffers.  Moved crt-init
 *				so that if we can't look at file, then we won't
 *				clear the screen.
 *		12 Feb 1985	added 'erstat' call to check on record-errors.
 *				Added 'more_name', to restrict length of name
 *				in summary-line.
 *		02 Feb 1985	use scrolling-margins
 *		26 Jan 1985	cleanup use of sound_alarm in out-of-bounds scroll.
 *		20 Jan 1985	corrected for zero-length records in /SQUEEZE.
 *		19 Jan 1985	make repeat-count work with U,D,F,B commands.
 *				Added I- and J-commands, also /TRIM option.
 *		17 Jan 1985	make "_" command something we can use with the
 *				other commands.  Also, recognize CTRL/K, CTRL/W
 *				in the read-buffer routine.
 *		02 Jan 1985	Use 'rmscc' to set default on '/JOIN'.  Added
 *				'Z' as synonym for QUIT.
 *		26 Dec 1984	Changed call on 'dclwild'.
 *		04 Dec 1984	Added 'X', to abort search/skip.  Use 'putraw',
 *				'whoami'.  Added '/JOIN', '/SQUEEZE' options.
 *		06 Nov 1984	Re-wrote search-compare, using 'strvcmp'.
 *				This permits BROWSE to find overlapping search-
 *				targets (as well as fix a reset-bug).
 *		23 Oct 1984	(had stepped on trailing-spaces)
 *		21 Oct 1984	pad the record-size allocation
 *		20 Oct 1984	cleanup of '^' transition, record-length.
 *		15 Oct 1984	added 'snapshot', version
 *		13 Oct 1984	completed multi-line record coding.
 *		29 Sep 1984	began re-code to permit record to span line
 *		27 Sep 1984	added 'col_base' logic to fix ^L display bug.
 *				Corrected combination-display of FIND_?.
 *		21 Sep 1984	permit 3-state O-flag, added M-flag toggle
 *		20 Sep 1984	add logic to do space-independent search
 *		17 Sep 1984	added O-flag toggle
 *		15 Sep 1984	eliminated dependency on 'stderr'
 *		11 Sep 1984	corrected position of FIND_R marker, broke out
 *				'fgetr' code while setting up RMSIO package,
 *				use macro-form of 'toupper'.
 *		23 Aug 1984	Added numeric argument (for fractional L/R)
 *		25 Jul 1984	Added 'cmd_arg' to 'argvdcl()' call.  Show
 *				filename w/o pathname if current default.
 *		13 Jul 1984	Use DCLOPT for options-parsing
 *		10 Jul 1984	No ^C command.  Show filename in blurb.
 *		04 Jul 1984	Use 'argvdcl()' to better parse argv.
 *				Fixed bug in backward search.
 *		27 Jun 1984
 *		20 Jun 1984	Cannot mix Bitgraph with FLIST (main_incl)
 *		18 Jun 1984	completed crt modification
 *		31 May 1984	(TED) Began re-code to use 'crt.c' module
 *		29 May 1984	(TED) Optimized user-buf read.
 *		27 May 1984	(TED) Adjustments to "/" search.
 *		22 May 1984	(TED) Use 'getpad' name to combine with FLIST.
 *		12 May 1984	(TED) Further altered getraw, error-return.
 *		07 May 1984	(TED) Altered (using MAIN-define) this source
 *				to permit it to be used as a subroutine, or
 *				as standalone source.
 *		26 Apr 1984	(TED) trim lines before printing them.  Allow
 *				more than 1 file to be inspected.
 *		18 Apr 1984	(TED) altered search command to show progress
 *		17 Apr 1984	(TED) added help message, "o" option, search cmd.
 *		16 Apr 1984	(TED), added L/R scrolling, Bitgraph option,
 *				rewrote to use DEC library routines to obtain
 *				the actual terminal characteristics, and to do
 *				correct VT52/VT100 cursor movement.
 *
 * Notes:	BROWSE does both left/right and up/down scrolling.  The total
 *		number of lines on the CRT screen is assumed even, so that
 *		when the status line is deducted, the remaining part is odd.
 *		This logic is implicit in the up/down scrolling, which overlaps
 *		each marked section by one line (e.g., scrolling forward one
 *		screen keeps one line the same).
 */

#include	<stdio.h>	/* for 'sprintf()' */
#include	<signal.h>	/* for 'sleep()' */

#include	<stdlib.h>
#include	<stdarg.h>
#include	<string.h>

#include	"rmsio.h"
#include	<ctype.h>
#include	<stsdef.h>

#include	"bool.h"
#include	"cmdstk.h"
#include	"edtcmd.h"
#include	"getpad.h"
#include	"getraw.h"

#include	"dclarg.h"
#include	"dclopt.h"
#include	"freelist.h"
#include	"names.h"
#include	"dspc.h"
#include	"whoami.h"

#include	"strutils.h"

#ifdef main_incl
#include	"flist.h"
#else
#define	CTL(c)	(037 & c)
#include	"warning.h"
#endif

static	int	more_0_bg (short *lpp_, short *width_);
static	int	more_0_vt (short *lpp_, short *width_);
static	void	more_0_line (void);
static	void	more_0_mark (void);
static	void	more_0_page (void);
static	void	more_char (char c);
static	void	more_conv (void);
static	void	more_ctlc (char *ctl_);
static	void	more_down (int frac, int rpt);
static	void	more_file (void);
static	void	more_getr (void);
static	int	more_lastp (void);
static	int	more_limit (int val, int lo, int hi);
static	void	more_line (void);
static	int	more_move (int dy, int dx);
static	int	more_msg (char *c_, int last);
static	char*	more_name (int maxlen);
static	void	more_next (int new);
static	void	more_null (int inx);
static	int	more_r_buf (char *co_, char *m1_, char *delim_);
static	int	more_r_cmd (void);
static	int	more_read (int rec);
static	void	more_right (void);
static	int	more_round (int num, int d);
static	void	more_screen (int view_size, int dirflg);
static	void	more_seek (int next);
static	int	more_size (int over);
static	int	more_skip (char *find_);
static	void	more_this (void);

#ifdef DEBUG
static	void	more_show (char *format, ...);
#endif

/*
 * Local (static) data:
 */
#define	MAX_PAGE	99999	/* maximum value for half-screen index	*/
#define	MAXREC		999999	/* maximum record # this program reads	*/
#define	MAXBFR		513	/* size of input/output buffers		*/
#define	MAXVEC		1024	/* granularity of seek-mark array	*/

#define	MAXCOL		(colmax-7)	/* threshold used in ruler-mode	*/

	/* Index to 'offset[]' for next, current previous pages */
#define	TOP_NEXT	(now)
#define	TOP_THIS	(now-2)
#define	TOP_PREV	(now-4)

	/* Record # (0,1,2,...) at 'offset[]' position:	*/
#define	LINEMARK(n)	(HalfPage*(n))

	/* Derived line-number, from 'inpage':			*/
#define	INPAGE_LINE	(inpage + BaseLine)
#define	INFILE_LINE	(crt_top() + INPAGE_LINE)

	/* Mark-index of last half-screen:			*/
#define	END_MARK	((last_page >= 0) ? end_mark : MAX_PAGE)

	/* Mark-index of last full (even) page:			*/
#define	LAST_PAGE	((last_page >= 0) ? last_page : end_mark)

#define	FIND_L	1	/* if off-screen on left		*/
#define	FIND_R	2	/* if off-screen on right		*/
#define	FIND_IN	4	/* if any in-screen			*/
#define	FIND_XX	8	/* debug: show half-screen marks	*/

#define	STRVCMP	strvcmp(find_bfr, &i_bfr[inx], lstate_end-inx)

#define	BELL	CTL('G')
#define	RUBOUT	'\177'
#define	toshift(c)	(iscntrl(c) ? ((c) | 0100) : (c))

#define	MARK_WIDTH	13	/* Columns used for 'M_OPT'	*/

#define	SHOW_OFF(n)	(n),offset[n].rfa,offset[n].cra

static	RFILE	*fp;

typedef	struct	{
	unsigned rfa;	/* Record's file-address (direct 'ftell/fseek')	*/
	short	cra;	/* Character's record-address (maintained here)	*/
	short	rec;	/* Actual record number				*/
	} OFFSET;
static
OFFSET	*offset,	/* 'ftell' positions, for retyping		*/
	rstate;		/* State of 'more_read', to extract line	*/

			/* State of current page-characters		*/
static
int			/* Terminal characteristics:			*/
	lpp,		/* maximum lines/page				*/
	width,		/* screen width					*/
	width2,		/* 1/2, for scrolling left/right		*/
	did_crt_init = FALSE,

	M_option,	/* TRUE iff "/MARK" set originally		*/
	O_option,	/* TRUE iff "/OVER" set originally		*/

	/*
	 * Miscellaneous flags:
	 */
	user_cmd,	/* (main-case): most recent command read	*/
	user_arg,	/* " " " numeric argument, if given		*/
	end_flag,	/* TRUE if current page is last in file		*/

	find_flg,	/* Set by 'STRVCMP', to number of chars to show	*/
	findmark,	/* Set iff find_bfr in current line		*/
	mark_width,	/* Actual number of columns used if /MARK	*/
	multiline,	/* TRUE iff a multi-line record was found	*/

	FullPage,	/* number of text lines in screen		*/
	HalfPage,	/* number of text lines in half-screen		*/
	BaseLine,	/* Adjustment after half-screen scroll		*/

	now,		/* Index to 'offset[]' for current page 	*/
	end_mark,	/* Highest known index to 'offset[]'		*/
	last_page,	/* ...index of last full page			*/
	last_line,	/* ...last display-line number in file		*/
	maxvec,		/* Number of locations allocated in 'offset[]'	*/

	i_recl,		/* maximum record length of input		*/
	i_size,		/* ... corresponding buffer size		*/
	i_line,		/* Line number in file (for display/read)	*/

	rstate_len,	/* length of current input record		*/
	lstate_len,	/* length of portion to be displayed		*/
	lstate_1st,	/* ...and index to portion to display		*/
	lstate_end,	/* ...and index above last char			*/

	/*
	 * State of ruler-mode:
	 */
	ruler_mode,	/* TRUE if we display a scale in status line	*/
	ruler_min,	/* First column of present ruler-text		*/
	ruler_max,	/* Last column of present ruler-text		*/
	ruler_x,	/* cursor-position: x = column			*/
	ruler_y,	/* cursor-position: y = row			*/

	/*
	 * State of output display:
	 */
	colmin, colmax,	/* limits on columns to display			*/
	column,		/* column # within line buffer			*/
	colend,		/* highest column # in page			*/
	*endcol,	/* end-column for each display line		*/

	inpage,		/* line number, within page			*/
	onpage;		/* printing-char's read in current page		*/

static
DCLARG	*WhatIsIt;	/* => name of file to display			*/

static
char	*i_bfr,		/* = input record				*/
	*o_bfr,		/* = current text				*/
	*obfr_,		/* => o_bfr, current character			*/
	*sbfr_,		/* => find_bfr, current search-try		*/
	*find_bfr,
	*ruler_text,	/* Ruler text				*/
	*fatal_msg;	/* Set iff I/O error			*/

/*
 * Define option keywords and their flags:
 */
static
int	B_opt,		/* Init for Bitgraph terminal		*/
	D_opt,		/* debug (values 1,2 or logical)	*/
	J_opt,		/* Join records ending in CR		*/
	O_opt,		/* show \r, \b codes			*/
	M_opt,		/* show ftell marks at each line	*/
	S_opt,		/* limit blank lines to double-space	*/
	T_opt,		/* ignore trailing blanks in J-command	*/
	W_opt,		/* wide (132-column) mode		*/
	_FALSE	= FALSE,
	_MAYBE	= (-1);
static
char	CmdFile[MAX_PATH],
	CmdDft[] = "SYS$LOGIN:BROWSE.CMD";

#define	SZ(n)	&n,sizeof(n)
static
DCLOPT	opts[] = {
	{"bg",		&_FALSE,	0,	SZ(B_opt), 	1, 00001},
	{"command",	0,		CmdDft,	SZ(CmdFile),	1, 00200},
	{"debug",	&_FALSE,	0,	SZ(D_opt), 	1, 00002},
	{"join",	&_FALSE,	0,	SZ(J_opt), 	1, 00020},
	{"marks",	&_FALSE,	0,	SZ(M_opt), 	1, 00004},
	{"over",	&_MAYBE,	0,	SZ(O_opt), 	1, 00010},
	{"squeeze",	&_MAYBE,	0,	SZ(S_opt), 	1, 00040},
	{"trim",	&_FALSE,	0,	SZ(T_opt), 	1, 00100},
	{"wide",	&_FALSE,	0,	SZ(W_opt), 	1, 00001}
	};

#ifdef	main_incl
#define	LOGFILE(s)	flist_log s;
#define	LOGARGS(c,n)	logfile(c,n)
static void logfile(int cmd, int count);

main_incl
#else
#define	LOGFILE(s)
#define	LOGARGS(c,n)	n
main
#endif

(
int	argc,
char	**argv)
{
#ifdef	main_incl
	DCLARG	*dcl_	= argvdcl (argc, argv, "", 2);
#else
	DCLARG	*dcl_	= argvdcl (argc, argv, "", 0);
#endif

	DCLARG	*opt_;
	int	(*if_bg)(short *lpp, short *width) = more_0_vt;
	char	*c_,
		msg	[MAXBFR];

	WhatIsIt = 0;

	if (dclchk (dcl_, msg))
	{
		warn (msg);
		return;
	}
#ifdef	main_incl
	dcl_ = dcl_->dcl_next;	/* Skip over program-name token	*/
#endif

	if (dclopt (msg, dcl_, (DCLOPT *)&opts, sizeof(opts)))
	{
		warn (msg);
		return;
	}
#ifndef	main_incl
	getraw_init (CmdFile, CmdDft);
#endif

	if (B_opt)	if_bg	= more_0_bg;
	if (D_opt)	M_opt	= TRUE;
	M_option = M_opt;	/* Save initial "/MARK" */

	/*
	 * Restrict /OVER to legal values, saving initial value in 'O_option':
	 *	0 - no control-character interpretation
	 *	1 - show \r as ^M, \b as ^H
	 *	2 - show \r as <CR>, \b as <BS>
	 *	3 - show all control characters as '.'
	 */
	if (O_opt > 3)	O_opt = 3;
	O_option = O_opt;
	if (O_option < 0)
	{
	    O_option = 1;
	    O_opt = 0;
	}

	for (opt_ = dcl_; opt_; opt_ = opt_->dcl_next)
	{
		if (! isopt(opt_->dcl_text[0]))
		{
			if (! (WhatIsIt || dclwild (opt_)))
			    WhatIsIt = opt_;
			else
			{
			    warn ("You must specify a single file");
			    freelist (dcl_);
			    return;
			}
		}
	}

	fatal_msg  = calloc(1, CRT_COLS);

	if (!WhatIsIt) {
		warn ("No file specified");
		freelist (dcl_);
		return;
	}

	if ((fp = ropen (WhatIsIt->dcl_text, "r")))
	{
	CMDSTK	*oldcmd_ = cmdstk_init ();
#ifdef main_incl
	int	oldtop = crt_top(),
		oldend = crt_end();

	    crt_set (TRUE, 0);
	    crt_set (FALSE, 0);
#else
	    crt_init (if_bg);
	    edtcmd_init ();
	    did_crt_init = TRUE;
	    crt_margin (1, crt_lpp()-1);
#endif
	    lpp	= crt_lpp ();
	    multiline = FALSE;
	    more_0_mark ();

	    i_recl	= rsize(fp);
	    i_size	= i_recl + 4;

	    if ((J_opt > 0) || (S_opt >= 0)) i_size <<= 2;
    			/* Allow extra if merge */

	    i_bfr	= calloc(1, i_size);
	    obfr_	=
	    o_bfr	= calloc(1, CRT_COLS);
	    find_bfr	= calloc(1, CRT_COLS);
	    ruler_text	= calloc(1, CRT_COLS);
	    offset	= calloc(maxvec = MAXVEC, sizeof(OFFSET));
	    endcol	= calloc(crt_lpp(), sizeof(endcol[0]));

	    more_file ();
	    rclose (fp);

	    cfree (i_bfr);
	    cfree (o_bfr);
	    cfree (find_bfr);
	    cfree (ruler_text);
	    cfree (offset);
	    cfree (endcol);

#ifdef	main_incl
	    crt_set (TRUE,  oldtop);
	    crt_set (FALSE, oldend);
	    clrwarn ();		/* Reset FLIST's screen-latch */
#else
	    crt_quit (TRUE);
#endif
	    cmdstk_free (oldcmd_);
	}
	else
	{
	    erstat (fp, fatal_msg, CRT_COLS);
	    warn (fatal_msg);
	}
	cfree (fatal_msg);
	freelist (dcl_);
}

/* <name>:
 * Given a display-buffer limit, attempt to show as much as possible of the
 * VMS filename.
 */
static
char	*more_name (int maxlen)
{
	register char	*c_ = WhatIsIt->dcl_text;
	register int	len = strlen (c_);
	static	char	bfr[CRT_COLS];

	if (len <= maxlen)	return (c_);
#define	ADJ(x)	c_ += WhatIsIt->x; len -= WhatIsIt->x;\
	if (len <= maxlen) return (c_);

	ADJ(dcl$b_node);
	ADJ(dcl$b_dev);
	ADJ(dcl$b_dir);
	strncpy (bfr, c_, len-4);
	strcpy (&bfr[len-4], " ...");
	return (c_);
}

/* <file>:
 * Do all processing for a single file:
 */
static
void	more_file (void)
{
	int	j,
		forward	= TRUE;

	find_bfr[0] = EOS;

	FullPage  = lpp - 1;		/* Expect 23 lines, normal display */
	HalfPage  = FullPage / 2;	/* ...line #11 is middle of 0..22 */
	width2	  = width / 2;

	colmin	  = 0;
	TOP_NEXT  = -1;			/* First read will be '0'	*/
	multiline = FALSE;		/* Assume one line per record	*/

	i_line	  = 0;			/* ...line #0			*/
	last_page = -1;			/* If minus, haven't found EOF	*/
	last_line = MAXREC;		/* Allow a large number of rec's*/
	end_mark  = 0;
	end_flag  = FALSE;

	rstate.rec= 0;			/* ...record #0			*/
	rstate.cra= -1;			/* Force first record-read	*/
	rstate.rfa= 0;
	rstate_len = lstate_len = lstate_end = 0;
	lstate_1st = -1;

	ruler_mode = ruler_x = ruler_y = 0;
	ruler_min = ruler_max = -1;

	crt_set (FALSE, crt_top() + FullPage-1); /* Init the scroller	*/
	more_screen (FullPage, 0);

	while (TRUE)
	{
	int	gold	= FALSE;
	    do
	    {
		user_cmd = more_r_cmd();
		gold	= gold || (user_cmd == GOLDKEY);
	    }
	    while (user_cmd == GOLDKEY);

	    if (! isascii(user_cmd))
	    {
	    int	j;
	    struct {
		int	input,	normal,	shifted;
		} KEYPAD[] = {
			{padENTER,	'M',	'M'},
			{HELPKEY,	'?',	'?'},
			{padPF3,	'N',	'/'},
			{padPF4,	'P',	'\\'},
			{pad0,		'H',	'H'},
			{pad1,		'L',	'L'},
			{pad2,		'R',	'R'},
			{pad3,		'J',	'J'},
			{pad4,		pad4,	'E'},
			{pad5,		pad5,	'T'},
			{pad6,		'I',	'I'},
			{pad9,		'_',	'_'}
			};

		if (user_cmd == pad7)
		    user_cmd = forward ? 'F' : 'B';
		else if (user_cmd == pad8)
		    user_cmd = forward ? 'D' : 'U';
		else for (j = 0; j < SIZEOF(KEYPAD); j++)
		{
		    if (KEYPAD[j].input == user_cmd)
		    {
			user_cmd = gold ? KEYPAD[j].shifted : KEYPAD[j].normal;
			break;
		    }
		}
	    }

	    switch (user_cmd)
	    {
	    case pad4:	forward = TRUE;		break;
	    case pad5:	forward = FALSE;	break;

	    case 'Z':
	    case 'Q':			/* Quit				*/
		LOGARGS('Z',0);
		getraw_flush();
		return;

	    case padUP:
		if (more_move (-1, 0))	break;
	    case 'U':			/* Display previous 1/2 screen	*/
		LOGARGS('U', user_arg = max(1, user_arg));
		if (TOP_THIS > 0)
		    more_next (TOP_THIS - user_arg);
		else
		    sound_alarm ();
		break;

	    case ' ':			/* (to look like Unix-more)	*/
	    case 'F':			/* Scroll forward 1 screen	*/
	    	LOGARGS('F', user_arg = max(1, user_arg));
	    	more_down (2, user_arg);
		break;

	    case padDOWN:
		if (more_move(1,0))	break;
	    case 'D':			/* Display another 1/2 screen	*/
		LOGARGS('D', user_arg = max(1, user_arg));
		more_down (1, user_arg);
		break;

	    case 'T':			/* Return to top-of-file	*/
		LOGARGS('T',0);
		if (TOP_THIS > 0)
		    more_next (0);
		else if (!ruler_y)
		    sound_alarm ();
		ruler_y = 0;
		break;

	    case 'B':			/* Back up one page		*/
		LOGARGS('B', user_arg = max(1, user_arg));
		if (TOP_THIS > 0)
		    more_next (TOP_THIS - (2 * user_arg));
		else
		    sound_alarm();
		break;

	    case 'E':			/* Go to end of file		*/
		LOGARGS('E',0);
		if (last_page < 0)
		{
		    int	old = TOP_NEXT;
		    while (last_page < 0 && ! ctlx_tst()) more_skip(nullC);
		    more_seek (old);	/* fix scroll */
		}
		if ((j = more_lastp()) != TOP_THIS)
		    more_next (j);
		else
		    sound_alarm();
		ruler_y = min(last_line-1, crt_end());
		break;

		/* patch: user_arg not used! */
	    case '\\':			/* Search backward for a string	*/
	    case 'P':			/* ...search for prev occurrence*/

	    case '/':			/* Search for a string		*/
	    case 'N':			/* ...search for next occurrence */
		if (ispunct(user_cmd))
		{
		    find_bfr[0] = EOS;
		    more_r_buf (find_bfr, "Search for:", nullC);
		    LOGFILE(("  $ %c%s", user_cmd, find_bfr))
		    strvcpy (find_bfr, nullC);
		    if (*find_bfr)	cmdstk_put (find_bfr);
		}
#ifdef	main_incl
		else {
			LOGARGS(user_cmd,user_arg);
		}
#endif/*main_incl*/
		if (find_bfr[0] == EOS)
		{
		    more_this ();	/* (clear old markers) */
		    sound_alarm ();
		}
		else if (user_cmd == 'P' && TOP_THIS <= 0)
		    sound_alarm();
		else
		{
		int	fwd = (user_cmd == '/' || user_cmd == 'N'),
			new, next,
			old = TOP_NEXT,
			got = FALSE;
		char	*c_ = find_bfr;

		    /*
		     * If first, start on current page.
		     */
		    switch (user_cmd)
		    {
		    case '/':
			more_seek (max(0,TOP_THIS));
			break;
		    case '\\':
			more_seek (new = TOP_NEXT-1);
			break;
		    case 'P':
			more_seek (new = TOP_THIS-1);
		    }

		    while (! (end_flag || ctlx_tst()))
		    {
			if (got = more_skip (find_bfr))
			    break;
			if (! fwd)
			{
			    if (--new >= 0)
				more_seek (new);
			    else
				break;
			}
		    }

		    /*
		     * If found, get pointer to top of half-page containing
		     * string, scroll to that screen.  The index 'now' is set
		     * to the mark at the bottom of the half-screen containing
		     * the string, if 'N', else top.
		     */
		    if (got)
		    {
			new = TOP_NEXT;
			if (user_cmd == 'N')	new--;
			more_seek (old); /* fix scroll */
			next = new;
				/* Prefer full-screen scrolling: */
			if ((old-1) == next)	next = old - 2;
			if ((old-3) == next)	next = old - 4;
				/* Restrict to legal limits: */
			next = max(0, min(next, more_lastp()));
#ifdef	DEBUG
			more_show ("(...)got:%d old:%d new:%d =>%d",
					got, old, new, next);
#endif
			more_next (next);
		    }
		    else
		    {
			TOP_NEXT = old;
			more_this ();	/* refresh old	*/
			sound_alarm ();
		    }
		}
		break;

	    case '^':			/* Toggle debug-mode		*/
		LOGARGS('^',0);
		if (!D_opt && !M_opt)
		{
		    M_opt = 1;
		    more_0_mark ();
		    more_this ();
		}
#ifdef	DEBUG
		D_opt = ! D_opt;
#endif
		break;

	    case 'O':			/* Toggle overlay mode		*/
		LOGARGS('O', user_arg);
		if (user_arg <= 3)	/* (maximum code permitted)	*/
		{
		    if (user_arg)
			O_opt = user_arg;
		    else
		    {
			if (O_opt)	O_opt = 0;
			else		O_opt = O_option;
		    }
		    more_this ();
		}
		else
		    sound_alarm();
		break;

	    case 'M':			/* Toggle mark-display mode	*/
		LOGARGS('M', user_arg);
		if (M_option || M_opt)
		{
		    if (M_opt == 0)		M_opt = 1; /* mark   */
		    else if (M_opt == 1)	M_opt = 2; /* length */
		    else			M_opt = 0; /* off    */
		    more_0_mark ();
		    more_this ();
		}
		else
		    sound_alarm ();
		break;

	    case 'K':			/* Make a snapshot of screen	*/
		LOGARGS('K',0);
		snapshot ();
		break;

	    case 'V':			/* Display current version	*/
		LOGARGS('V',0);
		more_msg ("BROWSE (v951027)", TRUE);
		sleep (3);
		break;

	    case 'G':			/* Toggle 80/132 columns	*/
		LOGARGS('G',0);
		{
		    int width, length;
		    termsize (FALSE, &width, &length);
		    if (width <= 80)	width = 132;
		    else		width = 80;
		    if (! termsize (TRUE, &width, &length))	break;
		}
	    case 'W':			/* Refresh current window	*/
		crt_clear ();
		more_this ();
		break;

	    case 'H':			/* scroll left to home-column	*/
		LOGARGS('H', user_arg = max(0, user_arg-1));
		if (colmin != user_arg && more_size(O_opt) > width)
		{
		    colmin = user_arg;
		    ruler_x = 0;
		    more_this ();
		}
		else if (ruler_x)	/* Force ruler-position if we	*/
		    ruler_x = 0;	/* ...are alreay in that screen	*/
		else if (ruler_mode)	/* Try to move up one row	*/
		{
		    if (!more_move (-1, 0))
		    {
			if (TOP_THIS > 0)	more_next (TOP_THIS-1);
			else			sound_alarm();
		    }
		}
		else
		    sound_alarm();
		break;

	    case 'I':			/* tab right (ruler only)	*/
		LOGARGS('I', j = max(0, user_arg-1));
		user_arg = 0;		/* kill the repeat-factor	*/
		if (ruler_mode)
		{
		    if (!more_move (0, (ruler_x | 7) + 1 + (8 * j) - ruler_x))
			more_right();
		}
		else
		    sound_alarm();
		break;

	    case 'J':			/* tab to end-column		*/
		LOGARGS('J', user_arg);
		if (ruler_mode)
		{
		    j = endcol[ruler_y - crt_top()];
		    j = max(0, j-1);
		    if (ruler_x == j)	/* repeated-action scrolls down	*/
		    {
			if (!more_move (1, 0))
			    more_down(1, user_arg = max(1, user_arg));
			user_arg = 0;
		    }
		    j = endcol[ruler_y - crt_top()];
		    j = max(0, j-1);
		    if (!more_move (0, j - ruler_x))
			more_right();
		}
		else if (colend < colmin || colend > colmax-1
		||	(colmin && colend < colmin + width2))
		{
		    colmin = width2 * ((colend - width2 - 1) / width2);
		    if (colmin < 0)	colmin = 0;
		    more_this();	/* Scroll to screen containing end
					   of longest record in display	*/
		}
		else
		    sound_alarm ();		/* No action performed		*/
		break;

	    case padLEFT:
		if (more_move(0, -1))	break;
		user_arg = -user_arg;
	    case 'L':			/* scroll left (1/2) screen	*/
		if (user_arg == 0) user_arg = width2;
		LOGARGS('L', user_arg);
		if (colmin > 0)
		{
		    colmin -= user_arg;
		    if (colmin < 0)
		    {
			colmin = 0;	/* Limit shift	*/
			sound_alarm ();	/* ...but yell	*/
		    }
		    more_this ();
		}
		else
		    sound_alarm();
		break;

	    case padRIGHT:
		if (more_move (0, 1))	break;
	    case 'R':			/* Scroll right (1/2) screen	*/
		LOGARGS('R', user_arg);
		more_right();
		break;

	    case '_':		/* Display a scale in the prompt-line */
		LOGARGS('_',0);
		ruler_mode = ! ruler_mode;
		break;

	    case '?':
		LOGARGS('?',0);
		crt_help (0, "BROWSE");
		break;

	    default:
		sound_alarm();
	    }
	}
}

/* <0_mark>:
 * Initialize display-width, based on terminal size and on use of '/MARK'.
 */
static
void	more_0_mark (void)
{
	width	= crt_width ();
	if (M_opt)
	{
		width -= (mark_width = MARK_WIDTH);
		if (multiline)
		{
#define	MULT_WIDTH	8
			width -= MULT_WIDTH;
			mark_width += MULT_WIDTH;
		}
	}
	width2 = width / 2;
}

/* <0_line>:
 * Re-init flags for current line (or record).
 */
static
void	more_0_line(void)
{
	obfr_	 = o_bfr; *obfr_ = EOS;
	findmark = 0;
	column   = 0;
}

/* <0_page>:
 * Re-init flags for current page.
 */
static
void	more_0_page(void)
{
	onpage	= inpage = 0;
	colmax	= colmin + width;
	more_0_line();
}

/* <move>:
 * Move the cursor in the specified direction.
 */
static
int	more_move (int dy, int dx)
{
	register
	int	rpt	= max(1, user_arg),
		top	= crt_top(),
		end	= crt_end();

	if (ruler_mode)
	{
	    user_arg = 0;
	    if (dx *= rpt)
	    {
		ruler_x += dx;
		ruler_x = more_limit(ruler_x, 0, more_size(O_opt));
		if (ruler_x < colmin)
		    user_arg = ruler_x - (colmin + 1);
		else if (ruler_x > MAXCOL)
		    user_arg = ruler_x - MAXCOL;
		user_arg = width2 * more_round (user_arg, width2);
	    }
	    else if (dy *= rpt)
	    {
		ruler_y += dy;
		ruler_y = more_limit (ruler_y, 0, last_line-1);
		if (ruler_y < top)
			user_arg = top - ruler_y;
		else if (ruler_y > end)
			user_arg = ruler_y - end;
		user_arg = more_round (user_arg, HalfPage);
	    }
	    return (user_arg == 0);
	}
	return (FALSE);
}

/* <limit>:
 * Limit a value to a specified range, sounding alarm if it falls out.
 */
static
int	more_limit (int val, int lo, int hi)
{
	if (val < lo)
	    sound_alarm(),	val = lo;
	else if (val > hi)
	    sound_alarm(),	val = hi;
	return (val);
}

/* <round>:
 * Given a number 'num', and the granularity 'd', return the closest multiple
 * of 'd'.
 */
static
int	more_round (int num, int d)
{
	if (num >= 0)
	    return ((num + d - 1) / d);
	else
	    return (-more_round(-num, d));
}

/* <lastp>:
 * Return the last seek-mark index which we may use to initiate a screen.
 */
static
int	more_lastp (void)
{
	return (max(0, LAST_PAGE));
}

/* <size>:
 * Return the maximum number of characters wide, given a value of /OVER:
 */
static
int	more_size (int over)
{
	int	j = i_recl;

	if (over == 0)		j <<= 3;
	else if (over <= 2)	j <<= 1;
	return ((j | 7) + 1);	/* Allow at least one tab stop	*/
}

/* <right>:
 * Shift screen right by either 1/2 screen, or by the number of columns
 * specified in 'user_arg'.
 */
static
void	more_right(void)
{
	int	j,
		endcol	= more_size(O_opt);

	if (user_arg == 0)	user_arg = width2;
	if (endcol > width)
	{
	    if ((j = more_limit (colmin + user_arg, 0, endcol)) != colmin)
	    {
		colmin = j;
		more_this ();
	    }
	}
	else
	    sound_alarm();
}

/* <down>:
 * Combined forward-2, down-1 commands taking into account repeat-factor.
 * We permit an "X" to abort the forward-search.
 */
static
void	more_down (int frac, int rpt)
{
	register
	int	old	= TOP_NEXT,
		target	= TOP_NEXT + ((--rpt) * frac),
		actual;

	/*
	 * If we haven't reached the end of the file before, we must skip
	 * along, setting pointers first, until we either reach the end of
	 * the file, or we reach the desired target-mark.
	 */
	if ((last_page < 0) && (target > end_mark))
	{
	    while ((last_page < 0) && (target > end_mark) && ! ctlx_tst())
		more_skip(nullC);
	    more_seek (old);	/* fix crt-scroll */
	}

	actual = max(0, min(target, more_lastp()));

	if (actual != TOP_THIS)
	{
	    more_seek (actual);
	    more_screen ((frac == 1 && rpt == 0 && actual == target)
			? HalfPage : FullPage, 1);
	}
	else		/* Yell if I didn't go anywhere	*/
	    sound_alarm();
}

/* <skip>:
 * Read through current half-page.  A page is ended when
 *	(a) The number of lines for the display screen have been passed, or
 *	(b) An end-of-file is found.
 *
 * If the 'find_' argument is nonnull, test the character stream as it is
 * read.  If a match is found within the page, return true, otherwise false.
 *
 * Because no fseek is done before beginning to read the half-screen, this
 * will not search overlapping half-screens:
 *
 *	'/' search begins on a mark (screen top), reads up to (but ends
 *	    BEFORE) the mark ending a half-screen.  Backward searches must
 *	    also do a seek-to-mark, and consequently do not read down to the
 *	    mark either.
 *	'N' search begins AFTER the present mark (screen bottom) and reads
 *	    up to (including) the mark ending the page.
 *
 * Thus, on exit from a search, the value of 'now' will be that of the TOP of
 * the half-screen containing the search target, or the BOTTOM, depending on
 * the search state.
 */
static
int	more_skip(
	char	*find_)		/* => 'find_bfr', or null	*/
{
	int	inx,
		found	= FALSE;
	char	msgbuf[CRT_COLS];

	if (find_)
		sprintf (msgbuf, "%3.3d  Find: '%s'", TOP_NEXT, find_);
	else
		sprintf (msgbuf, "%3.3d  Skipping...", TOP_NEXT);
	more_msg (msgbuf, FALSE);

	for (more_0_page(); inpage < HalfPage;)
	{
		if (more_read(i_line) < 0)	/* End of file ? */
			break;
		else
		{
			onpage += lstate_len;
			inpage++;
			if (find_ && !found)
			{
				for (inx = lstate_1st; inx < lstate_end; inx++)
				{
					if (STRVCMP)
					{
						found = i_line + 1;
						break;
					}
				}
			}
		}
	}

	/*
	 * The present position should be one display-line above the next
	 * to be displayed:
	 */
end_of_page:
#ifdef	DEBUG
	more_show ("skip(end=%d): [%d:%d,%d] => %d  after  [?:%d,%d] => %d",
		i_line,
		SHOW_OFF(now), LINEMARK(now),
		rstate.rfa, lstate_1st, inpage+LINEMARK(now-1));
#endif
	return (found);
}

/* <line>:
 * Put the portion of the output line which lies within the current column
 * limits onto the screen.
 */
static
void	more_line (void)
{
	int	len;
	char	msg	[MAXBFR],
		bfr2	[MAXBFR],
		field_0	[4],	field_1	[8],	field_2	[8],	field_r_l[8],
		*c_;

	/*
	 * If a search target was found on the current line, but not within
	 * the column limits, set visible indicators on the closest end of the
	 * line to the target.
	 */
	if (findmark & FIND_L)
	{
		if (o_bfr[0] == EOS)
			strcpy (o_bfr, " ");
		crt_high (o_bfr, 1);
	}
	if (findmark & FIND_R)
	{
		c_	= strnull(o_bfr);
		len	= strlen(o_bfr);
		while (len++ < width)
			strcpy (c_++, " ");
		crt_high (&o_bfr[width-2], 1);
	}
	obfr_ = strnull (c_ = o_bfr);

	/*
	 * M-option: If set, display one of two formats.  The 'findmark'
	 *	pattern (3 cols) and the line-number (low 2 digits) are always
	 *	shown.  The other two fields depend on the value of M_opt:
	 *
	 *	(1) the ftell-mark, offset of line-within-record
	 *	(2) the record length, length of line-within-record
	 *
	 * If 'multiline' is set, we display both the record number and the
	 * line number.
	 */
	if (M_opt)
	{
	static	char	cent[]	= ".%02d",
			thou[]	= ".%04d";

		sprintf (field_0, "%c%c%c",
			(findmark & FIND_L)  ? '<' : ' ',
			(findmark & FIND_IN) ? '=' : ' ',
			(findmark & FIND_R)  ? '>' : ' ');

		field_2[0] = field_r_l[0] = EOS;

		if (lstate_1st)	/* Are we doing continuation lines?*/
		{
			sprintf (field_1, "%6s", " ");
			if (multiline)
			{
				sprintf (field_2, thou,
					(M_opt == 1	? lstate_1st
							: lstate_len));
				strcpy (field_r_l, "...");
			}
		}
		else
		{
			sprintf (field_1, "%6d",
				(M_opt == 1 ? rstate.rfa : rstate_len));
			if (multiline)
			{
				if (M_opt == 1 || (lstate_len == rstate_len))
					strcpy (field_2, ".....");
				else
					sprintf (field_2, thou, lstate_len);
				sprintf (field_r_l, cent, (rstate.rec % 100));
			}
		}
		sprintf (strnull(field_r_l), cent,
			((INFILE_LINE+1) % 100));
		sprintf (msg, "%s%s%s%s:%s",
			field_0, field_1, field_2, field_r_l, o_bfr);
		c_ = msg;
	}

	if(D_opt && ((inpage % HalfPage) == 0))	/* FIND_XX	*/
	{
		strcpy (bfr2, c_);
		c_ = bfr2;
		crt_high (c_, strlen(c_));
	}
	crt_text (c_, INPAGE_LINE, 0);

	inpage++;
}

/* <char>:
 * Route a character into the output-display buffer, highlighting overstrikes
 * or search-targets.
 *
 * Track the current column to avoid putting characters into the buffer unless
 * within the selected column limits.
 */
static
void	more_char(char c)
{
	int	on_screen;

	if (isprint(c))
		column++;

	if ((T_opt && isgraph(c))	/* /TRIM: all but space		*/
	|| (!T_opt && isprint(c)))	/* /NOTRIM: all printing chars	*/
	{
	    if (column > endcol[INPAGE_LINE])
		endcol[INPAGE_LINE] = column;
	}

	on_screen = (column == 0 || ((column > colmin) && (column < colmax)));

	if (on_screen)
	{
		int	oldc, newc;
		/*
		 * Buffer latest character.  If I am writing onto the end of
		 * the buffer, 'oldc' will be null.  However, if I have gotten
		 * a backspace or carriage return, I may be overstriking a
		 * character which isn't blank.
		 *
		 * Make merge-logic test to overstrike alphas over punctuation;
		 * highlight overstrikes when no search is active.
		 */
		if (isprint(c))
		{
			obfr_	= o_bfr + (column - colmin - 1);
			oldc	= toascii(*obfr_),
			newc	= c;
			if (isgraph(oldc))
			{
				if ((newc == ' ')
				||  (isalnum(oldc))
				||  (newc == '_') )
					newc = oldc;
			}
			if (find_flg)	newc |= 0200;
			*obfr_++ = newc; /* Buffer latest character */
			if (!oldc)
				*obfr_	 = EOS;
			else if (!*find_bfr && (c != ' ') && (oldc != ' '))
				obfr_[-1] |= 0200;
		}
		/* else, BS, CR or LF */
		onpage++;		/* Count anything I buffer */
	}

	/*
	 * When a search is matched, set up visible indication for the display:
	 * If the string is at least partly visible, highlight those characters. 
	 * If it is not visible at all, set a flag for use in 'more_line' to
	 * highlight the end of the line closest to the string.
	 */
	if (find_flg)		/* Have I completed a search ?	*/
	{
		if (on_screen)
			findmark |= FIND_IN;
		else if (column <= colmin)
			findmark |= FIND_L;/* out-of-sight on left	*/
		else
			findmark |= FIND_R;/* out-of-sight on right	*/
	}
}

/* <ctlc>:
 * Send a string of characters per input character, according to the setting
 * of the /OVER option:
 */
static
void	more_ctlc (
	char	*ctl_)
{
	if (O_opt == 3)		more_char('.');
	else
	{
	    while (*ctl_)	more_char(*ctl_++);
	}
}

/* <conv>:
 * Given an input record, translate control characters as per options.
 */
static
void	more_conv (void)
{
int	inx,
	old_ok	= 0,
	col_base,		/* column to set on CR (or embedded FF)  */
	on_end;			/* TRUE when on last character of record */
char	c,
	*ctl_,
	showctl[4];

	col_base = 0;
	find_flg = 0;
	endcol[INPAGE_LINE] = 0;
	for (inx = lstate_1st; inx < lstate_end; inx++)
	{
		old_ok	= max((find_flg-1),0);
		if (*find_bfr)
		{
			find_flg = STRVCMP;
			find_flg = max(find_flg, old_ok);
		}
		on_end	= (inx >= (lstate_end-1));
		switch (c = i_bfr[inx])
		{
		/*
		 * Rubout is considered a control character, but does not map
		 * in the usual fashion.
		 */
		case 0x7f:
		    if (O_opt >= 2)	more_ctlc("<DEL>");
		    else		more_ctlc("^?");
		    break;
		/*
		 * Newline resets the column to zero because it begins a new
		 * display-line.  It always ends the display line (because it
		 * is the only reasonable way to show it).
		 */
		case '\n':
		    break;
		/*
		 * Carriage-return resets the column to the beginning of the
		 * current overstrike-region (marked by 'col_base').
		 *
		 * To see better binary records (and simplify the seek-logic)
		 * show carriage control in '^' form if it does not end the
		 * record, but is instead embedded.
		 */
		case '\r':
		    if ((!O_opt) || on_end)
		    {
			column	= col_base;
			obfr_	= o_bfr + strlen (o_bfr);
			more_char (c);
			break;
		    }
		/*
		 * Convert tabs to spaces to simplify left/right scrolling.
		 */
		case '\t':
		    if (!O_opt)
		    {
			register int j = 1 + (column | 7);
			while (column < j)
			    more_char (' ');
			break;
		    }
		/*
		 * Account for backspace:
		 */
		case '\b':
		    if (!O_opt)
		    {
			if (column > 0) column --;
			more_char(c);
			break;
		    }
		/*
		 * Convert control characters to a pair, display iff within
		 * column bounds.
		 */
		default:
		    if (iscntrl(c))
		    {
			ctl_ = &showctl[2];
			*ctl_-- = EOS;
			*ctl_-- = c | 0100;
			*ctl_   = '^';
			if (O_opt == 2) switch (c)
			{
			case CTL('H'):	ctl_ = "<BS>";	break;
			case CTL('I'):	ctl_ = "<TAB>";	break;
			case CTL('K'):	ctl_ = "<VT>";	break;
			case CTL('L'):	ctl_ = "<FF>";	break;
			case CTL('M'):	ctl_ = "<CR>";	break;
			case '\033':	ctl_ = "<ESC>";	break;
			}
			more_ctlc (ctl_);

			if (c == '\r' || c == '\n' || c == '\f')
			    col_base = column;
		    }
		    else
			more_char(c);
		}
	}
	more_line ();
}

/* <screen>:
 * Read and display either a full screen (one line less than actual screen
 * size), or a half screen (on either top or bottom, with appropriate
 * pre-scrolling).
 */
static
void	more_null (int inx)
{
	crt_text ("", inx-crt_top(), 0);
}

static
void	more_screen (
	int	view_size,
	int	dirflg)
{
	void	(*ffunc)(int) = (dirflg > 0) ? more_null : 0;
	int	old_top = crt_top(),
		old_end = crt_end(),
		new_top = max(0, (old_top + 1 - view_size)),
		new_end	= i_line + view_size - 1;

	more_0_page();
#ifdef	DEBUG
	more_show ("(screen)(%d,%d) old(%d,%d), new(%d,%d) rec:%d",
		view_size, dirflg, old_top, old_end, new_top, new_end, i_line);
#endif

	if (view_size == HalfPage && dirflg > 0 && old_end > new_top)
	    memcpy (&endcol[0], &endcol[HalfPage], HalfPage*sizeof(endcol[0]));

	/*
	 * Note: 'crt_scroll' is given a null display routine to simplify
	 * the logic of this program.  Thus, the end-line of the screen is
	 * reset in forward 1/2-screen scrolling.  MAXREC is used in controlling
	 * the scrolling to coerce 'crt_scroll' to be able to show a partly
	 * blank screen.
	 */
	if (view_size == HalfPage)
	{
		crt_scroll (new_end+1, MAXREC, ffunc);
		BaseLine = HalfPage;
		view_size++;	/* half-pages overlap by one line	*/
	}
	/*
	 * The view-size is not changed from full-page to half-page in
	 * backward scrolling, even if only half a page can be displayed.
	 * The 'crt' module will simply not update the screen, even though
	 * this program will read it.
	 */
	else
	{
		int next = (dirflg > 0 ? new_end : i_line);
		BaseLine = 0;
		if (dirflg)
		{
#ifdef	DEBUG
			more_show ("(...)scroll to:%d", next);
#endif
			crt_scroll (next, MAXREC, 0);
#ifdef	DEBUG
			more_show ("(...)old(%d,%d) new(%d,%d), mark %d",
				old_top, old_end, crt_top(), crt_end(), now);
#endif
		}
#ifdef	DEBUG
		else
			more_show ("(...) no scrolling");
#endif
	}

	while (inpage < view_size)
	{
	    if (more_read(inpage) < 0)
	    {
		last_line = INFILE_LINE;
		break;
	    }
	    more_conv ();
	}
	while (inpage < view_size)	/* (If EOF, clear remainder)	*/
	{
	    crt_text ("", INPAGE_LINE, 0);
	    endcol[INPAGE_LINE] = 0;
	    inpage++;
	}

	/*
	 * Obtain maximum line-length within the current screen:
	 */
	for (colend = inpage = 0; inpage < FullPage; inpage++)
		colend = max(colend, endcol[inpage]);
}

/* <this>:
 * Refresh the current page.  Note that if the file is shorter than one page,
 * I must restrict the backup to the 0-mark.
 */
static
void	more_this (void)
{
	more_next (max(0,TOP_THIS));
}

/* <next>:
 * This routine is used to display a page when jumping around in the file,
 * refreshing the current page, etc.
 */
static
void	more_next (
	int	new)
{
	int	old = now;

#ifdef	DEBUG
	more_show ("(next)(%d), was %d", new, old);
#endif
	if (new < 0)	new = 0;
	more_seek (new);
	more_screen (FullPage, 1 + (new - old));
}

/* <seek>:
 * Re-position using 'fseek' so that the next record-read will begin at the
 * top of a half-page screen section.
 */
static
void	more_seek (
	int	next)		/* next index in 'offset[]' to use	*/
{
	if (next < 0 || next > end_mark)
	{
		char bfr[CRT_COLS];
		sprintf (bfr, "fseek index(%d) out-of-range (0:%d)",
			next, end_mark);
		error (0, bfr);
	}
#ifdef	DEBUG
	more_show ("(seek)  [%d:%d,%d] => %d,  was [%d:%d,%d] => %d",
		SHOW_OFF(next), LINEMARK(next),
		SHOW_OFF(now),  LINEMARK(now));
#endif
	rseek(fp, offset[now=next].rfa, 0);

	/*
	 * Force the next call on 'more_read' to return the line which I
	 * am selecting, by adjusting 'lstate_???'.
	 */
	if (offset[now].cra)	more_getr ();

	rstate.rec = offset[now].rec;
	lstate_1st = offset[now].cra - 1;
	lstate_len = 1;
	lstate_end = lstate_1st + 1;

	if (lstate_1st >= 0)	rstate.rec = rstate.rec + 1;

	i_line	 = LINEMARK(now);	/* Reset actual-line-number	*/
	end_flag = FALSE;
	now--;		/* first record-read will re-increment */
}

/* <read>:
 * Read a new line from the input file (extracting, as needed, from records).
 * Do all 'ftell' calls to obtain points to repeat or restart the sequential
 * reads.
 *
 * State:
 *	rstate_len <= length of current record
 *	lstate_len <= length of line to display
 *	lstate_1st <= index into 'i_bfr[]' of line to display.
 *	lstate_end <= index past end of line
 */
static
int	more_read (int rec)		/* Either 'i_line' or 'inpage'	*/
{
	int	j;

	more_0_line();

	lstate_1st += lstate_len;
	lstate_len = 0;
	if ((lstate_1st >= rstate_len) || (lstate_1st <= 0))
	{
		lstate_1st = 0;
		more_getr ();
	}

	/*
	 * Find the newline which ends the newest line, compute the line-length.
	 */
	if (rstate_len >= 0)
	{
		++i_line;		/* Found a new record		*/
		for (j = lstate_1st;
			(j < rstate_len) && (i_bfr[j] != '\n');
				j++, lstate_len++);
		if (j < rstate_len && i_bfr[j] == '\n')	lstate_len++;
		if (rstate_len != lstate_len && !multiline)
		{
			multiline = TRUE;
			more_0_mark ();
		}
	}
	else
		lstate_len = -1;	/* eof-length is negative	*/

	lstate_end = lstate_1st + lstate_len;

	/*
	 * If this line falls on a display mark (top,mid,bot), or an end of
	 * file occurred, latch the filemarks:
	 */
	if (!(rec % HalfPage) || end_flag)
	{
		if (++now >= maxvec)
		{
			maxvec += MAXVEC;
			offset = realloc (offset, maxvec * sizeof(OFFSET));
		}
		if (now > end_mark || end_mark == 0)
		{
			end_mark = now;
			offset[now].rec = rstate.rec - 1;
			offset[now].rfa = rstate.rfa;
			offset[now].cra = lstate_1st;
		}

#ifdef	DEBUG
		more_show ("read(%d): [%d:%d.%d] => %d",
			end_mark, SHOW_OFF(now), LINEMARK(now));
#endif
	}

	return (lstate_len);
}

/* <getr>:
 * Read a record from the input file, setting end-of-file flag, record-length,
 * and stripping parity.  If "/FEED", then read/append as long as CR ends the
 * input record.
 */
static
void	more_getr (void)
{
	int	j;
	int	first	= TRUE;
	int	join;
	int	skip;
	int	len	= 0;
	int	size	= i_size;
	unsigned *rfa_	= &rstate.rfa;
	static	unsigned dummy_rfa;
	char	*s_;

	rstate_len = 0;
	for (;;)
	{
	    len = rgetr (fp, &i_bfr[rstate_len], size, rfa_);
	    erstat (fp, fatal_msg, CRT_COLS);
	    if (first)		rstate_len = len;
	    else if (len >= 0)	rstate_len += len;
	    if (len < 0 || !((J_opt > 0) || (S_opt >= 0)))	break;

	    /*
	     * If JOIN option is set, and we have anything in the record
	     * buffer, test for a trailing LF.  If not found, we can merge
	     * successive records.
	     */
	    join = FALSE;
	    if (J_opt > 0)
	    {
		join = TRUE;
		if ((rstate_len > 0)
		&& (i_bfr[rstate_len-1] == '\n'))	join = FALSE;
	    }

	    /*
	     * If SQUEEZE option is set (and if we did not JOIN), test the
	     * record buffer to see if it is a simple sequence of
	     * space/carriage control.  If we find only blank line(s), force
	     * these down to a single blank line and set the continuation to
	     * permit an additional record to be appended.
	     *
	     * Normally, this will cause multiple blank lines to be
	     * compressed into a single blank line.  It will not do anything
	     * to records containing (for example) a sequence of LF's
	     * in addition to non-space characters.
	     */
	    if (!join && (S_opt >= 0) && (rstate_len >= 0))
	    {
		join = TRUE;
		skip = 1;
		i_bfr[rstate_len] = EOS;
		for (s_ = i_bfr; *s_; s_++)
		{
		    if (*s_ == '\n')
			skip++;
		    else if (! isspace(*s_))
		    {
			join = FALSE;
			break;
		    }
		}
		/*
		 * Limit the number of successively-skipped lines to the value
		 * given in '/SQUEEZE'.
		 */
		if (join)
		{
		    if (skip > S_opt)	skip = S_opt;
		    i_bfr[skip] = EOS;
		    while (skip > 0)	i_bfr[--skip] = '\n';
		    rstate_len = len = strlen(i_bfr);
		    size = i_size;
		}
	    }
	    /*
	     * If I couldn't join or squeeze, exit the loop.  Otherwise,
	     * set up for the next combining.
	     */
	    if (!join)			break;
	    first = FALSE;
	    size -= len;
	    rfa_ = &dummy_rfa;
	}

	if (rstate_len < 0)
	{
	    rstate_len = -1;
	    if (last_page < 0)
	    {
		last_page = onpage ? now-1 : now-2;
		if (last_page < 0)	last_page = 0;
	    }
#ifdef	DEBUG
	    more_show ("read(%d): EOF last-rec:%d, last-page:%d",
			now, rstate.rec, last_page);
#endif
	    end_flag = TRUE;
	}
	else
	{
	    rstate.rec = rstate.rec + 1;
	    for (j = 0; j < rstate_len; j++)
		i_bfr[j] = toascii(i_bfr[j]);
	}
}

/* <r_cmd>:
 * Prompt and read a single character for commands:
 */
static
int	more_r_cmd(void)
{
int	command,
	len,
	top	= crt_top(),	/* display-line-min (0..n-1)	*/
	end	= crt_end(),	/* display-line-max (0..n-1)	*/
	left	= colmin,	/* leftmost column (1..n)	*/
	right	= colmax-1,	/* rightmost column (1..n)	*/
	width	= crt_width();	/* display-width (<= CRT_COLS)	*/
char	*c_,
	msg	[CRT_COLS],
	format	[20],
	numbfr	[MAXBFR];

	end	= min(last_line-1, end);

	if (fatal_msg[0])
	    more_msg (fatal_msg, TRUE);
	else if (ruler_mode)
	{
	    int	ruler_col = (M_opt ? mark_width : 0);
	    ruler_x = min(MAXCOL, max(left, ruler_x));
	    ruler_y = min(end, max(top, ruler_y));
	    if (ruler_min != colmin || ruler_max != colmax)
	    {
	    	dspc_init (ruler_text, ruler_col, colmin+1);
		ruler_min = colmin;
		ruler_max = colmax;
	    }
	    dspc_move (ruler_text, ruler_col + ruler_x - colmin, ruler_y);
	}
	else
	{
	    sprintf (numbfr, "  Lines %d:%d  Cols %d:%d (%d)",
			top+1, end+1,
			colmin+1, right, colend);
	    if (M_opt)
	    {
#ifdef	RMSIO
		sprintf (strnull(numbfr), ":%d", i_recl);
#endif
		if (multiline)
			sprintf (strnull(numbfr), "  Records %d+",
				offset[TOP_THIS].rec+1);
	    }
#ifdef	DEBUG
	    if (D_opt)
		strcat (numbfr, " (debug)");
#endif

	/* Protect against obscenely-long filenames: */
	    sprintf (format, "%%.%ds %%s", len = width - (strlen(numbfr)+3));
	    sprintf (msg, format, more_name(len), numbfr);
	    more_msg (msg, TRUE);
	}

	/*
	 * Read the user's reply, translating alpha's to controls
	 * (making them equivalent).
	 */
	while (((command = getpad()) == '\r')
	||	(command == '\n'));
	if (isascii(command) && isalpha(command)) command = CTL(command);

	user_arg = 0;			/* Inactive if zero		*/
	if (isascii(command) && isdigit(command))
	{
		sprintf (numbfr, "%c", command);
		command = more_r_buf (numbfr, "Repeat:", "BDFIJHLMORU");
		if (command > 128)
			command += 128;	/* 128..255 => 256..getpad	*/
		else if (isalpha(command))
			command = CTL(command);
		c_ = scanint (numbfr, &user_arg);
		if (c_ == numbfr || c_[1])
			command = -1;	/* Force an error-return	*/
	}
	return toshift(command);
}

/* <r_buf>:
 * Read a string-buffer, for either search target, or for numeric argument.
 * If the latter, 'delim_' is set, and we must do special actions.
 */
static
int	more_r_buf (
	char	*co_,			/* => Output buffer	*/
	char	*m1_,			/* Prompt-message	*/
	char	*delim_)		/* If non-null, list of terminators */
{
	int	len	= strlen(m1_);
	char	prompt	[MAXBFR];

	strcpy (prompt, m1_);
	crt_high (prompt, len);
	strcpy (co_, edtcmd (*co_,
			delim_, (delim_ ? 8 : 0),
		    	lpp-1, len+2, "BROWSE", co_, prompt));
	len = strlen(co_)-1;
	return (0xff & co_[max(len,0)]);
}

/* <msg>:
 * Put some text on the last line of the screen (highlighted), and position
 * the cursor at the end of the string.
 */
static
int	more_msg (char *c_, int last)
{
	int	len;
	char	bfr	[MAXBFR];

	strcpy (bfr, c_);
	crt_high (bfr, len = strlen(bfr));
	crt_text (bfr, lpp-1, 1);
	if (last)
	{
		len += 2;
		crt_move (lpp, len);
	}
	return (len);
}

#ifdef	DEBUG
/* <show>:
 * debug: show "printf" on screen-end
 */
static
void	more_show (char *format, ...)
{
	va_list	ap;
	char	bfr[1024];

	if (D_opt)
	{
		sleep(1);
		va_start (ap, format);
		vsprintf (bfr, format, ap);
		va_end (ap);
		crt_text (bfr, lpp-1, 0);
		if (D_opt > 1)
			getpad();
		else
			sleep(3);
	}
}
#endif

/* <0_bg>:
 * Special terminal initialization.  These routines may be called via 'crt_init'
 * to adjust the screen on the basis of BROWSE options.
 */

/* Set Bitgraph terminal to native mode, home and clear. */
static
int	more_0_bg(short	*lpp_, short *width_)
{
	int	j;

	for (j = 0; j < 3; j++)
	{
		putraw ("\033:0e");
		putraw ("\033[H");
		putraw ("\033[J");
	}
	*lpp_	= 64;
	*width_	= 80;
	crt__NL0 (FALSE);
	return 0;
}

/* <0_vt>:
 */
static
int	more_0_vt (short *lpp_, short *width_)
{
	if (W_opt)
		*width_ = 132;
	return 0;
}

/*
 * Define routines to make this source standalone.
 */
#ifndef	main_incl
void	warn (char *format, ...)
{
	va_list	ap;
	char	msg[CRT_COLS+MAX_PATH];

	whoami (msg, 3);
	strcat (msg, "-w-");
	va_start(ap, format);
	vsprintf (strnull(msg), format, ap);
	va_end(ap);
	putraw (msg);
}

void	error (int status, char *s_)
{
	char	msg[80],
		who[80];

	whoami (who, 3);	strcat (who, "-f-");
	if (s_)
	{
		sprintf (msg, "%s-%.60s\n", who, s_);
		putraw (msg);
	}
	else
		perror (who);
	if (did_crt_init) crt_quit(FALSE);
	exit (status ? status : (STS$M_INHIB_MSG | STS$K_ERROR));
}
#endif

#ifdef	main_incl
static
void	logfile(int cmd, int count)
{
	if (count)
		LOGFILE(("  $ %d%c", count, cmd))
	else
		LOGFILE(("  $ %c", cmd))
}
#endif/*main_incl*/
