/*
 * $Id: zle_refresh.c,v 1.16 1995/05/30 06:21:41 coleman Exp coleman $
 *
 * zle_refresh.c - screen update
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1995 Paul Falstad
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 *
 * In no event shall Paul Falstad or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Paul Falstad and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Paul Falstad and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Paul Falstad and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#define ZLE
#include "zsh.h"

#ifdef HAVE_SELECT
#define SELECT_ADD_COST(X)	cost += X
#else
#define SELECT_ADD_COST(X)
#endif

/* Oct/Nov 94: <mason> some code savagely redesigned to fix several bugs -
   refreshline() & tc_rightcurs() majorly rewritten; refresh() fixed -
   I've put my fingers into just about every routine in here -
   any queries about updates to mason@werple.apana.org.au */

char **nbuf = NULL,		/* new video buffer line-by-line char array */
    **obuf = NULL;		/* old video buffer line-by-line char array */
static char *pptbuf;            /* prompt buffer			    */
static int olnct,		/* previous number of lines		    */
    ovln,			/* previous video cursor position line	    */
    pptlen,                     /* length of prompt buffer                  */
    pptw,                       /* prompt width on screen                   */
    vcs, vln,			/* video cursor position column & line	    */
    vmaxln,			/* video maximum number of lines	    */
    winw, winh,			/* window width & height		    */
    winpos;			/* singlelinezle: line's position in window */
static unsigned pmpt_attr = 0,	/* text attributes after displaying prompt  */
    rpmpt_attr = 0;		/* text attributes after displaying rprompt */
static char am_char = ' ';	/* first col char if needed for automargin  */

/**/
void
resetvideo(void)
{
    int ln;
    static int lwinw = -1, lwinh = -1;	/* last window width & height */
 
    winw = (columns < 1) ? (columns = 80) : columns;  /* terminal width */
    if (isset(SINGLELINEZLE) || !termok)
	winh = 1;
    else
	winh = (lines < 2) ? 24 : lines;
    winpos = vln = vmaxln = 0;
    if (lwinw != winw || lwinh != winh) {
	if (nbuf) {
	    for (ln = 0; ln != lwinh; ln++) {
		zfree(nbuf[ln], lwinw + 1);
		zfree(obuf[ln], lwinw + 1);
	    }
	    free(nbuf);
	    free(obuf);
	}
	nbuf = (char **)zcalloc((winh + 1) * sizeof(char *));
	obuf = (char **)zcalloc((winh + 1) * sizeof(char *));
	nbuf[0] = (char *)zalloc(winw + 1);
	obuf[0] = (char *)zalloc(winw + 1);

	lwinw = winw;
	lwinh = winh;
    }
    for (ln = 0; ln != winh + 1; ln++) {
	if (nbuf[ln])
	    *nbuf[ln] = '\0';
	if (obuf[ln])
	    *obuf[ln] = '\0';
    }

    pptbuf = putprompt(lpmpt, &pptlen, &pptw, 1);
    pmpt_attr = txtchange;
    if (pptw) {
    	memset(nbuf[0], ' ', pptw);
	memset(obuf[0], ' ', pptw);
	nbuf[0][pptw] = obuf[0][pptw] = '\0';
    }

    vcs = pptw;
    olnct = nlnct = 0;
}

/**/
int
scrollwindow(void)
{
    int t0, hwinh = winh / 2;
    char *s;

    for (t0 = 0; t0 != winh - hwinh; t0++) {
	s = nbuf[t0];
	nbuf[t0] = nbuf[t0 + hwinh];
	nbuf[t0 + hwinh] = s;
    }
    memset(nbuf[0], ' ', pptw);
    t0 = winw - pptw;
    memset(nbuf[0] + pptw, '\0', t0 + 1);
    strncpy(nbuf[0] + pptw, ">....", t0 > 5 ? 5 : t0);
    return winh - hwinh;
}

/* this is the messy part. */
/* this define belongs where it's used!!! */

#define nextline				\
{						\
    *s = (unsigned char)'\0';			\
    if (ln == winh - 1)				\
	if (nvln != -1)				\
	    break;				\
	else					\
	    ln = scrollwindow() - 1;		\
    if (!nbuf[++ln])				\
	nbuf[ln] = (char *)zalloc(winw + 1);	\
    s = (unsigned char *)nbuf[ln];		\
    sen = s + winw;				\
}

