/* Copyright (c) 1990,1991,1992 Chris and John Downey */
#ifndef lint
static char *sccsid = "@(#)screen.c	2.10 (Chris & John Downey) 11/30/92";
#endif

/***

* program name:
    xvi
* function:
    PD version of UNIX "vi" editor, with extensions.
* module name:
    screen.c
* module function:
    Screen handling functions.
* history:
    STEVIE - ST Editor for VI Enthusiasts, Version 3.10
    Originally by Tim Thompson (twitch!tjt)
    Extensive modifications by Tony Andrews (onecom!wldrdg!tony)
    Heavily modified by Chris & John Downey

***/

#include "xvi.h"

/*
 * Size of command buffer - we won't allow anything more
 * to be typed when we get to this limit.
 */
#define	CMDSZ	80

/*
 * The following is used to optimise screen updating; we
 * keep a record of the real screen state and compare it
 * with the new version before actually doing any updating.
 *
 * The l_line part is guaranteed to be always null-terminated.
 */
typedef	struct	line_struct	{
    char		*l_line;	/* storage for characters in line */
    int			l_used;		/* number of bytes actually used */
    unsigned int	l_flags;	/* information bits */
} Sline;

/*
 * Bit definitions for l_flags.
 */
#define	L_TEXT		0x01		/* is an ordinary text line */
#define	L_MARKER	0x02		/* is a marker line ('@' or '~') */
#define	L_DIRTY		0x04		/* has been modified */
#define	L_MESSAGE	0x08		/* is a message line */
#define	L_COMMAND	0x10		/* is a command line */
#define	L_READONLY	0x20		/* message line for readonly buffer */

#define	L_STATUS	(L_MESSAGE | L_COMMAND | L_READONLY)
					/* is a status line */

static	Sline	*new_screen;		/* screen being updated */
static	Sline	*real_screen;		/* state of real screen */

/*
 * Status line glitch handling.
 *
 * Some terminals leave a space when changing colour. The number of spaces
 * left is returned by the v_colour_cost() method within the VirtScr, and
 * stored in the colour_cost variable herein - this is not perfect, it should
 * really be in the Xviwin structure, but what the hell.
 *
 * "st_spare_cols" is the number of columns which are not used at the
 * end of the status line; this is to prevent wrapping on this line,
 * as this can do strange things to some terminals.
 */

static	int	colour_cost = 0;
static	int	st_spare_cols = 1;

static	int	line_to_new P((Xviwin *, Line *, int, long));
static	void	file_to_new P((Xviwin *));
static	void	new_to_screen P((VirtScr *, int, int));
static	void	do_sline P((Xviwin *));
static	void	clrline P((int));

/*
 * This routine must be called to set up the screen memory -
 * if it is not, we will probably get a core dump.
 *
 * Note that, at the moment, it must be called with a whole-screen
 * window, i.e. the first window, and only that window, so that the
 * nrows and ncols fields represent the whole screen.
 */
/*ARGSUSED*/
void
init_screen(win)
Xviwin	*win;
{
    static char		*real_area, *new_area;
    register int	count;
    VirtScr		*vs;

    vs = win->w_vs;

    colour_cost = VScolour_cost(vs);
    st_spare_cols = 1 + (colour_cost * 2);

    /*
     * If we're changing the size of the screen, free the old stuff.
     */
    if (real_screen != NULL) {
	free((char *) real_screen);
	free((char *) new_screen);
	free(real_area);
	free(new_area);
    }

    /*
     * Allocate space for the lines, and for the structure holding
     * information about each line. Notice that we allocate an
     * extra byte at the end of each line for null termination.
     */
    real_screen = (Sline *) malloc((unsigned) VSrows(vs) * sizeof(Sline));
    new_screen = (Sline *) malloc((unsigned) VSrows(vs) * sizeof(Sline));
    real_area = malloc((unsigned) VSrows(vs) * (VScols(vs) + 1));
    new_area = malloc((unsigned) VSrows(vs) * (VScols(vs) + 1));

    if (real_screen == NULL || new_screen == NULL ||
	real_area == NULL || new_area == NULL) {
	/* What to do now? */
	sys_exit(0);
    }

    /*
     * Now assign all the rows ...
     */
    for (count = 0; count < VSrows(vs); count++) {
	register Sline	*rp, *np;
	register int	offset;

	rp = &real_screen[count];
	np = &new_screen[count];

	offset = count * (VScols(vs) + 1);

	rp->l_line = real_area + offset;
	np->l_line = new_area + offset;
	rp->l_line[0] = np->l_line[0] = '\0';
	rp->l_used = np->l_used = 0;
	rp->l_flags = np->l_flags = 0;
    }
}

