/***************************************************************************
 * This program is Copyright (C) 1986, 1987, 1988 by Jonathan Payne.  JOVE *
 * is provided to you without charge, and with no warranty.  You may give  *
 * away copies of JOVE, including sources, provided that this notice is    *
 * included in all the files.                                              *
 ***************************************************************************/

#include "jove.h"
#include "ctype.h"
#include "termcap.h"
#include "chars.h"
#include "fp.h"
#include "disp.h"
#include "ask.h"
#include "extend.h"
#include "fmt.h"
#include "insert.h"
#include "io.h"	/* for pwd() */
#ifdef	IPROCS
# include "sysprocs.h"
# include "iproc.h"
#endif
#include "loadavg.h"
#include "mac.h"
#include "move.h"
#include "macros.h"
#include "screen.h"
#include "term.h"
#include "wind.h"

#ifdef	MAC
# include "mac.h"
#else
# ifdef	STDARGS
#  include <stdarg.h>
# else
#  include <varargs.h>
# endif
# include <sys/stat.h>
#endif

#include <signal.h>

struct screenline
	*Screen = NULL,	/* the screen (a bunch of screenline) */
	*Curline = NULL;	/* current line */

private void
#ifdef	ID_CHAR
	DeTab proto((int, char *, char *, size_t, int)),
	DelChar proto((int, int, int)),
	InsChar proto((int, int, int, char *)),
#endif
	DoIDline proto((int)),
	do_cl_eol proto((int)),
	ModeLine proto((Window *)),
	GotoDot proto((void)),
	UpdLine proto((int)),
	UpdWindow proto((Window *, int));

#ifdef	MSDOS
extern void	dobell proto((int x));
#else
private void	dobell proto((int x));
#endif

#ifdef	ID_CHAR
private bool
	IDchar proto ((char *, int, int)),
	OkayDelete proto ((int, int, int)),
	OkayInsert proto ((int, int));
private int
	NumSimilar proto ((char *, char *, int)),
	IDcomp proto ((char *, char *, int));
#endif
private int
	AddLines proto((int, int)),
	DelLines proto((int, int));



int	DisabledRedisplay = NO;

/* Kludge windows gets called by the routines that delete lines from the
   buffer.  If the w->w_line or w->w_top are deleted and this procedure
   is not called, the redisplay routine will barf. */

void
ChkWindows(line1, line2)
Line	*line1,
	*line2;
{
	register Window	*w = fwind;
	register Line	*lp,
			*lend = line2->l_next;

	do {
		if (w->w_bufp == curbuf) {
			for (lp = line1->l_next; lp != lend; lp = lp->l_next) {
				if (lp == w->w_top)
					w->w_flags |= W_TOPGONE;
				if (lp == w->w_line)
					w->w_flags |= W_CURGONE;
			}
		}
		w = w->w_next;
	} while (w != fwind);
}

/* Deleted and killed Lines are about to be recycled: check for dangling refs */

void
ChkWinLines()
{
	register Window	*w = fwind;

	do {
		if (w->w_top == NULL || w->w_top->l_dline == NULL_DADDR)
			w->w_flags |= W_TOPGONE;
		if (w->w_line == NULL || w->w_line->l_dline == NULL_DADDR)
			w->w_flags |= W_CURGONE;
		w = w->w_next;
	} while (w != fwind);
}

private bool	RingBell;	/* So if we have a lot of errors ...
				  ring the bell only ONCE */

void
redisplay()
{
	register Window	*w = fwind;
	int	lineno,
		done_ID = NO,
		i;
	register struct scrimage	*des_p,
					*phys_p;

	if (DisabledRedisplay == YES)
		return;
	curwind->w_line = curwind->w_bufp->b_dot;
	curwind->w_char = curwind->w_bufp->b_char;
#ifdef	MAC
	/* To avoid calling redisplay() recursively,
	 * we must avoid calling CheckEvent(),
	 * so we must avoid calling charp().
	 */
	InputPending = NO;
#else
	if ((InputPending = charp()) != NO)
		return;
#endif
#ifdef	BSD_SIGS
	if (UpdFreq)
		SigHold(SIGALRM);
#endif
	if (RingBell) {
		dobell(1);
		RingBell = NO;
	}
	AbortCnt = BufSize;		/* initialize this now */
	if (UpdMesg)
		DrawMesg(YES);

	for (lineno = 0, w = fwind; lineno < ILI; w = w->w_next) {
		UpdWindow(w, lineno);
		lineno += w->w_height;
	}

	UpdModLine = NO;/* Now that we've called update window, we can
			   assume that the modeline will be updated.  But
			   if while redrawing the modeline the user types
			   a character, ModeLine() is free to set this on
			   again so that the modeline will be fully drawn
			   at the next redisplay. */

	des_p = DesiredScreen;
	phys_p = PhysScreen;
	for (i = 0; i < ILI; i++, des_p++, phys_p++) {
		if (!done_ID && (des_p->s_id != phys_p->s_id)) {
			DoIDline(i);
			done_ID = YES;
		}
		if ((des_p->s_flags & (DIRTY | L_MOD))
		|| des_p->s_id != phys_p->s_id
		|| des_p->s_vln != phys_p->s_vln
		|| des_p->s_offset != phys_p->s_offset)
			UpdLine(i);
		if (InputPending)
			goto ret;
	}

	if (Asking) {
		Placur(LI - 1, min(CO - 2, calc_pos(mesgbuf, AskingWidth)));
			/* Nice kludge */
		flushscreen();
	} else {
		GotoDot();
	}
ret:
    ;	/* yuck */

#ifdef	BSD_SIGS
	if (UpdFreq)
		SigRelse(SIGALRM);
#endif
#ifdef	MAC
	if (Windchange)
		docontrols();
#endif	/* MAC */
}

