/*
 * The functions in this file handle redisplay. There are two halves, the
 * ones that update the virtual display screen, and the ones that make the
 * physical display screen the same as the virtual display screen. These
 * functions use hints that are left in the windows by the commands.
 *
 *
 * $Header: /usr/build/vile/vile/RCS/display.c,v 1.259 1998/10/03 01:40:37 tom Exp $
 *
 */

#include	"estruct.h"
#include        "edef.h"
#include        "pscreen.h"

#define	NU_WIDTH 8

#define	MRK_EMPTY        '~'
#define	MRK_EXTEND_LEFT  '<'
#define	MRK_EXTEND_RIGHT '>'

VIDEO	**vscreen;			/* Virtual screen. */
VIDEO	**pscreen;			/* Physical screen. */
#if	MEMMAP
#define PSCREEN vscreen
#else
#define PSCREEN pscreen
#endif
static	int *lmap;

#if DISP_IBMPC
#define PScreen(n) scread((VIDEO *)0,n)
#else
#define	PScreen(n) pscreen[n]
#endif

#if OPT_SCROLLCODE && (DISP_IBMPC || !MEMMAP)
#define CAN_SCROLL 1
#else
#define CAN_SCROLL 0
#endif

static	int	i_displayed;		/* false until we're in screen-mode */
static	int	im_displaying;		/* flag set during screen updates */
static	int	mpresf;			/* zero if message-line empty */
#if OPT_WORKING
static	int	im_timing;
#endif

/*
 * MARK2COL may be greater than mark2col if the mark does not point to the end
 * of the line, and if it points to a nonprinting character.  We use that value
 * when setting visible attributes, to keep tabs and other nonprinting
 * characters looking 'right'.
 */
#define MARK2COL(wp, mk)  offs2col(wp, mk.l, mk.o + 1) - 1
#define mark2col(wp, mk)  offs2col(wp, mk.l, mk.o)

#ifdef WMDLINEWRAP
#define TopRow(wp) (wp)->w_toprow + (wp)->w_line.o
static	int	allow_wrap;
#else
#define TopRow(wp) (wp)->w_toprow
#endif

/* for window size changes */
static	int chg_width, chg_height;

/******************************************************************************/

typedef	void	(*OutFunc) (int c);

static	OutFunc	dfoutfn;

static	int	endofline(char *s, int n);
static	int	texttest (int vrow, int prow);
static	int	updext_before(int col);
static	int	updext_past(int col, int excess);
static	int	updpos(int *screenrowp, int *screencolp);
static	int	vtalloc (void);
static	void	l_to_vline(WINDOW *wp, LINEPTR lp, int sline);
static	void	mlmsg(const char *fmt, va_list *app);
static	void	modeline(WINDOW *wp);
static	void	reframe(WINDOW *wp);
static	void	scrscroll(int from, int to, int count);
static	void	updall (WINDOW *wp);
static	void	updateline (int row, int colfrom, int colto);
static	void	upddex (void);
static	void	updgar (void);
static	void	updone (WINDOW *wp);
static	void	updupd (int force);
static	void	vtlistc (int c);

#if OPT_VIDEO_ATTRS
static	void	updattrs(WINDOW *wp);
#endif

#if	OPT_UPBUFF
static	void	recompute_buffer(BUFFER *bp);
#endif

#if CAN_SCROLL
static	int	scrolls (int inserts);
#endif

/*--------------------------------------------------------------------------*/

/*
 * Format a number, right-justified, returning a pointer to the formatted
 * buffer.
 */
static char *
right_num (char *buffer, int len, long value)
{
	char	temp[NSTRING];
	register char	*p = lsprintf(temp, "%D", value);
	register char	*q = buffer + len;

	*q = EOS;
	while (q != buffer)
		*(--q) = (p != temp) ? *(--p) : ' ';
	return buffer;
}

/*
 * Do format a string.
 */
static int
dfputsn(OutFunc outfunc, const char *s, int n)
{
	register int c = 0;
	register int l = 0;
	TRACE(("...str=%.*s\n", n > 0 ? n : (int)strlen(s), s))
	while ((n-- != 0) && ((c = *s++) != EOS)) {
		(*outfunc)(c);
		l++;
	}
	return l;
}

/* as above, but uses null-terminated string's length */
static int
dfputs(OutFunc outfunc, const char *s)
{
	return dfputsn(outfunc, s, -1);
}

/*
 * Do format an integer, in the specified radix.
 */
#define vMAXINT ((int)((unsigned)(~0)>>1))	/* 0x7fffffff */
#define vMAXNEG (-vMAXINT)			/* 0x80000001 */
static int
dfputi(OutFunc outfunc, int i, int r)
{
	register int q;

	TRACE(("...int=%d\n", i))
	if (i < 0) {
		if (i < vMAXNEG) {
			return dfputs(outfunc,"OVFL");
		}
		(*outfunc)('-');
		return dfputi(outfunc, -i, r) + 1;
	}

	q = (i >= r) ? dfputi(outfunc, i/r, r) : 0;

	(*outfunc)(hexdigits[i%r]);
	return q + 1; /* number of digits printed */
}

/*
 * do the same except as a long integer.
 */
static int
dfputli(OutFunc outfunc, long l, int r)
{
	register int q;

	TRACE(("...long=%ld\n", l))
	if (l < 0) {
		(*outfunc)('-');
		return dfputli(outfunc, -l, r) + 1;
	}

	q = (l >= r) ? dfputli(outfunc, (long)(l/r), r) : 0;

	return q + dfputi(outfunc, (int)(l%r), r);
}

/*
 *	Do format a scaled integer with two decimal places
 */
static int
dfputf(OutFunc outfunc, int s)
{
	register int i;	/* integer portion of number */
	register int f;	/* fractional portion of number */

	/* break it up */
	i = s / 100;
	f = s % 100;

	/* send out the integer portion */
	i = dfputi(outfunc, i, 10);
	(*outfunc)('.');
	(*outfunc)((f / 10) + '0');
	(*outfunc)((f % 10) + '0');
	return i + 3;
}

/*
 * Generic string formatter.  Takes printf-like args, and calls
 * the global function (*dfoutfn)(c) for each c
 */
static void
dofmt(const char *fmt, va_list *app)
{
	register int c;		/* current char in format string */
	register int wid;
	register int n;
	register int nchars = 0;
	int islong;
	int radix;
	OutFunc outfunc = dfoutfn;  /* local copy, for recursion */

	TRACE(("dofmt fmt='%s'\n", fmt))
	while ((c = *fmt++) != 0 ) {
		if (c != '%') {
			(*outfunc)(c);
			nchars++;
			continue;
		}
		c = *fmt++;
		wid = 0;
		islong = FALSE;
		if (c == '*') {
			wid = va_arg(*app,int);
			c = *fmt++;
		} else while (isDigit(c)) {
			wid = (wid * 10) + c - '0';
			c = *fmt++;
		}
		if (c == 'l') {
			islong = TRUE;
			c = *fmt++;
		}
		switch (c) {
			case EOS:
				n = 0;
				break;
			case 'c':
				(*outfunc)(va_arg(*app,int));
				n = 1;
				break;

			case 'd':
				if (!islong) {
					n = dfputi(outfunc, va_arg(*app,int), 10);
					break;
				}
				/* FALLTHROUGH */
			case 'D':
				n = dfputli(outfunc, va_arg(*app,long), 10);
				break;

			case 'o':
				n = dfputi(outfunc, va_arg(*app,int), 8);
				break;

			case 'x':
				if (!islong) {
					n = dfputi(outfunc, va_arg(*app,int), 16);
					break;
				}
				/* FALLTHROUGH */
			case 'X':
				n = dfputli(outfunc, va_arg(*app,long), 16);
				break;

			case 'r':
			case 'R':
				radix = va_arg(*app, int);
				if (radix < 2 || radix > 36) radix = 10;
				if (islong || c == 'R')
					n = dfputli(outfunc,
						va_arg(*app,long), radix);
				else
					n = dfputi(outfunc,
						va_arg(*app,int), radix);
				break;

			case 's':
				n = dfputs(outfunc, va_arg(*app,char *));
				break;

			case 'S': /* use wid as max width */
				n = dfputsn(outfunc, va_arg(*app,char *),wid);
				break;

			case 'f':
				n = dfputf(outfunc, va_arg(*app,int));
				break;

			case 'P': /* output padding -- pads total output to
					"wid" chars, using c as the pad char */
				wid -= nchars;
				/* FALLTHROUGH */

			case 'p': /* field padding -- puts out "wid"
					copies of c */
				n = 0;
				c = va_arg(*app,int);
				while (n < wid) {
					(*outfunc)(c);
					n++;
				}
				break;

			default:
				(*outfunc)(c);
				n = 1;
		}
		wid -= n;
		nchars += n;
		while (wid-- > 0) {
			(*outfunc)(' ');
			nchars++;
		}
	}

}

/******************************************************************************/

/*
 * Line-number mode
 */
int
nu_width(WINDOW *wp)
{
	return w_val(wp,WMDNUMBER) ? NU_WIDTH : 0;
}

int
col_limit(WINDOW *wp)
{
#ifdef WMDLINEWRAP
	if (w_val(wp,WMDLINEWRAP))
		return curcol + 1;	/* effectively unlimited */
#endif
	return term.t_ncol - 1 - nu_width(wp);
}

/*
 * Initialize the data structures used by the display code. The edge vectors
 * used to access the screens are set up.
 */
int
vtinit(void)
{
    register int i;
    register VIDEO *vp;

#if	OPT_MLFORMAT
    if (!modeline_format)
    	modeline_format = strmalloc(
	    "%-%i%- %b %m:: :%f:is : :%=%F: : :%l:(:,:%c::) :%p::% :%S%-%-%|"
	);
#endif

    /* allocate new display memory */
    if (vtalloc() == FALSE) /* if we fail, only serious if not a realloc */
    	return (vscreen != NULL);

    for (i = 0; i < term.t_mrow; ++i) {
	vp = vscreen[i];
	vp->v_flag = 0;
#if OPT_COLOR
	ReqFcolor(vp) = gfcolor;
	ReqBcolor(vp) = gbcolor;
#endif
    }
#if OPT_WORKING
    if (!i_displayed && !im_displaying)
	imworking(0);
#endif
    return TRUE;
}

#if OPT_VIDEO_ATTRS
static void
set_vattrs(int row, int col, VIDEO_ATTR attr, size_t len)
{
	while (len--)
		vscreen[row]->v_attrs[col++] = attr;
}
#else
#define set_vattrs(row, col, attr, len) /*nothing*/
#endif

static void
freeVIDEO(register VIDEO *vp)
{
	if (vp != 0) {
#if OPT_VIDEO_ATTRS
		FreeIfNeeded (vp->v_attrs);
#endif
		free((char *)vp);
	}
}

int
video_alloc(VIDEO **vpp)
{
	register VIDEO *vp;
	/* struct VIDEO already has 4 of the bytes */
	vp = typeallocplus(VIDEO, term.t_mcol - 4);
	if (vp == 0)
		return FALSE;
	(void)memset((char *)vp, 0, sizeof(VIDEO) + term.t_mcol - 4);

#if OPT_VIDEO_ATTRS
	vp->v_attrs = typecallocn(VIDEO_ATTR, (ALLOC_T)term.t_mcol);
	if (vp->v_attrs == 0) {
		free((char *)vp);
		return FALSE;
	}
#endif
	freeVIDEO(*vpp);
	*vpp = vp;
	return TRUE;
}

static int
vtalloc(void)
{
	register int i, first;
	static int vcols, vrows;

	if (term.t_mrow > vrows) {
		GROW(vscreen, VIDEO *, vrows, term.t_mrow);
#if	! MEMMAP
		GROW(pscreen, VIDEO *, vrows, term.t_mrow);
#endif
		GROW(lmap, int, vrows, term.t_mrow);
	} else {
		for (i = term.t_mrow; i < vrows; i++) {
			freeVIDEO(vscreen[i]);
#if	! MEMMAP
			freeVIDEO(pscreen[i]);
#endif
		}
	}

	first = (term.t_mcol > vcols) ? 0 : vrows;

	for (i = first; i < term.t_mrow; ++i) {
		if (!video_alloc(&vscreen[i]))
			return FALSE;
#if	! MEMMAP
		if (!video_alloc(&pscreen[i]))
			return FALSE;
#endif	/* !MEMMAP */
	}
	vcols = term.t_mcol;
	vrows = term.t_mrow;

	return TRUE;
}

