/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is	*
 * provided to you without charge, and with no warranty.  You may give	*
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.						*
 ************************************************************************/
/*
 * Modified: 1988-1990 by T.R.Hageman
 * some efficiency improvements;
 * re-worked ModeLine;
 * added horizontal window shift to DrawMesg if Asking;
 * explicitly erase Standout Mode lines instead of usurping 8th bit of
 * character as standout flag (see also screen.c and term.c)
 * move ID_CHAR stuff to screen.c
 */

#define NO_PROCDECL		/* kludge for teensy pdp11 compiler */

#include "jove.h"

RCS("$Id: disp.c,v 14.32.0.10 1994/04/29 11:59:22 tom Exp tom $")

#include "ctype.h"
#include "io.h"
#include "process.h"	/* for {HOLD,RELSE}_ALARM */
#include "screen.h"
#ifndef NO_PROCDECL
#   include "termcap.h"
#else
extern const char *XS, *SO;
extern int	SG;
#endif

#ifdef TINY
#   undef putch
#endif

private	void	AddLines __(( int _(num), int _(top), int _(bot) )),
		DelLines __(( int _(num), int _(top), int _(bot) )),
		DoIDline __(( int _(start) )),
		UpdLine __(( int _(linenum) ));

private int	ModeLine __(( Window *_(w) ));

DEF_INT( "mode-line-should-standout", BriteMode, V_BOOL|V_MODELINE ) = YES; _IF(def PRIVATE)
DEF_INT( "visible-bell", VisBell, V_BOOL ) ZERO;

int	RingBell ZERO;	/* So if we have a lot of errors ...
			   ring the bell only ONCE */
void
rbell()
{
	if (False(Quiet))
		RingBell++;
}

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

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

	line2 = line2->l_next;

	do {
		for (lp = line1; (lp = lp->l_next) != line2; ) {
			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);
}

void
redisplay()
{
	if (!Asking) {		/* ...to allow scrolling while in ask */
		register Window	*w = curwind;	/* make curwind consistent */
		register Buffer	*b = w->w_bufp;

		w->w_line = b->b_dot;
		w->w_char = b->b_char;
	}

	if (InputPending = charp())
		return;

	HOLD_ALARM;

		if (RingBell) {			/* ring the bell */
			dobell(1);
			RingBell = 0;
		}
		if (UpdMesg)			/* update message line */
			DrawMesg(YES);
	    {					/* update windows */
		register int	lineno = 0;
		register Window	*w = fwind;

		do {
			UpdWindow(w, lineno);
			lineno += w->w_height;
			w = w->w_next;
		} while (lineno < ILI);
	    }
		scursoff();
	    {					/* update changed lines */
		register struct scrimage *des_p = DesiredScreen;
		register struct scrimage *phys_p = PhysScreen;
		register int	i = 0,
				done_ID = NO;
		do {
			if (!done_ID && (des_p->s_id != phys_p->s_id)) {
				DoIDline(i);
				done_ID++;
			}
			if ((des_p->s_flags != phys_p->s_flags) ||
			    (des_p->s_id != phys_p->s_id) ||
			    (des_p->s_vln != phys_p->s_vln) ||
#ifdef COLOR
			    (des_p->s_color != phys_p->s_color) ||
#endif
			    (des_p->s_offset != phys_p->s_offset)) {
				UpdLine(i);
				if (InputPending)
					goto ret;
			}
			des_p++, phys_p++;
		} while (++i < ILI);
	    }
	    {
		register int	line, col;

		if (Asking) {			/* position cursor */
			line = ILI;
			col = calc_pos(mesgbuf, Asking); /* nice kludge... */
		} else {
			register Window	*w = curwind;
#ifdef WINDOWS
			if (w->w_control)
				SetScrollBar(w->w_control);
#endif
			line = w->w_dotline;
			col = w->w_dotcol - PhysScreen[line].s_offset;
		}
		Placur(line, col);
		set_color(PhysScreen[line].s_color);
	    }
		curson();
		UpdModLine = 0;
ret:
	RELSE_ALARM;
#ifdef WINDOWS
	if (Windchange)
		docontrols();
#endif
}

DEF_CMD( "update-screen", UpdScreen, NO )
{
	inIOread++;	/* to force redisplay to complete. */
	redisplay();
	inIOread--;
}

/* calc_pos() returns the position on the line, that c_char represents
   in line. */

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

	while ((--c_char >= 0) && (c = *cp++)) {
		UpdVisPos(pos, c);
	}
	return pos;
}

