/**************************************************************************
 * This program is Copyright (C) 1986-2002 by Jonathan Payne.  JOVE is    *
 * provided by Jonathan and Jovehacks without charge and without          *
 * warranty.  You may copy, modify, and/or distribute JOVE, provided that *
 * this notice is included in all the source files and documentation.     *
 **************************************************************************/

#include "jove.h"
#include "jctype.h"
#include "chars.h"
#include "fp.h"
#include "disp.h"
#include "ask.h"
#include "extend.h"
#include "fmt.h"
#include "insert.h"
#ifdef IPROCS
# include "iproc.h"
#endif
#include "move.h"
#include "macros.h"
#include "screen.h"
#include "term.h"
#include "wind.h"

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

/* define a couple of unique daddrs */
#define	NOWHERE_DADDR	(~NULL_DADDR | DDIRTY)	/* not in tmp file */
#define	UNSAVED_CURLINE_DADDR	((NOWHERE_DADDR - 1) | DDIRTY)	/* not yet in tmp file */

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

private void
	DeTab(char *, int, char *, char *, jbool),
	DoIDline(int),
	do_cl_eol(int),
	ModeLine(Window *, char *, int),
	GotoDot(void),
	UpdLine(int),
	UpdWindow(Window *, int);

#ifdef ID_CHAR
private void
	DelChar(int, int, int),
	InsChar(int, int, int, char *);

private jbool
	IDchar(char *, int),
	OkayDelete(int, int, jbool),
	OkayInsert(int, int);

private int
	NumSimilar(char *, char *, int),
	IDcomp(char *, char *, int);
#endif /* ID_CHAR */

private jbool
	AddLines(int, int),
	DelLines(int, int);

jbool	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(LinePtr line1, LinePtr line2)
{
	Window	*w = fwind;
	LinePtr	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);
}


#ifdef WINRESIZE

volatile jbool
	ResizePending = NO;	/* asynch request for screen resize */

private void
resize(void)
{
	jbool	oldDisabledRedisplay = DisabledRedisplay;
	int
		oldILI = ILI,
		oldCO = CO;

	DisabledRedisplay = YES;	/* prevent tragedy */
	ResizePending = NO;	/* early & safe */
	ttsize();	/* update line (LI and ILI) and col (CO) info. */

	if (oldILI != ILI || oldCO != CO) {
		int	total;
		Window	*wp;

		/* Go through the window list, changing each window size in
		 * proportion to the resize.  If the window would become too
		 * small, we delete it.
		 *
		 * Actually, we must do the deletion in
		 * a separate pass because del_wind donates the space to either
		 * neighbouring window.  We test the windows in a funny order:
		 * top-most, then bottom-up.  Although it isn't necessary
		 * for correctness, it means that we consider any donated
		 * space, cutting down the number of windows we decide to delete.
		 * Loop termination is tricky: fwind may have changed due to a
		 * del_wind.  As a simple fix, we start over whenever we
		 * delete a window.  Although this is O(n**2), it can't
		 * really be expensive.
		 *
		 * After scaling all the windows, we
		 * give any space remaining to the current window (which would
		 * have changed if the old current window had been deleted).
		 *
		 * This seems fairer than just resizing the current window.
		 */
		wp = fwind;
		for (;;) {
			int	newsize = ILI * wp->w_height / oldILI;

			if (newsize < 2) {
				del_wind(wp);
				wp = fwind;
			} else {
				wp = wp->w_prev;
				if (wp == fwind)
					break;
			}
		}

		total = 0;
		do {
			int	newsize = ILI * wp->w_height / oldILI;

			wp->w_height = newsize;
			total += newsize;
			wp = wp->w_next;
		} while (wp != fwind);

		curwind->w_height += ILI - total;

		/* Make a new screen structure */
		make_scr();

		/* Do a 'hard' update on the screen - clear and redraw */
		ClAndRedraw();
#ifdef WIN32
		ResizeWindow();
#endif
	}
	DisabledRedisplay = oldDisabledRedisplay;
}