/* free all video memory, in anticipation of a (growing) resize */
#if NO_LEAKS
static void
vtfree(void)
{
	register int i;

	if (vscreen) {
		for (i = 0; i < term.t_mrow; ++i) {
			freeVIDEO(vscreen[i]);
		}
		free ((char *)vscreen);
		vscreen = 0;
	}

#if	! MEMMAP
	if (pscreen) {
		for (i = 0; i < term.t_mrow; ++i) {
			freeVIDEO(pscreen[i]);
		}
		free ((char *)pscreen);
		pscreen = 0;
	}
#endif
	FreeIfNeeded (lmap);
}
#endif


/*
 * Set the virtual cursor to the specified row and column on the virtual
 * screen. There is no checking for nonsense values.
 */
static void
vtmove(int row, int col)
{
    vtrow = row;
    vtcol = col;
}

/* Write a character to the virtual screen. The virtual row and
   column are updated. If we are not yet on left edge, don't print
   it yet. If the line is too long put a ">" in the last column.
   This routine only puts printing characters into the virtual
   terminal buffers. Only column overflow is checked.
*/

static void
vtputc(int c)
{
	/* since we don't allow wrapping on the message line, we only need
	 * to evaluate this once.  */
	int lastcol = vtrow == term.t_nrow-1 ?  term.t_ncol-1 : term.t_ncol;
	register VIDEO *vp;	/* ptr to line being updated */

#ifdef WMDLINEWRAP
	if (vtrow < 0) {
		static VIDEO *fake_line;
		static int   length;
		if (length != term.t_mcol || fake_line == 0) {
			if (!video_alloc(&fake_line))
				return;
			length = term.t_mcol;
		}
		vp = fake_line;
	}
	else
#endif
	 vp = vscreen[vtrow];

	if (isPrint(c) && vtcol >= 0 && vtcol < lastcol) {
		VideoText(vp)[vtcol++] = (c & (N_chars-1));
#ifdef WMDLINEWRAP
		if ((allow_wrap != 0)
		 && (vtcol == lastcol)
		 && (vtrow <  allow_wrap)) {
			vtcol = 0;
			if (++vtrow >= 0)
				vscreen[vtrow]->v_flag |= VFCHG;
			taboff += lastcol;
		}
#endif
		return;
	}

	if (vtcol >= lastcol) {
		VideoText(vp)[lastcol - 1] = MRK_EXTEND_RIGHT;
	} else if (c == '\t') {
		do {
			vtputc(' ');
		} while (((vtcol + taboff)%curtabval) != 0
		          && vtcol < lastcol);
	} else if (c == '\n') {
		return;
	} else if (isPrint(c)) {
		++vtcol;
	} else {
		vtlistc(c);
	}
}

/* how should high-bit unprintable chars be shown? */
static int vt_octal;

/* shows non-printing character */
static void
vtlistc(int c)
{
	if (isPrint(c)) {
	    vtputc(c);
	    return;
	}

	if (c & HIGHBIT) {
	    vtputc('\\');
	    if (vt_octal) {
		vtputc(((c>>6)&3)+'0');
		vtputc(((c>>3)&7)+'0');
		vtputc(((c   )&7)+'0');
	    } else {
		vtputc('x');
		vtputc(hexdigits[(c>>4) & 0xf]);
		vtputc(hexdigits[(c   ) & 0xf]);
	    }
	} else {
	    vtputc('^');
	    vtputc(toalpha(c));
	}
}

static int
vtgetc(int col)
{
	return vscreen[vtrow]->v_text[col];
}

static void
vtputsn(const char *s, int n)
{
	int c = 0;
	while (n-- > 0 && (c = *s++) != EOS)
		vtputc(c);
}

/*
 * Write a line to the screen at the current video coordinates, allowing for
 * line-wrap or right-shifting.
 */
static void
vtset(LINEPTR lp, WINDOW *wp)
{
	register char *from;
	register int n = llength(lp);
	BUFFER	*bp  = wp->w_bufp;
	int	skip = -vtcol,
		list = w_val(wp,WMDLIST);

	vt_octal = w_val(wp,WMDNONPRINTOCTAL);

#ifdef WMDLINEWRAP
	/*
	 * If the window's offset is negative, we've got a case of linewrap
	 * where the line's beginning is forced before the beginning of the
	 * window.
	 */
	if (wp->w_line.o < 0) {
		vtrow -= wp->w_line.o;
		skip = col2offs(wp, lp, -(wp->w_line.o * term.t_ncol));
		n -= skip;
	}
	else
#endif
	if (w_val(wp,WMDNUMBER)) {
		register int j, k, jk;
		L_NUM	line = line_no(bp, lp);
		int	fill = ' ';
		char	temp[NU_WIDTH+2];

		vtcol = 0;	/* make sure we always see line numbers */
		vtputsn(right_num(temp, NU_WIDTH-2, (long)line), NU_WIDTH-2);
		vtputsn("  ", 2);
		taboff = skip - vtcol;

		/* account for leading fill; this repeats logic in vtputc so
		 * I don't have to introduce a global variable... */
		from = lp->l_text;
		for (j = k = jk = 0; (j < n) && (k < skip); j++) {
			register int	c = from[j];
			if ((list || (c != '\t')) && !isPrint(c)) {
			    	if (c & HIGHBIT) {
				    k += 4;
				    fill = '\\';  /* FIXXXX */
				} else {
				    k += 2;
				    fill = toalpha(c);
				}
			} else {
				if (c == '\t')
					k += (curtabval - (k % curtabval));
				else if (isPrint(c))
					k++;
				fill = ' ';
			}
			jk = j+1;
		}
		while (k-- > skip)
			vtputc(fill);
		if ((skip = jk) < 0)
			skip = 0;
		n -= skip;
	} else
		skip = 0;

#if OPT_B_LIMITS
	taboff -= w_left_margin(wp);
#endif
	from = lp->l_text + skip;
#ifdef WMDLINEWRAP
	allow_wrap = w_val(wp,WMDLINEWRAP) ? mode_row(wp)-1 : 0;
#endif

	/*
	 * The loops below are split up for a reason - there's a hidden side effect
	 * which makes the final increment of vtcol inconsistent, so it won't
	 * terminate until the end of the line.  For very long lines, that's a
	 * performance hit.
	 */
#define VTSET_PUT(ch,count) if(list) vtlistc(ch); else vtputc(ch); count
#ifdef WMDLINEWRAP
	if (w_val(wp,WMDLINEWRAP))
	{
		while ((vtcol < term.t_ncol)
		  &&   ((vtrow == term.t_nrow-1) || (vtrow < mode_row(wp)))
		  &&   (n > 0)) {
			VTSET_PUT(*from++,n--);
		}
		if ((vtcol <= term.t_ncol)
		  &&   ((vtrow == term.t_nrow-1) || (vtrow < mode_row(wp)))
		  &&   (n > 0)) {
			VTSET_PUT(*from++,n--);
		}
	}
	else
#endif
	{
		while ((vtcol < term.t_ncol)
		  &&   (n > 0)) {
			VTSET_PUT(*from++,n--);
		}
		if ((vtcol <= term.t_ncol)
		  &&   (n > 0)) {
			VTSET_PUT(*from++,n--);
		}
	}

	/* Display a "^J" if 'list' mode is active, unless we've suppressed
	 * it for some reason.
	 */
	if (list && (n >= 0)) {
		if (b_is_scratch(bp) && listrimmed(lp))
			/*EMPTY*/;
		else if (!b_val(bp,MDNEWLINE) && (lforw(lp) == buf_head(bp)))
			/*EMPTY*/;
		else
			vtlistc('\n');
	}
#ifdef WMDLINEWRAP
	allow_wrap = 0;
#endif
}

/*
 * Erase from the end of the software cursor to the end of the line on which
 * the software cursor is located.
 */
static void
vteeol(void)
{
	if (vtcol < term.t_ncol) {
	    int n = (vtcol >= 0) ? vtcol : 0;
#ifdef WMDLINEWRAP
	    if (vtrow >= 0)
#endif
		if (n >= 0) {
			(void)memset(&vscreen[vtrow]->v_text[n],
				' ', (SIZE_T)(term.t_ncol-n));
		}
		vtcol = term.t_ncol;
	}
}

/* upscreen:	user routine to force a screen update
		always finishes complete update		*/
#if !SMALLER
/* ARGSUSED */
int
upscreen(int f GCC_UNUSED, int n GCC_UNUSED)
{
	return update(TRUE);
}
#endif

static	UINT	scrflags;

/* line to virtual column */
int
mk_to_vcol (MARK mark, int expanded, int base)
{
	int	col = 0;
	int	i = base;
	int	c;
	int	lim;
	LINEPTR lp;

	lp = mark.l;
	lim = mark.o
		+ ((!global_g_val(GMDALTTABPOS) && !insertmode) ? 1 : 0);
	if (lim > llength(lp))
		lim = llength(lp);

	while (i < lim) {
		c = lgetc(lp, i++);
		if (c == '\t' && !expanded) {
			col += curtabval - (col%curtabval);
		} else {
			if (!isPrint(c)) {
				col += (c & HIGHBIT) ? 3 : 1;
			}
			++col;
		}

	}
	col += base;
	if (!global_g_val(GMDALTTABPOS) && !insertmode &&
			col != 0 && mark.o < llength(lp))
		col--;
	return col;
}

void
kbd_openup(void)
{
#if !MEMMAP && !OPT_PSCREEN
	int i;
	size_t alen = sizeof(VIDEO_ATTR) * term.t_ncol;
#endif
	kbd_flush();
	bottomleft();
	TTputc('\n');
	TTputc('\r');
	TTflush();
#if !MEMMAP && !OPT_PSCREEN
	if (pscreen != 0) {
		for (i = 0; i < term.t_nrow-1; ++i) {
			(void)memcpy(
				pscreen[i]->v_text,
				pscreen[i+1]->v_text,
				(SIZE_T)(term.t_ncol));
#if OPT_VIDEO_ATTRS
			(void)memcpy(
				pscreen[i]->v_attrs,
				pscreen[i+1]->v_attrs,
				alen);
#endif
		}
		(void)memset(pscreen[i]->v_text, ' ', (SIZE_T)(term.t_ncol));
#if OPT_VIDEO_ATTRS
		(void)memset(pscreen[i]->v_attrs, VADIRTY, alen);
#endif
	}
#endif
}

/* cannot be allocated since it's used by OPT_RAMSIZE */
static char my_overlay[20];

/* save/erase text for the overlay on the message line */
void
kbd_overlay(const char *s)
{
	my_overlay[0] = EOS;
	if ((mpresf = (s != 0 && *s != EOS)) != 0) {
		strncpy(my_overlay, s, sizeof(my_overlay)-1);
	}
}

void
kbd_flush(void)
{
	int ok;

	beginDisplay();
	if (vscreen != 0) {
		int row = term.t_nrow - 1;

		vtmove(row, -w_val(wminip,WVAL_SIDEWAYS));

		ok = (wminip != 0
		   && wminip->w_dot.l != 0
		   && wminip->w_dot.l->l_text != 0);
		if (ok) {
			TRACE(("SHOW:%2d:%.*s\n",
				llength(wminip->w_dot.l),
				llength(wminip->w_dot.l),
				wminip->w_dot.l->l_text));
			lsettrimmed(wminip->w_dot.l);
			vtset(wminip->w_dot.l, wminip);
		}

		vteeol();
		set_vattrs(row, 0,
			(VIDEO_ATTR) (miniedit
					? global_g_val(GVAL_MINI_HILITE)
					: 0),
			term.t_ncol);
		if (my_overlay[0] != EOS) {
			int n = term.t_ncol - strlen(my_overlay) - 1;
			if (n > 0) {
				(void)memcpy(&vscreen[row]->v_text[n],
					my_overlay,
					strlen(my_overlay));
			}
		}
		vscreen[row]->v_flag |= VFCHG;
		updateline(row, 0, term.t_ncol);
		if (ok)
			movecursor(row,
				offs2col(wminip, wminip->w_dot.l, wminip->w_dot.o));
	}
	TTflush();
	endofDisplay();
}

static int
TypeAhead(int force)
{
	if (force != TRUE) {
		if ((force == FALSE && !global_g_val(GMDSMOOTH_SCROLL))
		 || (force == SORTOFTRUE))
			return (keystroke_avail());
	}
	return FALSE;
}

/*
 * Make sure that the display is right. This is a three part process. First,
 * scan through all of the windows looking for dirty ones. Check the framing,
 * and refresh the screen. Second, make sure that "currow" and "curcol" are
 * correct for the current window. Third, make the virtual and physical
 * screens the same.
 */