int	UpdModLine ZERO,
	UpdMesg ZERO,
	CanScroll ZERO;
	/*
	 * [TRH] Add scroll limit variable. If the fraction of lines to be
	 * added or deleted compared to the affected region is larger than
	 * this limit, the affected region is repainted instead of being
	 * scrolled. The variable ScrollLimit can be changed by the user.
	 * It is given as a percentage.  A suitable default value for
	 * ScollLimit is chosen by IDline_setup.
	 */
DEF_INT( "repaint-threshold", ScrollLimit, V_BASE10|MAX(100) ) ZERO;

#define ShouldScroll(n,reg)	(((n)*100)/(reg) < ScrollLimit)

private void
DoIDline(start)
{
	register struct scrimage
			*des_p,
			*phys_p;
	register int	i,
			top,
			num,
			bottom;
	register void	(*doit)__(( int _(num), int _(top), int _(bottom) ));

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

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

	i = start;
	do {
		for (des_p = &DesiredScreen[i], phys_p = &PhysScreen[i];
		     des_p->s_id == phys_p->s_id;  des_p++, phys_p++)
			if (++i >= ILI)
				return;

		/* found a "dirty" region; scan it for scrolled lines */

		bottom = ILI;
		top = i;

		for (;;) {
			if (++i < bottom) {
				des_p++;
				phys_p++;
			} else {
				/*
				 * We hit the end of the dirty region.
				 * Advance top of region, reset scan pointers;
				 * bail out when only a single line left.
				 */
				++top;
				if ((i = top + 1) >= bottom)
					return;

				des_p = &DesiredScreen[i];
				phys_p = &PhysScreen[i];
			}

			if (des_p->s_id == phys_p->s_id) {
				/* the "real" end of dirty region */
				bottom = i;
				continue;
			}
			if (des_p->s_id == PhysScreen[top].s_id) {
				phys_p = &PhysScreen[top];
				doit = AddLines;
				break;
			}
			if (phys_p->s_id == DesiredScreen[top].s_id) {
				des_p = &DesiredScreen[top];
				doit = DelLines;
				break;
			}
		}
		/* we only get here when we found a scrollable region */

		num = i - top;
		/*
		 * scan for end of scrolled region.
		 * At this point we made sure that des_p and phys_p point to
		 * equal lines, so advance pointers first.
		 */
		while ((++des_p)->s_id == (++phys_p)->s_id && des_p->s_id)
			if (++i >= ILI)
				break;

		/* scroll it */
		if (ShouldScroll(num, i - top)) {
			set_color(phys_p[-1].s_color);
			cursoff();
			(*doit)(num, top, i);
		}

		/* continue the scan */
	} while (++i < ILI);
}

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

/* PRE: ((num > 0) && (top + num < bot)) */

private void
AddLines(num, top, bot)
{
	register struct scrimage
		*dest = &PhysScreen[bot],
		*src = &dest[-num],
		*end = &PhysScreen[top];

	v_ins_line(num, top, bot);

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

	do {
		*dest = *src;
	} while (--dest, --src >= end);

	do {
		dest->s_id = 0;
		dest->s_flags = 0;	/* for standout */
#ifdef COLOR
		if (XS) dest->s_color = 0;
#endif
	} while (--dest >= end);
}

private void
DelLines(num, top, bot)
{
	register struct scrimage
		*dest = &PhysScreen[top],
		*src = &dest[num],
		*end = &PhysScreen[bot];

	v_del_line(num, top, bot);

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

	do {
		*dest = *src;
	} while (++dest, ++src <= end);

	do {
		dest->s_id = 0;
		dest->s_flags = 0;	/* for standout */
#ifdef COLOR
		if (XS) dest->s_color = 0;
#endif
	} while (++dest <= end);
}

DEF_INT( "scroll-all-lines", ScrollAll, V_BOOL ) ZERO; _IF(def PRIVATE)
DEF_INT( "auto-scroll-margin", AutoScroll, V_BASE10|MAX(50) ) ZERO; _IF(def AUTOSCROLL)_IF(def PRIVATE)
#if (HIGHLIGHT)
DEF_INT( "highlight", HighLight, V_BOOL ) = YES; _IF( HIGHLIGHT)
#endif

/*
 * 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.
 */