#endif /* WINRESIZE */

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

void
redisplay(void)
{
	if (DisabledRedisplay)
		return;

#ifdef WINRESIZE
	do
#endif
	{
		Window
			*w;
		int
			lineno,
			i;
		jbool
			done_ID = NO,
			old_UpdModLine;
		struct scrimage
			*des_p,
			*phys_p;

#ifdef WINRESIZE
		if (ResizePending)
			resize();
#endif
		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 (PreEmptOutput())
			return;
#endif
		if (RingBell) {
			dobell(1);
			RingBell = NO;
		}
		AbortCnt = ScrBufSize;		/* 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;
		}

		/* 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.  Furthermore, if output
		 * is preempted, we'll restore the old value because
		 * we can't be sure that the updating has happened.
		 */
		old_UpdModLine = UpdModLine;
		UpdModLine = NO;

		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 & (s_DIRTY | s_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 (CheapPreEmptOutput()) {
				if (old_UpdModLine)
					UpdModLine = YES;
				goto suppress;
			}
		}

		if (Asking) {
			int p = calc_pos(mesgbuf, AskingWidth);
			jdbg("redisplay CO=%d aw=%d p=%d \"%s\"\n", CO, AskingWidth, p, mesgbuf);
			Placur(ILI, jmin(CO - 2, p));
				/* Nice kludge */
			flushscreen();
		} else {
			GotoDot();
		}
suppress: ;
	}
#ifdef WINRESIZE
	/**/ while (ResizePending);
#endif
#ifdef MAC
	if (Windchange)
		docontrols();
#endif
}

/* find_pos() returns the position on the line, that C_CHAR represents
 * in LINE
 */
private int
find_pos(LinePtr line, int c_char)
{
	return calc_pos(lcontents(line), c_char);
}

/* calc_pos calculates the screen column of character c_char.
 *
 * Note: the calc_pos, how_far, and DeTab must be in synch --
 * each thinks it knows how characters are displayed.
 */

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

	while ((--c_char >= 0) && (c = ZXC(*lp++)) != 0) {
		if (c == '\t' && tabstop != 0) {
			pos += TABDIST(pos);
		} else if (jisprint(c)) {
			pos += 1;
		} else {
			if (c <= DEL)
				pos += 2;
			else
				pos += 4;
		}
	}
	return pos;
}

volatile jbool
	UpdModLine = NO;
jbool	UpdMesg = NO;