int
update(
int force)	/* force update past type ahead? */
{
	register WINDOW *wp;
	int origrow, origcol;
	int screenrow, screencol;

	/* Get row and column prior to doing the update in case we are
	 * reading the message line.
	 */
	origrow = ttrow;
	origcol = ttcol;

	if (!curbp || !vscreen) /* not initialized */
		return FALSE;
	if (TypeAhead(force))
		return SORTOFTRUE;
#if	OPT_VISIBLE_MACROS == 0
	if (force == FALSE && kbd_replaying(TRUE) && (get_recorded_char(FALSE) != -1))
		return SORTOFTRUE;
#endif

	beginDisplay();

	/* first, propagate mode line changes to all instances of
		a buffer displayed in more than one window */
	for_each_visible_window(wp) {
		if (wp->w_flag & WFMODE) {
			if (wp->w_bufp->b_nwnd > 1) {
				/* make sure all previous windows have this */
				register WINDOW *owp;
				for_each_visible_window(owp)
					if (owp->w_bufp == wp->w_bufp)
						owp->w_flag |= WFMODE;
			}
		}
	}

	/* look for scratch-buffers that should be recomputed.  */
#if	OPT_UPBUFF
	for_each_visible_window(wp)
		if (b_is_obsolete(wp->w_bufp))
			recompute_buffer(wp->w_bufp);
#endif

	/* look for windows that need the ruler updated */
#ifdef WMDRULER
	for_each_visible_window(wp) {
		if (w_val(wp,WMDRULER)) {
			int	line  = line_no(wp->w_bufp, wp->w_dot.l);
			int	col   = mk_to_vcol(wp->w_dot, w_val(wp,WMDLIST), 0) + 1;

			if (line != wp->w_ruler_line
			 || col  != wp->w_ruler_col) {
				wp->w_ruler_line = line;
				wp->w_ruler_col  = col;
				wp->w_flag |= WFMODE;
			}
		} else if (wp->w_flag & WFSTAT) {
			wp->w_flag |= WFMODE;
		}
		wp->w_flag &= ~WFSTAT;
	}
#endif

	do {
		/* update any windows that need refreshing */
		for_each_visible_window(wp) {
			if (wp->w_flag) {
				curtabval = tabstop_val(wp->w_bufp);
				/* if the window has changed, service it */
				reframe(wp);	/* check the framing */
				if (wp->w_flag & (WFKILLS|WFINS)) {
					scrflags |= (wp->w_flag & (WFINS|WFKILLS));
					wp->w_flag &= ~(WFKILLS|WFINS);
				}
				if ((wp->w_flag & ~(WFMODE)) == WFEDIT)
					updone(wp);	/* update EDITed line */
				else if (wp->w_flag & ~(WFMOVE))
					updall(wp);	/* update all lines */
#if OPT_SCROLLBARS
				if (wp->w_flag & (WFHARD | WFSBAR))
					gui_update_scrollbar(wp);
#endif /* OPT_SCROLLBARS */

#if OPT_VIDEO_ATTRS
				if (wp->w_flag & (WFHARD | WFEDIT))
					updattrs(wp);
#endif
				if (scrflags || (wp->w_flag & (WFMODE|WFCOLR)))
					modeline(wp);	/* update modeline */
				wp->w_flag = 0;
				wp->w_force = 0;
			}
		}
		curtabval = tabstop_val(curbp);

	/* Recalculate the current hardware cursor location.  If true, we've
	 * done a horizontal scroll.
	 */
	} while (updpos(&screenrow, &screencol));

	/* check for lines to de-extend */
	upddex();

	/* if screen is garbage, re-plot it */
	if (sgarbf)
		updgar();

	/* update the virtual screen to the physical screen */
	updupd(force);

	/* update the cursor and flush the buffers */
	if (reading_msg_line)
	    movecursor(origrow, origcol);
	else
	    movecursor(screenrow, screencol);

	TTflush();
	endofDisplay();
	i_displayed = TRUE;

	while (chg_width || chg_height)
		newscreensize(chg_height,chg_width);
	return(TRUE);
}

/*	reframe:	check to see if the cursor is on in the window
			and re-frame it if needed or wanted		*/
static void
reframe(WINDOW *wp)
{
	register LINEPTR dlp;
	register LINEPTR lp;
	register int i = 0;
	register int rows;
	int	founddot = FALSE;	/* set to true iff we find dot */
	int	tildecount;

	/* if not a requested reframe, check for a needed one */
	if ((wp->w_flag & WFFORCE) == 0) {
		/* initial update in main.c may not set these first... */
		if (wp->w_dot.l == (LINE *)0) {
			wp->w_dot.l = lforw(win_head(wp));
			wp->w_dot.o = 0;
		}
		if (wp->w_line.l == (LINE *)0) {
			wp->w_line.l = wp->w_dot.l;
			wp->w_line.o = 0;
		}
#if CAN_SCROLL
		/* loop from one line above the window to one line after */
		lp = lback(wp->w_line.l);
		i  = -line_height(wp,lp);
#else
		/* loop through the window */
		lp = wp->w_line.l;
		i  = 0;
#endif
		for_ever {
			/* if the line is in the window, no reframe */
			if (lp == wp->w_dot.l) {
				founddot = TRUE;
#if CAN_SCROLL
				/* if not _quite_ in, we'll reframe gently */
				if ( i < 0 || i >= wp->w_ntrows) {
					/* if the terminal can't help, then
						we're simply outside */
					if (term.t_scroll == null_t_scroll)
						i = wp->w_force;
					break;
				}
#endif
#ifdef WMDLINEWRAP
				if (w_val(wp,WMDLINEWRAP)
				 && i > 0
				 && i + line_height(wp,lp) > wp->w_ntrows) {
					i = wp->w_ntrows;
					break;
				}
#endif
				lp = wp->w_line.l;
				goto kill_tildes;
			}

			/* if we are at the end of the file, reframe */
			if (i >= 0 && lp == win_head(wp))
				break;

			/* on to the next line */
			if (i >= wp->w_ntrows) {
				i = 0;	/* dot-not-found */
				break;
			}
			i += line_height(wp,lp);
			lp = lforw(lp);
		}
	}

#if CAN_SCROLL
	if (i < 0) {	/* we're just above the window */
		i = 1;	/* put dot at first line */
		scrflags |= WFINS;
	} else if (founddot && (i >= wp->w_ntrows)) {
		/* we're just below the window */
		i = -1;	/* put dot at last line */
		scrflags |= WFKILLS;
	} else /* put dot where requested */
#endif
		i = wp->w_force;  /* (is 0, unless reposition() was called) */

	wp->w_flag |= WFMODE;
	wp->w_line.o = 0;

	/* w_force specifies which line of the window dot should end up on */
	/* 	positive --> lines from the top				*/
	/* 	negative --> lines from the bottom			*/
	/* 	zero --> middle of window				*/

	lp = wp->w_dot.l;

#ifdef WMDLINEWRAP
	/*
	 * Center dot in middle of screen with line-wrapping
	 */
	if (i == 0 && w_val(wp,WMDLINEWRAP)) {
		rows = (wp->w_ntrows - line_height(wp,lp) + 2) / 2;
		while (rows > 0) {
			dlp = lback(lp);
			if (dlp == win_head(wp))
				break;
			if ((rows -= line_height(wp, dlp)) < 0)
				break;
			lp = dlp;
		}
	} else
#endif
	{
		rows = (i != 0)
			? wp->w_ntrows
			: wp->w_ntrows / 2;
		while (rows > 0) {
			if ((i > 0)
			 && (--i <= 0))
				break;
			dlp = lback(lp);
			if (dlp == win_head(wp))
				break;
			if ((rows -= line_height(wp, lp)) < 0)
				break;
			lp = dlp;
		}
		if (rows < line_height(wp, lp)
		 && (lp != wp->w_dot.l)) {
			while (i++ < 0) {
				dlp = lforw(lp);
				if (dlp == win_head(wp))
					break;
				else
					lp = dlp;
			}
		}
	}
kill_tildes:
	/* Eliminate as many tildes as possible from bottom */
	dlp = lp;
	rows = wp->w_ntrows;
	while (rows > 0 && (dlp != win_head(wp))) {
		rows -= line_height(wp, dlp);
		dlp = lforw(dlp);
	}
	dlp = lback(lp);

	tildecount = (wp->w_ntrows * ntildes)/100;
	if (tildecount == wp->w_ntrows)
		tildecount--;

	while (dlp != win_head(wp)
	       && (rows -= line_height(wp, dlp)) >= tildecount) {
		lp = dlp;
		dlp = lback(lp);
	}

	/* and reset the current line-at-top-of-window */
	if (lp != win_head(wp) /* mouse click could be past end */
	 && lp != wp->w_line.l) { /* no need to set it if already there */
		wp->w_line.l = lp;
		wp->w_flag |= WFHARD;
		wp->w_flag &= ~WFFORCE;
	}
#ifdef WMDLINEWRAP
	/*
	 * Ensure that dot will be visible, by adjusting the w_line.o value if
	 * necessary.  That's used to start the beginning of the first line in
	 * a window "before" the start of the window.
	 */
	if (w_val(wp,WMDLINEWRAP)
	 && sameline(wp->w_line, wp->w_dot)) {
		int want = mark2col(wp, wp->w_dot) / term.t_ncol;
		if (want + wp->w_line.o >= wp->w_ntrows) {
			wp->w_line.o = wp->w_ntrows - want - 1;
			wp->w_flag |= WFHARD;
			wp->w_flag &= ~WFFORCE;
		}
		else if (want + wp->w_line.o < 0) {
			wp->w_line.o = -want;
			wp->w_flag |= WFHARD;
			wp->w_flag &= ~WFFORCE;
		}
	}
#endif
}

/*	updone:	update the current line	to the virtual screen		*/

static void
updone(
WINDOW *wp)	/* window to update current line in */
{
	register LINEPTR lp;	/* line to update */
	register int sline;	/* physical screen line to update */

	/* search down the line we want */
	lp = wp->w_line.l;
	sline = TopRow(wp);
	while (lp != wp->w_dot.l) {
		sline += line_height(wp,lp);
		lp = lforw(lp);
	}

	l_to_vline(wp,lp,sline);
	vteeol();
}

/*	updall:	update all the lines in a window on the virtual screen */

static void
updall(
WINDOW *wp)	/* window to update lines in */
{
	register LINEPTR lp;	/* line to update */
	register int sline;	/* physical screen line to update */

	/* search down the lines, updating them */
	lp = wp->w_line.l;
	sline = TopRow(wp);
	while (sline < mode_row(wp)) {
		l_to_vline(wp,lp,sline);
		vteeol();
		sline += line_height(wp,lp);
		if (lp != win_head(wp))
			lp = lforw(lp);
	}
}

/* line to virtual screen line */
static void
l_to_vline(
WINDOW *wp,	/* window to update lines in */
LINEPTR lp,
int sline)
{
	C_NUM	left;

	/*
	 * Mark the screen lines changed, resetting the requests for reverse
	 * video.  Set the global 'taboff' to the amount of horizontal
	 * scrolling.
	 */
#ifdef WMDLINEWRAP
	if (w_val(wp,WMDLINEWRAP)) {
		int top_line = sline - wp->w_line.o;
		register int	m = (top_line >= 0) ? top_line : 0;
		register int	n = top_line + line_height(wp, lp);
		while (n > m)
			if (--n < mode_row(wp)) {
				vscreen[n]->v_flag |= VFCHG;
				vscreen[n]->v_flag &= ~VFREQ;
			}
		taboff = 0;
	} else
#endif
	{
		vscreen[sline]->v_flag |= VFCHG;
		vscreen[sline]->v_flag &= ~VFREQ;
		if (w_val(wp,WVAL_SIDEWAYS))
			taboff = w_val(wp,WVAL_SIDEWAYS);
	}
	left = taboff;

	if (lp != win_head(wp)) {
		vtmove(sline, -left);
		vtset(lp, wp);
		if (left && sline >= 0) {
			register int	zero = nu_width(wp);
			vscreen[sline]->v_text[zero] = MRK_EXTEND_LEFT;
			if (vtcol <= zero) vtcol = zero+1;
		}
	} else {
		vtmove(sline, 0);
		vtputc(MRK_EMPTY);
	}
	taboff = 0;
#if	OPT_COLOR
	if (sline >= 0) {
		ReqFcolor(vscreen[sline]) = gfcolor;
		ReqBcolor(vscreen[sline]) = gbcolor;
	}
#endif
}

/*	updpos:	update the position of the hardware cursor and handle extended
		lines. This is the only update for simple moves.
		returns the screen column for the cursor, and
		a boolean indicating if full sideways scroll was necessary */