void
UpdWindow(w, start)
register Window *w;
{
	register Line	*lp;
	int		dot_offset,	/* start column of dot line */
			num_offset = 0;	/* set if lines are numbered */
#ifdef COLOR
	char		color = w->w_bufp->b_color;
#endif
#if (W_TOPNUM)
	int		old_dotline = w->w_dotline;
#endif

	if (w->w_flags & W_NUMLINES)
		num_offset = 8;

	if (w->w_flags & W_CURGONE) {
		register Buffer	*b = w->w_bufp;

		w->w_line = b->b_dot;
		w->w_char = b->b_char;
	}
	if (w->w_flags & W_TOPGONE)
		CentWind(w);			/* Reset topline of screen */

	w->w_flags &= ~(W_CURGONE|W_TOPGONE);

	/* First, make sure that current line is visible in the window... */
    {
	register int	i,
			end = start + SIZE(w);	/* Bottom of window */
      {
	register int	ntries = 0;	/* # of tries at updating window. */

	i = start;
	lp = w->w_top;
	while (lp != w->w_line) {
		if (lp && ++i < end) {
			lp = lp->l_next;
			continue;
		}
		/* if we get here, we didn't find dotline in the window */
		if (ntries == 0)
			CalcWind(w);
#ifndef TINY
		else if (ntries == 1) {		/* this shouldn't happen. */
			w->w_top = w->w_line = w->w_bufp->b_first;
			dobell(2);
			f_mess("?redisplay?");
		}
#endif
		else			/* this REALLY shouldn't happen! */
			finish(1);
		ntries++;
		i = start;
		lp = w->w_top;
	}
      }
	/* Now, do some calculations for the current line.  If the new
	   dotcol is out of range, reselect a horizontal window.  Allow
	   cursor at rightmost column, i.e., don't scroll horizontally
	   if we are at end of line. */
      {
#ifndef TINY
	char	*base = lcontents(lp);
#   define	NUM_OFFSET (base[w->w_char] ? num_offset : num_offset - 1)
#else
#   define	base	lcontents(lp)
#   define	NUM_OFFSET num_offset
#endif
	w->w_dotcol = calc_pos(base, w->w_char);
	if (True(ScrollAll) || (dot_offset = PhysScreen[i].s_offset) == 0)
		dot_offset = w->w_offset;
	dot_offset = HorWindow(w->w_dotcol, dot_offset, NUM_OFFSET);
	/* compensate for displayed line numbers */
	w->w_dotcol += num_offset;
	/* update window's offset if we're scrolling rigidly with point */
	if (True(ScrollAll) && (w->w_offset != dot_offset)) {
		updmodline();
		w->w_offset = dot_offset;
	}
	lp = w->w_top;
#undef base
#undef NUM_OFFSET
      }
#ifdef AUTOSCROLL
	/* Now, do the EDT-style auto-scrolling, IF not inhibited, AND
	   auto-scrolling is enabled, AND we are current window, AND
	   we have a transition INTO the `forbidden' region. */
      {
	register int	scrollmargin;

	if (w->w_flags & W_NOAUTOSCROLL)
		w->w_flags &= ~W_NOAUTOSCROLL;
	else if ((scrollmargin = AutoScroll) && (w == curwind) &&
		 (scrollmargin = scrollmargin * SIZE(w) / 100)) {
		start += scrollmargin;
		if (i < start) {
			if (start <= w->w_dotline) {
				/* try to scroll down (by moving topline up)
				   so that dotline ends up at auto-scroll
				   margin.  This may not go all the way if
				   we are near the beginning of the buffer. */
				w->w_topnum -= (start - i);
				do {
					if (lp->l_prev)
						lp = lp->l_prev;
					else {
						w->w_topnum += (start - i);
						break;
					}
				} while (++i < start);
			}
		}
		else {
			/* border case. sigh... */
			if ((end -= scrollmargin) == start)
				end += 1;
			if ((i >= end) && (w->w_dotline < end)) {
				/* scroll up (by moving topline down) so that
				   dotline ends up at auto-scroll margin.
				   This cannot fail. */
				w->w_topnum += (i - end + 1);
				do {
					lp = lp->l_next;
				} while (--i >= end);
			}
		}
		start -= scrollmargin;
		w->w_top = lp;
	}
      }
#endif /* AUTOSCROLL */
	w->w_dotline = i;
    }
    {
	register struct scrimage
			*des_p = &DesiredScreen[start],
			*end_p = &des_p[SIZE(w)];
	register int	lnum = (num_offset) ? w->w_topnum : 0;

	do {
		if (lp == NULL) {
			/* no buffer line associated with this screen line */
			static struct scrimage	clean_plate ZERO;

			*des_p = clean_plate;
#ifdef COLOR
			des_p->s_color = color;
#endif
			continue;
		}

		des_p->s_window = w;
		des_p->s_lp = lp;
		des_p->s_id = lp->l_dline & ~DIRTY;
		des_p->s_flags = (int)(lp->l_dline) & DIRTY;
		if (des_p->s_vln = lnum)
			lnum++;
		des_p->s_offset = (lp == w->w_line) ? dot_offset : w->w_offset;
#if (HIGHLIGHT)
		if (lp == w->w_highlighted_line && True(HighLight))
			des_p->s_flags |= HIGHLIGHT;
#endif
		lp = lp->l_next;
#ifdef COLOR
		des_p->s_color = color;
#endif
	} while (++des_p < end_p);

	/* mode line */
	des_p->s_window = w;
	des_p->s_flags = (UpdModLine
#if (W_TOPNUM)
			  || (w->w_flags & W_TOPNUM &&
			      w->w_line != PhysScreen[old_dotline].s_lp)
#endif
			  ) ? MODELINE|DIRTY : MODELINE;
	des_p->s_id = (disk_line) w->w_bufp;
#ifdef COLOR
	des_p->s_color = color;
#endif
	if (False(BriteMode))
		return;

	if (!SO) {
		BriteMode = NO;
		return;
	}
	des_p->s_flags |= STANDOUT;
#ifdef COLOR
	/*
	 * (we only get here when mode lines stand out.)
	 * Black-and-white mode line looks better; try to make it stand out
	 * against the rest of the window.  Also make sure the mode line of
    	 * the PREVIOUS window stands out against the body of both current
    	 * and previous window.  In the case of adjacent black- and white-
	 * background windows we resort to a white-on-blue mode line.
	 * Do the same check against the message line for the last window.
	 */

	if (color == 0)
		color = DefColor;

	des_p->s_color = (BG_COLOR(color) == FG_COLOR(DEF_COLOR)) ?
			MK_COLOR(BG_COLOR(DEF_COLOR), FG_COLOR(DEF_COLOR)) :
			DEF_COLOR;

	if (w->w_next == fwind) {  /* last window; check against mesgline */
		register char	mcolor = mesg_color;

		if (mcolor == 0)
			mcolor = DefColor;

		if (BG_COLOR(mcolor) == FG_COLOR(des_p->s_color)) {
			if (BG_COLOR(color) != BG_COLOR(des_p->s_color))
				des_p->s_color ^= MK_COLOR(WHITE, WHITE);
			else
				des_p->s_color = MK_COLOR(BLUE, WHITE);
		}
	}
	if (start == 0)		/* first window */
		return;

	des_p = &DesiredScreen[start - 1];

	if (BG_COLOR(color) == FG_COLOR(des_p->s_color)) {
		color = des_p[-1].s_color;

		if (color == 0)
			color = DefColor;

		if (BG_COLOR(color) != BG_COLOR(des_p->s_color))
			des_p->s_color ^= MK_COLOR(WHITE, WHITE);
		else
			des_p->s_color = MK_COLOR(BLUE, WHITE);
	}
#endif
    }
}