#ifdef TIOCGWINSZ
int winchanged;			/* window size changed */
#endif

int cleareol,			/* clear to end-of-line (if can't cleareod) */
    clearf = 0,			/* alwayslastprompt used immediately before */
    hasam;			/* terminal should have auto-margin	    */
static int put_rpmpt,		/* whether we should display right-prompt   */
    oput_rpmpt;			/* whether displayed right-prompt last time */
extern int clearflag;		/* set to non-zero if alwayslastprompt used */

/**/
void
refresh(void)
{
    int ln = 0,			/* current line we're working on	     */
	nvcs = 0, nvln = -1,	/* video cursor column and line		     */
	rpw,                    /* right-prompt width 			     */
	t0 = -1;		/* tmp					     */
    unsigned char *s,		/* pointer into the video buffer	     */
	*t,			/* pointer into the real buffer		     */
	*sen,			/* pointer to end of the video buffer (eol)  */
	*scs = line + cs;	/* pointer to cursor position in real buffer */
    char **qbuf;		/* tmp					     */

#ifdef HAVE_SELECT
    cost = 0;			/* reset */
#endif
    cleareol = 0;		/* unset */
    if (resetneeded) {
	setterm();
#ifdef TIOCGWINSZ
	if (winchanged) {
	    moveto(0, 0);
	    t0 = olnct;		/* this is to clear extra lines even when */
	    winchanged = 0;	/* the terminal cannot TCCLEAREOD	  */
	}
#endif
	resetvideo();
	resetneeded = 0;	/* unset */
	oput_rpmpt = 0;		/* no right-prompt currently on screen */
        if (!clearflag)
            if (tccan(TCCLEAREOD))
                tcout(TCCLEAREOD);
            else
                cleareol = 1;   /* set */
        if (t0 > -1)
            olnct = t0;
        if (isset(SINGLELINEZLE) || !termok)
            vcs = 0;
        else if (pptlen && !clearflag) {
            fwrite(pptbuf, pptlen, 1, shout);
	    fflush(shout);
	}
	if (clearflag)
	    putc('\r', shout), vcs = 0, moveto(0, pptw);
	clearf = clearflag;
    } else if (winw != columns)
	resetvideo();

/* now winw equals columns; now all width comparisons can be made to winw */

    if (isset(SINGLELINEZLE) || !termok) {
	singlerefresh();
	return;
    }

/* first, we generate the video line buffers so we know what to put on
   the screen - also determine final cursor position (nvln, nvcs) */

    s = (unsigned char *)(nbuf[ln = 0] + pptw);
    t = line;
    sen = (unsigned char *)(*nbuf + winw);
    for (; *t; t++) {
	if (t == scs)			/* if cursor is here, remember it */
	    nvcs = s - (unsigned char *)(nbuf[nvln = ln]);

	if (*t == '\n')			/* newline */
	    nextline
	else if (*t == '\t') {		/* tab */
	    t0 = (char *)s - nbuf[ln];
	    if ((t0 | 7) + 1 >= winw)
		nextline
	    else
		do
		    *s++ = ' ';
		while ((++t0) & 7);
	} else if (icntrl(*t)) {	/* other control character */
	    *s++ = '^';
	    if (s == sen)
		nextline
	    *s++ = (*t == 127) ? '?' : (*t | '@');
	} else				/* normal character */
	    *s++ = *t;
	if (s == sen)
	    nextline
    }

/* if we're really on the next line, don't fake it; do everything properly */
    if (t == scs && (nvcs = s - (unsigned char *)(nbuf[nvln = ln])) == winw) {
	*s = '\0';
	if (!nbuf[++ln])
	    nbuf[ln] = (char *)zalloc(winw + 1);
	s = (unsigned char *)nbuf[ln];
	nvcs = 0;
	nvln++;
    }
    *s = '\0';
    nlnct = ln + 1;

    if (statusline) {
	if (nvln == winh - 1) {
	/* cursor is on the last line which is also to be the status line,
	   so we'll have to scroll, though no need to scroll half screen */
	    s = (unsigned char *)(nbuf[1]);
	    for (t0 = 2; t0 < winh; t0++)
		nbuf[t0 - 1] = nbuf[t0];
	    nbuf[winh - 1] = (char *)s;
	    nvln--;
	    memset(nbuf[0], ' ', pptw);
	    t0 = winw - pptw;
	    memset(nbuf[0] + pptw, '\0', t0 + 1);
	    strncpy(nbuf[0] + pptw, ">....", t0 > 5 ? 5 : t0);

	}
	if (!nbuf[(nlnct == winh) ? winh - 1 : nlnct++])
	    nbuf[nlnct - 1] = (char *)zalloc(winw + 1);
	s = (unsigned char *)nbuf[nlnct - 1];
	t = (unsigned char *)statusline;
	sen = (unsigned char *)(*nbuf + winw);
	for (; *t; t++) {
	    if (icntrl(*t)) {	/* simplified processing in the status line */
		if (s == sen)
		    nextline;
		*s++ = '^';
		if (s == sen)
		    nextline
		*s++ = (*t == 127) ? '?' : (*t | '@');
	    } else {
		if (s == sen)
		    nextline
		*s++ = *t;
	    }
	}
	*s = '\0';
    }
    for (ln = nlnct; ln < winh; ln++)
	zfree(nbuf[ln], winw + 1), nbuf[ln] = NULL;

    /* determine whether the right-prompt exists and can fit on the screen */
    pptbuf = putprompt(rpmpt, &pptlen, &rpw, 1);
    rpmpt_attr = txtchange;
    put_rpmpt = pptlen && (int)strlen(nbuf[0]) + rpw < winw - 1;

    for (ln = 0; !clearf && (ln < nlnct); ln++) {
	/* if we have more lines than last time, clear the newly-used lines */
	if (ln == olnct)
	    cleareol = 1;

    /* if old line and new line are different,
       see if we can insert/delete a line to speed up update */

	if (ln < olnct - 1 && !(hasam && vcs == winw) &&
	    nbuf[ln] && obuf[ln] &&
	    strncmp(nbuf[ln], obuf[ln], 16)) {
	    if (tccan(TCDELLINE) && obuf[ln + 1] && obuf[ln + 1][0] &&
		nbuf[ln] && !strncmp(nbuf[ln], obuf[ln + 1], 16)) {
		moveto(ln, 0);
		tcout(TCDELLINE);
		zfree(obuf[ln], winw + 1);
		for (t0 = ln; t0 != olnct; t0++)
		    obuf[t0] = obuf[t0 + 1];
		obuf[--olnct] = NULL;
	    }
	/* don't try to insert a line if olnct = vmaxln (vmaxln is the number
	   of lines that have been displayed by this routine) so that we don't
	   go off the end of the screen. */

	    else if (tccan(TCINSLINE) && olnct < vmaxln && nbuf[ln + 1] &&
		     obuf[ln] && !strncmp(nbuf[ln + 1], obuf[ln], 16)) {
		moveto(ln, 0);
		tcout(TCINSLINE);
		for (t0 = olnct; t0 != ln; t0--)
		    obuf[t0] = obuf[t0 - 1];
		obuf[ln] = NULL;
		olnct++;
	    }
	}

    /* update the single line */
	refreshline(ln);

    /* output the right-prompt if appropriate */
	if (put_rpmpt && !ln && !oput_rpmpt) {
	    moveto(0, winw - 1 - rpw);
	    fwrite(pptbuf, pptlen, 1, shout);
	    vcs = winw - 1;
	/* reset character attributes to that set by the main prompt */
	    txtchange = pmpt_attr;
	    if (txtchangeisset(TXTNOBOLDFACE) && (rpmpt_attr & TXTBOLDFACE))
		tsetcap(TCALLATTRSOFF, 0);
	    if (txtchangeisset(TXTNOSTANDOUT) && (rpmpt_attr & TXTSTANDOUT))
		tsetcap(TCSTANDOUTEND, 0);
	    if (txtchangeisset(TXTNOUNDERLINE) && (rpmpt_attr & TXTUNDERLINE))
		tsetcap(TCUNDERLINEEND, 0);
	    if (txtchangeisset(TXTBOLDFACE) && (rpmpt_attr & TXTNOBOLDFACE))
		tsetcap(TCBOLDFACEBEG, 0);
	    if (txtchangeisset(TXTSTANDOUT) && (rpmpt_attr & TXTNOSTANDOUT))
		tsetcap(TCSTANDOUTBEG, 0);
	    if (txtchangeisset(TXTUNDERLINE) && (rpmpt_attr & TXTNOUNDERLINE))
		tsetcap(TCUNDERLINEBEG, 0);
	}
    }

/* if old buffer had extra lines, set them to be cleared and refresh them
individually */

    if (olnct > nlnct) {
	cleareol = 1;
	for (ln = nlnct; ln < olnct; ln++)
	    refreshline(ln);
    }

/* reset character attributes */
    if (clearf && postedit) {
	if ((txtchange = pmpt_attr ? pmpt_attr : rpmpt_attr)) {
	    if (txtchangeisset(TXTNOBOLDFACE))
		tsetcap(TCALLATTRSOFF, 0);
	    if (txtchangeisset(TXTNOSTANDOUT))
		tsetcap(TCSTANDOUTEND, 0);
	    if (txtchangeisset(TXTNOUNDERLINE))
		tsetcap(TCUNDERLINEEND, 0);
	    if (txtchangeisset(TXTBOLDFACE))
		tsetcap(TCBOLDFACEBEG, 0);
	    if (txtchangeisset(TXTSTANDOUT))
		tsetcap(TCSTANDOUTBEG, 0);
	    if (txtchangeisset(TXTUNDERLINE))
		tsetcap(TCUNDERLINEBEG, 0);
	}
    }
    clearf = 0;

/* move to the new cursor position */
    moveto(nvln, nvcs);

/* swap old and new buffers - better than freeing/allocating every time */
    qbuf = nbuf;
    nbuf = obuf;
    obuf = qbuf;
/* store current values so we can use them next time */
    ovln = nvln;
    olnct = nlnct;
    oput_rpmpt = put_rpmpt;
    if (nlnct > vmaxln)
	vmaxln = nlnct;
    fflush(shout);		/* make sure everything is written out */
}