#ifndef	IBMPC
private void
dobell(n)
int	n;
{
	while (--n >= 0) {
#ifdef	MAC
		SysBeep(5);
#else
		if (VisBell && VB)
			putstr(VB);
		else
			putpad(BL, 1);
#endif
	}
	flushscreen();
}
#endif	/* IBMPC */

/* find_pos() returns the position on the line, that C_CHAR represents
   in LINE */

private int
find_pos(line, c_char)
Line	*line;
int	c_char;
{
	return calc_pos(lcontents(line), c_char);
}

int
calc_pos(lp, c_char)
register char	*lp;
register int	c_char;
{
	register int	pos = 0;
	register int	c;


	while ((--c_char >= 0) && ((c = *lp++) & CHARMASK) != 0) {
		if (c == '\t')
			pos += TABDIST(pos);
		else if (jiscntrl(c))
			pos += 2;
		else
			pos += 1;
	}
	return pos;
}

bool	UpdModLine = NO,
	UpdMesg = NO;

private void
DoIDline(start)
int	start;
{
	register struct scrimage	*des_p = &DesiredScreen[start];
	struct scrimage	*phys_p = &PhysScreen[start];
	register int	i,
			j;

	/* Some changes have been made.  Try for insert or delete lines.
	   If either case has happened, Addlines and/or DelLines will do
	   necessary scrolling, also CONVERTING PhysScreen to account for the
	   physical changes.  The comparison continues from where the
	   insertion/deletion takes place; this doesn't happen very often,
	   usually it happens with more than one window with the same
	   buffer. */

	if (!CanScroll)
		return;		/* We should never have been called! */

	for (i = start; i < ILI; i++, des_p++, phys_p++)
		if (des_p->s_id != phys_p->s_id)
			break;

	for (; i < ILI; i++) {
		for (j = i + 1; j < ILI; j++) {
			des_p = &DesiredScreen[j];
			phys_p = &PhysScreen[j];
			if (des_p->s_id != NULL_DADDR && des_p->s_id == phys_p->s_id)
				break;
			if (des_p->s_id == PhysScreen[i].s_id) {
				if (des_p->s_id == NULL_DADDR)
					continue;
				if (AddLines(i, j - i)) {
					DoIDline(j);
					return;
				}
				break;
			}
			if ((des_p = &DesiredScreen[i])->s_id == phys_p->s_id) {
				if (des_p->s_id == NULL_DADDR)
					continue;
				if (DelLines(i, j - i)) {
					DoIDline(i);
					return;
				}
				break;
			}
		}
	}
}

/* Make DesiredScreen reflect what the screen should look like when we are done
   with the redisplay.  This deals with horizontal scrolling.  Also makes
   sure the current line of the Window is in the window. */

bool	ScrollAll = NO;