/*
 * Set the L_DIRTY bit for a given line in both real_screen &
 * new_screen if the stored representations are in fact different:
 * otherwise clear it.
 */
static void
mark_dirty(row)
    int		row;
{
    Sline	*rp;
    Sline	*np;
    int		used;

    rp = &real_screen[row];
    np = &new_screen[row];
    if (

	(rp->l_flags & ~L_DIRTY) != (np->l_flags & ~L_DIRTY)
	||
	(used = rp->l_used) != np->l_used
	||
	strncmp(rp->l_line, np->l_line, used) != 0
    ) {
	/*
	 * The lines are different.
	 */
	np->l_flags |= L_DIRTY;
	rp->l_flags |= L_DIRTY;
    } else {
	rp->l_flags = (np->l_flags &= ~L_DIRTY);
    }
}

/*
 * Transfer the specified window line into the "new" screen array, at
 * the given row. Returns the number of screen lines taken up by the
 * logical buffer line lp, or 0 if the line would not fit; this happens
 * with longlines at the end of the screen. In this case, the lines
 * which could not be displayed will have been marked with an '@'.
 */
static int
line_to_new(window, lp, start_row, line)
Xviwin		*window;
Line		*lp;
int		start_row;
long		line;
{
    register unsigned	c;	/* next character from file */
    register Sline	*curr_line;	/* output line - used for efficiency */
    register char	*ltext;		/* pointer to text of line */
    register int	curr_index;	/* current index in line */
    bool_t		eoln;		/* true when line is done */
    char		extra[MAX_TABSTOP];
					/* Stack for extra characters. */
    int			nextra = 0;	/* index into stack */
    int			srow, scol;	/* current screen row and column */
    int			vcol;		/* virtual column */

    ltext = lp->l_text;
    srow = start_row;
    scol = vcol = 0;
    curr_line = &new_screen[srow];
    curr_index = 0;
    eoln = FALSE;

    if (Pb(P_number)) {
	static Flexbuf	ftmp;

	flexclear(&ftmp);
	(void) lformat(&ftmp, NUM_FMT, line);
	(void) strcpy(curr_line->l_line, flexgetstr(&ftmp));
	scol += NUM_SIZE;
    }

    while (!eoln) {
	/*
	 * Get the next character to put on the screen.
	 */

	/*
	 * "extra" is a stack containing any extra characters
	 * we have to put on the screen - this is for chars
	 * which have a multi-character representation, and
	 * for the $ at end-of-line in list mode.
	 */

	if (nextra > 0) {
	    c = extra[--nextra];
	} else {
	    unsigned	n;

	    c = (unsigned char) (ltext[curr_index++]);

	    /*
	     * Deal with situations where it is not
	     * appropriate just to copy characters
	     * straight onto the screen.
	     */
	    if (c == '\0') {

		if (Pb(P_list)) {
		    /*
		     * Have to show a '$' sign in list mode.
		     */
		    extra[nextra++] = '\0';
		    c = '$';
		}

	    } else {
		char	*p;

		n = vischar((int) c, &p, vcol);
		/*
		 * This is a bit paranoid assuming
		 * that Pn(P_tabstop) can never be
		 * greater than sizeof (extra), but
		 * so what.
		 */
		if (nextra + n > sizeof extra)
		    n = (sizeof extra - nextra);
		/*
		 * Stack the extra characters so that
		 * they appear in the right order.
		 */
		while (n > 1) {
		    extra[nextra++] = p[--n];
		}
		c = p[0];
	    }
	}

	if (c == '\0') {
	    /*
	     * End of line. Terminate it and finish.
	     */
	    eoln = TRUE;
	    curr_line->l_flags = L_TEXT;
	    curr_line->l_used = scol;
	    curr_line->l_line[scol] = '\0';
	    mark_dirty(srow);
	    break;
	} else {
	    /*
	     * Sline folding.
	     */
	    if (scol >= window->w_ncols) {
		curr_line->l_flags = L_TEXT;
		curr_line->l_used = scol;
		curr_line->l_line[scol] = '\0';
		mark_dirty(srow);
		srow += 1;
		scol = 0;
		curr_line = &new_screen[srow];
	    }

	    if (srow >= window->w_cmdline) {
		for (srow = start_row; srow < window->w_cmdline; srow++) {
		    curr_line = &new_screen[srow];

		    curr_line->l_flags = L_MARKER;
		    curr_line->l_used = 1;
		    curr_line->l_line[0] = '@';
		    curr_line->l_line[1] = '\0';
		    mark_dirty(srow);
		}
		return(0);
	    }

	    /*
	     * Store the character in new_screen.
	     */
	    curr_line->l_line[scol++] = c;
	    vcol++;
	}
    }

    return((srow - start_row) + 1);
}