private void
DoIDline(int start)
{
	struct scrimage	*des_p = &DesiredScreen[start],
			*phys_p = &PhysScreen[start];
	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.
	 */
#ifdef TERMCAP
	if (!CanScroll)
		return;		/* We should never have been called! */
#endif

	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.
 */
jbool	ScrollAll = NO;	/* VAR: when current line scrolls, scroll whole window? */
int	ScrollWidth = 10;	/* VAR: unit of horizontal scrolling */

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

	do {
		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 + WSIZE(w);
		for (i = upper, lp = w->w_top; ; lp = lp->l_next, i++) {
			if (i == lower || lp == NULL) {
				/* we've run out of window without finding dot */
				ntries += 1;
				if (ntries == 1) {
					CalcWind(w);
				} 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);
				} else {
					writef("\n\rOops, still lost, quitting ...\r\n");
					finish(-1);	/* die! */
					/*NOTREACHED*/
				}
				break;
			}
			if (lp == w->w_line) {
				ntries = 0;	/* happiness: dot is in window */
				break;
			}
		}
	} while (ntries != 0);

	/* first do some calculations for the current line */
	{
		int
			nw = W_NUMWIDTH(w),
			dot_col,
			end_col;

		strt_col = ScrollAll? w->w_LRscroll : PhysScreen[i].s_offset;
		end_col = strt_col + (CO - 1) - (nw + SIWIDTH(strt_col));
		/* Right now we are displaying from strt_col to
		 * end_col of the buffer line.  These are PRINT
		 * columns, not actual characters.
		 */
		dot_col = 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
		|| !(strt_col <= dot_col && dot_col < end_col))
		{
			/* If dot_col is within first step left of screen, step left.
			 * Otherwise, if ditto for right.
			 * Otherwise, if it is in first screenwidth, start from begining.
			 * Otherwise, center dot_col.
			 * Fudge: if a scroll left would work except for the necessary
			 * appearance of an ! on the left, we scroll an extra column.
			 */
			int
				step = jmin(ScrollWidth, end_col - strt_col);

			strt_col =
				strt_col > dot_col && strt_col - step <= dot_col
					? jmax(strt_col - step, 0)
				: dot_col >= end_col && dot_col < end_col + step
					? jmin(strt_col + step
					  + (strt_col == 0 && dot_col == end_col + step - 1? 1 : 0)
					  , dot_col)
				: dot_col < ((CO - 1) - nw)
					? 0
				: dot_col - ((CO - nw) / 2);

			if (ScrollAll) {
				if (w->w_LRscroll != strt_col)
					UpdModLine = YES;
				w->w_LRscroll = strt_col;
			}
		}
		w->w_dotline = i;
		w->w_dotcol = dot_col + nw + SIWIDTH(strt_col);
	}

	lp = w->w_top;
	des_p = &DesiredScreen[upper];
	phys_p = &PhysScreen[upper];
	for (i = upper; i < lower; i++, des_p++, phys_p++) {
		if (lp != NULL) {
			des_p->s_offset = (lp == w->w_line)? strt_col : w->w_LRscroll;
			des_p->s_flags = isdirty(lp) ? s_L_MOD : 0;
			des_p->s_vln = (w->w_flags & W_NUMLINES)?
				w->w_topnum + (i - upper) : 0;
			des_p->s_id = (lp == curline && DOLsave)?
				UNSAVED_CURLINE_DADDR : lp->l_dline & ~DDIRTY;
			des_p->s_lp = lp;
			lp = lp->l_next;
		} else {
			/* display line beyond end of buffer */
			static const struct scrimage
				clean_plate = { 0, 0, 0, NULL_DADDR, NULL, NULL };

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

	/* mode line: */

	/* ??? The following assignment to des_p->s_id is tricky:
	 *
	 * We count on the cast pointer value being distinct from
	 * any other daddr, but equal to itself.  Turning
	 * the "DDIRTY" bit on should ensure that it is distinct
	 * from legitimate daddr values (except for NOWHERE_DADDR
	 * and UNSAVED_CURLINE_DADDR).
	 * If sizeof(Buffer *)>sizeof(daddr), nothing ensures that
	 * these pointers are even distinct from each other.
	 *
	 * There also seems to be an assumption that every modeline
	 * for a particular buffer will be the same.  This is not
	 * always the case: the last modeline on the screen is usually
	 * different from any other modeline, even for the same buffer.
	 * Currently, I think that only very contrived cases could cause
	 * problems (probably involving window resizing).
	 * Further problems will arise if JOVE is changed so that there are
	 * other ways in which a modeline can reflect the window state
	 * (instead of just the buffer state).
	 *
	 * -- DHR
	 */
	des_p->s_window = w;
	des_p->s_id = (jwid_t) w->w_bufp | DDIRTY;
	des_p->s_flags = (des_p->s_id != phys_p->s_id || UpdModLine)?
		s_MODELINE | s_DIRTY : 0;
	des_p->s_offset = 0;
	des_p->s_vln = 0;
	des_p->s_lp = NULL;

#ifdef MAC
	if (UpdModLine)
		Modechange = YES;
	if (w == curwind && w->w_control != NULL)
		SetScrollBar(w);
#endif
}

/* Write whatever is in mesgbuf (maybe we are Asking, or just printed
 * a message).  Turns off the UpdateMesg line flag.
 */
void
DrawMesg(jbool abortable)
{
	char	outbuf[MAXCOLS + PPWIDTH];	/* assert(CO <= MAXCOLS); */

#ifndef MAC		/* same reason as in redisplay() */
	if (PreEmptOutput())
		return;
#endif
	i_set(ILI, 0);
	jdbg("DrawMesg aw=%d \"%s\"\n", AskingWidth, mesgbuf);
	DeTab(mesgbuf, 0, outbuf, outbuf + CO, NO);
	if (swrite(outbuf, NOEFFECT, 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(void)
{
	if (!CheapPreEmptOutput()) {
		Placur(curwind->w_dotline,
			curwind->w_dotcol - PhysScreen[curwind->w_dotline].s_offset);
		flushscreen();
	}
}

private int
UntilEqual(int start)
{
	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 jbool
AddLines(int at, int num)
{
	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 jbool
DelLines(int at, int num)
{
	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;
}

jbool	MarkHighlighting = YES;	/* VAR: highlight mark when visible */

/* Update line linenum in window w.  Only set PhysScreen to DesiredScreen
 * if the swrite or cl_eol works, that is nothing is interrupted by
 * characters typed.
 */
private void
UpdLine(int linenum)
{
	struct scrimage	*des_p = &DesiredScreen[linenum];
	Window	*w = des_p->s_window;
	char	outbuf[MAXCOLS + PPWIDTH];	/* assert(CO <= MAXCOLS); */

	i_set(linenum, 0);
	if (des_p->s_flags & s_MODELINE) {
		ModeLine(w, outbuf, linenum);
	} else if (des_p->s_id != NULL_DADDR) {
		char	*lptr;
		int	fromcol = W_NUMWIDTH(w);
#ifdef HIGHLIGHTING
		static struct LErange lr = {0, 0, LENULLPROC, US_effect};
		Mark	*mark = b_curmark(w->w_bufp);
		jbool	marked_line = (MarkHighlighting
# ifdef TERMCAP
			       && US != NULL
# endif
			       && mark != NULL
			       && mark->m_line == des_p->s_lp);
#endif /* HIGHLIGHTING */

		des_p->s_lp->l_dline &= ~DDIRTY;
		des_p->s_flags &= ~(s_DIRTY | s_L_MOD);

		if (w->w_flags & W_NUMLINES) {
			outbuf[fromcol-2] = ' ';
			swritef(outbuf, sizeof(outbuf), "%6D  ", des_p->s_vln);
			if (outbuf[fromcol-2] != ' ') {
			    outbuf[fromcol-2] = '*';
			    outbuf[fromcol-1] = ' ';
			}
		}
		if (des_p->s_offset != 0) {
			outbuf[fromcol++] = '!';
			outbuf[fromcol] = '\0';
		}
		lptr = lcontents(des_p->s_lp);
		DeTab(lptr, des_p->s_offset, outbuf + fromcol,
		      outbuf + CO, (w->w_flags & W_VISSPACE) != 0);
#ifdef HIGHLIGHTING
		if (marked_line) {
			lr.start = calc_pos(lptr, mark->m_char)
				- des_p->s_offset + fromcol;
			lr.width = 1;
			if (lr.start < sizeof(outbuf) - 1 && outbuf[lr.start] == '\0') {
				outbuf[lr.start] = ' ';
				outbuf[lr.start + 1] = '\0';
			}
		}
#endif /* HIGHLIGHTING */
#ifdef ID_CHAR
		/* REMIND: This code, along with the rest of the
		 * ID_CHAR, belongs in the screen driver for
		 * termcap based systems.  mac and pc's and other
		 * window-based drivers don't give a hoot about
		 * ID_CHAR.
		 */
		/* attempt to exploit insert or delete character capability
		 * but only if not highlighting some part of the line
		 */
		if (UseIC && Curline->s_effects == NOEFFECT
# ifdef HIGHLIGHTING
		&& !marked_line
# endif /* HIGHLIGHTING */
		) {
			if (IDchar(outbuf, linenum)) {
				/* success: clean up and go home */
				PhysScreen[linenum] = *des_p;
				return;
			}
			/* failure: re-initialize various cursors */
			i_set(linenum, 0);
		}
#endif /* ID_CHAR */

		if (swrite(outbuf,
#ifdef HIGHLIGHTING
			marked_line ? &lr : NOEFFECT,
#else
			NOEFFECT,
#endif
			YES))
		{
			do_cl_eol(linenum);
		} else {
			/* interrupted: mark indeterminate state */
			PhysScreen[linenum].s_id = NOWHERE_DADDR;
		}
	} else if (PhysScreen[linenum].s_id != NULL_DADDR) {
		/* not the same ... make sure */
		do_cl_eol(linenum);
	}
}

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

/* Expand tabs (and other funny characters) of a section of "buf"
 * into "outbuf".
 *
 * Note: outbuf must allow for at least PPWIDTH extra characters.
 * This is sufficient room for one extra character to be displayed,
 * streamlining the code.
 *
 * Note: the calc_pos, how_far, and DeTab must be in synch --
 * each thinks it knows how characters are displayed.
 */

private void
DeTab(char *src, int start_offset, char *dst, char *dst_limit, jbool visspace)
{
	ZXchar	c;
	int	offset = start_offset;

	/* At any time, the number of characters we've output is
	 * start_offset - offset.  This is needed to correctly
	 * calculate TABDIST() without having to add another
	 * variable (pos) to be incremented for each call to addc.
	 */
#define addc(ch) { if (--offset < 0) *dst++ = (ch); }

	while ((c = ZXC(*src++)) != '\0') {
		if (c == '\t' && tabstop != 0) {
			int	nchars = TABDIST(start_offset - offset);

			c = visspace? '>' : ' ';
			while (--nchars > 0 && dst < dst_limit) {
				addc(c);
				c = ' ';
			}
		} else if (jisprint(c)) {
			if (visspace && c == ' ')
				c = '_';
		} else {
			char	buf[PPWIDTH];
			char	*p;

			PPchar(c, buf);
			/* assert(buf[0] != '\0'); */
			for (p = buf; (c = *p++), *p != '\0'; )
				addc(c);
		}
		if (--offset < 0) {
			*dst++ = c;
			if (dst >= dst_limit) {
				/* we've run out of real estate: truncate and flag it */
				dst = dst_limit-1;
				*dst++ = '!';
				break;
			}
		}
	}
#undef	addc
	*dst = '\0';
}


#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.
 */
jbool	IN_INSmode = NO;

void
INSmode(jbool on)
{
	if (on != IN_INSmode) {
		putpad(on? IM : EI, 1);
		IN_INSmode = on;
	}
}

/* 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 jbool
IDchar(char *new, int lineno)
{
	int	col = 0;
	struct screenline
		*sline = &Screen[lineno];
	char	*old = sline->s_line;
	int	newlen = strlen(new);

	for (;;) {
		int	oldlen = sline->s_roof - old;
		int	i;

		for (; ; col++) {
			if (col == oldlen || col == newlen)
				return oldlen == newlen;	/* one ended; happy if both ended */

			if (old[col] != new[col])
				break;
		}

		/* col now is first difference, and not the end of either */

		/* see if an insertion will help */

		for (i = col + 1; i < newlen; i++) {
			if (new[i] == old[col]) {
				/* The number of saved characters is (roughly)
				 * the number of characters we can retain after
				 * the insertion, minus the number that we
				 * could have salvaged without moving them.
				 */
				int	NumSaved = IDcomp(new + i, old + col, oldlen-col)
						- NumSimilar(new + col, old + col, jmin(i, oldlen)-col);

				if (OkayInsert(NumSaved, i - col)) {
					InsChar(lineno, col, i - col, new);
					col = i;
					break;
				}
			}
		}
		if (i != newlen)
			continue;

		/* see if a deletion will help */

		for (i = col + 1; i < oldlen; i++) {
			if (new[col] == old[i]) {
				int	NumSaved = IDcomp(new + col, old + i, oldlen - i);

				if (OkayDelete(NumSaved, i - col, newlen == oldlen)) {
					DelChar(lineno, col, i - col);
					break;
				}
			}
		}
		if (i != oldlen)
			continue;

		return NO;
	}
}

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

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

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

	for (i = 0; i < len; i++) {
		char	c = *s++;

		if (c == '\0' || c != *t++)
			break;

		if (c != ' ')
			nonspace = 1;
		num += nonspace;
	}

	return num;
}

private jbool
OkayDelete(int Saved, int num, jbool samelength)
{
	/* If the old and the new have different lengths, then the competition
	 * will have to clear to end of line.  We take that into consideration.
	 * Note: we must avoid multiplying by INFINITY.
	 */
	return Saved + (samelength ? 0 : CElen) >
		jmin(MDClen, DC != NULL? DClen * num : INFINITY);
}

private jbool
OkayInsert(int Saved, int num)
{
# ifdef NCURSES_BUG
	/* Note: with the ncurses version of termcap/terminfo, we must use
	 * any one of insert mode, insert character, or multiple insert character.
	 */
	int	n = INFINITY;

	if (IC != NULL)		/* Per character prefixes */
		n = num * IClen;
	n = jmin(n, MIClen);
	n = jmin(n, IMEIlen);
# else /* !NCURSES_BUG */
	/* Note: the way termcap/terminfo is defined, we must use *both*
	 * IC and IM to insert, but normally only one will be defined.
	 * See terminfo(5), under the heading "Insert/Delete Character".
	 */
	int	n = 0;

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

	if (!IN_INSmode)
		n += IMEIlen;
# endif /* !NCURSES_BUG */
	n += num;	/* The characters themselves */

	return Saved > n;
}

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

	Placur(lineno, col);
	putmulti(DC, M_DC, num, 1);

	/* Shift the remaining characters left in the buffer to close the gap.
	 * byte_copy cannot be used since the destination overlaps the source.
	 */
	{
		char	*to = sp->s_line + col,
				*from = to + num;
		size_t	len = sp->s_roof - from;
		byte_move(from, to, len);
	}
	clrline(sp->s_roof - num, sp->s_roof);
	sp->s_roof -= num;
}

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

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

	if (sp2 > cursend) {
		i_set(lineno, CO - num - 1);
		cl_eol();
		sp2 = cursend;
	}
	Curline->s_roof = 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);