private void
UpdWindow(w, start)
register Window	*w;
int	start;
{
	Line	*lp;
	int	i,
		upper,		/* top of window */
		lower,		/* bottom of window */
		strt_col,	/* starting print column of current line */
		ntries = 0;	/* # of tries at updating window */
	register struct scrimage	*des_p,
					*phys_p;
	Buffer	*bp = w->w_bufp;

retry:
	if (w->w_flags & W_CURGONE) {
		w->w_line = bp->b_dot;
		w->w_char = bp->b_char;
	}
	if (w->w_flags & W_TOPGONE)
		CentWind(w);	/* reset topline of screen */
	w->w_flags &= ~(W_CURGONE | W_TOPGONE);

	/* make sure that the current line is in the window */
	upper = start;
	lower = upper + w->w_height - 1;	/* don't include modeline */
	for (i = upper, lp = w->w_top; i < lower && lp != NULL; lp = lp->l_next, i++)
		if (lp == w->w_line)
			break;
	if (i == lower || lp == NULL) {
		ntries += 1;
		if (ntries == 1) {
			CalcWind(w);
			goto retry;
		} else if (ntries == 2) {
			w->w_top = w->w_line = w->w_bufp->b_first;
			writef("\rERROR in redisplay: I got hopelessly lost!");
			dobell(2);
			goto retry;
		} else if (ntries == 3) {
			writef("\n\rOops, still lost, quitting ...\r\n");
			finish(SIGHUP);
		}
	}

	/* first do some calculations for the current line */
	{
		int	diff = (w->w_flags & W_NUMLINES) ? 8 : 0,
			end_col;

		strt_col = ScrollAll? w->w_LRscroll : PhysScreen[i].s_offset;
		end_col = strt_col + (CO - 2) - diff;
		/* Right now we are displaying from strt_col to
		   end_col of the buffer line.  These are PRINT
		   columns, not actual characters. */
		w->w_dotcol = find_pos(w->w_line, w->w_char);
		/* if the new dotcol is out of range, reselect
		   a horizontal window */
		if (PhysScreen[i].s_offset == -1
		|| w->w_dotcol < strt_col
		|| w->w_dotcol >= end_col)
		{
			if (w->w_dotcol < ((CO - 2) - diff))
				strt_col = 0;
			else
				strt_col = w->w_dotcol - (CO / 2);
			if (ScrollAll) {
				if (w->w_LRscroll != strt_col)
					UpdModLine = YES;
				w->w_LRscroll = strt_col;
			}
		}
		w->w_dotline = i;
		w->w_dotcol += diff;
	}

	des_p = &DesiredScreen[upper];
	phys_p = &PhysScreen[upper];
	for (i = upper, lp = w->w_top; lp != NULL && i < lower; i++, des_p++, phys_p++, lp = lp->l_next) {
		des_p->s_window = w;
		des_p->s_lp = lp;
		des_p->s_id = (lp == curline && DOLsave)?
			NOWHERE_DADDR : lp->l_dline & ~DIRTY;
		des_p->s_flags = isdirty(lp) ? L_MOD : 0;
		if (w->w_flags & W_NUMLINES)
			des_p->s_vln = w->w_topnum + (i - upper);
		else
			des_p->s_vln = 0;

		if (lp == w->w_line)
			des_p->s_offset = strt_col;
		else
			des_p->s_offset = w->w_LRscroll;
	}

	/* Is structure assignment faster than copy each field separately? */
	if (i < lower) {
		for (; i < lower; i++, des_p++, phys_p++) {
			static const struct scrimage	clean_plate = { 0, 0, 0, 0, 0, 0 };

			*des_p = clean_plate;
			if (phys_p->s_id != NULL_DADDR)
				des_p->s_flags = DIRTY;
		}
	}

	des_p->s_window = w;
	des_p->s_flags = 0;
	/* ??? The following assignment is very questionable:
	 * - it stores a pointer in a daddr variable
	 * - it counts on these values being distinct from
	 *   any other daddr, but equal to itself.
	 * -- DHR
	 */
	des_p->s_id = (daddr) w->w_bufp;
	if (des_p->s_id != phys_p->s_id || UpdModLine)
		des_p->s_flags = MODELINE | DIRTY;
#ifdef	MAC
	if (UpdModLine)
		Modechange = YES;
	if (w == curwind && w->w_control)
		SetScrollBar(w->w_control);
#endif
}

/* Write whatever is in mesgbuf (maybe we are Asking, or just printed
   a message).  Turns off the UpdateMesg line flag. */

void
DrawMesg(abortable)
bool	abortable;
{
#ifndef	MAC		/* same reason as in redisplay() */
	if (charp())
		return;
#endif
	i_set(ILI, 0);
	if (swrite(mesgbuf, NO, abortable)) {
		cl_eol();
		UpdMesg = NO;
	}
	flushscreen();
}

/* Goto the current position in the current window.  Presumably redisplay()
   has already been called, and curwind->{w_dotline,w_dotcol} have been set
   correctly. */

private void
GotoDot()
{
	if (!InputPending) {
		Placur(curwind->w_dotline,
			curwind->w_dotcol - PhysScreen[curwind->w_dotline].s_offset);
		flushscreen();
	}
}

private int
UntilEqual(start)
register int	start;
{
	register struct scrimage	*des_p = &DesiredScreen[start],
					*phys_p = &PhysScreen[start];

	while ((start < ILI) && (des_p->s_id != phys_p->s_id)) {
		des_p += 1;
		phys_p += 1;
		start += 1;
	}

	return start;
}

/* Calls the routine to do the physical changes, and changes PhysScreen to
   reflect those changes. */

private int
AddLines(at, num)
register int	at,
		num;
{
	register int	i;
	int	bottom = UntilEqual(at + num);

	if (num == 0 || num >= ((bottom - 1) - at))
		return NO;				/* we did nothing */
	v_ins_line(num, at, bottom - 1);

	/* Now change PhysScreen to account for the physical change. */

	for (i = bottom - 1; i - num >= at; i--)
		PhysScreen[i] = PhysScreen[i - num];
	for (i = 0; i < num; i++)
		PhysScreen[at + i].s_id = NULL_DADDR;
	return YES;					/* we did something */
}