/*
 * file_to_new()
 *
 * Based on the current value of topline, transfer a screenful
 * of stuff from file to new_screen, and update botline.
 */
static void
file_to_new(win)
register Xviwin	*win;
{
    register int	row;
    register Line	*line;
    register Buffer	*buffer;
    long		lnum;

    buffer = win->w_buffer;
    row = win->w_winpos;
    line = win->w_topline;
    lnum = lineno(buffer, line);

    while (row < win->w_cmdline && line != buffer->b_lastline) {
	int nlines;

	nlines = line_to_new(win, line, row, lnum);
	if (nlines == 0) {
	    /*
	     * Make it look like we have updated
	     * all the screen lines, since they
	     * have '@' signs on them.
	     */
	    row = win->w_cmdline;
	    break;
	} else {
	    row += nlines;
	    line = line->l_next;
	    lnum++;
	}
    }

    win->w_botline = line;

    /*
     * If there are any lines remaining, fill them in
     * with '~' characters.
     */
    for ( ; row < win->w_cmdline; row++) {
	register Sline	*curr_line;

	curr_line = &new_screen[row];

	curr_line->l_flags = L_MARKER;
	curr_line->l_used = 1;
	curr_line->l_line[0] = '~';
	curr_line->l_line[1] = '\0';
	mark_dirty(row);
    }
}

/*
 * new_to_screen
 *
 * Transfer the contents of new_screen to the screen,
 * starting at "start_row", for "nlines" lines,
 * using real_screen to avoid unnecessary output.
 */
static void
new_to_screen(vs, start_row, nlines)
VirtScr		*vs;
int		start_row;
int		nlines;
{
    int         row;                    /* current row */
    int         end_row;                /* row after last one to be updated */
    int         columns;

    columns = VScols(vs);

    if (!(echo & e_CHARUPDATE)) {
	return;
    }

    end_row = start_row + nlines;

    VSset_colour(vs, Pn(P_colour));

    for (row = start_row; row < end_row; row++) {
	register int            col;   /* current column */
	register Sline          *new,
				*real;  /* pointers to current lines */
	register unsigned       nflags;
	unsigned                rflags; /* flags for current lines */
	register char           *ntextp,
				*rtextp;
					/* pointers to line text */
	register int            nc;     /* current character in new_screen */
	int			n_used,
				r_used;

	nflags = (new = &new_screen[row])->l_flags;
	rflags = (real = &real_screen[row])->l_flags;

	/*
	 * If the real and new screens are both "clean",
	 * don't bother.
	 */
	if (!((nflags & L_DIRTY) || (rflags & L_DIRTY))) {
	    continue;
	}

	ntextp = new->l_line;
	rtextp = real->l_line;

	n_used = new->l_used;
	r_used = real->l_used;

	if ((nflags & L_MESSAGE) ||
				(rflags & L_STATUS) != (nflags & L_STATUS)) {
	    /*
	     * If it's a message line, or its status (text line,
	     * command line or message line) has changed, and either
	     * the real line or the new line is "dirty", better update
	     * the whole thing; if any colour changes are required,
	     * the effects of cursor movements may not be predictable
	     * on some terminals.
	     */
	    VSgoto(vs, row, 0);
	    if ((nflags & L_MESSAGE) || ((nflags & L_COMMAND) && colour_cost == 0)) {
		VSset_colour(vs, (nflags & L_READONLY) ? Pn(P_roscolour) :
						Pn(P_statuscolour));
	    }
	    if ((nc = ntextp[0]) != '\0') {
		VSputc(vs, row, 0, nc);
	    }
	    /*
	     * For command lines, only the first character should be
	     * highlighted, except if (colour_cost != 0).
	     */
	    if ((nflags & L_COMMAND) && colour_cost == 0) {
		VSset_colour(vs, Pn(P_colour));
	    }
	    if (nc != '\0') {
		VSwrite(vs, row, 1, &ntextp[1]);
	    }

	    /*
	     * Clear the rest of the line, if
	     * there is any left to be cleared.
	     */
	    if (n_used < columns) {
		VSclear_line(vs, row, n_used);
	    }

	    /*
	     * Change back to text colour if we have to.
	     */
	    if ((nflags & L_MESSAGE) != 0) {
		VSset_colour(vs, Pn(P_colour));
	    }

	    (void) strncpy(rtextp, ntextp, (int) (columns - st_spare_cols));
	} else {
	    /*
	     * Look at each character in the line, comparing
	     * the new version with the one on the screen.
	     * If they differ, put it out.
	     *
	     * There is some optimisation here to avoid large
	     * use of tty_goto.
	     */
	    register int        last_col;
				/* last column to be updated */

	    for (col = last_col = 0; col < n_used && col < r_used; col++) {
		if ((nc = ntextp[col]) != rtextp[col]) {
		    /*
		     * They are different. Get to the right
		     * place before putting out the char.
		     */
		    if (col != 0) {
			VSadvise(vs, row, last_col + 1,
					    col - last_col - 1,
					    ntextp + last_col + 1);
		    } else {
			VSgoto(vs, row, col);
			/*
			 * A command line should have the first
			 * character - and only the first character -
			 * highlighted, except if (colour_cost != 0),
			 * because that complicates our updating
			 * algorithm too much.
			 */
			if (col == 0 && (nflags & L_COMMAND) &&
						colour_cost == 0) {
			    VSset_colour(vs, (nflags & L_READONLY) ?
				    Pn(P_roscolour) : Pn(P_statuscolour));
			}
		    }

		    VSputc(vs, row, col, nc);

		    if (col == 0 && (nflags & L_COMMAND) &&
		    				colour_cost == 0) {
			VSset_colour(vs, Pn(P_colour));
		    }
		    last_col = col;
		    rtextp[col] = nc;
		}
	    }

	    if (n_used > r_used) {
		/*
		 * We have got to the end of the previous
		 * screen line; if there is anything left,
		 * we should just display it.
		 */
		(void) strcpy(&rtextp[col], &ntextp[col]);
		if (col == 0 && (nflags & L_COMMAND) && colour_cost == 0) {
		    /*
		     * A command line should have the first character
		     * - and only the first character - highlighted,
		     * as long as (colour_cost != 0).
		     */
		    VSgoto(vs, row, 0);
		    VSset_colour(vs, Pn(P_statuscolour));
		    VSputc(vs, row, 0, ntextp[0]);
		    VSset_colour(vs, Pn(P_colour));
		    col = 1;
		} else {
		    /*
		     * Skip over initial whitespace.
		     */
		    while (ntextp[col] == ' ') {
			col++;
		    }
		}
		if (col < columns) {
		    VSwrite(vs, row, col, &ntextp[col]);
		}
	    } else if (r_used > n_used) {
		/*
		 * We have got to the end of the new screen
		 * line, but the old one still has stuff on
		 * it. We must therefore clear it.
		 */
		VSclear_line(vs, row, col);
	    }
	}

	real->l_line[n_used] = '\0';
	real->l_used = n_used;

	/*
	 * The real screen line is a message or command line if the
	 * newly-updated one was. Otherwise, it isn't.
	 *
	 * Both the new and real screens may now be considered
	 * "clean".
	 */
	real->l_flags = (
			  /*
			   * Turn these flags off first ...
			   */
			  (rflags & ~(L_STATUS | L_DIRTY))
			  /*
			   * ... then set whatever L_STATUS flags are
			   * set in new_screen.
			   */
			  | (nflags & L_STATUS)
			);
	new->l_flags &= ~L_DIRTY;
    }
}