#define tcinscost(X)   (tccan(TCMULTINS) ? tclen[TCMULTINS] : (X)*tclen[TCINS])
#define tcdelcost(X)   (tccan(TCMULTDEL) ? tclen[TCMULTDEL] : (X)*tclen[TCDEL])
#define tc_delchars(X)	(void) tcmultout(TCDEL, TCMULTDEL, (X))
#define tc_inschars(X)	(void) tcmultout(TCINS, TCMULTINS, (X))
#define tc_upcurs(X)	(void) tcmultout(TCUP, TCMULTUP, (X))
#define tc_leftcurs(X)	(void) tcmultout(TCLEFT, TCMULTLEFT, (X))

/* refresh one line, using whatever speed-up tricks are provided by the tty */

/**/
void
refreshline(int ln)
{
    char *nl, *ol, *p1;		/* line buffer pointers			 */
    int ccs = 0,		/* temporary count for cursor position	 */
	col_cleareol,		/* clear to end-of-line from this column */
	i, j,			/* tmp					 */
	nllen, ollen;		/* new and old line buffer lengths	 */

    nl = nbuf[ln];
    nllen = nl ? strlen(nl) : 0;
    ol = obuf[ln] ? obuf[ln] : "";
    ollen = strlen(ol);

/* 1: pad out the new buffer with spaces to contain _all_ of the characters
      which need to be written. do this now to allow some pre-processing */

    if (cleareol ||		/* request to clear to end of line */
	!nllen) {		/* no line buffer given */
	p1 = halloc(winw + 1);
	if (nllen)
	    strncpy(p1, nl, nllen);
	memset(p1 + nllen, ' ', winw - nllen);
	p1[winw] = '\0';
	nl = p1;
	nllen = winw;
    } else if (ln == 0 && put_rpmpt != oput_rpmpt && winw - 1 > nllen) {
	/* prompt changed */
	p1 = halloc(winw);
	if (nllen)
	    strncpy(p1, nl, nllen);
	memset(p1 + nllen, ' ', winw - 1 - nllen);
	p1[winw - 1] = '\0';
	nl = p1;
	nllen = winw - 1;
    } else if (ollen > nllen) { /* make new line at least as long as old */
	p1 = halloc(ollen + 1);
	strncpy(p1, nl, nllen);
	memset(p1 + nllen, ' ', ollen - nllen);
	p1[ollen] = '\0';
	nl = p1;
	nllen = ollen;
    }

/* 2: see if we can clear to end-of-line, and if it's faster, work out where
   to do it from - we can normally only do so if there's no right-prompt */

    col_cleareol = -1;
    if (tccan(TCCLEAREOL) &&
	(nllen == winw ||	/* new buffer goes to the end of the line */
	!put_rpmpt)) {
	for (i = nllen; i && nl[i - 1] == ' '; i--);
	for (j = ollen; j && ol[j - 1] == ' '; j--);
	if ((j > i + tclen[TCCLEAREOL]) || /* new buf's spaces early enough */
	    (nllen == winw && nl[winw - 1] == ' '))
	    col_cleareol = i;
    }

/* 3: set character for first column, in case automargin stuff needs doing */
    am_char = *nl;

/* 4: main display loop - write out the buffer using whatever tricks we can */

    for (;;) {
    /* skip past all matching characters */
	for (; *nl && (*nl == *ol); nl++, ol++, ccs++) ;

	if (!*nl)		/* we've finished writing the new line */
	    return;

	moveto(ln, ccs);	/* move to where we do all output from */

    /* if we can finish quickly, do so */
	if ((col_cleareol != -1) && (ccs >= col_cleareol)) {
	    tcout(TCCLEAREOL);
	    SELECT_ADD_COST(tclen[TCCLEAREOL]);
	    return;
	}

    /* if we've reached the end of the old buffer, then there are few tricks
       we can do, so we just dump out what we must and clear if we can */
	if (!*ol) {
	    i = (col_cleareol != -1) ? col_cleareol : nllen;
	    i -= ccs;
	    fwrite(nl, i, 1, shout);
	    SELECT_ADD_COST(i);
	    vcs += i;
	    if (col_cleareol != -1) {
		tcout(TCCLEAREOL);
		SELECT_ADD_COST(tclen[TCCLEAREOL]);
	    }
	    return;
	}

    /* if there's no right-prompt,
       see whether we can insert or delete characters */
	if (ln || !put_rpmpt || !oput_rpmpt) {

	/* deleting characters - see if we can find a match series that
	   makes it cheaper to delete intermediate characters
	   eg. oldline: hifoobar } hopefully cheaper here to delete two
	       newline: foobar	 } characters, then we have six matches */

	    if (tccan(TCDEL) && nl[1] && ol[1] && (ol[1] != nl[1])) {
		for (i = 0, p1 = ol; *p1; p1++, i++)
		    if (tcdelcost(i) < pfxlen(p1, nl)) {
			tc_delchars(i);
			SELECT_ADD_COST(i);
			ol = p1;
			break;
		    }
		if (*p1)
		    continue;
	    }
	/* inserting characters - characters pushed off the right should be
	   annihilated, but we don't do this if we're on the last line lest
	   undesired scrolling occurs due to `illegal' characters on screen */

	    if ((vln != lines - 1) &&	/* not on last line */
		tccan(TCINS) && nl[1] && ol[1] && (ol[1] != nl[1])) {
		for (i = 0, p1 = nl; *p1; p1++, i++)
		    if (tcinscost(i) < pfxlen(p1, ol)) {
			tc_inschars(i);
			SELECT_ADD_COST(2 * i);
			fwrite(nl, i, 1, shout);
			ccs = (vcs += i);
			nl = p1;
			break;
		    }
		if (*p1)
		    continue;
	    }
	}
    /* we can't do any fancy tricks, so just dump the single character
       and keep on trying */
	putc(*nl, shout);
	SELECT_ADD_COST(1);
	nl++, ol++;
	ccs++, vcs++;
    }
}