static int
updpos(
int *screenrowp,
int *screencolp)
{
	register LINEPTR lp;
#ifdef WMDLINEWRAP
	register int i;
#endif
	register int col, excess;
	register int collimit;
	int moved = FALSE;
	int nuadj = is_empty_buf(curwp->w_bufp) ? 0 : nu_width(curwp);
	int liadj = (w_val(curwp,WMDLIST)) ? 1 : 0;

	/* find the current row */
	lp = curwp->w_line.l;
	currow = TopRow(curwp);
	while (lp != DOT.l) {
		currow += line_height(curwp,lp);
		lp = lforw(lp);
		if (lp == curwp->w_line.l
		 || currow > mode_row(curwp)) {
			mlforce("BUG:  lost dot updpos().  setting at top");
			lp = curwp->w_line.l = DOT.l = lforw(buf_head(curbp));
			currow = TopRow(curwp);
		}
	}

	/* find the current column */
	col = mk_to_vcol(DOT, w_val(curwp,WMDLIST), w_left_margin(curwp));

#ifdef WMDLINEWRAP
	if (w_val(curwp,WMDLINEWRAP)) {
		curcol = col;
		collimit = term.t_ncol - nuadj;
		*screenrowp = currow;
		if (col >= collimit) {
			col -= collimit;
			*screenrowp += 1;
			if (col >= term.t_ncol)
				*screenrowp += (col / term.t_ncol);
			*screencolp = col % term.t_ncol;
		} else {
			*screencolp = col + nuadj;
		}
		/* kludge to keep the cursor within the window */
		i = mode_row(curwp) - 1;
		if (*screenrowp > i) {
			*screenrowp = i;
			*screencolp = term.t_ncol - 1;
		}
		return FALSE;
	} else
#endif
	 *screenrowp = currow;

	/* ...adjust to offset from shift-margin */
	curcol = col - w_val(curwp,WVAL_SIDEWAYS);
	*screencolp = curcol;

	/* if extended, flag so and update the virtual line image */
	collimit = col_limit(curwp);
	excess = curcol - collimit + liadj;
	if ((excess > 0) || (excess == 0 &&
			(DOT.o < llength(DOT.l) - 1 ))) {
		if (w_val(curwp,WMDHORSCROLL)) {
			(void)mvrightwind(TRUE, excess + collimit/2 );
			moved = TRUE;
		} else {
			*screencolp = updext_past(col, excess);
		}
	} else if (w_val(curwp,WVAL_SIDEWAYS) && (curcol < 1)) {
		if (w_val(curwp,WMDHORSCROLL)) {
			(void)mvleftwind(TRUE, -curcol + collimit/2 + 1);
			moved = TRUE;
		} else {
			*screencolp = updext_before(col);
		}
	} else {
		if (vscreen[currow]->v_flag & VFEXT) {
			l_to_vline(curwp,lp,currow);
			vteeol();
			/* this line no longer is extended */
			vscreen[currow]->v_flag &= ~VFEXT;
		}
	}
	if (!moved)
		*screencolp += nuadj;
	return moved;
}

/*	upddex:	de-extend any line that deserves it		*/

static void
upddex(void)
{
	register WINDOW *wp;
	register LINEPTR lp;
	register int i;

	for_each_visible_window(wp) {
		lp = wp->w_line.l;
		i = TopRow(wp);

		curtabval = tabstop_val(wp->w_bufp);

		while (i < mode_row(wp)) {
			if (i >= 0
			 && vscreen[i]->v_flag & VFEXT) {
				if ((wp != curwp)
				 || (lp != wp->w_dot.l)
				 || ((i != currow)
				  && (curcol < col_limit(wp)))) {
					l_to_vline(wp,lp,i);
					vteeol();
					/* this line no longer is extended */
					vscreen[i]->v_flag &= ~VFEXT;
				}
			}
			i += line_height(wp,lp);
			lp = lforw(lp);
		}
	}
	curtabval = tabstop_val(curbp);
}

/*	updgar:	if the screen is garbage, clear the physical screen and
		the virtual screen and force a full update		*/

static void
updgar(void)
{
#if !MEMMAP && !OPT_PSCREEN
	register int j;
#endif
	register int i;

	for (i = 0; i < term.t_nrow; ++i) {
		vscreen[i]->v_flag |= VFCHG;
#if	OPT_REVSTA
		vscreen[i]->v_flag &= ~VFREV;
#endif
#if	OPT_COLOR
		CurFcolor(vscreen[i]) = -1;
		CurBcolor(vscreen[i]) = -1;
#endif
#if	! MEMMAP && ! OPT_PSCREEN
		for (j = 0; j < term.t_ncol; ++j) {
			CELL_TEXT(i,j) = ' ';
#if OPT_VIDEO_ATTRS
			CELL_ATTR(i,j) = 0;
#endif /* OPT_VIDEO_ATTRS */
		}
#endif
	}
#if !OPT_PSCREEN
#if	OPT_COLOR
	TTforg(gfcolor);		 /* Need to set before erasing. */
	TTbacg(gbcolor);
#endif
	movecursor(0, 0);		 /* Erase the screen. */
	TTeeop();
#else
	kbd_erase_to_end(0);
#endif
	sgarbf = FALSE;			 /* Erase-page clears */
	kbd_flush();
}

/*	updupd:	update the physical screen from the virtual screen	*/

static void
updupd(
int force GCC_UNUSED)	/* forced update flag */
{
	register int i;

#if CAN_SCROLL
	if (scrflags & WFKILLS)
		(void)scrolls(FALSE);
	if (scrflags & WFINS)
		(void)scrolls(TRUE);
	scrflags = 0;
#endif

	for (i = 0; i < term.t_nrow; ++i) {
		/* for each line that needs to be updated*/
		if ((vscreen[i]->v_flag & (VFCHG|VFCOL)) != 0) {
#if !DISP_X11
			if (TypeAhead(force))
				return;
#endif
			updateline(i, 0, term.t_ncol);
		}
	}
}

#if OPT_VIDEO_ATTRS
static void
updattrs(WINDOW *wp)
{
    AREGION *ap;
    int i;

    L_NUM start_wlnum, end_wlnum;
    LINEPTR lp;
    int rows;

    /*
     * Clear portion of virtual screen associated with window pointer
     * of all attributes.
     */
    /* FIXME: color; need to set to value indicating fg and bg for window */
    for (i = wp->w_toprow + wp->w_ntrows - 1; i >= wp->w_toprow; i--)
    	set_vattrs(i, 0, 0, term.t_ncol);

    /*
     * No need to do any more work on this window if there are no
     * attributes.
     */
    if (wp->w_bufp->b_attribs == NULL)
	return;

    /*
     * Compute starting and ending line numbers for the window.  We
     * also fill in lmap which is used for mapping line numbers to
     * screen row numbers.
     */
    lp = wp->w_line.l;
    start_wlnum =
    end_wlnum = line_no(wp->w_bufp, lp);
    rows = wp->w_ntrows;
    lmap[end_wlnum - start_wlnum] = TopRow(wp);
    while ( (rows -= line_height(wp,lp)) > 0) {
	lp = lforw(lp);
	end_wlnum++;
	lmap[end_wlnum - start_wlnum] = TopRow(wp) + wp->w_ntrows - rows;
    }

    /*
     * Set current attributes in virtual screen associated with window
     * pointer.
     */
    for (ap = wp->w_bufp->b_attribs; ap != NULL;) {
	VIDEO_ATTR attr;
	C_NUM start_col, end_col;
	C_NUM rect_start_col = 0, rect_end_col = 0;
	L_NUM start_rlnum, end_rlnum, lnum, start_lnum, end_lnum;
	start_rlnum = line_no(wp->w_bufp, ap->ar_region.r_orig.l);
	end_rlnum = line_no(wp->w_bufp, ap->ar_region.r_end.l);

	/* Garbage collect empty visible regions */
	if (start_rlnum == end_rlnum
	 && VATTRIB(ap->ar_vattr) != 0
	 && ap->ar_region.r_orig.o >= ap->ar_region.r_end.o) {
	    AREGION *nap = ap->ar_next;
	    free_attrib(wp->w_bufp, ap);
	    ap = nap;
	    continue;
	}

	if (start_rlnum > start_wlnum) {
	    start_lnum = start_rlnum;
	    lp = ap->ar_region.r_orig.l;
	}
	else {
	    start_lnum = start_wlnum;
	    lp = wp->w_line.l;
	}
	end_lnum = (end_rlnum < end_wlnum) ? end_rlnum : end_wlnum;
	attr = ap->ar_vattr;
	if (ap->ar_shape == RECTANGLE) {
	    int n;
	    rect_start_col = mark2col(wp, ap->ar_region.r_orig);
	    rect_end_col   = mark2col(wp, ap->ar_region.r_end);
	    if (rect_end_col < rect_start_col) {
		    C_NUM col = rect_end_col;
		    rect_end_col = rect_start_col;
		    rect_start_col = col;
		    n = MARK2COL(wp, ap->ar_region.r_orig);
	    } else {
		    n = MARK2COL(wp, ap->ar_region.r_end);
	    }
	    if (rect_end_col < n)
	    	rect_end_col = n;
	}
	for (lnum = start_lnum; lnum <= end_lnum; lnum++, lp = lforw(lp)) {
	    int row, col;
	    if (ap->ar_shape == RECTANGLE) {
		start_col = rect_start_col;
	    } else if (lnum == start_rlnum) {
		start_col = mark2col(wp, ap->ar_region.r_orig);
	    } else {
		start_col = w_left_margin(wp) + nu_width(wp);
	    }

	    if (start_col < w_left_margin(wp))
		start_col = (lnum == start_rlnum)
			? w_left_margin(wp) + nu_width(wp)
			: w_left_margin(wp);

	    if (ap->ar_shape == RECTANGLE) {
		end_col = rect_end_col;
	    } else if (lnum == end_rlnum) {
		int n = mark2col(wp, ap->ar_region.r_end) - 1;
		end_col = offs2col(wp, ap->ar_region.r_end.l,
				   ap->ar_region.r_end.o - 1);
		if (end_col < n)
			end_col = n;
	    } else {
		end_col = offs2col(wp, lp, llength(lp));
#ifdef WMDLINEWRAP
		if (w_val(wp,WMDLINEWRAP)
		 && (end_col % term.t_ncol) == 0)
		    end_col--;	/* cannot highlight the newline */
#endif
	    }
	    row = lmap[lnum - start_wlnum];
#ifdef WMDLINEWRAP
	    if (w_val(wp,WMDLINEWRAP))
	    {
		for (col = start_col; col <= end_col; col++) {
		    int x = row + col / term.t_ncol;
		    if  (x < 0)
			continue;
		    if (x < mode_row(wp)) {
			int y = col % term.t_ncol;
			vscreen[x]->v_attrs[y] =
			    (vscreen[x]->v_attrs[y] | (attr & ~VAREV))
			    ^ (attr & VAREV);
		    }
		    else
			break;
		}
	    }
	    else
#endif
	    {
		if (end_col >= term.t_ncol)
		    end_col = term.t_ncol-1;
		for (col = start_col; col <= end_col; col++)
		    vscreen[row]->v_attrs[col] =
			(vscreen[row]->v_attrs[col] | (attr & ~VAREV))
			^ (attr & VAREV);
	    }
	}
	ap = ap->ar_next;
    }
}
#endif /* OPT_VIDEO_ATTRS */

/*
 * Translate offset (into a line's text) into the display-column, taking into
 * account the tabstop, sideways, number- and list-modes.
 */
int
offs2col(
WINDOW	*wp,
LINEPTR	lp,
C_NUM	offset)
{
	int	length = llength(lp);
	int	column = 0;
	int	tabs = tabstop_val(wp->w_bufp);
	int	list = w_val(wp,WMDLIST);
	int	left =
#ifdef WMDLINEWRAP	/* overrides left/right scrolling */
			w_val(wp,WMDLINEWRAP) ? 0 :
#endif
			w_val(wp,WVAL_SIDEWAYS);

	register C_NUM	n, c;

	/* this makes the how-much-to-select calculation easier above */
	if (offset < 0)
		return offset;

	if (lp == win_head(wp)) {
		column = 0;
	} else {
		for (n = w_left_margin(wp); (n < offset) && (n <= length); n++) {
			c = (n >= length) ? '\n' : lp->l_text[n];
			if (isPrint(c)) {
				column++;
			} else if (list || (c != '\t')) {
				column += (c & HIGHBIT) ? 4 : 2;
			} else if (c == '\t') {
				column = ((column / tabs) + 1) * tabs;
			}
		}
		column = column - left + nu_width(wp) + w_left_margin(wp);
	}
	return column;
}

/*
 * Translate a display-column (assuming an infinitely-wide display) into the
 * line's offset, taking into account the tabstop, sideways, number and list
 * modes.
 */