private int
DelLines(at, num)
register int	at,
		num;
{
	register int	i;
	int	bottom = UntilEqual(at + num);

	if (num == 0 || num >= ((bottom - 1) - at))
		return NO;
	v_del_line(num, at, bottom - 1);

	for (i = at; num + i < bottom; i++)
		PhysScreen[i] = PhysScreen[num + i];
	for (i = bottom - num; i < bottom; i++)
		PhysScreen[i].s_id = NULL_DADDR;
	return YES;
}

/* Update line linenum in window w.  Only set PhysScreen to DesiredScreen
   if the swrite or cl_eol works, that is nothing is interupted by
   characters typed. */

private void
UpdLine(linenum)
register int	linenum;
{
	register struct scrimage	*des_p = &DesiredScreen[linenum];
	register Window	*w = des_p->s_window;

	i_set(linenum, 0);
	if (des_p->s_flags & MODELINE) {
		ModeLine(w);
	} else if (des_p->s_id != NULL_DADDR) {
		des_p->s_lp->l_dline &= ~DIRTY;
		des_p->s_flags &= ~(DIRTY | L_MOD);
#ifdef	ID_CHAR
		if (UseIC) {
			char	outbuf[MAXCOLS],
				*lptr;
			int	fromcol = (w->w_flags & W_NUMLINES) ? 8 : 0;

			if (w->w_flags & W_NUMLINES)
				swritef(outbuf, sizeof(outbuf), "%6d  ",
					des_p->s_vln);
			lptr = lcontents(des_p->s_lp);
			DeTab(des_p->s_offset, lptr, outbuf + fromcol,
				(sizeof outbuf) - 1 - fromcol,
				des_p->s_window->w_flags & W_VISSPACE);
			if (IDchar(outbuf, linenum, 0))
				PhysScreen[linenum] = *des_p;
			else if (i_set(linenum, 0), swrite(outbuf, NO, YES))
				do_cl_eol(linenum);
			else
				PhysScreen[linenum].s_id = NOWHERE_DADDR;
		} else {
#endif	/* ID_CHAR */
			if (w->w_flags & W_NUMLINES)
				(void) swrite(sprint("%6d  ", des_p->s_vln), NO, YES);
			if (BufSwrite(linenum))
				do_cl_eol(linenum);
			else
				PhysScreen[linenum].s_id = NOWHERE_DADDR;
#ifdef	ID_CHAR
		}
#endif
	} else if (PhysScreen[linenum].s_id != NULL_DADDR) { /* not the same ... make sure */
		do_cl_eol(linenum);
	}
}

private void
do_cl_eol(linenum)
register int	linenum;
{
	cl_eol();
	PhysScreen[linenum] = DesiredScreen[linenum];
}

#ifdef	ID_CHAR

/* From here to the end of the file is code that tries to utilize the
   insert/delete character feature on some terminals.  It is very confusing
   and not so well written code, AND there is a lot of it.  You may want
   to use the space for something else. */

bool	IN_INSmode = NO;

bool	UseIC = NO;

int	IMlen;

private int
	DClen,
	IClen,
	MDClen,
	MIClen,
	CElen;

void
disp_opt_init()
{
	DClen = DC ? strlen(DC) : 0;
	MDClen = M_DC ? strlen(M_DC) : 9999;
	IClen = IC ? strlen(IC) : 0;
	MIClen = M_IC ? strlen(M_IC) : 9999;
	IMlen = IM ? strlen(IM) : 0;
	CElen = CE ? strlen(CE) : 0;

	UseIC = (IC || IM || M_IC);
}

void
INSmode(on)
bool	on;
{
	if (on && !IN_INSmode) {
		putpad(IM, 1);
		IN_INSmode = YES;
	} else if (!on && IN_INSmode) {
		putpad(EI, 1);
		IN_INSmode = NO;
	}
}

private void
DeTab(s_offset, buf, outbuf, limit, visspace)
int	s_offset;
register char	*buf;
char	*outbuf;
size_t	limit;
int	visspace;
{
	register char	*phys_p = outbuf,
			c;
	register int	pos = 0;
	char		*limitp = &outbuf[limit];

#define OkayOut(ch)	{ \
	if ((pos++ >= s_offset) && (phys_p < limitp)) \
		*phys_p++ = (ch); \
}

	while ((c = *buf++) != '\0') {
		if (c == '\t') {
			int	nchars = TABDIST(pos);

			if (visspace) {
				OkayOut('>');
				nchars -= 1;
			}
			while (--nchars >= 0)
				OkayOut(' ');

		} else if (jiscntrl(c)) {
			OkayOut('^');
			OkayOut(c == 0177 ? '?' : c + '@');
		} else {
			if (visspace && c == ' ')
				c = '_';
			OkayOut(c);
		}
		if (pos - s_offset >= CO) {
			phys_p = &outbuf[CO - 1];
			*phys_p++ = '!';
			break;
		}
	}
	*phys_p = '\0';