/*
 * Update the status line of the given window, and cause the status
 * line to be written out. Note that we call new_to_screen() to cause
 * the output to be generated; since there will be no other changes,
 * only the status line will be changed on the screen.
 */
void
update_sline(win)
Xviwin	*win;
{
    do_sline(win);
    new_to_screen(win->w_vs, (int) win->w_cmdline, 1);
    VSflush(win->w_vs);
}

/*
 * Update the status line of the given window,
 * from the one in win->w_statusline.
 */
static void
do_sline(win)
Xviwin	*win;
{
    register char	*from;
    register char	*to;
    register char	*end;
    Sline		*slp;

    from = sline_text(win);
    slp = &new_screen[win->w_cmdline];
    to = slp->l_line;
    end = to + win->w_ncols - st_spare_cols;

    while (*from != '\0' && to < end) {
	*to++ = *from++;
    }

    /*
     * Fill with spaces, and null-terminate.
     */
    while (to < end) {
	*to++ = ' ';
    }
    *to = '\0';

    slp->l_used = win->w_ncols - st_spare_cols;
    slp->l_flags = L_MESSAGE;
    if (is_readonly(win->w_buffer)) {
	slp->l_flags |= L_READONLY;
    }
    mark_dirty(win->w_cmdline);
}

void
update_cline(win)
Xviwin	*win;
{
    Sline	*clp;
    unsigned width, maxwidth;

    clp = &new_screen[win->w_cmdline];

    maxwidth = win->w_ncols - st_spare_cols;
    if ((width = flexlen(&win->w_statusline)) > maxwidth) {
	width = maxwidth;
    }
    (void) strncpy(clp->l_line, sline_text(win), (int) width);
    clp->l_used = width;
    clp->l_line[width] = '\0';
    clp->l_flags = (L_COMMAND | L_DIRTY);
    /*
     * We don't bother calling mark_dirty() here: it isn't worth
     * it because the line's contents have almost certainly
     * changed.
     */
    new_to_screen(win->w_vs, (int) win->w_cmdline, 1);
    VSflush(win->w_vs);
}