#if OPT_MOUSE || defined(WMDLINEWRAP)
int
col2offs(
WINDOW	*wp,
LINEPTR	lp,
C_NUM	col)
{
	int	tabs = tabstop_val(wp->w_bufp);
	int	list = w_val(wp,WMDLIST);
	int	left =
#ifdef WMDLINEWRAP	/* overrides left/right scrolling */
			w_val(wp,WMDLINEWRAP) ? 0 :
#endif
			w_val(wp,WVAL_SIDEWAYS);
	int	goal = col + left - nu_width(wp) - w_left_margin(wp);

	register C_NUM	n;
	register C_NUM	offset;
	register C_NUM	len	= llength(lp);
	register char	*text	= lp->l_text;

	if (lp == win_head(wp)) {
		offset = 0;
	} else {
		for (offset = w_left_margin(wp), n = 0;
			(offset < len) && (n < goal);
				offset++) {
			register int c = text[offset];
			if (isPrint(c)) {
				n++;
			} else if (list || (c != '\t')) {
				n += (c & HIGHBIT) ? 4 : 2;
			} else if (c == '\t') {
				n = ((n / tabs) + 1) * tabs;
			}
			if (n > goal)
				break;
		}
	}
	return offset;
}
#endif

/*
 * Compute the number of rows required for displaying a line.
 */
#ifdef WMDLINEWRAP
int
line_height(
WINDOW	*wp,
LINEPTR	lp)
{
	int hi = 1;
	if (w_val(wp,WMDLINEWRAP)) {
		int	len = llength(lp);
		if (len > 0) {
			int col = offs2col(wp,lp,len) - 1;
			if (ins_mode(wp) != FALSE
			 && lp == DOT.l
			 && len <= DOT.o) {
				col++;
				if (w_val(wp,WMDLIST))
					col++;
			} else if (w_val(wp,WMDLIST)) {
				col += 2;
			}
			hi = (col / term.t_ncol) + 1;
		}
	}
	return hi;
}
#endif

/*
 * Given a row on the screen, determines which window it belongs to.  Returns
 * null only for the message line.
 */
#if defined(WMDLINEWRAP) || OPT_MOUSE
WINDOW *
row2window (int row)
{
	register WINDOW *wp;

	for_each_visible_window(wp)
		if (row >= wp->w_toprow && row <= mode_row(wp))
			return wp;
	return 0;
}
#endif

/*
 * Highlight the requested portion of the screen.  We're mucking with the video
 * attributes on the line here, so this is NOT good code - it would be better
 * if there was an individual colour attribute per character, rather than per
 * row, but I didn't write the original code.  Anyway, hilite is called only
 * once so far, so it's not that big a deal.
 */
void
hilite(
int	row,		/* row to start highlighting */
int	colfrom,	/* column to start highlighting */
int	colto,		/* column to end highlighting */
int	on)		/* start highlighting */
{
#if !OPT_VIDEO_ATTRS
	register VIDEO *vp1 = vscreen[row];
#endif
#ifdef WMDLINEWRAP
	WINDOW	*wp = row2window(row);
	if (w_val(wp,WMDLINEWRAP)) {
		if (colfrom < 0)
			colfrom = 0;
		if (colfrom > term.t_ncol) {
			do {
				row++;
				colfrom -= term.t_ncol;
				colto   -= term.t_ncol;
				hilite(row, colfrom, colto, on);
			} while (colto > term.t_ncol);
			return;
		}
	}
#endif
	if (row < term.t_nrow-1 && (colfrom >= 0 || colto <= term.t_ncol)) {
		if (colfrom < 0)
			colfrom = 0;
		if (colto > term.t_ncol)
			colto = term.t_ncol;
#if OPT_VIDEO_ATTRS
		if (on) {
		    int col;
		    for (col=colfrom; col<colto; col++)
			vscreen[row]->v_attrs[col] |= VAREV;
		}
		else {
		    int col;
		    for (col=colfrom; col<colto; col++)
			vscreen[row]->v_attrs[col] &= ~VAREV;
		}
		vscreen[row]->v_flag |= VFCHG;
		updateline(row, 0, term.t_ncol);
#else /* OPT_VIDEO_ATTRS */
		if (on) {
			vp1->v_flag |= VFREQ;
		} else {
			vp1->v_flag &= ~VFREQ;
		}
		updateline(row, colfrom, colto);
#endif /* OPT_VIDEO_ATTRS */
	}
}

#if CAN_SCROLL
/* optimize out scrolls (line breaks, and newlines) */
/* arg. chooses between looking for inserts or deletes */
static int
scrolls(int inserts)	/* returns true if it does something */
{
	struct	VIDEO *vpv ;	/* virtual screen image */
	struct	VIDEO *vpp ;	/* physical screen image */
	int	i, j, k ;
	int	rows, cols ;
	int	first, match, count, ptarget = 0, vtarget = 0;
	SIZE_T	end;
	int	longmatch, longcount;
	int	longinplace, inplace;	/* count of lines which are already
					   in the right place */
	int	from, to;

	if (term.t_scroll == null_t_scroll) /* no way to scroll */
		return FALSE;

	rows = term.t_nrow -1;
	cols = term.t_ncol ;

	first = -1 ;
	for (i = 0; i < rows; i++) {	/* find first wrong line */
		if (!texttest(i,i)) {
			first = i;
			break;
		}
	}

	if (first < 0)
		return FALSE;		/* no text changes */

	vpv = vscreen[first] ;
	vpp = PScreen(first) ;

	if (inserts) {
		/* determine types of potential scrolls */
		end = endofline(vpv->v_text,cols) ;
		if ( end == 0 )
			ptarget = first ;		/* newlines */
		else if ( memcmp(vpp->v_text, vpv->v_text, end) == 0 )
			ptarget = first + 1 ;	/* broken line newlines */
		else
			ptarget = first ;
		from = ptarget;
	} else {
		from = vtarget = first + 1 ;
	}

	/* find the matching shifted area */
	longmatch = -1;
	longcount = 0;
	longinplace = 0;
	for (i = from+1; i < rows; i++) {
		if (inserts ? texttest(i,from) : texttest(from,i) ) {
			match = i ;
			count = 1 ;
			inplace = texttest(match, match) ? 1 : 0;
			for (j=match+1, k=from+1; j<rows && k<rows; j++, k++) {
				if (inserts ? texttest(j,k) : texttest(k,j)) {
					count++ ;
					if (texttest(j,j))
						inplace++;
				}
				else
					break ;
			}
			if (longcount - longinplace < count - inplace) {
				longcount = count;
				longmatch = match;
				longinplace = inplace;
			}
		}
	}
	match = longmatch;
	count = longcount;

	if (!inserts) {
		/* full kill case? */
		if (match > 0 && texttest(first, match-1)) {
			vtarget-- ;
			match-- ;
			count++ ;
		}
	}

	/* do the scroll */
	if (match>0 && count>2) {		 /* got a scroll */
		/* move the count lines starting at ptarget to match */
		/* mlwrite("scrolls: move the %d lines starting at %d to %d",
						count,ptarget,match);
		*/
		if (inserts) {
			from = ptarget;
			to = match;
		} else {
			from = match;
			to = vtarget;
		}
#if OPT_PSCREEN
		/*
		 * Update lines _before_ the scroll so that they will
		 * be available for any updates which need to be done
		 * (due to a GraphicsExpose event in X11...these occur
		 * when scrolling a partially obscured window).  Note
		 * that in the typical case of scrolling a line or two
		 * that very few memory accesses are performed.  We
		 * mostly shuffle pointers around.
		 */
#define SWAP_PLINE(a, b) do { VIDEO *temp = pscreen[a];	\
			      pscreen[a] = pscreen[b];	\
			      pscreen[b] = temp; } one_time
#define CLEAR_PLINE(a)  do {						\
			    MARK_LINE_DIRTY(a);				\
			    for (j = 0; j < term.t_ncol; j++) {		\
				CELL_TEXT(a,j) = ' ';			\
				CELL_ATTR(a,j) = 0;			\
			    }						\
			  } one_time
		if (from < to) {
		    /* FIXME: color */
		    for (i = from; i < to; i++)
			CLEAR_PLINE(i+count);
		    for (i = count-1; i >= 0; i--)
			SWAP_PLINE(from+i, to+i);
		}
		else {
		    /* FIXME: color */
		    for (i = to; i < from; i++)
			CLEAR_PLINE(i);
		    for (i = 0; i < count; i++)
			SWAP_PLINE(from+i, to+i);
		}
#endif /* OPT_PSCREEN */
		scrscroll(from, to, count) ;
#if !OPT_PSCREEN
		for (i = 0; i < count; i++) {
			vpp = PScreen(to+i) ;
			vpv = vscreen[to+i];
			(void)memcpy(vpp->v_text, vpv->v_text, (SIZE_T)cols) ;
		}
#if OPT_VIDEO_ATTRS && !MEMMAP
#define SWAP_ATTR_PTR(a, b) do { VIDEO_ATTR *temp = pscreen[a]->v_attrs;  \
			         pscreen[a]->v_attrs = pscreen[b]->v_attrs; \
			         pscreen[b]->v_attrs = temp; } one_time
		if (from < to) {
		    /* FIXME: color */
		    for (i = from; i < to; i++)
			for (j = 0; j < term.t_ncol; j++)
			    CELL_ATTR(i+count,j) = 0;
		    for (i = count-1; i >= 0; i--)
			SWAP_ATTR_PTR(from+i, to+i);

		}
		else {
		    /* FIXME: color */
		    for (i = to; i < from; i++)
			for (j = 0; j < term.t_ncol; j++)
			    CELL_ATTR(i,j) = 0;
		    for (i = 0; i < count; i++)
			SWAP_ATTR_PTR(from+i, to+i);
		}
#undef SWAP_ATTR_PTR
#endif /* OPT_VIDEO_ATTRS */
		if (inserts) {
			from = ptarget;
			to = match;
		} else {
			from = vtarget+count;
			to = match+count;
		}
		for (i = from; i < to; i++) {
			char *txt;
			txt = PScreen(i)->v_text;
			for (j = 0; j < term.t_ncol; ++j)
				txt[j] = ' ';
			vscreen[i]->v_flag |= VFCHG;
		}
#endif /* !OPT_PSCREEN */
		return(TRUE) ;
	}
	return(FALSE) ;
}

/* move the "count" lines starting at "from" to "to" */
static void
scrscroll(int from, int to, int count)
{
	beginDisplay();
	ttrow = ttcol = -1;
	TTscroll(from,to,count);
	endofDisplay();
}

static int
texttest(		/* return TRUE on text match */
int	vrow,		/* virtual row */
int	prow)		/* physical row */
{
	struct	VIDEO *vpv = vscreen[vrow] ;	/* virtual screen image */
	struct	VIDEO *vpp = PScreen(prow)  ;	/* physical screen image */

	return (!memcmp(vpv->v_text, vpp->v_text, (SIZE_T)term.t_ncol)) ;
}

/* return the index of the first blank of trailing whitespace */
static int
endofline(char *s, int n)
{
	int	i;
	for (i = n - 1; i >= 0; i--)
		if (s[i] != ' ') return(i+1) ;
	return(0) ;
}

#endif /* CAN_SCROLL */


/* Update the extended line which the cursor is currently on at a column
 * greater than the terminal width.  The line will be scrolled right or left to
 * let the user see where the cursor is.
 */
static int
updext_past(int col, int excess)
{
	register int rcursor;
	register int zero = nu_width(curwp);

	/* calculate what column the real cursor will end up in */
	rcursor = ((excess - 1) % term.t_scrsiz) + term.t_margin;
	taboff = col - rcursor;

	/* Scan through the line outputting characters to the virtual screen
	 * once we reach the left edge.  */

	/* start scanning offscreen */
	vtmove(currow, -taboff);
	vtset(DOT.l, curwp);

	/* truncate the virtual line, restore tab offset */
	vteeol();
	taboff = 0;

	/* and put a marker in column 1 */
	vscreen[currow]->v_text[zero] = MRK_EXTEND_LEFT;
	vscreen[currow]->v_flag |= (VFEXT | VFCHG);
	return rcursor;
}

/* Update the extended line which the cursor is currently on at a column less
 * than the terminal width.  The line will be scrolled right or left to let the
 * user see where the cursor is.
 */
static int
updext_before(int col)
{
	register int rcursor;

	curcol = col;

	/* calculate what column the real cursor will end up in */
	rcursor = (col % (term.t_ncol - term.t_margin));
	taboff = col - rcursor;

	/* Scan through the line outputting characters to the virtual screen
	 * once we reach the left edge.  */
	vtmove(currow, -taboff);	/* start scanning offscreen */
	vtset(DOT.l, curwp);

	/* truncate the virtual line, restore tab offset */
	vteeol();
	taboff = 0;

	if (col != rcursor) { /* ... put a marker in column 1 */
		vscreen[currow]->v_text[nu_width(curwp)] = MRK_EXTEND_LEFT;
		vscreen[currow]->v_flag |= VFEXT;
	}
	vscreen[currow]->v_flag |= (VFEXT|VFCHG);
	return rcursor;
}



/*
 * Update a single line. This does not know how to use insert or delete
 * character sequences; we are using VT52 functionality. Update the physical
 * row and column variables. It does try an exploit erase to end of line.
 */