#undef	OkayOut
}

/* ID character routines full of special cases and other fun stuff like that.
   It actually works though ...

	Returns Non-Zero if you are finished (no differences left). */

private bool
IDchar(new, lineno, col)
register char	*new;
int	lineno,
	col;
{
	register int	i;
	int	j,
		oldlen,
		NumSaved;
	register struct screenline	*sline = &Screen[lineno];

	oldlen = sline->s_length - sline->s_line;

	for (i = col; i < oldlen && new[i] != '\0'; i++)
		if (sline->s_line[i] != new[i])
			break;
	if (new[i] == '\0' || i == oldlen)
		return new[i] == '\0' && i == oldlen;

	for (j = i + 1; j < oldlen && new[j]; j++) {
		if (new[j] == sline->s_line[i]) {
			NumSaved = IDcomp(new + j, sline->s_line + i,
					(int)strlen(new)) + NumSimilar(new + i,
						sline->s_line + i, j - i);
			if (OkayInsert(NumSaved, j - i)) {
				InsChar(lineno, i, j - i, new);
				return IDchar(new, lineno, j);
			}
		}
	}

	for (j = i + 1; j < oldlen && new[i]; j++) {
		if (new[i] == sline->s_line[j]) {
			NumSaved = IDcomp(new + i, sline->s_line + j,
					oldlen - j);
			if (OkayDelete(NumSaved, j - i, new[oldlen] == '\0')) {
				DelChar(lineno, i, j - i);
				return IDchar(new, lineno, i);
			}
		}
	}
	return NO;
}

private int
NumSimilar(s, t, n)
register char	*s,
		*t;
int	n;
{
	register int	num = 0;

	while (n--)
		if (*s++ == *t++)
			num += 1;
	return num;
}

private int
IDcomp(s, t, len)
register char	*s,
		*t;
int	len;
{
	register int	i;
	int	num = 0,
		nonspace = 0;
	char	c;

	for (i = 0; i < len; i++) {
		if ((c = *s++) != *t++)
			break;
		if (c != ' ')
			nonspace++;
		if (nonspace)
			num += 1;
	}

	return num;
}

private bool
OkayDelete(Saved, num, samelength)
int	Saved,
	num,
	samelength;
{
	/* If the old and the new are the same length, then we don't
	 * have to clear to end of line.  We take that into consideration.
	 */
	return ((Saved + (!samelength ? CElen : 0))
		> min(MDClen, DClen * num));
}

private bool
OkayInsert(Saved, num)
int	Saved,
	num;
{
	register int	n = 0;

	if (IC)		/* Per character prefixes */
		n = min(num * IClen, MIClen);

	if (IM && !IN_INSmode) {
		/* Good terminal.  Fewer characters in this case */
		n += IMlen;
	}

	n += num;	/* The characters themselves */

	return Saved > n;
}

private void
DelChar(lineno, col, num)
int	lineno,
	col,
	num;
{
	register char	*from,
			*to;
	register int	i;
	struct screenline *sp = (&Screen[lineno]);

	Placur(lineno, col);
	if (M_DC && num > 1) {
		putargpad(M_DC, num, num);
	} else {
		for (i = num; --i >= 0; )
			putpad(DC, 1);
	}

	to = sp->s_line + col;
	from = to + num;

	byte_copy(from, to, (size_t) (sp->s_length - from + 1));
	clrline(sp->s_length - num, sp->s_length);
	sp->s_length -= num;
}

private void
InsChar(lineno, col, num, new)
int	lineno,
	col,
	num;
char	*new;
{
	register char	*sp1,
			*sp2,	/* To push over the array. */
			*sp3;	/* Last character to push over. */
	int	i;

	i_set(lineno, 0);
	sp2 = Curline->s_length + num;

	if (sp2 >= cursend) {
		i_set(lineno, CO - num - 1);
		cl_eol();
		sp2 = cursend - 1;
	}
	Curline->s_length = sp2;
	sp1 = sp2 - num;
	sp3 = Curline->s_line + col;

	while (sp1 >= sp3)
		*sp2-- = *sp1--;

	new += col;
	byte_copy(new, sp3, (size_t) num);
	/* The internal screen is correct, and now we have to do
	   the physical stuff. */

	Placur(lineno, col);
	if (IM) {
		if (!IN_INSmode)
			INSmode(YES);
	} else if (M_IC && num > 1) {
		putargpad(M_IC, num, num);
	} else if (IC) {
		for (i = 0; i < num; i++)
			putpad(IC, 1);
	}
	for (i = 0; i < num; i++) {
		jputchar(new[i]);
		if (IN_INSmode)
			putpad(IP, 1);
	}
	CapCol += num;
}

#endif	/* ID_CHAR */

#ifdef	UNIX		/* obviously ... no mail today if not Unix*/

/* chkmail() returns nonzero if there is new mail since the
   last time we checked. */