# ifdef NCURSES_BUG
	/* Note: with the ncurses version of termcap/terminfo, we must use
	 * any one of insert mode, insert character, or multiple insert character.
	 */
	if (IN_INSmode
	|| ((IC == NULL || IMEIlen < num * IClen) && IMEIlen < MIClen))
		INSmode(YES);
	else
		putmulti(IC, M_IC, num, 1);
# else /* !NCURSES_BUG */
	/* Note: the way termcap/terminfo is defined, we must use *both*
	 * IC and IM, but normally only one will be defined.
	 * See terminfo(5), under the heading "Insert/Delete Character".
	 */
	if (IC != NULL)
		putmulti(IC, M_IC, num, 1);
	if (IM != NULL)
		INSmode(YES);
# endif /* !NCURSES_BUG */

	for (i = 0; i < num; i++) {
		scr_putchar(new[i]);
		if (IN_INSmode)
			putpad(IP, 1);
	}
	CapCol += num;
}

#endif /* ID_CHAR */

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

/* chkmail() returns YES if there is new mail since the
 * last time we checked.
 */
char	Mailbox[FILESIZE];	/* VAR: mailbox name */
int	MailInt = 60;		/* VAR: mail check interval (seconds) */

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

	if (MailInt == 0 || Mailbox[0] == '\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;
jbool	BriteMode = YES;		/* VAR: make the mode line inverse? */

private void
mode_app(const char *str)
{
	ZXchar	c;

	while (mode_p < mend_p && (c = ZXC(*str++)) != '\0') {
		/* don't expand tabs: treat them as suspects */
		if (jisprint(c)) {
			*mode_p++ = c;
		} else {
			char	buf[PPWIDTH];

			PPchar(c, buf);
			mode_app(buf);
		}
	}

}

/* VAR: mode line format string */
char	ModeFmt[MAXCOLS] = "%[Jove%]%w%w%c(%M)%3c[%b:%n]%2c\"%f\"%c%i# %m*-%2c%p%2s%(%d%e(%t)%)";

private void
ModeLine(Window *w, char *line, int linenum)
{
	int	n,
		glue = 0;
	jbool	ign_some = NO,
		td = NO;	/* is time (kludge: or mail status) displayed? */
	char	*fmt = ModeFmt,
		fillc,
		c;
	Buffer	*thisbuf = w->w_bufp,
		*bp;
	LineEffects
		highlighting;

	mode_p = line;
	mend_p = &line[CO - 1];

#ifdef TERMCAP
	if (SO == NULL)
		BriteMode = NO;	/* we can't do it */
#endif
	/* ??? On Mac, perhaps '_' looks better than '-' */
	fillc = BriteMode? ' ' : '-';

	while ((c = *fmt++)!='\0' && mode_p<mend_p) {
		if (c != '%') {
			if (c == '\\')
				if ((c = *fmt++) == '\0')
					break;

			if (!ign_some) {
				static char	x[] = "x";

				x[0] = c;
				mode_app(x);
			}
			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 '%':
			mode_app("%");
			break;

		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 */
			td = YES;	/* kludge: reflect old behaviour where alarm could trigger mail check */
			if (chkmail(NO))
				mode_app("[New mail]");
			break;
#endif /* UNIX */

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

			/*
			 * Minor mode names clue to the command needed * to turn
			 * them off
			 */
			static const char	*const minname[] = {
				" AuFill",
				" WoAbbr",
				" OvWrt",
				" AuInd",
				" ReadO",
				" ShoMat",
#ifdef IPROCS
				" DBX",
#endif
			};

			int minors = thisbuf->b_minor;
			const char  *const *p;

			mode_app(majname[thisbuf->b_major]);

			for (p = minname; minors != 0; p++) {
				if (minors & 01)
					mode_app(*p);
				minors >>= 1;
			}

			if (InMacDefine)
				mode_app(" Def");
			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(jbasename(thisbuf->b_fname));
			}
			break;

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

			*mode_p++ = w->w_bufp->b_diverged ? yea : nay;
			break;
			}

		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;
			}

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

				swritef(tmp, sizeof(tmp), "(%s)", pstate(thisbuf->b_process));
				mode_app(tmp);
			}