/*
 * updateline() - update the line the cursor is on
 *
 * Updateline() is called after changes that only affect the line that
 * the cursor is on. This improves performance tremendously for normal
 * insert mode operation. The only thing we have to watch for is when
 * the cursor line grows or shrinks around a row boundary. This means
 * we have to repaint other parts of the screen appropriately.
 */
void
updateline(window)
Xviwin	*window;
{
    Line	*currline;
    int		nlines;
    int		curs_row;

    currline = window->w_cursor->p_line;

    /*
     * Find out which screen line the cursor line starts on.
     * This is not necessarily the same as window->w_row,
     * because longlines are different.
     */
    if (plines(window, currline) > 1) {
	curs_row = (int) cntplines(window, window->w_topline, currline);
    } else {
	curs_row = window->w_row;
    }

    nlines = line_to_new(window, currline,
			(int) (curs_row + window->w_winpos),
			(long) lineno(window->w_buffer, currline));

    if (nlines != window->w_c_line_size) {
	update_buffer(window->w_buffer);
    } else {
	new_to_screen(window->w_vs,
			(int) (curs_row + window->w_winpos), nlines);
	VSflush(window->w_vs);
    }
}

/*
 * Completely update the representation of the given window.
 */
void
update_window(window)
Xviwin	*window;
{
    if (window->w_nrows > 1) {
	file_to_new(window);
	new_to_screen(window->w_vs,
			(int) window->w_winpos, (int) window->w_nrows);
	VSflush(window->w_vs);
    }
}

/*
 * Update all windows.
 */
void
update_all()
{
    Xviwin	*w = curwin;

    do {
	if (w->w_nrows > 1) {
	    file_to_new(w);
	}
	if (w->w_nrows > 0) {
	    do_sline(w);
	}
    } while ((w = next_window(w)) != curwin);

    new_to_screen(w->w_vs, 0, (int) VSrows(w->w_vs));
    VSflush(w->w_vs);
}

/*
 * Totally redraw the screen.
 */
void
redraw_screen()
{
    if (curwin != NULL) {
	clear(curwin);
	update_all();
    }
}

void
clear(win)
Xviwin	*win;
{
    register int	row;
    int		nrows;

    nrows = VSrows(win->w_vs);

    VSset_colour(win->w_vs, Pn(P_colour));
    VSclear_all(win->w_vs);

    /*
     * Clear the real screen lines, and mark them as modified.
     */
    for (row = 0; row < nrows; row++) {
	clrline(row);
    }
}

/*
 * The rest of the routines in this file perform screen manipulations.
 * The given operation is performed physically on the screen. The
 * corresponding change is also made to the internal screen image. In
 * this way, the editor anticipates the effect of editing changes on
 * the appearance of the screen. That way, when we call screenupdate a
 * complete redraw isn't usually necessary. Another advantage is that
 * we can keep adding code to anticipate screen changes, and in the
 * meantime, everything still works.
 */

/*
 * s_ins(win, row, nlines) - insert 'nlines' lines at 'row'
 */
void
s_ins(win, row, nlines)
Xviwin		*win;
register int	row;
int		nlines;
{
    register int	from, to;
    int			count;
    VirtScr		*vs;

    if (!(echo & e_SCROLL))
	return;

    /*
     * There's no point in scrolling more lines than there are
     * (below row) in the window, or in scrolling 0 lines.
     */
    if (nlines == 0 || nlines + row >= win->w_nrows - 1)
	return;

    /*
     * The row specified is relative to the top of the window;
     * add the appropriate offset to make it into a screen row.
     */
    row += win->w_winpos;

    /*
     * Note that we avoid the use of 1-line scroll regions; these
     * only ever occur at the bottom of a window, and it is better
     * just to leave the line to be updated in the best way by
     * update{line,screen}.
     */
    if (nlines == 1 && row + 1 == win->w_cmdline) {
	return;
    }

    vs = win->w_vs;

    if (vs->v_scroll != NOFUNC) {
	if (!VSscroll(vs, row, (int) win->w_cmdline - 1, -nlines)) {
	    /*
	     * Can't scroll what we were asked to - try scrolling
	     * the whole window including the status line.
	     */
	    VSclear_line(vs, (int) win->w_cmdline, 0);
	    clrline(win->w_cmdline);
	    if (!VSscroll(vs, row, (int) win->w_cmdline, -nlines)) {
		/*
		 * Failed.
		 */
		return;
	    }
	}
    } else {
	return;
    }

    /*
     * Update the stored screen image so it matches what has
     * happened on the screen.
     */

    /*
     * Move section of text down to the bottom.
     *
     * We do this by rearranging the pointers within the Slines,
     * rather than copying the characters.
     */
    for (to = win->w_cmdline - 1, from = to - nlines; from >= row;
							--from, --to) {
	register char	*temp;

	temp = real_screen[to].l_line;
	real_screen[to].l_line = real_screen[from].l_line;
	real_screen[from].l_line = temp;
	real_screen[to].l_used = real_screen[from].l_used;
    }

    /*
     * Clear the newly inserted lines.
     */
    for (count = row; count < row + nlines; count++) {
	clrline(count);
    }
}