#if	MEMMAP
/*	UPDATELINE specific code for the IBM-PC and other compatibles */

static void
updateline(

int	row,		/* row of screen to update */
int	colfrom,	/* first column on screen */
int	colto)		/* last column on screen */

{
	register struct VIDEO *vp1 = vscreen[row];	/* virtual screen image */
	register int	req = (vp1->v_flag & VFREQ) == VFREQ;

#if	OPT_COLOR
	CurFcolor(vp1) = ReqFcolor(vp1);
	CurBcolor(vp1) = ReqBcolor(vp1);
#endif
#if OPT_VIDEO_ATTRS
	scwrite(row, colfrom, colto - colfrom,
		VideoText(vp1),
		VideoAttr(vp1),
		ReqFcolor(vp1),
		ReqBcolor(vp1));
#else	/* highlighting, anyway */
	scwrite(row, colfrom, colto - colfrom,
		VideoText(vp1),
		(VIDEO_ATTR *)0,
		req ? ReqBcolor(vp1) : ReqFcolor(vp1),
		req ? ReqFcolor(vp1) : ReqBcolor(vp1));
#endif
	vp1->v_flag &= ~(VFCHG | VFCOL); /* flag this line as updated */
	if (req)
		vp1->v_flag |= VFREV;
	else
		vp1->v_flag &= ~VFREV;
}

#else	/* !MEMMAP */
#if	OPT_PSCREEN
static void
updateline(
    int	row,		/* row of screen to update */
    int	colfrom,	/* col to start updating from */
    int	colto)		/* col to go to */
{
    register char *vc, *pc, *evc;
    register VIDEO_ATTR *va, *pa;
    int nchanges = 0;

    if ((vscreen[row]->v_flag & VFCHG) == 0)
	return;

    vc  = &vscreen[row]->v_text[colfrom];
    evc = &vscreen[row]->v_text[colto];
    va  = &vscreen[row]->v_attrs[colfrom];
    pc  = &CELL_TEXT(row,colfrom);
    pa  = &CELL_ATTR(row,colfrom);

    while (vc < evc) {
	if (*vc != *pc || VATTRIB(*va) != VATTRIB(*pa)) {
	    *pc = *vc;
	    *pa = *va | VADIRTY;
	    nchanges++;
	}
	vc++;
	pc++;
	va++;
	pa++;
    }

    if (nchanges > 0)
	MARK_LINE_DIRTY(row);
    vscreen[row]->v_flag &= ~(VFCHG | VFCOL); /* mark virtual line updated */
}

#else  /* !OPT_PSCREEN */

/*	UPDATELINE code for all other versions		*/

#define TTattr(a) TTrev(a) /* FIXME */

static void
updateline(

int	row,		/* row of screen to update */
int	colfrom,	/* first column on screen */
int	colto)		/* first column on screen */

{
	struct VIDEO *vp1 = vscreen[row];	/* virtual screen image */
	struct VIDEO *vp2 = PSCREEN[row];	/* physical screen image */
	register int xl = colfrom;
	register int xr = colto;
	register int xx;

	register char *cp1 = VideoText(vp1);
	register char *cp2 = VideoText(vp2);
	register int nbflag;	/* non-blanks to the right flag? */

#if OPT_VIDEO_ATTRS
	register VIDEO_ATTR *ap1 = VideoAttr(vp1);
	register VIDEO_ATTR *ap2 = VideoAttr(vp2);
	VIDEO_ATTR Blank = 0;	/* FIXME: Color? */
#else
	UINT rev;		/* reverse video flag */
	UINT req;		/* reverse video request flag */
#endif

#if !OPT_VIDEO_ATTRS
#if	OPT_COLOR
	TTforg(ReqFcolor(vp1));
	TTbacg(ReqBcolor(vp1));
#endif

#if	OPT_REVSTA || OPT_COLOR
	/* if we need to change the reverse video status of the
	   current line, we need to re-write the entire line     */
	rev = (vp1->v_flag & VFREV) == VFREV;
	req = (vp1->v_flag & VFREQ) == VFREQ;
	if ((rev != req)
#if	OPT_COLOR
	    || (CurFcolor(vp1) != ReqFcolor(vp1))
	    || (CurBcolor(vp1) != ReqBcolor(vp1))
#endif
			) {
		movecursor(row, colfrom);	/* Go to start of line. */
		/* set rev video if needed */
		if (req)
			TTrev(req);

		/* scan through the line and dump it to the screen and
		   the virtual screen array				*/
		for (; xl < colto; xl++) {
			TTputc(cp1[xl]);
			++ttcol;
			cp2[xl] = cp1[xl];
		}
		/* turn rev video off */
		if (req)
			TTrev(FALSE);

		/* update the needed flags */
		vp1->v_flag &= ~(VFCHG|VFCOL);
		if (req)
			vp1->v_flag |= VFREV;
		else
			vp1->v_flag &= ~VFREV;
#if	OPT_COLOR
		CurFcolor(vp1) = ReqFcolor(vp1);
		CurBcolor(vp1) = ReqBcolor(vp1);
#endif
		return;
	}
#else
	rev = FALSE;
#endif	/* OPT_REVSTA || OPT_COLOR */
#endif	/* !OPT_VIDEO_ATTRS */

	/* advance past any common chars at the left */
#if !OPT_VIDEO_ATTRS
	if (!rev)
#endif	/* !OPT_VIDEO_ATTRS */
		while (xl != colto
		    && cp1[xl] == cp2[xl]
#if OPT_VIDEO_ATTRS
		    && VATTRIB(ap1[xl]) == VATTRIB(ap2[xl])
#endif	/* OPT_VIDEO_ATTRS */
		      ) {
			++xl;
		}

/* This can still happen, even though we only call this routine on changed
 * lines. A hard update is always done when a line splits, a massive
 * change is done, or a buffer is displayed twice. This optimizes out most
 * of the excess updating. A lot of computes are used, but these tend to
 * be hard operations that do a lot of update, so I don't really care.
 */
	/* if both lines are the same, no update needs to be done */
	if (xl == colto) {
		vp1->v_flag &= ~VFCHG;	/* flag this line unchanged */
		return;
	}

	/* find out if there is a match on the right */
	nbflag = FALSE;

#if !OPT_VIDEO_ATTRS
	if (!rev)
#endif
		while (cp1[xr-1] == cp2[xr-1]
#if OPT_VIDEO_ATTRS
		    && VATTRIB(ap1[xr-1]) == VATTRIB(ap2[xr-1])
#endif
		 ) {
			--xr;
			/* Note if any nonblank in right match */
			if (cp1[xr] != ' '
#if OPT_VIDEO_ATTRS
			 || VATTRIB(ap1[xr] != Blank)
#endif
			  )
				nbflag = TRUE;
		}

	xx = xr;

	/* Erase to EOL ? */
	if (nbflag == FALSE
	 && eolexist == TRUE
#if	OPT_REVSTA && !OPT_VIDEO_ATTRS
	 && (req != TRUE)
#endif
	   ) {
		while ((xx != xl)
		    && cp1[xx-1] == ' '
#if OPT_VIDEO_ATTRS
		    && VATTRIB(ap1[xx-1]) == Blank
#endif
		)
			xx--;

		if ((xr - xx) <= 3)		/* Use only if erase is */
			xx = xr;		/* fewer characters. */
	}

	movecursor(row, xl - colfrom);		/* Go to start of line. */
#if OPT_VIDEO_ATTRS
	while (xl < xx) {
		register int j = xl;
		VIDEO_ATTR attr = VATTRIB(ap1[j]);
		while ((j < xx) && (attr == VATTRIB(ap1[j])))
			j++;
		TTattr(attr);
		for (; xl < j; xl++) {
			TTputc(cp1[xl]);
			++ttcol;
			cp2[xl] = cp1[xl];
			ap2[xl] = ap1[xl];
		}
	}
	TTattr(0);

	if (xx != xr) {				/* Erase. */
		TTeeol();
		for (; xl < xr; xl++) {
			if (cp2[xl] != cp1[xl]
			 || VATTRIB(ap2[xl]) != VATTRIB(ap1[xl]))
				ap2[xl] = ap1[xl];
			cp2[xl] = cp1[xl];
		}
	}
#else /* OPT_VIDEO_ATTRS */
#if	OPT_REVSTA
	TTrev(rev);
#endif

	for (; xl < xr; xl++) {		/* Ordinary. */
		TTputc(cp1[xl]);
		++ttcol;
		cp2[xl] = cp1[xl];
	}

	if (xx != xr) {		/* Erase. */
		TTeeol();
		for (; xl < xr; xl++) {
			cp2[xl] = cp1[xl];
		}
	}
#if	OPT_REVSTA
	TTrev(FALSE);
#endif
#endif /* OPT_VIDEO_ATTRS */
	vp1->v_flag &= ~(VFCHG|VFCOL);	/* flag this line as updated */
	return;
}
#endif  /* OPT_PSCREEN(updateline) */
#endif	/* MEMMAP(updateline) */

/*
 * Redisplay the mode line for the window pointed to by the "wp".
 * modeline() is the only routine that has any idea of how the modeline is
 * formatted.  You can change the modeline format by hacking at this
 * routine.  Called by "update" any time there is a dirty window.
 */
#if OPT_MLFORMAT
static void
mlfs_prefix(
    char **fsp,
    char **msp,
    int lchar)
{
    register char *fs = *fsp;
    register char *ms = *msp;
    if (*fs == ':') {
	fs++;
	while (*fs && *fs != ':') {
	    if (*fs != '%')
		*ms++ = *fs++;
	    else {
		fs++;
		switch(*fs++) {
		    case EOS :
			fs--;
			break;
		    case '%' :
			*ms++ = '%';
			break;
		    case ':' :
			*ms++ = ':';
			break;
		    case '-' :
			*ms++ = (char)lchar;
			break;
		    default :
			*ms++ = '%';
			*ms++ = *(fs-1);
			break;
		}
	    }
	}
    }
    *fsp = fs;
    *msp = ms;
}

static void
mlfs_suffix(
    char **fsp,
    char **msp,
    int lchar)
{
    mlfs_prefix(fsp, msp, lchar);
    if (**fsp == ':')
	(*fsp)++;
}

static void
mlfs_skipfix(char **fsp)
{
    register char *fs = *fsp;
    if (*fs == ':') {
	for (fs++;*fs && *fs != ':'; fs++);
	if (*fs == ':')
	    fs++;
	for (;*fs && *fs != ':'; fs++);
	if (*fs == ':')
	    fs++;
    }
    *fsp = fs;
}
#endif /* OPT_MLFORMAT */

#define PutModename(format, name) { \
		if (ms != 0) { \
			ms = lsprintf(ms, format, \
				mcnt ? ' ' : '[', \
				name); \
		} \
		mcnt++; \
	}

#define PutMode(mode,name) \
	if (b_val(bp, mode)) PutModename("%c%s", name)

#if OPT_MAJORMODE
#define PutMajormode(bp) if (bp->majr != 0) PutModename("%c%smode", bp->majr->name)
#else
#define PutMajormode(bp) /*nothing*/
#endif

static int
modeline_modes(
BUFFER *bp,
char	**msptr)
{
	register char *ms = msptr ? *msptr : 0;
	register SIZE_T mcnt = 0;

#if CC_CANNOT_OFFSET_CASES
	PutMajormode(bp)
#if !OPT_MAJORMODE
	PutMode(MDCMOD,		"cmode")
#endif
#if OPT_ENCRYPT
	PutMode(MDCRYPT,	"crypt")
#endif
	PutMode(MDDOS,		"dos-style")
	PutMode(MDREADONLY,	"read-only")
	PutMode(MDVIEW,		"view-only")
#if OPT_LCKFILES
	PutMode(MDLOCKED,	"locked by")
#endif
#else
	static	const	struct {
		int   mode;
		const char *name;
	} table[] = {
#if !OPT_MAJORMODE
		{MDCMOD,    "cmode"},
#endif
#if OPT_ENCRYPT
		{MDCRYPT,   "crypt"},
#endif
		{MDDOS,     "dos-style"},
		{MDREADONLY,"read-only"},
		{MDVIEW,    "view-only"},
#if OPT_LCKFILES
		{MDLOCKED,  "locked by"},  /* keep this last */
#endif
	};
	register SIZE_T j;

	PutMajormode(bp)
	for (j = 0; j < TABLESIZE(table); j++) {
		PutMode(table[j].mode, table[j].name)
	}
#endif
#if OPT_LCKFILES
	if (ms != 0 && b_val(bp, MDLOCKED))
		ms = lsprintf(ms, " %s", b_val_ptr(bp,VAL_LOCKER));
#endif
	if (mcnt && ms)
		*ms++ = ']';
	if (b_is_changed(bp)) {
		if (ms != 0)
			ms = lsprintf(ms, "%s[modified]", mcnt ? " " : "");
		mcnt++;
	}
	if (ms != 0)
		*msptr = ms;
	return (mcnt != 0);
}