/* Write whatever is in mesgbuf (maybe we are Asking, or just printed
 * a message).	Turns off the UpdMesg line flag.
 */
void
DrawMesg(abortable)
{
	if (abortable && charp())
		return;

	i_set(ILI, 0);
	req_color(mesg_color);

	if (swrite(mesgbuf, abortable)) {
		cl_eol();
#ifdef COLOR
		PhysScreen[ILI].s_color = mesg_color;
#endif
		UpdMesg = 0;
	}
	flusho();
}

/* Reselect a new horizontal window */

int
HorWindow(goal, offset, adjust)
register int	goal;
register int	offset;
int		adjust;
{
	register int	length = CO - adjust;

	if (goal <= offset)
		offset = 0;
	if (goal >= offset + length - 1)
		offset = goal - (length >> 1);
	return offset;
}

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

private void
UpdLine(linenum)
int	linenum;
{
	register int			vis_attrib = linenum;
#	define linenum	vis_attrib /* alias! */
	register struct scrimage	*phys_p = &PhysScreen[linenum];
	register struct scrimage	*des_p = &DesiredScreen[linenum];

	i_set(linenum, 0);
#	undef linenum
	des_p->s_flags &= ~DIRTY;

	req_color(des_p->s_color);

	if (phys_p->s_flags & ~des_p->s_flags & VIS_ATTRIB)
		cl_standout();

	if (vis_attrib = des_p->s_flags & VIS_ATTRIB)
		standout(vis_attrib);

	if (des_p->s_flags & MODELINE) {
		if (!ModeLine(des_p->s_window))
			goto ret;
	}
	else if (des_p->s_id == 0) {
		if (phys_p->s_id == 0)	/* Not the same ... make sure */
			goto ret;
	}
	else {
		des_p->s_lp->l_dline &= ~DIRTY;
#ifdef ID_CHAR
		if (True(UseIC)) {
			char	outbuf[MAXCOLS+1];
			int	fromcol = 0;

			if (des_p->s_vln) {
				fromcol = sprintf(outbuf, "%6d  ", des_p->s_vln);
			}

			DeTab(des_p->s_offset, lcontents(des_p->s_lp),
			      (outbuf + fromcol), (CO - fromcol),
			      (des_p->s_window->w_flags & W_VISSPACE));

			if (IDchar(outbuf, linenum, 0)) {
				*phys_p = *des_p;
				goto ret;
			}
			i_set(linenum, 0);
			if (!swrite(outbuf, YES)) {
				phys_p->s_id = -1;
				goto ret;
			}
		}
		else /* the following `if' */
#endif /* ID_CHAR */
		if ((des_p->s_vln &&
		     !swrite(sprint("%6d  ", des_p->s_vln), YES)) ||
		    !BufSwrite(des_p)) {
			phys_p->s_id = -1;
			goto ret;
		}

	}
	*phys_p = *des_p;
	cl_eol();
ret:
	if (vis_attrib)
		standout(0);
}