#endif
			break;
		case 's':
			if (mode_p[-1] != ' ')
				*mode_p++ = ' ';
			break;

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

			td = YES;
			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) {
		/* 1 space unused, plus padding for magic cookies */
		char	*to = &line[CO - 1 - (4 * SG)],
			*from = mode_p;

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

				glue--;
				*to = fillc;
				while (--portion >= 0)
					*--to = fillc;
			}
		}
	} else {
		while (mode_p < &line[CO - 1 - (4 * SG)])
			*mode_p++ = fillc;
	}

	*mode_p = '\0';

	/* Highlight mode line. */
	highlighting = NOEFFECT;
	if (BriteMode) {
		highlighting = WindowRange(w);
#ifdef HIGHLIGHTING
		{
			char
				*p = &line[highlighting->start],
				*e = p + highlighting->width;

			for (; p != e; p++)
				if (*p == ' ')
					*p = '-';
		}
#endif
	}
	if (w->w_next == fwind && TimeDisplayed != td) {
		TimeDisplayed = td;
#ifdef UNIX
		SetClockAlarm(YES);
#endif
	}
#ifdef ID_CHAR
	INSmode(NO);
#endif
	if (swrite(line, highlighting, YES))
		do_cl_eol(linenum);
	else
		UpdModLine = YES;
}