static int
modeline_show(
WINDOW *wp,
int lchar)
{
	register int ic = lchar;
	register BUFFER *bp = wp->w_bufp;

	if (b_val(bp, MDSHOWMODE)) {
#ifdef insertmode	/* insert mode is a trait for each window */
		if (wp->w_traits.insmode == INSERT)
			ic = 'I';
		else if (wp->w_traits.insmode == REPLACECHAR)
			ic = 'R';
		else if (wp->w_traits.insmode == OVERWRITE)
			ic = 'O';
#else 			/* insertmode is a variable global to all windows */
		if (wp == curwp) {
			if (insertmode == INSERT)
				ic = 'I';
			else if (insertmode == REPLACECHAR)
				ic = 'R';
			else if (insertmode == OVERWRITE)
				ic = 'O';
		}
#endif /* !defined(insertmode) */
#if OPT_ICURSOR
		TTicursor(ic != lchar);
#endif
	}
	return ic;
}

static const char *
rough_position(WINDOW *wp)
{
	LINE *lp = wp->w_line.l;
	int rows = wp->w_ntrows;
	const char *msg = 0;

	while (rows-- > 0) {
		lp = lforw(lp);
		if (lp == win_head(wp)) {
			msg = "bot";
			break;
		}
	}
	if (lback(wp->w_line.l) == win_head(wp)) {
		if (msg) {
			if (wp->w_line.l == win_head(wp))
				msg = "emp";
			else
				msg = "all";
		} else {
			msg = "top";
		}
	}
	if (!msg)
		msg = "mid";
	return msg;
}

static void
modeline(WINDOW *wp)
{
#if OPT_MLFORMAT
    char *fs = modeline_format;
    int fc;
#endif
    char temp[NFILEN];
    char left_ms[NFILEN*2];
    char right_ms[NFILEN*2];
    char *ms;
    register int n;
    int lchar;
    int col;
    int right_len;
    int need_eighty_column_indicator = FALSE;
    register BUFFER *bp;

    left_ms[0] = right_ms[0] = EOS;
    ms = left_ms;

    n = mode_row(wp);      	/* Location. */
#if OPT_VIDEO_ATTRS
    {
	VIDEO_ATTR attr;
	if (wp == curwp)
	    attr = VAMLFOC;
	else
	    attr = VAML;
#if	OPT_REVSTA
	attr |= VAREV;
#endif
	vscreen[n]->v_flag |= VFCHG;
	set_vattrs(n, 0, attr, term.t_ncol);
    }
#else
    vscreen[n]->v_flag |= VFCHG | VFREQ | VFCOL;/* Redraw next time. */
#endif
#if	OPT_COLOR
    ReqFcolor(vscreen[n]) = gfcolor;
    ReqBcolor(vscreen[n]) = gbcolor;
#endif
    bp = wp->w_bufp;
    vtmove(n, 0);                       	/* Seek to right line. */
    if (wp == curwp) {				/* mark the current buffer */
	lchar = '=';
    } else {
#if	OPT_REVSTA
	if (revexist)
	    lchar = ' ';
	else
#endif
	    lchar = '-';
    }

#if OPT_MLFORMAT
    while (*fs) {
	if (*fs != '%')
	    *ms++ = *fs++;
	else {
	    fs++;
	    switch ((fc = *fs++)) {
		case EOS :			/* Null character ! */
		    fs--;
		    break;
		case '%' :
		case ':' :
		    *ms++ = *(fs-1);
		    break;
		case '|' :
		    need_eighty_column_indicator = TRUE;
		    break;
		case '-' :
		    *ms++ = (char)lchar;
		    break;
		case '=' :
		    *ms = EOS;
		    ms = right_ms;
		    break;
		case 'i' :			/* insert mode indicator */
		    *ms++ = (char)modeline_show(wp, lchar);
		    break;
		case 'b' :
		    ms = lsprintf(ms, "%s", bp->b_bname);
		    break;
		case 'm' :
		    if (modeline_modes(bp, (char **)0)) {
			mlfs_prefix(&fs, &ms, lchar);
			(void)modeline_modes(bp, &ms);
			mlfs_suffix(&fs, &ms, lchar);
		    }
		    else
			mlfs_skipfix(&fs);
		    break;
		case 'f' :
		case 'F' : {
		    char *p = 0;
		    if (bp->b_fname != 0
		     && (p = shorten_path(strcpy(temp,bp->b_fname), FALSE)) != 0
		     && *p
		     && !eql_bname(bp,p)
		     && (fc == 'f' ? !is_internalname(p)
			           : is_internalname(p))) {
			mlfs_prefix(&fs, &ms, lchar);
			for (; *p == ' '; p++);
			ms = lsprintf(ms, "%s", p);
			mlfs_suffix(&fs, &ms, lchar);
		    }
		    else
			mlfs_skipfix(&fs);
		    break;
		}
#ifdef WMDRULER
		case 'l' :		/* line number */
		case 'c' :		/* column number */
		case 'p' :		/* percentage */
		case 'L' :		/* number of lines in buffer */

		    if (w_val(wp,WMDRULER) && !is_empty_buf(wp->w_bufp)) {
			int val = 0;
			switch (fc) {
			    case 'l' : val = wp->w_ruler_line; break;
			    case 'L' : val = line_count(wp->w_bufp); break;
			    case 'c' : val = wp->w_ruler_col; break;
			    case 'p' : val = wp->w_ruler_line*100
			                     / line_count(wp->w_bufp); break;
			}
			mlfs_prefix(&fs, &ms, lchar);
			ms = lsprintf(ms, "%d", val);
			mlfs_suffix(&fs, &ms, lchar);
		    }
		    else
			mlfs_skipfix(&fs);
		    break;

#endif
		case 'S' :
		    if (
#ifdef WMDRULER
			!w_val(wp, WMDRULER) ||
#endif
		        is_empty_buf(wp->w_bufp)) {
			mlfs_prefix(&fs, &ms, lchar);
			ms = lsprintf(ms, " %s ", rough_position(wp));
			mlfs_suffix(&fs, &ms, lchar);
		    }
		    else
			mlfs_skipfix(&fs);
		    break;
		default :
		    *ms++ = '%';
		    *ms++ = *(fs-1);
		    break;
	    }
	}
    }
#else	/* hard-coded format */
    ms = lsprintf(ms, "%c%c%c %s ",
    	lchar, modeline_show(wp, lchar), lchar, bp->b_bname);
    if (modeline_modes(bp, &ms))
    	*ms++ = ' ';
    if (bp->b_fname != 0
    && (shorten_path(strcpy(temp,bp->b_fname), FALSE))
    && !eql_bname(bp,temp)) {
	if (is_internalname(temp)) {
	    for (n = term.t_ncol - (13 + strlen(temp) + (int)(ms - left_ms));
			n > 0; n--)
		*ms++ = lchar;
	} else {
	    ms = lsprintf(ms, "is");
	}
	ms = lsprintf(ms, " %s ", temp);
    }
#ifdef WMDRULER
    if (w_val(wp, WMDRULER))
	(void)lsprintf(right_ms, " (%d,%d) %3p",
		wp->w_ruler_line, wp->w_ruler_col, lchar);
    else
#endif
     (void) lsprintf(right_ms, " %s %3p", rough_position(wp), lchar);
#endif /* OPT_MLFORMAT */

    *ms++ = EOS;
    right_len = strlen(right_ms);
    vtputsn(left_ms, term.t_ncol);
    for (n = term.t_ncol - strlen(left_ms) - right_len; n > 0; n--)
	vtputc(lchar);
    vtcol = term.t_ncol - right_len;
    if (vtcol < 0) {
	n = -vtcol;
	vtcol = 0;
    }
    else
	n = 0;
    vtputsn(right_ms+n, term.t_ncol - vtcol);
    if (need_eighty_column_indicator) {		/* mark column 80 */
	int left = -nu_width(wp);
#ifdef WMDLINEWRAP
	if (!w_val(wp,WMDLINEWRAP))
#endif
	 left += w_val(wp,WVAL_SIDEWAYS);
	n = term.t_ncol + left;
	col = 80 - left;

	if ((n > 80) && (col >= 0) && (vtgetc(col) == lchar)) {
	    vtcol = col;
	    vtputc('|');
	}
    }
}

void
upmode(void)	/* update all the mode lines */
{
	register WINDOW *wp;

	for_each_window(wp)
		wp->w_flag |= WFMODE;
}

/*
 * Recompute the given buffer. Save/restore its modes and position information
 * so that a redisplay will show as little change as possible.
 */
#if	OPT_UPBUFF
typedef	struct	{
	WINDOW	*wp;
	struct VAL w_vals[MAX_W_VALUES];
	int	top;
	int	line;
	int	col;
	} SAVEWIN;

static	SAVEWIN	*recomp_tbl;
static	ALLOC_T	recomp_len;

static void
recompute_buffer(BUFFER *bp)
{
	register WINDOW *wp;
	register SAVEWIN *tbl;

	struct VAL b_vals[MAX_B_VALUES];
	ALLOC_T	num = 0;
	BUFFER *savebp = curbp;
	WINDOW *savewp = curwp;
	int	mygoal = curgoal;

	if (!b_val(bp,MDUPBUFF)) {
		b_clr_obsolete(bp);
		return;
	}
	if (recomp_len < bp->b_nwnd) {
		recomp_len = bp->b_nwnd + 1;
		recomp_tbl = (recomp_tbl != 0)
			? typereallocn(SAVEWIN,recomp_tbl,recomp_len)
			: typeallocn(SAVEWIN,recomp_len);
		if (recomp_tbl == 0) {
			recomp_len = 0;
			return;
		}
	}
	tbl = recomp_tbl;

	/* remember where we are, to reposition */
	/* ...in case line is deleted from buffer-list */
	relisting_b_vals = 0;
	relisting_w_vals = 0;
	if (curbp == bp) {
		relisting_b_vals = b_vals;
 	} else {
		curbp = bp;
		curwp = bp2any_wp(bp);
	}
	for_each_visible_window(wp) {
		if (wp->w_bufp == bp) {
			if (wp == savewp)
				relisting_w_vals = tbl[num].w_vals;
			curwp = wp;	/* to make 'getccol()' work */
			curbp = curwp->w_bufp;
			tbl[num].wp   = wp;
			tbl[num].top  = line_no(bp, wp->w_line.l);
			tbl[num].line = line_no(bp, wp->w_dot.l);
			tbl[num].col  = getccol(FALSE);
			save_vals(NUM_W_VALUES, global_w_values.wv,
				tbl[num].w_vals, wp->w_values.wv);
			if (++num >= recomp_len)
				break;
		}
	}
	curwp = savewp;
	curbp = savebp;

	save_vals(NUM_B_VALUES, global_b_values.bv, b_vals, bp->b_values.bv);
	(bp->b_upbuff)(bp);
	copy_mvals(NUM_B_VALUES, bp->b_values.bv, b_vals);

	/* reposition and restore */
	while (num-- != 0) {
		curwp = wp = tbl[num].wp;
		curbp = curwp->w_bufp;
		(void)gotoline(TRUE, tbl[num].top);
		wp->w_line.l = wp->w_dot.l;
		wp->w_line.o = 0;
		if (tbl[num].line != tbl[num].top)
			(void)gotoline(TRUE, tbl[num].line);
		(void)gocol(tbl[num].col);
        	wp->w_flag |= WFMOVE;
		copy_mvals(NUM_W_VALUES, wp->w_values.wv, tbl[num].w_vals);
	}
	curwp = savewp;
	curbp = savebp;
	curgoal = mygoal;
	b_clr_obsolete(bp);
	relisting_b_vals = 0;
	relisting_w_vals = 0;
}
#endif	/* OPT_UPBUFF */

/*
 * Send a command to the terminal to move the hardware cursor to row "row"
 * and column "col". The row and column arguments are origin 0. Optimize out
 * random calls. Update "ttrow" and "ttcol".
 */
void
movecursor(int row, int col)
{
	beginDisplay();
	if ((row!=ttrow || col!=ttcol)
	 && (row >= 0 && row < term.t_nrow)
	 && (col >= 0 && col < term.t_ncol))
        {
	        ttrow = row;
	        ttcol = col;
	        TTmove(row, col);
        }
	endofDisplay();
}

void
bottomleft(void)
{
	movecursor(term.t_nrow-1, 0);
}

/*
 * Erase the message line. This is a special routine because the message line
 * is not considered to be part of the virtual screen. It always works
 * immediately; the terminal buffer is flushed via a call to the flusher.
 */