/* Print the mode line. */

private char	*mend_p;

private char *mode_app __(( char *_(mp), const char *_(str) ));
private char *
mode_app(mp, str)
register char		*mp;
register const char	*str;
{
	while (mp <= mend_p)
		if (!(*mp++ = *str++)) {
			--mp;		/* back over the null */
			break;
		}
	return mp;
}

DEF_STR( "mode-line", ModeFmt, 120, V_STRING|V_MODELINE ) _IF(def PRIVATE) =
#ifndef TINY
	"%3c %w %[%sJOVE (%M)   Buffer: %b  \"%f\" %P %]%s%m*- %p%s%((%t)%s%)%e";
#else
	"%3c %w %[%sJOVE (%M)   Buffer: %b  \"%f\" %]%s%m*- %e";
#endif

private int
ModeLine(w)
Window *w;
{
	extern int	i_line;
	char		line[MAXCOLS+2],
			fillc = True(BriteMode) ? ' ' : '-';
	const char	*app_p;
	register char	*mode_p = line;
	register char	*fmt = ModeFmt;
	register int	n,
			ign_some = NO,
			glue = 0;
	register Buffer	*thisbuf = w->w_bufp;

	mend_p = &mode_p[CO - SG - SG];

			/* one too far so too long mode lines will be `!'ed */
	while ((*mode_p = *fmt++) && mode_p <= mend_p) {
		if (*mode_p != '%') {
#if NO
			if (*mode_p == '\\')
				if ((*mode_p = *fmt++) == '\0') {
					--fmt;
					break;
				}
#endif
			if (!ign_some)
				mode_p++;
			continue;
		}
		n = 0;
		while (isdigit(*fmt))
			n = n * 10 + *fmt++ - '0';

		if (*fmt == '\0')	/* char after the '%' */
			break;

		if (ign_some) {		/* %( %) may be nested. */
			if (*fmt == '(')
				ign_some++;
			if (*fmt++ == ')')
				ign_some--;
			continue;
		}
		switch (*fmt++) {
		case '%':
			mode_p++;
			continue;

		case '(':	/* conditionally ignore */
			if (n == 0) {
				if (w->w_next == fwind) /* bottom window. */
					continue;
#ifndef TINY
			} else if (n < 20) {		/* special cases: */

				if (n >= 10) {	/* negate condition */
					n -= 10;
					ign_some++;
				}
				switch (n) {

				case 7:	/* "~F" filename is undefined. */
					if (thisbuf->b_fname)
						continue;
					break;

				case 8:	/* "B=F" bufname equals filename. */
					if (thisbuf->b_fname == NULL ||
					    strcmp(basename(thisbuf->b_fname),
						   thisbuf->b_name) != 0)
						continue;
					break;

				default: /* <n> matches buffer type. */
					if (thisbuf->b_type == n)
						continue;
					break;
				}
#endif
			} else {
				if (CO >= n)	/* screen wide enough. */
					continue;
			}
#ifdef TINY
			ign_some++;
#else
			ign_some ^= 1;
#endif
			continue;

		case '[':	/* edit-recursion depth */
		case ']':
			for (n = RecDepth;  --n >= 0 && mode_p < mend_p; )
				*mode_p++ = fmt[-1];
			continue;

		case 'b':	/* buffer name */
			app_p = thisbuf->b_name;
			break;

		case 'C':
#ifdef MAIL
			if (chkmail(NO)) {
#   if !vms
				app_p = "[New mail]";
#   else
				app_p = "[Message]";
#   endif
				break;
			}
#else
#   if !unix		/* (ab)use it as CapsLock indicator */
		    {	extern int	CapsLock;
			if (CapsLock) {
				app_p = "[Caps]";
				break;
			}
		    }
#   endif
#endif
			continue;

		case 'c':	/* fill character(s) */
			do {
				*mode_p++ = fillc;
			} while (mode_p < mend_p && --n > 0);
			continue;
#ifdef CHDIR
		case 'd':	/* print working directory */
			app_p = pr_name(pwd());
			break;
#endif
		case 'e':	/* stretchable glue */
			do {
				glue++;
				*mode_p++ = '\0';	/* glue marker */
			} while (mode_p < mend_p && --n > 0);
			continue;

		case 'F':	/* short file name */
			if (thisbuf->b_fname) {
				app_p = basename(thisbuf->b_fname);
				break;
			}
			/* else fall into... */
		case 'f':	/* full file name */
			app_p = filename(thisbuf);
			break;
#ifdef LOAD_AV
		case 'l':	/* load-average */
		    {
			int	theavg = get_la();

			app_p = sprint("%d.%02d", theavg / 100, theavg % 100);
		    }
		    break;
#endif
		case 'M':	/* Major + Minor modes */
		    {
			extern const char	* const MajorName[],
						* const MinorName[];
			register const char	* const *minor_p = MinorName;

			mode_p = mode_app(mode_p, MajorName[thisbuf->b_major]);
			n = thisbuf->b_minor;
			do {
				if (n & 1)
					mode_p = mode_app(mode_p, *minor_p);
				*minor_p++;
			} while (n >>= 1);

			if (Defining)
				mode_p = mode_app(mode_p, "Def ");
			mode_p--;	/* Back over the extra space. */
			continue;
		    }

		case 'm':	/* buffer modified flag */
			*mode_p++ = (IsModified(thisbuf)) ? *fmt++ : *++fmt;
			fmt++;			/* skip the other character */
			continue;

		case 'n':	/* buffer number */
			app_p = itos(bufno(thisbuf));
			break;
#ifdef IPROCS
		case 'p':
			if (thisbuf->b_type != B_IPROCESS)
				continue;
			app_p = sprint("(%s)", pstate(thisbuf->b_process));
			break;
#endif /* IPROCS */
#if (W_TOPNUM)
		case 'P':	/* estimated position in buffer */
		    {
			register Line	*lp;

			/* Get the last line in the window.  This assumes
			   that `i_line' is set up to the on_screen line
		           number of this mode line. */
			if ((lp = DesiredScreen[i_line - 1].s_lp) == NULL ||
			    lastp(thisbuf, lp)) {
				app_p = (firstp(thisbuf, w->w_top)) ?
						"All" : "End";
				break;
			}
			if (firstp(thisbuf, w->w_top)) {
				app_p = "Top";
				break;
			}
			/* fall through */
		    }
		case 'L':	/* Line number */

			/* make sure topnum is accurate */
			if (!(w->w_flags & (W_TOPNUM|W_NUMLINES)))
				w->w_topnum = lineno(w->w_top);

#   define W_NEEDTOP	0x8000	/* temp. flag to indicate that %P/%L was seen;
				   it will be turned off and #undef'ed later
				   in this routine. */

			w->w_flags |= W_TOPNUM | W_NEEDTOP;

			/* calculate line number of current line from what
			   we already know: (topnum + (dotline - FLine(w)) */
			n = w->w_topnum + w->w_dotline + SIZE(w) - i_line;

			if (fmt[-1] == 'L')
				app_p = itos(n);
			else
				app_p = sprint("%2ld%%", (long) n * 100 /
					       lineno(thisbuf->b_last));
			break;
#endif /* W_TOPNUM */
		case 's':	/* conditonal fill character(s) */
			if (mode_p[-1] != '\0' && mode_p[-1] != ' ') do {
				*mode_p++ = ' ';
			} while (mode_p < mend_p && --n > 0);
			continue;

		case 't':	/* time HH:MM */
		    {
			extern const char *hh_mm __(( void ));

			app_p = hh_mm();
			break;
		    }
		case 'V':
			app_p = version;
			break;

		case 'w':
			if (w->w_offset > 0)
				*mode_p++ = '>';
			continue;

		default:	/* just ignore unknown formats */
			continue;
		}
		/* append only if we get here */
		mode_p = mode_app(mode_p, app_p);
	}

#if (W_TOPNUM)
	/* some administration: we don't need to calculate topnum if %P or %L
	   are not present in the mode line, so turn off the request flag;
	   otherwise just turn off the `%P or %L seen' flag. */
	w->w_flags &= (w->w_flags & W_NEEDTOP) ? ~W_NEEDTOP : ~W_TOPNUM;
#   undef W_NEEDTOP
#endif

	*mode_p = '\0';

	/* 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.
	   [TRH] And it obsoletes my %< and %>. */

	if (glue) {
#		define d	fmt	/* alias! */
#		define s	mode_p

		d = mend_p;
		*d = '\0';
		/* if mode line is too long, we still have to scan
		   to replace glue markers with something visible. */
		if (s > d)
			s = d;
		do {
			while (*--d = *--s) ;	/* shift tail upto marker */
			n = (d - s) / glue;
			++d;			/* 1 extra to replace marker */
			do {
				*--d = fillc;
			} while (--n >= 0);
		} while (--glue);

		mode_p = mend_p;

#		undef s
#		undef d
	}

	return swrite(line, YES);
}

