File: [local] / src / usr.bin / mg / display.c (download)
Revision 1.41, Fri May 31 18:03:44 2013 UTC (11 years ago) by lum
Branch: MAIN
CVS Tags: OPENBSD_5_6_BASE, OPENBSD_5_6, OPENBSD_5_5_BASE, OPENBSD_5_5, OPENBSD_5_4_BASE, OPENBSD_5_4 Changes since 1.40: +6 -6 lines
Make the system bell toggleable via 'audible-bell', and if switched
off, make available an alternative 'visible-bell'.
ok florian@ jasper@ Feedback Sunil Nimmagadda.
|
/* $OpenBSD: display.c,v 1.41 2013/05/31 18:03:44 lum Exp $ */
/* This file is in the public domain. */
/*
* The functions in this file handle redisplay. The
* redisplay system knows almost nothing about the editing
* process; the editing functions do, however, set some
* hints to eliminate a lot of the grinding. There is more
* that can be done; the "vtputc" interface is a real
* pig.
*/
#include "def.h"
#include "kbd.h"
#include <ctype.h>
/*
* You can change these back to the types
* implied by the name if you get tight for space. If you
* make both of them "int" you get better code on the VAX.
* They do nothing if this is not Gosling redisplay, except
* for change the size of a structure that isn't used.
* A bit of a cheat.
*/
#define XCHAR int
#define XSHORT int
#ifdef STANDOUT_GLITCH
#include <term.h>
#endif
/*
* A video structure always holds
* an array of characters whose length is equal to
* the longest line possible. v_text is allocated
* dynamically to fit the screen width.
*/
struct video {
short v_hash; /* Hash code, for compares. */
short v_flag; /* Flag word. */
short v_color; /* Color of the line. */
XSHORT v_cost; /* Cost of display. */
char *v_text; /* The actual characters. */
};
#define VFCHG 0x0001 /* Changed. */
#define VFHBAD 0x0002 /* Hash and cost are bad. */
#define VFEXT 0x0004 /* extended line (beond ncol) */
/*
* SCORE structures hold the optimal
* trace trajectory, and the cost of redisplay, when
* the dynamic programming redisplay code is used.
* If no fancy redisplay, this isn't used. The trace index
* fields can be "char", and the cost a "short", but
* this makes the code worse on the VAX.
*/
struct score {
XCHAR s_itrace; /* "i" index for track back. */
XCHAR s_jtrace; /* "j" index for trace back. */
XSHORT s_cost; /* Display cost. */
};
void vtmove(int, int);
void vtputc(int);
void vtpute(int);
int vtputs(const char *);
void vteeol(void);
void updext(int, int);
void modeline(struct mgwin *, int);
void setscores(int, int);
void traceback(int, int, int, int);
void ucopy(struct video *, struct video *);
void uline(int, struct video *, struct video *);
void hash(struct video *);
int sgarbf = TRUE; /* TRUE if screen is garbage. */
int vtrow = HUGE; /* Virtual cursor row. */
int vtcol = HUGE; /* Virtual cursor column. */
int tthue = CNONE; /* Current color. */
int ttrow = HUGE; /* Physical cursor row. */
int ttcol = HUGE; /* Physical cursor column. */
int tttop = HUGE; /* Top of scroll region. */
int ttbot = HUGE; /* Bottom of scroll region. */
int lbound = 0; /* leftmost bound of the current */
/* line being displayed */
struct video **vscreen; /* Edge vector, virtual. */
struct video **pscreen; /* Edge vector, physical. */
struct video *video; /* Actual screen data. */
struct video blanks; /* Blank line image. */
/*
* This matrix is written as an array because
* we do funny things in the "setscores" routine, which
* is very compute intensive, to make the subscripts go away.
* It would be "SCORE score[NROW][NROW]" in old speak.
* Look at "setscores" to understand what is up.
*/
struct score *score; /* [NROW * NROW] */
#ifndef LINENOMODE
#define LINENOMODE TRUE
#endif /* !LINENOMODE */
static int linenos = LINENOMODE;
static int colnos = FALSE;
/* Is macro recording enabled? */
extern int macrodef;
/* Is working directory global? */
extern int globalwd;
/*
* Since we don't have variables (we probably should) these are command
* processors for changing the values of mode flags.
*/
/* ARGSUSED */
int
linenotoggle(int f, int n)
{
if (f & FFARG)
linenos = n > 0;
else
linenos = !linenos;
sgarbf = TRUE;
return (TRUE);
}
/* ARGSUSED */
int
colnotoggle(int f, int n)
{
if (f & FFARG)
colnos = n > 0;
else
colnos = !colnos;
sgarbf = TRUE;
return (TRUE);
}
/*
* Reinit the display data structures, this is called when the terminal
* size changes.
*/
int
vtresize(int force, int newrow, int newcol)
{
int i;
int rowchanged, colchanged;
static int first_run = 1;
struct video *vp;
if (newrow < 1 || newcol < 1)
return (FALSE);
rowchanged = (newrow != nrow);
colchanged = (newcol != ncol);
#define TRYREALLOC(a, n) do { \
void *tmp; \
if ((tmp = realloc((a), (n))) == NULL) { \
panic("out of memory in display code"); \
} \
(a) = tmp; \
} while (0)
/* No update needed */
if (!first_run && !force && !rowchanged && !colchanged)
return (TRUE);
if (first_run)
memset(&blanks, 0, sizeof(blanks));
if (rowchanged || first_run) {
int vidstart;
/*
* This is not pretty.
*/
if (nrow == 0)
vidstart = 0;
else
vidstart = 2 * (nrow - 1);
/*
* We're shrinking, free some internal data.
*/
if (newrow < nrow) {
for (i = 2 * (newrow - 1); i < 2 * (nrow - 1); i++) {
free(video[i].v_text);
video[i].v_text = NULL;
}
}
TRYREALLOC(score, newrow * newrow * sizeof(struct score));
TRYREALLOC(vscreen, (newrow - 1) * sizeof(struct video *));
TRYREALLOC(pscreen, (newrow - 1) * sizeof(struct video *));
TRYREALLOC(video, (2 * (newrow - 1)) * sizeof(struct video));
/*
* Zero-out the entries we just allocated.
*/
for (i = vidstart; i < 2 * (newrow - 1); i++)
memset(&video[i], 0, sizeof(struct video));
/*
* Reinitialize vscreen and pscreen arrays completely.
*/
vp = &video[0];
for (i = 0; i < newrow - 1; ++i) {
vscreen[i] = vp;
++vp;
pscreen[i] = vp;
++vp;
}
}
if (rowchanged || colchanged || first_run) {
for (i = 0; i < 2 * (newrow - 1); i++)
TRYREALLOC(video[i].v_text, newcol * sizeof(char));
TRYREALLOC(blanks.v_text, newcol * sizeof(char));
}
nrow = newrow;
ncol = newcol;
if (ttrow > nrow)
ttrow = nrow;
if (ttcol > ncol)
ttcol = ncol;
first_run = 0;
return (TRUE);
}
#undef TRYREALLOC
/*
* Initialize the data structures used
* by the display code. The edge vectors used
* to access the screens are set up. The operating
* system's terminal I/O channel is set up. Fill the
* "blanks" array with ASCII blanks. The rest is done
* at compile time. The original window is marked
* as needing full update, and the physical screen
* is marked as garbage, so all the right stuff happens
* on the first call to redisplay.
*/
void
vtinit(void)
{
int i;
ttopen();
ttinit();
/*
* ttinit called ttresize(), which called vtresize(), so our data
* structures are setup correctly.
*/
blanks.v_color = CTEXT;
for (i = 0; i < ncol; ++i)
blanks.v_text[i] = ' ';
}
/*
* Tidy up the virtual display system
* in anticipation of a return back to the host
* operating system. Right now all we do is position
* the cursor to the last line, erase the line, and
* close the terminal channel.
*/
void
vttidy(void)
{
ttcolor(CTEXT);
ttnowindow(); /* No scroll window. */
ttmove(nrow - 1, 0); /* Echo line. */
tteeol();
tttidy();
ttflush();
ttclose();
}
/*
* Move the virtual cursor to an origin
* 0 spot on the virtual display screen. I could
* store the column as a character pointer to the spot
* on the line, which would make "vtputc" a little bit
* more efficient. No checking for errors.
*/
void
vtmove(int row, int col)
{
vtrow = row;
vtcol = col;
}
/*
* Write a character to the virtual display,
* dealing with long lines and the display of unprintable
* things like control characters. Also expand tabs every 8
* columns. This code only puts printing characters into
* the virtual display image. Special care must be taken when
* expanding tabs. On a screen whose width is not a multiple
* of 8, it is possible for the virtual cursor to hit the
* right margin before the next tab stop is reached. This
* makes the tab code loop if you are not careful.
* Three guesses how we found this.
*/
void
vtputc(int c)
{
struct video *vp;
c &= 0xff;
vp = vscreen[vtrow];
if (vtcol >= ncol)
vp->v_text[ncol - 1] = '$';
else if (c == '\t'
#ifdef NOTAB
&& !(curbp->b_flag & BFNOTAB)
#endif
) {
do {
vtputc(' ');
} while (vtcol < ncol && (vtcol & 0x07) != 0);
} else if (ISCTRL(c)) {
vtputc('^');
vtputc(CCHR(c));
} else if (isprint(c))
vp->v_text[vtcol++] = c;
else {
char bf[5];
snprintf(bf, sizeof(bf), "\\%o", c);
vtputs(bf);
}
}
/*
* Put a character to the virtual screen in an extended line. If we are not
* yet on left edge, don't print it yet. Check for overflow on the right
* margin.
*/
void
vtpute(int c)
{
struct video *vp;
c &= 0xff;
vp = vscreen[vtrow];
if (vtcol >= ncol)
vp->v_text[ncol - 1] = '$';
else if (c == '\t'
#ifdef NOTAB
&& !(curbp->b_flag & BFNOTAB)
#endif
) {
do {
vtpute(' ');
} while (((vtcol + lbound) & 0x07) != 0 && vtcol < ncol);
} else if (ISCTRL(c) != FALSE) {
vtpute('^');
vtpute(CCHR(c));
} else {
if (vtcol >= 0)
vp->v_text[vtcol] = c;
++vtcol;
}
}
/*
* Erase from the end of the software cursor to the end of the line on which
* the software cursor is located. The display routines will decide if a
* hardware erase to end of line command should be used to display this.
*/
void
vteeol(void)
{
struct video *vp;
vp = vscreen[vtrow];
while (vtcol < ncol)
vp->v_text[vtcol++] = ' ';
}
/*
* 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.
*/
void
update(int modelinecolor)
{
struct line *lp;
struct mgwin *wp;
struct video *vp1;
struct video *vp2;
int c, i, j;
int hflag;
int currow, curcol;
int offs, size;
if (charswaiting())
return;
if (sgarbf) { /* must update everything */
wp = wheadp;
while (wp != NULL) {
wp->w_rflag |= WFMODE | WFFULL;
wp = wp->w_wndp;
}
}
if (linenos || colnos) {
wp = wheadp;
while (wp != NULL) {
wp->w_rflag |= WFMODE;
wp = wp->w_wndp;
}
}
hflag = FALSE; /* Not hard. */
for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
/*
* Nothing to be done.
*/
if (wp->w_rflag == 0)
continue;
if ((wp->w_rflag & WFFRAME) == 0) {
lp = wp->w_linep;
for (i = 0; i < wp->w_ntrows; ++i) {
if (lp == wp->w_dotp)
goto out;
if (lp == wp->w_bufp->b_headp)
break;
lp = lforw(lp);
}
}
/*
* Put the middle-line in place.
*/
i = wp->w_frame;
if (i > 0) {
--i;
if (i >= wp->w_ntrows)
i = wp->w_ntrows - 1;
} else if (i < 0) {
i += wp->w_ntrows;
if (i < 0)
i = 0;
} else
i = wp->w_ntrows / 2; /* current center, no change */
/*
* Find the line.
*/
lp = wp->w_dotp;
while (i != 0 && lback(lp) != wp->w_bufp->b_headp) {
--i;
lp = lback(lp);
}
wp->w_linep = lp;
wp->w_rflag |= WFFULL; /* Force full. */
out:
lp = wp->w_linep; /* Try reduced update. */
i = wp->w_toprow;
if ((wp->w_rflag & ~WFMODE) == WFEDIT) {
while (lp != wp->w_dotp) {
++i;
lp = lforw(lp);
}
vscreen[i]->v_color = CTEXT;
vscreen[i]->v_flag |= (VFCHG | VFHBAD);
vtmove(i, 0);
for (j = 0; j < llength(lp); ++j)
vtputc(lgetc(lp, j));
vteeol();
} else if ((wp->w_rflag & (WFEDIT | WFFULL)) != 0) {
hflag = TRUE;
while (i < wp->w_toprow + wp->w_ntrows) {
vscreen[i]->v_color = CTEXT;
vscreen[i]->v_flag |= (VFCHG | VFHBAD);
vtmove(i, 0);
if (lp != wp->w_bufp->b_headp) {
for (j = 0; j < llength(lp); ++j)
vtputc(lgetc(lp, j));
lp = lforw(lp);
}
vteeol();
++i;
}
}
if ((wp->w_rflag & WFMODE) != 0)
modeline(wp, modelinecolor);
wp->w_rflag = 0;
wp->w_frame = 0;
}
lp = curwp->w_linep; /* Cursor location. */
currow = curwp->w_toprow;
while (lp != curwp->w_dotp) {
++currow;
lp = lforw(lp);
}
curcol = 0;
i = 0;
while (i < curwp->w_doto) {
c = lgetc(lp, i++);
if (c == '\t'
#ifdef NOTAB
&& !(curbp->b_flag & BFNOTAB)
#endif
) {
curcol |= 0x07;
curcol++;
} else if (ISCTRL(c) != FALSE)
curcol += 2;
else if (isprint(c))
curcol++;
else {
char bf[5];
snprintf(bf, sizeof(bf), "\\%o", c);
curcol += strlen(bf);
}
}
if (curcol >= ncol - 1) { /* extended line. */
/* flag we are extended and changed */
vscreen[currow]->v_flag |= VFEXT | VFCHG;
updext(currow, curcol); /* and output extended line */
} else
lbound = 0; /* not extended line */
/*
* Make sure no lines need to be de-extended because the cursor is no
* longer on them.
*/
wp = wheadp;
while (wp != NULL) {
lp = wp->w_linep;
i = wp->w_toprow;
while (i < wp->w_toprow + wp->w_ntrows) {
if (vscreen[i]->v_flag & VFEXT) {
/* always flag extended lines as changed */
vscreen[i]->v_flag |= VFCHG;
if ((wp != curwp) || (lp != wp->w_dotp) ||
(curcol < ncol - 1)) {
vtmove(i, 0);
for (j = 0; j < llength(lp); ++j)
vtputc(lgetc(lp, j));
vteeol();
/* this line no longer is extended */
vscreen[i]->v_flag &= ~VFEXT;
}
}
lp = lforw(lp);
++i;
}
/* if garbaged then fix up mode lines */
if (sgarbf != FALSE)
vscreen[i]->v_flag |= VFCHG;
/* and onward to the next window */
wp = wp->w_wndp;
}
if (sgarbf != FALSE) { /* Screen is garbage. */
sgarbf = FALSE; /* Erase-page clears. */
epresf = FALSE; /* The message area. */
tttop = HUGE; /* Forget where you set. */
ttbot = HUGE; /* scroll region. */
tthue = CNONE; /* Color unknown. */
ttmove(0, 0);
tteeop();
for (i = 0; i < nrow - 1; ++i) {
uline(i, vscreen[i], &blanks);
ucopy(vscreen[i], pscreen[i]);
}
ttmove(currow, curcol - lbound);
ttflush();
return;
}
if (hflag != FALSE) { /* Hard update? */
for (i = 0; i < nrow - 1; ++i) {/* Compute hash data. */
hash(vscreen[i]);
hash(pscreen[i]);
}
offs = 0; /* Get top match. */
while (offs != nrow - 1) {
vp1 = vscreen[offs];
vp2 = pscreen[offs];
if (vp1->v_color != vp2->v_color
|| vp1->v_hash != vp2->v_hash)
break;
uline(offs, vp1, vp2);
ucopy(vp1, vp2);
++offs;
}
if (offs == nrow - 1) { /* Might get it all. */
ttmove(currow, curcol - lbound);
ttflush();
return;
}
size = nrow - 1; /* Get bottom match. */
while (size != offs) {
vp1 = vscreen[size - 1];
vp2 = pscreen[size - 1];
if (vp1->v_color != vp2->v_color
|| vp1->v_hash != vp2->v_hash)
break;
uline(size - 1, vp1, vp2);
ucopy(vp1, vp2);
--size;
}
if ((size -= offs) == 0) /* Get screen size. */
panic("Illegal screen size in update");
setscores(offs, size); /* Do hard update. */
traceback(offs, size, size, size);
for (i = 0; i < size; ++i)
ucopy(vscreen[offs + i], pscreen[offs + i]);
ttmove(currow, curcol - lbound);
ttflush();
return;
}
for (i = 0; i < nrow - 1; ++i) { /* Easy update. */
vp1 = vscreen[i];
vp2 = pscreen[i];
if ((vp1->v_flag & VFCHG) != 0) {
uline(i, vp1, vp2);
ucopy(vp1, vp2);
}
}
ttmove(currow, curcol - lbound);
ttflush();
}
/*
* Update a saved copy of a line,
* kept in a video structure. The "vvp" is
* the one in the "vscreen". The "pvp" is the one
* in the "pscreen". This is called to make the
* virtual and physical screens the same when
* display has done an update.
*/
void
ucopy(struct video *vvp, struct video *pvp)
{
vvp->v_flag &= ~VFCHG; /* Changes done. */
pvp->v_flag = vvp->v_flag; /* Update model. */
pvp->v_hash = vvp->v_hash;
pvp->v_cost = vvp->v_cost;
pvp->v_color = vvp->v_color;
bcopy(vvp->v_text, pvp->v_text, ncol);
}
/*
* updext: 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.
*/
void
updext(int currow, int curcol)
{
struct line *lp; /* pointer to current line */
int j; /* index into line */
if (ncol < 2)
return;
/*
* calculate what column the left bound should be
* (force cursor into middle half of screen)
*/
lbound = curcol - (curcol % (ncol >> 1)) - (ncol >> 2);
/*
* scan through the line outputing characters to the virtual screen
* once we reach the left edge
*/
vtmove(currow, -lbound); /* start scanning offscreen */
lp = curwp->w_dotp; /* line to output */
for (j = 0; j < llength(lp); ++j) /* until the end-of-line */
vtpute(lgetc(lp, j));
vteeol(); /* truncate the virtual line */
vscreen[currow]->v_text[0] = '$'; /* and put a '$' in column 1 */
}
/*
* Update a single line. This routine only
* uses basic functionality (no insert and delete character,
* but erase to end of line). The "vvp" points at the video
* structure for the line on the virtual screen, and the "pvp"
* is the same for the physical screen. Avoid erase to end of
* line when updating CMODE color lines, because of the way that
* reverse video works on most terminals.
*/
void
uline(int row, struct video *vvp, struct video *pvp)
{
char *cp1;
char *cp2;
char *cp3;
char *cp4;
char *cp5;
int nbflag;
if (vvp->v_color != pvp->v_color) { /* Wrong color, do a */
ttmove(row, 0); /* full redraw. */
#ifdef STANDOUT_GLITCH
if (pvp->v_color != CTEXT && magic_cookie_glitch >= 0)
tteeol();
#endif
ttcolor(vvp->v_color);
#ifdef STANDOUT_GLITCH
cp1 = &vvp->v_text[magic_cookie_glitch > 0 ? magic_cookie_glitch : 0];
/*
* The odd code for magic_cookie_glitch==0 is to avoid
* putting the invisible glitch character on the next line.
* (Hazeltine executive 80 model 30)
*/
cp2 = &vvp->v_text[ncol - (magic_cookie_glitch >= 0 ?
(magic_cookie_glitch != 0 ? magic_cookie_glitch : 1) : 0)];
#else
cp1 = &vvp->v_text[0];
cp2 = &vvp->v_text[ncol];
#endif
while (cp1 != cp2) {
ttputc(*cp1++);
++ttcol;
}
#ifndef MOVE_STANDOUT
ttcolor(CTEXT);
#endif
return;
}
cp1 = &vvp->v_text[0]; /* Compute left match. */
cp2 = &pvp->v_text[0];
while (cp1 != &vvp->v_text[ncol] && cp1[0] == cp2[0]) {
++cp1;
++cp2;
}
if (cp1 == &vvp->v_text[ncol]) /* All equal. */
return;
nbflag = FALSE;
cp3 = &vvp->v_text[ncol]; /* Compute right match. */
cp4 = &pvp->v_text[ncol];
while (cp3[-1] == cp4[-1]) {
--cp3;
--cp4;
if (cp3[0] != ' ') /* Note non-blanks in */
nbflag = TRUE; /* the right match. */
}
cp5 = cp3; /* Is erase good? */
if (nbflag == FALSE && vvp->v_color == CTEXT) {
while (cp5 != cp1 && cp5[-1] == ' ')
--cp5;
/* Alcyon hack */
if ((int) (cp3 - cp5) <= tceeol)
cp5 = cp3;
}
/* Alcyon hack */
ttmove(row, (int) (cp1 - &vvp->v_text[0]));
#ifdef STANDOUT_GLITCH
if (vvp->v_color != CTEXT && magic_cookie_glitch > 0) {
if (cp1 < &vvp->v_text[magic_cookie_glitch])
cp1 = &vvp->v_text[magic_cookie_glitch];
if (cp5 > &vvp->v_text[ncol - magic_cookie_glitch])
cp5 = &vvp->v_text[ncol - magic_cookie_glitch];
} else if (magic_cookie_glitch < 0)
#endif
ttcolor(vvp->v_color);
while (cp1 != cp5) {
ttputc(*cp1++);
++ttcol;
}
if (cp5 != cp3) /* Do erase. */
tteeol();
}
/*
* Redisplay the mode line for the window pointed to by the "wp".
* This is the only routine that has any idea of how the mode line is
* formatted. You can change the modeline format by hacking at this
* routine. Called by "update" any time there is a dirty window. Note
* that if STANDOUT_GLITCH is defined, first and last magic_cookie_glitch
* characters may never be seen.
*/
void
modeline(struct mgwin *wp, int modelinecolor)
{
int n, md;
struct buffer *bp;
char sl[21]; /* Overkill. Space for 2^64 in base 10. */
int len;
n = wp->w_toprow + wp->w_ntrows; /* Location. */
vscreen[n]->v_color = modelinecolor; /* Mode line color. */
vscreen[n]->v_flag |= (VFCHG | VFHBAD); /* Recompute, display. */
vtmove(n, 0); /* Seek to right line. */
bp = wp->w_bufp;
vtputc('-');
vtputc('-');
if ((bp->b_flag & BFREADONLY) != 0) {
vtputc('%');
if ((bp->b_flag & BFCHG) != 0)
vtputc('*');
else
vtputc('%');
} else if ((bp->b_flag & BFCHG) != 0) { /* "*" if changed. */
vtputc('*');
vtputc('*');
} else {
vtputc('-');
vtputc('-');
}
vtputc('-');
n = 5;
n += vtputs("Mg: ");
if (bp->b_bname[0] != '\0')
n += vtputs(&(bp->b_bname[0]));
while (n < 42) { /* Pad out with blanks. */
vtputc(' ');
++n;
}
vtputc('(');
++n;
for (md = 0; ; ) {
n += vtputs(bp->b_modes[md]->p_name);
if (++md > bp->b_nmodes)
break;
vtputc('-');
++n;
}
/* XXX These should eventually move to a real mode */
if (macrodef == TRUE)
n += vtputs("-def");
if (globalwd == TRUE)
n += vtputs("-gwd");
vtputc(')');
++n;
if (linenos && colnos)
len = snprintf(sl, sizeof(sl), "--L%d--C%d", wp->w_dotline,
getcolpos(wp));
else if (linenos)
len = snprintf(sl, sizeof(sl), "--L%d", wp->w_dotline);
else if (colnos)
len = snprintf(sl, sizeof(sl), "--C%d", getcolpos(wp));
if ((linenos || colnos) && len < sizeof(sl) && len != -1)
n += vtputs(sl);
while (n < ncol) { /* Pad out. */
vtputc('-');
++n;
}
}
/*
* Output a string to the mode line, report how long it was.
*/
int
vtputs(const char *s)
{
int n = 0;
while (*s != '\0') {
vtputc(*s++);
++n;
}
return (n);
}
/*
* Compute the hash code for the line pointed to by the "vp".
* Recompute it if necessary. Also set the approximate redisplay
* cost. The validity of the hash code is marked by a flag bit.
* The cost understand the advantages of erase to end of line.
* Tuned for the VAX by Bob McNamara; better than it used to be on
* just about any machine.
*/
void
hash(struct video *vp)
{
int i, n;
char *s;
if ((vp->v_flag & VFHBAD) != 0) { /* Hash bad. */
s = &vp->v_text[ncol - 1];
for (i = ncol; i != 0; --i, --s)
if (*s != ' ')
break;
n = ncol - i; /* Erase cheaper? */
if (n > tceeol)
n = tceeol;
vp->v_cost = i + n; /* Bytes + blanks. */
for (n = 0; i != 0; --i, --s)
n = (n << 5) + n + *s;
vp->v_hash = n; /* Hash code. */
vp->v_flag &= ~VFHBAD; /* Flag as all done. */
}
}
/*
* Compute the Insert-Delete
* cost matrix. The dynamic programming algorithm
* described by James Gosling is used. This code assumes
* that the line above the echo line is the last line involved
* in the scroll region. This is easy to arrange on the VT100
* because of the scrolling region. The "offs" is the origin 0
* offset of the first row in the virtual/physical screen that
* is being updated; the "size" is the length of the chunk of
* screen being updated. For a full screen update, use offs=0
* and size=nrow-1.
*
* Older versions of this code implemented the score matrix by
* a two dimensional array of SCORE nodes. This put all kinds of
* multiply instructions in the code! This version is written to
* use a linear array and pointers, and contains no multiplication
* at all. The code has been carefully looked at on the VAX, with
* only marginal checking on other machines for efficiency. In
* fact, this has been tuned twice! Bob McNamara tuned it even
* more for the VAX, which is a big issue for him because of
* the 66 line X displays.
*
* On some machines, replacing the "for (i=1; i<=size; ++i)" with
* i = 1; do { } while (++i <=size)" will make the code quite a
* bit better; but it looks ugly.
*/
void
setscores(int offs, int size)
{
struct score *sp;
struct score *sp1;
struct video **vp, **pp;
struct video **vbase, **pbase;
int tempcost;
int bestcost;
int j, i;
vbase = &vscreen[offs - 1]; /* By hand CSE's. */
pbase = &pscreen[offs - 1];
score[0].s_itrace = 0; /* [0, 0] */
score[0].s_jtrace = 0;
score[0].s_cost = 0;
sp = &score[1]; /* Row 0, inserts. */
tempcost = 0;
vp = &vbase[1];
for (j = 1; j <= size; ++j) {
sp->s_itrace = 0;
sp->s_jtrace = j - 1;
tempcost += tcinsl;
tempcost += (*vp)->v_cost;
sp->s_cost = tempcost;
++vp;
++sp;
}
sp = &score[nrow]; /* Column 0, deletes. */
tempcost = 0;
for (i = 1; i <= size; ++i) {
sp->s_itrace = i - 1;
sp->s_jtrace = 0;
tempcost += tcdell;
sp->s_cost = tempcost;
sp += nrow;
}
sp1 = &score[nrow + 1]; /* [1, 1]. */
pp = &pbase[1];
for (i = 1; i <= size; ++i) {
sp = sp1;
vp = &vbase[1];
for (j = 1; j <= size; ++j) {
sp->s_itrace = i - 1;
sp->s_jtrace = j;
bestcost = (sp - nrow)->s_cost;
if (j != size) /* Cd(A[i])=0 @ Dis. */
bestcost += tcdell;
tempcost = (sp - 1)->s_cost;
tempcost += (*vp)->v_cost;
if (i != size) /* Ci(B[j])=0 @ Dsj. */
tempcost += tcinsl;
if (tempcost < bestcost) {
sp->s_itrace = i;
sp->s_jtrace = j - 1;
bestcost = tempcost;
}
tempcost = (sp - nrow - 1)->s_cost;
if ((*pp)->v_color != (*vp)->v_color
|| (*pp)->v_hash != (*vp)->v_hash)
tempcost += (*vp)->v_cost;
if (tempcost < bestcost) {
sp->s_itrace = i - 1;
sp->s_jtrace = j - 1;
bestcost = tempcost;
}
sp->s_cost = bestcost;
++sp; /* Next column. */
++vp;
}
++pp;
sp1 += nrow; /* Next row. */
}
}
/*
* Trace back through the dynamic programming cost
* matrix, and update the screen using an optimal sequence
* of redraws, insert lines, and delete lines. The "offs" is
* the origin 0 offset of the chunk of the screen we are about to
* update. The "i" and "j" are always started in the lower right
* corner of the matrix, and imply the size of the screen.
* A full screen traceback is called with offs=0 and i=j=nrow-1.
* There is some do-it-yourself double subscripting here,
* which is acceptable because this routine is much less compute
* intensive then the code that builds the score matrix!
*/
void
traceback(int offs, int size, int i, int j)
{
int itrace, jtrace;
int k;
int ninsl, ndraw, ndell;
if (i == 0 && j == 0) /* End of update. */
return;
itrace = score[(nrow * i) + j].s_itrace;
jtrace = score[(nrow * i) + j].s_jtrace;
if (itrace == i) { /* [i, j-1] */
ninsl = 0; /* Collect inserts. */
if (i != size)
ninsl = 1;
ndraw = 1;
while (itrace != 0 || jtrace != 0) {
if (score[(nrow * itrace) + jtrace].s_itrace != itrace)
break;
jtrace = score[(nrow * itrace) + jtrace].s_jtrace;
if (i != size)
++ninsl;
++ndraw;
}
traceback(offs, size, itrace, jtrace);
if (ninsl != 0) {
ttcolor(CTEXT);
ttinsl(offs + j - ninsl, offs + size - 1, ninsl);
}
do { /* B[j], A[j] blank. */
k = offs + j - ndraw;
uline(k, vscreen[k], &blanks);
} while (--ndraw);
return;
}
if (jtrace == j) { /* [i-1, j] */
ndell = 0; /* Collect deletes. */
if (j != size)
ndell = 1;
while (itrace != 0 || jtrace != 0) {
if (score[(nrow * itrace) + jtrace].s_jtrace != jtrace)
break;
itrace = score[(nrow * itrace) + jtrace].s_itrace;
if (j != size)
++ndell;
}
if (ndell != 0) {
ttcolor(CTEXT);
ttdell(offs + i - ndell, offs + size - 1, ndell);
}
traceback(offs, size, itrace, jtrace);
return;
}
traceback(offs, size, itrace, jtrace);
k = offs + j - 1;
uline(k, vscreen[k], pscreen[offs + i - 1]);
}