/* move the cursor to line ln (relative to the prompt line),
   absolute column cl; update vln, vcs - video line and column */

/**/
void
moveto(int ln, int cl)
{
    if (ln == vln && cl == vcs)
	return;

    if (vcs == winw) {
	if (!hasam) {
	    putc('\r', shout);
	    putc('\n', shout);
	    SELECT_ADD_COST(2);
	} else {
	    putc(am_char, shout);
	    tcout(TCLEFT);
	    SELECT_ADD_COST(1 + tclen[TCLEFT]);
	}
	vln++, vcs = 0;
    }
    am_char = ' ';

/* move up */
    if (ln < vln) {
	tc_upcurs(vln - ln);
	vln = ln;
    }
/* move down; if we might go off the end of the screen, use newlines
   instead of TCDOWN */

    while (ln > vln) {
	if (vln < vmaxln - 1)
	    if (ln > vmaxln - 1) {
		if (tc_downcurs(vmaxln - 1 - vln))
		    vcs = 0;
		vln = vmaxln - 1;
	    } else {
		if (tc_downcurs(ln - vln))
		    vcs = 0;
		vln = ln;
		continue;
	    }
	putc('\r', shout), vcs = 0; /* safety precaution */
	SELECT_ADD_COST(1);
	while (ln > vln) {
	    putc('\n', shout);
	    SELECT_ADD_COST(1);
	    vln++;
	}
    }
/* choose cheapest movements for ttys without multiple movement capabilities -
   do this now because it's easier (to code) */
    if (cl <= vcs / 2) {
	putc('\r', shout);
	SELECT_ADD_COST(1);
	vcs = 0;
    }
    if (vcs < cl)
	tc_rightcurs(cl);
    else if (vcs > cl)
	tc_leftcurs(vcs - cl);
    vcs = cl;
}