/*
 * s_del(win, row, nlines) - delete 'nlines' lines starting at 'row'.
 */
void
s_del(win, row, nlines)
register Xviwin		*win;
int			row;
int			nlines;
{
    register int	from, to;
    int			count;
    VirtScr		*vs;

    if (!(echo & e_SCROLL))
	return;

    /*
     * There's no point in scrolling more lines than there are
     * (below row) in the window, or in scrolling 0 lines.
     */
    if (nlines == 0 || nlines + row >= win->w_nrows - 1)
	return;

    /*
     * The row specified is relative to the top of the window;
     * add the appropriate offset to make it into a screen row.
     */
    row += win->w_winpos;

    /*
     * We avoid the use of 1-line scroll regions, since they don't
     * work with many terminals, especially if we are using
     * (termcap) DO to scroll the region.
     */
    if (nlines == 1 && row + 1 == win->w_cmdline) {
	return;
    }

    vs = win->w_vs;

    if (vs->v_scroll != NOFUNC) {
	if (!VSscroll(vs, row, (int) win->w_cmdline - 1, nlines)) {
	    /*
	     * Can't scroll what we were asked to - try scrolling
	     * the whole window including the status line.
	     */
	    VSclear_line(vs, (int) win->w_cmdline, 0);
	    clrline(win->w_cmdline);
	    if (!VSscroll(vs, row, (int) win->w_cmdline, nlines)) {
		/*
		 * Failed.
		 */
		return;
	    }
	}
    } else {
	return;
    }

    /*
     * Update the stored screen image so it matches what has
     * happened on the screen.
     */

    /*
     * Move section of text up from the bottom.
     *
     * We do this by rearranging the pointers within the Slines,
     * rather than copying the characters.
     */
    for (to = row, from = to + nlines;
	 from < win->w_cmdline;
	 from++, to++) {
	register char	*temp;

	temp = real_screen[to].l_line;
	real_screen[to].l_line = real_screen[from].l_line;
	real_screen[from].l_line = temp;
	real_screen[to].l_used = real_screen[from].l_used;
    }

    /*
     * Clear the deleted lines.
     */
    for (count = win->w_cmdline - nlines; count < win->w_cmdline; count++) {
	clrline(count);
    }
}

/*
 * Insert a character at the cursor position, updating the screen as
 * necessary. Note that this routine doesn't have to do anything, as
 * the screen will eventually be correctly updated anyway; it's just
 * here for speed of screen updating.
 */
void
s_inschar(window, newchar)
Xviwin			*window;
int			newchar;
{
    register char	*curp;
    register char	*cp;
    register char	*sp;
    Sline		*rp;
    Posn		*pp;
    VirtScr		*vs;		/* the VirtScr for this window */
    char		*newstr;	/* printable string for newchar */
    register unsigned	nchars;		/* number of  chars in newstr */
    unsigned		currow;
    unsigned		curcol;
    unsigned		columns;

    vs = window->w_vs;
    if (vs->v_insert == NOFUNC) {
	return;
    }

    if (!(echo & e_CHARUPDATE)) {
	return;
    }

    pp = window->w_cursor;

    /*
     * If we are at (or near) the end of the line, it's not worth
     * the bother. Define near as 0 or 1 characters to be moved.
     */
    cp = pp->p_line->l_text + pp->p_index;
    if (*cp == '\0' || *(cp+1) == '\0') {
	return;
    }

    curcol = window->w_col;

    /*
     * If the cursor is on a longline, and not on the last actual
     * screen line of that longline, we can't do it.
     */
    if (window->w_c_line_size > 1 && curcol != window->w_virtcol) {
	return;
    }

    nchars = vischar(newchar, &newstr, curcol);

    /*
     * And don't bother if we are (or will be) at the last screen column.
     */
    columns = window->w_ncols;
    if (curcol + nchars >= columns) {
	return;
    }

    /*
     * Also, trying to push tabs rightwards doesn't work very
     * well. It's usually better not to use the insert character
     * sequence because in most cases we'll only have to update
     * the line as far as the tab anyway.
     */
    if ((!Pb(P_list) && Pb(P_tabs)) && strchr(cp, '\t') != NULL) {
	return;
    }

    /*
     * Okay, we can do it.
     */
    currow = window->w_row;

    VSinsert(vs, window->w_winpos + currow, curcol, newstr);

    /*
     * Update real_screen.
     */
    rp = &real_screen[window->w_winpos + currow];
    curp = &rp->l_line[curcol];
    if ((rp->l_used += nchars) > columns) {
	rp->l_used = columns;
    }
    cp = &rp->l_line[rp->l_used - 1];
    cp[1] = '\0';
    if (cp - curp >= nchars)
    {
	sp = cp - nchars;
	for (;;) {
	    *cp-- = *sp;
	    if (sp-- <= curp)
		break;
	}
    }

    /*
     * This is the string we've just inserted.
     */
    sp = newstr;
    while (nchars-- > 0) {
	*curp++ = *sp++;
    }
}