char	Mailbox[FILESIZE];	/* initialized in main */
int	MailInt = 60;		/* check no more often than 60 seconds */
#ifdef	BIFF
bool	BiffChk = NO;		/* whether to turn off biff while in JOVE */
#endif

int
chkmail(force)
int	force;
{
	time_t	now;
	static int	state = NO;	/* assume unknown */
	static time_t	last_chk = 0,
			mbox_time = 0;
	struct stat	stbuf;

	if (MailInt == 0)
		return NO;
	time(&now);
	if ((force == NO) && (now < last_chk + MailInt))
		return state;
	last_chk = now;
	if (stat(Mailbox, &stbuf) < 0) {
		state = NO;		/* no mail */
		return NO;
	}
	if ((stbuf.st_atime > stbuf.st_mtime && stbuf.st_atime > mbox_time)
	|| stbuf.st_size == 0)
	{
		mbox_time = stbuf.st_atime;
		state = NO;
	} else if (stbuf.st_mtime > mbox_time) {
		if (mbox_time > 0)
			dobell(2);		/* announce the change */
		mbox_time = stbuf.st_mtime;
		state = YES;
	}
	return state;
}

#endif	/* UNIX */

/* Print the mode line. */

private char	*mode_p,
		*mend_p;
bool	BriteMode = YES;		/* modeline should standout */

private void
mode_app(str)
register const char	*str;
{
	do ; while ((mode_p < mend_p) && (*mode_p++ = *str++)!='\0');
	mode_p -= 1;	/* back over the null */
}

char	ModeFmt[120] = "%3c %w %[%sJOVE (%M)   Buffer: %b  \"%f\" %]%s%m*- %((%t)%s%)%e";

private void
ModeLine(w)
register Window	*w;
{
	int	n,
		glue = 0;
	bool	ign_some = NO;
	char	line[MAXCOLS],
		*fmt = ModeFmt,
		fillc,
		c;
	register Buffer	*thisbuf = w->w_bufp;
	register Buffer *bp;

	mode_p = line;
	mend_p = &line[(sizeof line) - 1];

#ifdef	IBMPC
	/* very subtle - don't mess up attributes too much */
	fillc = '-';
#else	/* !IBMPC */
#  ifdef	MAC
	fillc = '_';	/* looks better on a Mac */
#  else	/* !MAC */
	if (SO == NULL)
		BriteMode = NO;
	fillc = BriteMode ? ' ' : '-';
#  endif	/* !MAC */
#endif	/* !IBMPC */

	while ((c = *fmt++)!='\0' && mode_p<mend_p) {
		if (c != '%') {
			if (c == '\\')
				if ((c = *fmt++) == '\0')
					break;
			if (!ign_some)
				*mode_p++ = c;
			continue;
		}
		if ((c = *fmt++) == '\0')	/* char after the '%' */
			break;
		if (ign_some && c != ')')
			continue;
		n = 1;
		if (c >= '0' && c <= '9') {
			n = 0;
			while (c >= '0' && c <= '9') {
				n = n * 10 + (c - '0');
				c = *fmt++;
			}
			if (c == '\0')
				break;
		}
		switch (c) {
		case '(':
			if (w->w_next != fwind)	/* Not bottom window. */
				ign_some = YES;
			break;

		case ')':
			ign_some = NO;
			break;

		case '[':
		case ']':
			for (n=RecDepth; n>0 && mode_p<mend_p; n--)
				*mode_p++ = c;
			break;

#ifdef	UNIX
		case 'C':	/* check mail here */
			if (chkmail(NO) == YES)
				mode_app("[New mail]");
			break;

#endif	/* UNIX */

		case 'M':
		    {
			static const char	*const mmodes[] = {
				"Fundamental ",
				"Text ",
				"C ",
#ifdef	LISP
				"Lisp ",
#endif
				NULL
			};

			mode_app(mmodes[thisbuf->b_major]);

			if (BufMinorMode(thisbuf, Fill))
				mode_app("Fill ");
			if (BufMinorMode(thisbuf, Abbrev))
				mode_app("Abbrev ");
			if (BufMinorMode(thisbuf, OverWrite))
				mode_app("OvrWt ");
			if (BufMinorMode(thisbuf, Indent))
				mode_app("Indent ");
			if (BufMinorMode(thisbuf, ReadOnly))
				mode_app("RO ");
			if (InMacDefine)
				mode_app("Def ");
			mode_p -= 1;	/* Back over the extra space. */
			break;
		    }

		case 'c':
			while (--n>=0 && mode_p<mend_p)
				*mode_p++ = fillc;
			break;

		case 'd':	/* print working directory */
			mode_app(pr_name(pwd(), YES));
			break;

		case 'e':	/* stretchable glue */
			*mode_p++ = '\0';	/* glue marker */
			glue++;
			break;

		case 'b':
			mode_app(thisbuf->b_name);
			break;

		case 'f':
		case 'F':
			if (thisbuf->b_fname == NULL)
				mode_app("[No file]");
			else {
				if (c == 'f')
					mode_app(pr_name(thisbuf->b_fname, YES));
				else
					mode_app(basename(thisbuf->b_fname));
			}
			break;

#ifdef	LOAD_AV
		case 'l':
		    {
			int	la = get_la();
			char	minibuf[10];

			swritef(minibuf, sizeof(minibuf), "%d.%02d",
			       la/100, la%100);
			mode_app(minibuf);
			break;
		    }
#endif

		case 'm':
		    {
			char	yea = (*fmt == '\0') ? '*' : *fmt++;
			char	nay = (*fmt == '\0') ? ' ' : *fmt++;

			*mode_p++ = IsModified(w->w_bufp) ? yea : nay;
			break;
		    }

		case 'n':
		    {
			char	tmp[16];

			for (bp = world, n = 1; bp != NULL; bp = bp->b_next, n++)
				if (bp == thisbuf)
					break;

			swritef(tmp, sizeof(tmp), "%d", n);
			mode_app(tmp);
			break;
		    }

#ifdef	IPROCS
		case 'p':
			if (thisbuf->b_type == B_PROCESS) {
				char	tmp[40];
				Process	*p = thisbuf->b_process;

				swritef(tmp, sizeof(tmp), "(%s%s)",
					((p == NULL || p->p_dbx_mode == NO)
					 ? NullStr : "DBX "),
					((p == NULL) ? "No process" :
					 pstate(p)));
				mode_app(tmp);
			}
			break;
#endif

		case 's':
			if (mode_p[-1] != ' ')
				*mode_p++ = ' ';
			break;

		case 't':
		    {
			char	timestr[12];

			mode_app(get_time((time_t *)NULL, timestr, 11, 16));
			break;
		    }

		case 'w':
			if (w->w_LRscroll > 0)
				mode_app(">");
			break;

		default:
			mode_app("?");
			break;
		}
	}

	/* Glue (Knuth's term) is a field that expands to fill
	 * any leftover space.  Multiple glue fields compete
	 * on an equal basis.  This is a generalization of a
	 * mechanism to allow centring and right-justification.
	 * The original meaning of %e (fill the rest of the
	 * line) has also been generalized.  %e can now
	 * meaningfully be used 0 or more times.
	 */

	if  (glue) {
		/* 2 space pad plus padding for magic cookies */
		register char	*to = &line[CO - 2 - (2 * SG)],
				*from = mode_p;

		if (to < from)
			to = from;
		mode_p = to;
		while (from != line) {
			if ((*--to = *--from) == '\0') {
				register int	portion = (to-from) / glue;

				glue--;
				*to = fillc;
				while (--portion >= 0)
					*--to = fillc;
			}
		}
	}

	*mode_p = '\0';

	/* Highlight mode line. */
	if (BriteMode) {
#ifdef	ID_CHAR
		if (IN_INSmode)
			INSmode(NO);
#endif
		SO_on();
	}
	if (swrite(line, BriteMode, YES))
		do_cl_eol(i_line);
	else
		UpdModLine = YES;
	if (BriteMode)
		SO_off();
}