/* 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(void)
{
	int	line;
	LinePtr	newtop = prev_line((curwind->w_line = curline),
				arg_or_default(WSIZE(curwind)/2));

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

void
ClAndRedraw(void)
{
	cl_scr(YES);
}

void
NextPage(void)
{
	LinePtr	newline;

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

void
PrevPage(void)
{
	LinePtr	newline;

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

void
UpScroll(void)
{
	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(void)
{
	SetTop(curwind, prev_line(curwind->w_top, arg_value()));
	if (curwind->w_bufp == curbuf
	&& in_window(curwind, curline) == -1)
		SetLine(curwind->w_top);
}

jbool	VisBell = NO;	/* VAR: use visible bell (if possible) */

void
rbell(void)
{
	RingBell = YES;
}

/* Message prints the null terminated string onto the bottom line of the
 * terminal.
 */
void
message(const char *str)
{
	if (InJoverc)
		return;

	UpdMesg = YES;
	stickymsg = NO;
	if (str != mesgbuf)
		truncstr(mesgbuf, str);
}

/* End of Window */

void
Eow(void)
{
	if (Asking)
		return;

	SetLine(next_line(curwind->w_top, WSIZE(curwind) - 1 -
			jmin(WSIZE(curwind) - 1, arg_value() - 1)));
	if (!is_an_arg())
		Eol();
}