DEF_CMD( "clear-and-redraw", ClAndRedraw, NO )
{
	cl_scr(YES);
}

void
ClrWindow(w)
register Window	*w;
{
	register int	line = FLine(w),
			i = w->w_height;

	set_color(w->w_bufp->b_color);
	do {
		i_set(line++, 0);
		v_inval();
	} while (--i > 0);	/* including modeline! */
}

DEF_CMD( "redraw-display", RedrawDisplay, NO )
{
	register Window	*w = curwind;
	register int	n;
	register Line	*newtop;

	if (exp_p == NO)
		n = HALF(w);		/* default: center */
	else if ((n = exp) < 0)		/* >= 0: from top of window */
		n += SIZE(w);		/* < 0: from bottom of window */

	newtop = prev_line((w->w_line = w->w_bufp->b_dot), n);

	if (newtop == w->w_top)
		ClrWindow(w);
	else
		SetTop(w, newtop);
#ifdef AUTOSCROLL
	w->w_flags |= W_NOAUTOSCROLL;
#endif
}

/* [TRH] a little bit of reorganisation here...
 * in my opinion, leaving out the Asking check has no ill effects
 * while it allows to scroll completion windows and the like.
 * [May 89] Add programmable scroll step rate.
 */
private int	PageStep;

DEF_CMD( "previous-page", NextPage, NEGATE );
DEF_CMD( "next-page",	  NextPage, NO )
{
	DefExp(&PageStep);
	DoScroll(exp, (exp_p - YES)); /* exp_p != YES */
}