private void
v_clear(line1, line2)
register int	line1;
int	line2;
{
	register struct scrimage	*phys_p, *des_p;

	phys_p = &PhysScreen[line1];
	des_p = &DesiredScreen[line1];

	while (line1 <= line2) {
		i_set(line1, 0);
		cl_eol();
		phys_p->s_id = des_p->s_id = NULL_DADDR;
		phys_p += 1;
		des_p += 1;
		line1 += 1;
	}
}

/* This tries to place the current line of the current window in the
   center of the window, OR to place it at the arg'th line of the window.
   This also causes the horizontal position of the line to be centered,
   if the line needs scrolling, or moved all the way back to the left,
   if that's possible. */
void
RedrawDisplay()
{
	int	line;
	Line	*newtop = prev_line((curwind->w_line = curline), is_an_arg() ?
				arg_value() : HALF(curwind));

	if ((line = in_window(curwind, curwind->w_line)) != -1)
		PhysScreen[line].s_offset = -1;
	if (newtop == curwind->w_top)
		v_clear(FLine(curwind), FLine(curwind) + SIZE(curwind));
	else
		SetTop(curwind, newtop);
}

void
ClAndRedraw()
{
	cl_scr(YES);
}

void
NextPage()
{
	Line	*newline;

	if (Asking) {
		/* don't do it */
	} else if (arg_value() < 0) {
		negate_arg();
		PrevPage();
	} else if (is_an_arg()) {
		UpScroll();
	} else {
		if (in_window(curwind, curwind->w_bufp->b_last) != -1) {
			rbell();
			return;
		}
		newline = next_line(curwind->w_top, max(1, SIZE(curwind) - 1));
		SetTop(curwind, curwind->w_line = newline);
		if (curwind->w_bufp == curbuf)
			SetLine(newline);
	}
}