/* Beginning of Window */

void
Bow(void)
{
	if (Asking)
		return;

	SetLine(next_line(curwind->w_top, jmin(WSIZE(curwind) - 1, arg_value() - 1)));
}

/* Typeout Mechanism */

jbool	UseBuffers = NO,	/* VAR: use buffers with Typeout() */
	TOabort = NO;

private int	LineNo;	/* screen line for Typeout (if not UseBuffers) */

private Window	*old_wind;	/* curwind before preempted by typeout to buffer */

/* 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(const char *name)
{
	if (UseBuffers) {
		old_wind = curwind;
		pop_wind(name, YES, B_SCRATCH);
	} else
		DisabledRedisplay = YES;
	TOabort = NO;
	LineNo = 0;
}

private void
TOlineFits(char *s)
{
	i_set(LineNo, 0);
	(void) swrite(s, NOEFFECT, NO);
	PhysScreen[LineNo].s_id = NOWHERE_DADDR;
	cl_eol();
	flushscreen();
}

private void
TOprompt(char *s)
{
	if (!TOabort) {
		ZXchar	c;

		TOlineFits(s);
		c = kbd_getch();
		TOlineFits("");
		if (c != ' ') {
			TOabort = YES;
			if (c != AbortChar)
				kbd_ungetch(c);
		}
	}
}

void
Typeout(const char *fmt, ...)
{
	char	string[MAX_TYPEOUT+1];
	va_list	ap;

	va_start(ap, fmt);
	format(string, sizeof string, fmt, ap);
	va_end(ap);
	if (UseBuffers) {
		ins_str(string);
		ins_str("\n");
	} else {
		char	outbuf[MAXCOLS + PPWIDTH];	/* assert(CO <= MAXCOLS); */

		if (LineNo == ILI - 2) {
			TOprompt("--more--");
			LineNo = 0;
		}
		if (!TOabort) {
			DeTab(string, 0, outbuf, outbuf + CO, NO);
			TOlineFits(outbuf);
			LineNo += 1;
		}
	}
}

void
TOstop(void)
{
	if (UseBuffers) {
		ToFirst();
		SetWind(old_wind);
	} else {
		TOprompt("--end--");
		DisabledRedisplay = NO;
	}
}