/**/
int
tcmultout(int cap, int multcap, int ct)
{
    if (tccan(multcap) && (!tccan(cap) || tclen[multcap] < tclen[cap] * ct)) {
	tcoutarg(multcap, ct);
	SELECT_ADD_COST(tclen[multcap]);
	return 1;
    } else if (tccan(cap)) {
	SELECT_ADD_COST((tclen[cap] * ct));
	while (ct--)
	    tcout(cap);
	return 1;
    }
    return 0;
}

/**/
void
tc_rightcurs(int cl)
{
    int ct = cl - vcs,		/* number of characters to move across	    */
	horz_tabs = 0,		/* number of horizontal tabs if we do them  */
	i = vcs,		/* cursor position after initial movements  */
	j = 0;			/* number of chars outputted if we use tabs */
    char *t;

/* calculate how many horizontal tabs it would take, if we can do them -
   tabs are assumed to be 8 spaces */
    if (tccan(TCNEXTTAB) && ((vcs | 7) < cl)) {
	horz_tabs = 1;
	i = (vcs | 7) + 1;
	for (; i + 8 <= cl; i += 8)
	    horz_tabs++;
	j = cl - i;		/* number of chars after last tab */
	if (tccan(TCRIGHT))
	    j *= tclen[TCRIGHT];
	j += (horz_tabs * tclen[TCNEXTTAB]); /* # of chars if we use tabs */
    }

/* do a multright if we can - if it's cheaper or we can't use other tricks */
    if (tccan(TCMULTRIGHT) &&
	(!tccan(TCRIGHT) || (tclen[TCMULTRIGHT] < tclen[TCRIGHT] * ct) ||
	 !tccan(TCNEXTTAB) || (tclen[TCMULTRIGHT] < j))) {
	tcoutarg(TCMULTRIGHT, ct);
	SELECT_ADD_COST(tclen[TCMULTRIGHT]);
	return;
    }

/* try to go with tabs if a multright is not feasible/convenient */
    if (horz_tabs) {
	SELECT_ADD_COST((tclen[TCNEXTTAB] * horz_tabs));
	for (; horz_tabs--;)
	    tcout(TCNEXTTAB);
	if ((ct = cl - i) == 0) /* number of chars still to move across */
	    return;
    }

/* or try to dump lots of right movements */
    if (tccan(TCRIGHT)) {
	SELECT_ADD_COST((tclen[TCRIGHT] * ct));
	for (; ct--;)
	    tcout(TCRIGHT);
	return;
    }

/* otherwise _carefully_ write the contents of the video buffer */
    SELECT_ADD_COST(ct);
    for (j = 0, t = nbuf[vln]; *t && (j < i); j++, t++);
    if (j == i)
	for ( ; *t && ct; ct--, t++)
	    putc(*t, shout);
    while (ct--)
	putc(' ', shout);	/* not my fault your terminal can't go right */
}