DEF_CMD( "scroll-down", UpScroll, NEGATE );
DEF_CMD( "scroll-up",	UpScroll, NO )
{
	DoScroll(exp, NO);
}

DEF_INT( "page-overlap", PageOverlap, V_BASE10 ) = 2; _IF(def PRIVATE)

void
DoScroll(numlines, page)
register int	numlines;
int		page;
{
	register Window	*w = curwind;

	if (page) {
		register int	pagestep;

		/* [TRH Jul-90] refuse to page down if the end of
		   the buffer is already visible in the window. */
		if (numlines > 0 && in_window(w, w->w_bufp->b_last) >= 0)
			return;
		if ((pagestep = SIZE(w) - PageOverlap) > 0)
			numlines *= pagestep;
	}
    {
	register Line	*newtop;

	/* next_line handles negative line count itself */
	newtop = next_line(w->w_top, numlines);

	SetTop(w, newtop);

	if (page || in_window(w, w->w_bufp->b_dot) < 0) {
		w->w_line = newtop;
		if (w->w_bufp == curbuf)
			SetLine(newtop);
	}
    }
}

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

void
message(str)
const char	*str;
{
	if (InJoverc)
		return;
	updmesg();
	errormsg = 0;
	if (str != mesgbuf)
		null_ncpy(mesgbuf, str, (sizeof mesgbuf) - 1);
}

/* move dot to Beginning (or end, if EXP < 0) of Window */