void
mlerase(void)
{
	beginDisplay();
	kbd_erase_to_end(0);
	kbd_flush();
	endofDisplay();
}

static char *mlsavep;

void
mlsavec(int c)
{
	if (mlsavep - mlsave < NSTRING-1) {
		*mlsavep++ = (char)c;
		*mlsavep = EOS;
	}
}

/*
 * Write a message into the message line only if appropriate.
 */
/* VARARGS1 */
void
mlwrite(const char *fmt, ...)
{
	va_list ap;
	/* if we are not currently echoing on the command line, abort this */
	if (global_b_val(MDTERSE) || kbd_replaying(FALSE) || discmd == FALSE) {
		bottomleft();
		return;
	}
	va_start(ap,fmt);
	mlmsg(fmt,&ap);
	va_end(ap);
}

/*	Put a string out to the message line regardless of the
	current $discmd setting. This is needed when $debug is TRUE
	and for the write-message and clear-message-line commands
	Also used for most errors, to be sure they're seen.
*/
/* VARARGS1 */
void
mlforce(const char *fmt, ...)
{
	va_list ap;
	va_start(ap,fmt);
	mlmsg(fmt,&ap);
	va_end(ap);
}

/* VARARGS1 */
void
mlprompt(const char *fmt, ...)
{
	va_list ap;
	int osgarbf = sgarbf;
	if (discmd == FALSE) {
		bottomleft();
		return;
	}
	sgarbf = FALSE;
	va_start(ap,fmt);
	mlmsg(fmt,&ap);
	va_end(ap);
	sgarbf = osgarbf;
}

/* VARARGS */
void
dbgwrite(const char *fmt, ...)
{
	char temp[80];

	va_list ap;	/* ptr to current data field */
	va_start(ap,fmt);
	lsprintf(temp, "[press ^G to continue] %s", fmt);
	mlmsg(temp,&ap);
	va_end(ap);
	beginDisplay();
	while (TTgetc() != '\007')
		;
	endofDisplay();
}

/*
 * Do the real message-line work.  Keep track of the physical cursor
 * position. A small class of printf like format items is handled.
 * Set the "message line" flag TRUE.
 */
static void
mlmsg(const char *fmt, va_list *app)
{
	static	int	recur;
	int	end_at;
	int	do_crlf = (strchr(fmt, '\n') != 0
			|| strchr(fmt, '\r') != 0);

	if (recur++) {
		/*EMPTY*/;
	} else if (sgarbf) {
		/* then we'll lose the message on the next update(), so save it now */
		mlsavep = mlsave;
#if	OPT_POPUP_MSGS
		if (global_g_val(GMDPOPUP_MSGS) || (curwp == 0)) {
			TRACE(("mlmsg popup_msgs #1 for '%s'\n", fmt))
			popup_msgs();
			msg_putc('\n');
			dfoutfn = msg_putc;
		} else
#endif
		  dfoutfn = mlsavec;
		dofmt(fmt,app);
	} else {
		beginDisplay();

		kbd_expand = -1;
#if	OPT_POPUP_MSGS
		if (global_g_val(GMDPOPUP_MSGS)) {
			TRACE(("mlmsg popup_msgs #2 for '%s'\n", fmt))
			popup_msgs();
			if (mlsave[0] == EOS) {
				msg_putc('\n');
				dfoutfn = msg_putc;
			} else {
				dfoutfn = kbd_putc;
			}
		} else
#endif
		  dfoutfn = kbd_putc;

		if (*fmt != '\n') {
			kbd_erase_to_end(0);
			dofmt(fmt,app);
			kbd_expand = 0;

			/* if we can, erase to the end of screen */
			end_at = wminip->w_dot.o;
			kbd_erase_to_end(end_at);
			mlsave[0] = EOS;
			kbd_flush();
		}
		if (do_crlf) {
			kbd_openup();
		}
		endofDisplay();
	}
	recur--;
}

/*
 * Do the equivalent of 'perror()' on the message line
 */
void
mlerror(const char *s)
{
#if HAVE_STRERROR
	if (errno > 0)
		mlwarn("[Error %s: %s]", s, strerror(errno));
	else
		mlwarn("[Error %s: unknown system error %d]", s, errno);
#else
#if HAVE_SYS_ERRLIST
	if (errno > 0 && errno < sys_nerr)
		mlwarn("[Error %s: %s]", s, sys_errlist[errno]);
	else
		mlwarn("[Error %s: unknown system error %d]", s, errno);
#else
	mlwarn("[Error %s, errno=%d]", s, errno);
#endif /* HAVE_SYS_ERRLIST */
#endif /* HAVE_STRERROR */
}

/*
 * Emit a warning message (with alarm)
 */
/* VARARGS1 */
void
mlwarn(const char *fmt, ...)
{
	va_list ap;
	va_start(ap,fmt);
	mlmsg(fmt,&ap);
	va_end(ap);
	kbd_alarm();
}

/*
 * Local sprintf -- similar to standard libc, but
 *  returns pointer to null character at end of buffer, so it can
 *  be called repeatedly, as in:
 *	cp = lsprintf(cp, fmt, args...);
 *
 */

static	char *lsp;

static void
lspputc(int c)
{
	*lsp++ = (char)c;
}

/* VARARGS1 */
char *
lsprintf(char *buf, const char *fmt, ...)
{
	va_list ap;
	va_start(ap,fmt);

	lsp = buf;
	dfoutfn = lspputc;

	dofmt(fmt,&ap);
	va_end(ap);

	*lsp = EOS;
	return lsp;
}


/*
 * Buffer printf -- like regular printf, but puts characters
 *	into the BUFFER.
 */
void
bputc(int c)
{
	if (c == '\n')
		(void)lnewline();
	else
		(void)linsert(1,c);
}

/* printf into curbp, at DOT */
/* VARARGS */
void
bprintf(const char *fmt, ...)
{
	va_list ap;

	dfoutfn = bputc;

	va_start(ap,fmt);
	dofmt(fmt,&ap);
	va_end(ap);

}

#if defined( SIGWINCH) && ! DISP_X11
/* ARGSUSED */
SIGT
sizesignal (int ACTUAL_SIG_ARGS GCC_UNUSED)
{
	int w, h;
	int old_errno = errno;

	getscreensize (&w, &h);

	if ((h > 1 && h != term.t_nrow) || (w > 1 && w != term.t_ncol))
		newscreensize(h, w);

	setup_handler(SIGWINCH, sizesignal);
	errno = old_errno;
	SIGRET;
}
#endif

void
newscreensize (int h, int w)
{
	/* do the change later */
	if (im_displaying
#if OPT_WORKING
	|| !i_displayed
#endif
	) {
		chg_width = w;
		chg_height = h;
		return;
	}
	chg_width = chg_height = 0;
	if ((h > term.t_mrow) || (w > term.t_mcol)) {
		int or, oc;
		or = term.t_mrow;
		oc = term.t_mcol;
		term.t_mrow = h;
		term.t_mcol = w;
		if (!vtinit()) { /* allocation failure */
			term.t_mrow = or;
			term.t_mcol = oc;
			return;
		}
	}
	if (!newlength(TRUE,h) || !newwidth(TRUE,w))
		return;

	(void)update(TRUE);
}

#if OPT_WORKING
/*
 * Start the timer that controls the "working..." message.
 */
static void
start_working(void)
{
	setup_handler(SIGALRM,imworking);
	(void)alarm(1);
	im_timing = TRUE;
}

/*
 * When we stop the timer, we should cleanup the "working..." message.
 */
static void
stop_working(void)
{
	if (mpresf) {	/* erase leftover working-message */
		int	save_row = ttrow;
		int	save_col = ttcol;
		kbd_overlay(0);
		kbd_flush();
		movecursor(save_row, save_col);
		TTflush();
	}
}

/*
 * Displays alternate
 *	"working..." and
 *	"...working"
 * at the end of the message line if it has been at least a second since
 * displaying anything or waiting for keyboard input.  The cur_working and
 * max_working values are used in 'slowreadf()' to show the progress of reading
 * large files.
 */

/*ARGSUSED*/
SIGT
imworking (int ACTUAL_SIG_ARGS GCC_UNUSED)
{
	static	const	char *const msg[] = {"working", "..."};
	static	int	flip;
	static	int	skip;

	signal_was = SIGALRM;	/* remember this was an alarm */

	if (no_working) /* brute force, for debugging */
		return;

	/* (if GMDWORKING is _not_ set, or MDTERSE is set, we're allowed
	 * to erase, but not to write.  and if we do erase, we don't
	 * reschedule the alarm, since setting the mode will call us
	 * again to start things up)
	 */

	if (im_displaying || !i_displayed) { /* look at the semaphore first! */
		/*EMPTY*/;
	} else if (im_waiting(-1)) {
		im_timing = FALSE;
		stop_working();
		return;
	} else if (ShowWorking()) {
		if (skip) {
			skip = FALSE;
		} else {
#if DISP_X11
			x_working();
#else
			char	result[20];
			result[0] = EOS;
			if (cur_working != 0
			 && cur_working != old_working) {
				char	temp[20];
				int	len = cur_working > 999999L ? 10 : 6;

				old_working = cur_working;
				strcat(result, right_num(temp, len, cur_working));
				if (len == 10)
					/*EMPTY*/;
				else if (max_working != 0) {
					strcat(result, " ");
					strcat(result, right_num(temp, 2,
						(100 * cur_working) / max_working));
					strcat(result, "%");
				} else
					strcat(result, " ...");
			} else {
				strcat(result, msg[ flip]);
				strcat(result, msg[!flip]);
			}
			kbd_overlay(result);
			kbd_flush();
#endif
		}
	} else {
		stop_working();
		skip = TRUE;
		return;
	}
	start_working();
	flip = !flip;
}
#endif	/* OPT_WORKING */

/*
 * Maintain a flag that records whether we're waiting for keyboard input.  As a
 * side-effect, restart the 'working' timer if we see that it's been made
 * inactive while we were waiting.
 */
int
im_waiting(int flag)
{
	static int waiting;
	if (flag >= 0) {	/* TRUE or FALSE set, negative used to query */
		waiting = flag;
#if OPT_WORKING
		if (!waiting && !im_timing && ShowWorking())
			start_working();
#endif
	}
	return waiting;
}

#if defined(SIGWINCH) || OPT_WORKING
/*
 * Set the semaphore so that we don't try to do I/O while we're being
 * interrupted.
 */
void
beginDisplay (void)
{
	im_displaying++;
}

/*
 * Reset the semaphore.
 */
void
endofDisplay (void)
{
	if (im_displaying)
		im_displaying--;
}
#endif

#if	OPT_PSCREEN
/* Most of the code in this section is for making the message line work
 * right...it shouldn't be called to display the rest of the screen.
 */
static int psc_row;
static int psc_col;

#define SWAP_INT(x,y) \
	do { (x) = (x)+(y); (y) = (x)-(y); (x) = (x)-(y); } one_time
#define SWAP_VT_PSC \
	do { SWAP_INT(vtcol, psc_col); SWAP_INT(vtrow, psc_row); } one_time

OUTC_DCL
psc_putchar(OUTC_ARGS)
{
    if (c == '\b') {
	if (psc_col > 0)
	    psc_col--;
    }
    else {
	SWAP_VT_PSC;
	vtputc(c);
	vscreen[vtrow]->v_flag |= VFCHG;
	SWAP_VT_PSC;
    }
    OUTC_RET c;
}

void
psc_flush(void)
{
    updateline(term.t_nrow-1, 0, term.t_ncol);
    TTpflush();
}

void
psc_move(int row, int col)
{
    psc_row = row;
    psc_col = col;
}

void
psc_eeol(void)
{
    if (ttrow >= 0 && ttrow < term.t_nrow && ttcol >= 0) {
	VIDEO_ATTR *vp = &vscreen[ttrow]->v_attrs[ttcol];
	char *cp = &vscreen[ttrow]->v_text[ttcol];
	char *cplim = &vscreen[ttrow]->v_text[term.t_ncol];
	vscreen[ttrow]->v_flag |= VFCHG;
	while (cp < cplim) {
	    *vp++ = 0;
	    *cp++ = ' ';
	}
    }
}

void
psc_eeop(void)
{
    int saverow = ttrow;
    int savecol = ttcol;
    while (ttrow < term.t_nrow) {
	psc_eeol();
	ttrow++;
	ttcol = 0;
    }
    ttrow = saverow;
    ttcol = savecol;
}

/* ARGSUSED */
void
psc_rev(UINT huh GCC_UNUSED)
{
    /* do nothing */
}

#endif	/* OPT_PSCREEN */

/* For memory-leak testing (only!), releases all display storage. */
#if NO_LEAKS
void	vt_leaks(void)
{
	vtfree();
#if OPT_UPBUFF
	FreeIfNeeded(recomp_tbl);
#endif
}
#endif