/**/
int
tc_downcurs(int ct)
{
    int ret = 0;

    if (ct && !tcmultout(TCDOWN, TCMULTDOWN, ct)) {
	SELECT_ADD_COST(ct + 1);
	while (ct--)
	    putc('\n', shout);
	putc('\r', shout), ret = -1;
    }
    return ret;
}

/* I'm NOT going to worry about padding unless anyone complains. */

/**/
int
putshout(int c)
{
    putc(c, shout);
    return 0;
}

/**/
void
tcout(int cap)
{
    tputs(tcstr[cap], 1, putshout);
}

/**/
void
tcoutarg(int cap, int arg)
{
    tputs(tgoto(tcstr[cap], arg, arg), 1, putshout);
}

/**/
void
clearscreen(void)
{
    tcout(TCCLEARSCREEN);
    resetneeded = 1;
    clearflag = 0;
}

/**/
void
redisplay(void)
{
    moveto(0, pptw);
    putc('\r', shout);
    resetneeded = 1;
    clearflag = 0;
}

/**/
void
singlerefresh(void)
{
    char *vbuf, *vp,		/* video buffer and pointer    */
	**qbuf,			/* tmp			       */
	*refreshop = *obuf;	/* pointer to old video buffer */
    int t0,			/* tmp			       */
	vsiz,			/* size of new video buffer    */
	nvcs = 0;		/* new video cursor column     */

    nlnct = 1;
/* generate the new line buffer completely */
    for (vsiz = 1 + pptw, t0 = 0; t0 != ll; t0++, vsiz++)
	if (line[t0] == '\t')
	    vsiz = (vsiz | 7) + 1;
	else if (icntrl(line[t0]))
	    vsiz++;
    vbuf = (char *)zalloc(vsiz);

    memcpy(vbuf, pptbuf + pptlen - pptw, pptw); /* only use last part of prompt */
    vbuf[pptw] = '\0';
    vp = vbuf + pptw;

    for (t0 = 0; t0 != ll; t0++) {
	if (line[t0] == '\t')
	    for (*vp++ = ' '; (vp - vbuf) & 7; )
		*vp++ = ' ';
	else if (line[t0] == '\n') {
	    *vp++ = '\\';
	    *vp++ = 'n';
	} else if (line[t0] == 0x7f) {
	    *vp++ = '^';
	    *vp++ = '?';
	} else if (icntrl(line[t0])) {
	    *vp++ = '^';
	    *vp++ = line[t0] | '@';
	} else
	    *vp++ = line[t0];
	if (t0 == cs)
	    nvcs = vp - vbuf - 1;
    }
    if (t0 == cs)
	nvcs = vp - vbuf;
    *vp = '\0';

/* determine which part of the new line buffer we want for the display */
    if ((winpos && nvcs < winpos + 1) || (nvcs > winpos + winw - 2)) {
	if ((winpos = nvcs - ((winw - hasam) / 2)) < 0)
	    winpos = 0;
    }
    if (winpos)
	vbuf[winpos] = '<';	/* line continues to the left */
    if ((int)strlen(vbuf + winpos) > (winw - hasam)) {
	vbuf[winpos + winw - hasam - 1] = '>';	/* line continues to right */
	vbuf[winpos + winw - hasam] = '\0';
    }
    strcpy(nbuf[0], vbuf + winpos);
    zfree(vbuf, vsiz);
    nvcs -= winpos;

/* display the `visable' portion of the line buffer */
    for (t0 = 0, vp = *nbuf;;) {
    /* skip past all matching characters */
	for (; *vp && *vp == *refreshop; t0++, vp++, refreshop++) ;

	if (!*vp && !*refreshop)
	    break;

	singmoveto(t0);		/* move to where we do all output from */

	if (!*refreshop) {
	    if ((t0 = strlen(vp)))
		fwrite(vp, t0, 1, shout);
	    vcs += t0;
	    break;
	}
	if (!*vp) {
	    if (tccan(TCCLEAREOL))
		tcout(TCCLEAREOL);
	    else
		for (; *refreshop++; vcs++)
		    putc(' ', shout);
	    break;
	}
	putc(*vp, shout);
	vcs++, t0++;
	vp++, refreshop++;
    }
/* move to the new cursor position */
    singmoveto(nvcs);

    qbuf = nbuf;
    nbuf = obuf;
    obuf = qbuf;
    fflush(shout);		/* make sure everything is written out */
}

/**/
void
singmoveto(int pos)
{
    if (pos == vcs)
	return;
    if (pos <= vcs / 2) {
	putc('\r', shout);
	vcs = 0;
    }
    if (pos < vcs) {
	tc_leftcurs(vcs - pos);
	vcs = pos;
    }
    if (pos > vcs) {
	if (tcmultout(TCRIGHT, TCMULTRIGHT, pos - vcs))
	    vcs = pos;
	else
	    while (pos > vcs) {
		putc(nbuf[0][vcs], shout);
		vcs++;
	    }
    }
}