void
wind_goto(win)
Xviwin	*win;
{
    VirtScr	*vs;

    if (echo & e_CHARUPDATE) {
	vs = win->w_vs;
	VSgoto(vs, (int) win->w_winpos + win->w_row, win->w_col);
	VSflush(vs);
    }
}

static	char		inbuf[CMDSZ];		/* command input buffer */
static	unsigned int	inpos = 0;		/* posn of next input char */
static	unsigned char	colposn[CMDSZ];		/* holds n chars per char */

/*
 * cmd_init(window, firstch)
 *
 * Initialise command line input.
 */
void
cmd_init(win, firstch)
Xviwin	*win;
int	firstch;
{
    if (inpos > 0) {
	show_error(win, "Internal error: re-entered command line input mode");
	return;
    }

    State = CMDLINE;

    flexclear(&win->w_statusline);
    (void) flexaddch(&win->w_statusline, firstch);
    inbuf[0] = firstch;
    inpos = 1;
    update_cline(win);
    colposn[0] = 0;
}

/*
 * cmd_input(window, character)
 *
 * Deal with command line input. Takes an input character and returns
 * one of cmd_CANCEL (meaning they typed ESC or deleted past the
 * prompt character), cmd_COMPLETE (indicating that the command has
 * been completely input), or cmd_INCOMPLETE (indicating that command
 * line is still the right mode to be in).
 *
 * Once cmd_COMPLETE has been returned, it is possible to call
 * get_cmd(win) to obtain the command line.
 */
Cmd_State
cmd_input(win, ch)
Xviwin	*win;
int	ch;
{
    static bool_t	literal_next = FALSE;

    if (!literal_next) {
	switch (ch) {
	case CTRL('V'):
	    literal_next = TRUE;
	    return(cmd_INCOMPLETE);

	case '\n':		/* end of line */
	case '\r':
	    inbuf[inpos] = '\0';	/* terminate input line */
	    inpos = 0;
	    State = NORMAL;		/* return state to normal */
	    do_sline(win);		/* line is now a message line */
	    return(cmd_COMPLETE);	/* and indicate we are done */

	case '\b':		/* backspace or delete */
	case DEL:
	{
	    unsigned len;

	    inbuf[--inpos] = '\0';
	    len = colposn[inpos - 1] + 1;
	    while (flexlen(&win->w_statusline) > len)
		(void) flexrmchar(&win->w_statusline);
	    update_cline(win);
	    if (inpos == 0) {
		/*
		 * Deleted past first char;
		 * go back to normal mode.
		 */
		State = NORMAL;
		return(cmd_CANCEL);
	    }
	    return(cmd_INCOMPLETE);
	}

	case '\033':
	case EOF:
	case CTRL('U'):		/* line kill */
	    inpos = 1;
	    inbuf[1] = '\0';
	    flexclear(&win->w_statusline);
	    (void) flexaddch(&win->w_statusline, inbuf[0]);
	    update_cline(win);
	    return(cmd_INCOMPLETE);

	default:
	    break;
	}
    }

    literal_next = FALSE;

    if (inpos >= sizeof(inbuf) - 1) {
	/*
	 * Must not overflow buffer.
	 */
	beep(win);
    } else {
	unsigned	curposn;
	unsigned	w;
	char		*p;

	curposn = colposn[inpos - 1];
	w = vischar(ch, &p, (int) curposn);
	if (curposn + w >= win->w_ncols - 1) {
	    beep(win);
	} else {
	    colposn[inpos] = curposn + w;
	    inbuf[inpos++] = ch;
	    (void) lformat(&win->w_statusline, "%s", p);
	    update_cline(win);
	}
    }

    return(cmd_INCOMPLETE);
}