#ifdef	MSDOS

void
PageScrollUp()
{
	int i, n;

    n = max(1, SIZE(curwind) - 1);
	for (i=0; i<n; i++) {
	    UpScroll();
	    redisplay();
	}
}

void
PageScrollDown()
{
	int i, n;

	n = max(1, SIZE(curwind) - 1);
	for (i=0; i<n; i++) {
	    DownScroll();
	    redisplay();
	}
}
#endif	/* MSDOS */

void
PrevPage()
{
	Line	*newline;

	if (Asking) {
		/* don't do it */
	} else if (arg_value() < 0) {
		negate_arg();
		NextPage();
	} else if (is_an_arg()) {
		DownScroll();
	} else {
		newline = prev_line(curwind->w_top, max(1, SIZE(curwind) - 1));
		SetTop(curwind, curwind->w_line = newline);
		if (curwind->w_bufp == curbuf)
			SetLine(newline);
	}
}

void
UpScroll()
{
	SetTop(curwind, next_line(curwind->w_top, arg_value()));
	if (curwind->w_bufp == curbuf
	&& in_window(curwind, curline) == -1)
		SetLine(curwind->w_top);
}

void
DownScroll()
{
	SetTop(curwind, prev_line(curwind->w_top, arg_value()));
	if (curwind->w_bufp == curbuf
	&& in_window(curwind, curline) == -1)
		SetLine(curwind->w_top);
}

bool	VisBell = NO;

void
rbell()
{
	RingBell = YES;
}

/* Message prints the null terminated string onto the bottom line of the
   terminal. */

void
message(str)
char	*str;
{
	if (InJoverc)
		return;
	UpdMesg = YES;
	errormsg = NO;
	if (str != mesgbuf)
		null_ncpy(mesgbuf, str, (sizeof mesgbuf) - 1);
}

/* End of Window */

void
Eow()
{
	if (Asking)
		return;
	SetLine(next_line(curwind->w_top, SIZE(curwind) - 1 -
			min(SIZE(curwind) - 1, arg_value() - 1)));
	if (!is_an_arg())
		Eol();
}

/* Beginning of Window */

void
Bow()
{
	if (Asking)
		return;
	SetLine(next_line(curwind->w_top, min(SIZE(curwind) - 1, arg_value() - 1)));
}

private int	LineNo,
		last_col;
private bool
		DoAutoNL;
private Window	*old_wind;	/* save the window we were in BEFORE
				   before we were called, if UseBuffers
				   is nonzero */

bool	UseBuffers = NO,
	TOabort = NO;

/* This initializes the typeout.  If send-typeout-to-buffers is set
   the buffer NAME is created (emptied if it already exists) and output
   goes to the buffer.  Otherwise output is drawn on the screen and
   erased by TOstop() */

void
TOstart(name, auto_newline)
char	*name;
bool	auto_newline;
{
	if (UseBuffers) {
		old_wind = curwind;
		pop_wind(name, YES, B_SCRATCH);
	} else
		DisabledRedisplay = YES;
	TOabort = NO;
	LineNo = last_col = 0;
	DoAutoNL = auto_newline;
}

#ifdef	STDARGS
	void
Typeout(char *fmt, ...)
#else
	/*VARARGS1*/ void
Typeout(fmt, va_alist)
	char	*fmt;
	va_dcl
#endif
{
	if (TOabort)
		return;

	if (!UseBuffers && (LineNo == ILI - 1)) {
		register int	c;

		LineNo = 0;
		last_col = 0;
		f_mess("--more--");
		if ((c = jgetchar()) != ' ') {
			TOabort = YES;
			if (c != AbortChar && c != RUBOUT)
				Ungetc(c);
			f_mess(NullStr);
			return;
		}
		f_mess(NullStr);
	}

	if (fmt) {
		char	string[132];
		va_list	ap;

		va_init(ap, fmt);
		format(string, sizeof string, fmt, ap);
		va_end(ap);
		if (UseBuffers) {
			ins_str(string, NO);
		} else {
			i_set(LineNo, last_col);
			(void) swrite(string, NO, YES);
			last_col = i_col;
		}
	}
	if (!UseBuffers) {
		PhysScreen[LineNo].s_id = NOWHERE_DADDR;
		if (fmt == NULL || DoAutoNL) {
			cl_eol();
			flushscreen();
			LineNo += 1;
			last_col = 0;
		}
	} else if (fmt == NULL || DoAutoNL)
		ins_str("\n", NO);
}

void
TOstop()
{
	int	c;

	if (UseBuffers) {
		ToFirst();
		SetWind(old_wind);
	} else {
		if (TOabort) {
			DisabledRedisplay = NO;
		} else {
			if (last_col != 0)
				Typeout((char *)NULL);
			Typeout("----------");
			cl_eol();
			flushscreen();
			c = jgetchar();
			if (c != ' ')
				Ungetc(c);
			DisabledRedisplay = NO;
		}
	}
}