DEF_CMD( "end-of-window",	Bow, NEGATE );
DEF_CMD( "beginning-of-window", Bow, NO )
{
	register Window	*w = curwind;
	register int	num,
			wheight = w->w_height;

	if (Asking)
		return;

	if ((num = exp) < 0)
		num += wheight;		/* Eow() */

	if (--num < 0)
		num = 0;
	else if (num > (wheight -= 2))
		num = wheight;

	SetLine(next_line(w->w_top, num));
	if (exp < 0 && exp_p == NO)
		Eol();
#ifdef AUTOSCROLL
	w->w_flags |= W_NOAUTOSCROLL;
#endif
}

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

DEF_INT( "send-typeout-to-buffer", UseBuffers, V_BOOL ) ZERO;

int	TOabort ZERO;

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

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

/* VARARGS1 */

void
DEFVARG(Typeout, (const char *fmt, ...), (fmt, va_alist) const char *fmt;)
{
	register const char	*f;

	if (False(UseBuffers)) {
		if (TOabort)
			return;

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

			f_mess("--more--");
			c = getchar();
			f_mess(NullStr);

			if (c == ' ') {
				LineNo = 0;
			} else if (c == CR) {
				/* really like "more(1)" */
				LineNo--;
				set_color(Gcolor[B_SCRATCH]);
				v_del_line(1, 0, LineNo);
				if (!CanScroll) {
					/* the brute way */
					i_set(ILI - 1, 0);
					Overtype(NullStr); /* invalidate mode line */
					cl_eol();
					curstoLL();
					putch('\n');
				}
			} else {
				TOabort++;
				if (c != AbortChar && c != RUBOUT)
					add_stroke(c),
					Ungetc(c);
				return;
			}
			last_col = 0;
		}
		i_set(LineNo, last_col);
		cl_standout();
		req_color(Gcolor[B_SCRATCH]);
	}
	if (f = fmt) {
		char		string[LBSIZE];
		va_register va_list ap;

		va_begin(ap, fmt);
		format(string, sizeof string, f, ap);
		va_end(ap);
		if (True(UseBuffers))
			ins_str(string, NO);
		else
			last_col = Overtype(string);
	}
	if (!f || DoAutoNL) {
		if (True(UseBuffers))
			ins_str("\n", NO);
		else {
			Overtype(NullStr);
			cl_eol();
			flusho();
			LineNo++;
			last_col = 0;
		}
	}
}

void
TOstop()
{
	if (True(UseBuffers)) {
		ToFirst();
		/* do this by hand since SetWind won't if Asking */
		tiewind(curwind, curbuf);
		SetWind(old_wind);
	}
	else if (!TOabort) {
		static char	Dash[] = "----------";
		register int	c;

		if (last_col)
			Typeout(NULL);
		if (LineNo == ILI - 1) {
			f_mess(Dash);
		}
		else {
#ifdef COLOR		/* so the rest of the line will keep its original
			   color, which looks nicer. */
			c = PhysScreen[LineNo].s_color;
			DoAutoNL = NO;
#endif
			Typeout(Dash);
			req_color(c);
			cl_eol();
			Placur(LineNo - DoAutoNL, sizeof Dash - 1);
			curson();
		}
		c = getchar();
		s_mess(NullStr);
		if (c != ' ' && c != CR)
			add_stroke(c),
			Ungetc(c);
	}
}

/*======================================================================
 * $Log: disp.c,v $
 * Revision 14.32.0.10  1994/04/29  11:59:22  tom
 * (redisplay): transpose cursor placement and set_color;
 * (UpdLine): transpose *phys_p assignment and cl_eol();
 * (Typeout): use `va_register va_list'.
 *
 * Revision 14.32.0.2  1993/07/14  13:47:20  tom
 * (TOstop): fix COLOR bug that surfaced due to changes in req_color in 14.32.
 *
 * Revision 14.32  1993/04/09  00:46:25  tom
 * (ModeLine): make %P display "All" if complete buffer is visible.
 *
 * Revision 14.31  1993/02/15  02:27:35  tom
 * ModeLine: enable %t option for TINY joves.
 *
 * Revision 14.30  1993/01/27  01:33:20  tom
 * cleanup whitespace; parenthize && || expr to avoid compiler warning.
 *
 * Revision 14.28  1992/10/02  15:38:58  tom
 * support user-variable "default-color".
 *
 * Revision 14.26  1992/08/26  23:56:51  tom
 * PRIVATE-ized some Variable defs; add RCS directives.
 *
 */