/*ARGSUSED*/
char *
get_cmd(win)
Xviwin	*win;
{
    return(inbuf);
}

/*ARGSUSED*/
void
gotocmd(win, clr)
Xviwin	*win;
bool_t	clr;
{
    VirtScr	*vs;

    vs = win->w_vs;
    if (clr) {
	VSclear_line(vs, (int) win->w_cmdline, 0);
    }
    VSgoto(vs, (int) win->w_cmdline, 0);
    VSflush(vs);
}

/*
 * Display a prompt on the bottom line of the screen.
 */
void
prompt(message)
    char	*message;
{
    VirtScr	*vs;
    int	row;

    vs = curwin->w_vs;

    row = VSrows(vs) - 1;
    VSgoto(vs, row, 0);
    VSset_colour(vs, Pn(P_statuscolour));
    VSwrite(vs, row, 0, message);
    VSset_colour(vs, Pn(P_colour));
    VSgoto(vs, row, strlen(message));
    VSflush(vs);
}

/*
 * Sound the alert.
 */
void
beep(window)
register Xviwin *window;
{
    VSbeep(window->w_vs);
}

static char	*(*disp_func) P((void));
static int	disp_colwidth;
static int	disp_maxcol;
static bool_t	disp_listmode;

/*
 * Start off "display" mode. The "func" argument is a function pointer
 * which will be called to obtain each subsequent string to display.
 * The function returns NULL when no more lines are available.
 */
void
disp_init(win, func, colwidth, listmode)
Xviwin		*win;
char		*(*func) P((void));
int		colwidth;
bool_t		listmode;
{
    State = DISPLAY;
    disp_func = func;
    if (colwidth > win->w_ncols)
	colwidth = win->w_ncols;
    disp_colwidth = colwidth;
    disp_maxcol = (win->w_ncols / colwidth) * colwidth;
    disp_listmode = listmode;
    (void) disp_screen(win);
}

/*
 * Display text in glass-teletype mode, in approximately the style of
 * the more(1) program.
 *
 * If the return value from (*disp_func)() is NULL, it means we've got
 * to the end of the text to be displayed, so we wait for another key
 * before redisplaying our editing screen.
 */
bool_t
disp_screen(win)
Xviwin	*win;
{
    int		row;	/* current screen row */
    int		col;	/* current screen column */
    static bool_t	finished = FALSE;
    VirtScr		*vs;

    vs = win->w_vs;

    if (finished || kbdintr) {
	/*
	 * Clear the screen, and then ensure that the window
	 * on the current buffer is in the right place and
	 * updated; finally update the whole screen.
	 */
	clear(win);
	move_window_to_cursor(win);
	update_all();
	State = NORMAL;
	finished = FALSE;
	if (kbdintr) {
	    imessage = TRUE;
	}
	return(TRUE);
    }

    VSclear_all(vs);

    for (col = 0; col < disp_maxcol; col += disp_colwidth) {
	for (row = 0; row < VSrows(vs) - 1; row++) {
	    static char	*line;
	    int		width;

	    if (line == NULL && (line = (*disp_func)()) == NULL) {
		/*
		 * We've got to the end.
		 */
		prompt("[Hit return to continue] ");
		finished = TRUE;
		return(FALSE);
	    }

	    for (width = 0; *line != '\0'; line++) {
		char		*p;
		unsigned	w;

		w = vischar(*line, &p, disp_listmode ? -1 : width);

		if ((width += w) <= disp_colwidth) {
		    VSwrite(vs, row, col + width - w, p);
		} else {
		    /*
		     * The line is too long, so we
		     * have to wrap around to the
		     * next screen line.
		     */
		    break;
		}
	    }

	    if (*line == '\0') {
		if (disp_listmode) {
		    /*
		     * In list mode, we have to
		     * display a '$' to show the
		     * end of a line.
		     */
		    if (width < disp_colwidth) {
			VSputc(vs, row, col + width, '$');
		    } else {
			/*
			 * We have to wrap it
			 * to the next screen
			 * line.
			 */
			continue;
		    }
		}
		/*
		 * If we're not in list mode, or we
		 * were able to display the '$', we've
		 * finished with this line.
		 */
		line = NULL;
	    }
	}
    }

    prompt("[More] ");

    return(FALSE);
}

/*
 * Clear the given line, marking it as dirty.
 */
static void
clrline(line)
int	line;
{
    real_screen[line].l_used = 0;
    real_screen[line].l_line[0] = '\0';
    mark_dirty(line);
}
