xref: /openbsd/usr.bin/mg/display.c (revision 4cfece93)
1 /*	$OpenBSD: display.c,v 1.48 2017/07/06 19:27:37 schwarze Exp $	*/
2 
3 /* This file is in the public domain. */
4 
5 /*
6  * The functions in this file handle redisplay. The
7  * redisplay system knows almost nothing about the editing
8  * process; the editing functions do, however, set some
9  * hints to eliminate a lot of the grinding. There is more
10  * that can be done; the "vtputc" interface is a real
11  * pig.
12  */
13 
14 #include <sys/queue.h>
15 #include <ctype.h>
16 #include <signal.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <term.h>
21 
22 #include "def.h"
23 #include "kbd.h"
24 
25 /*
26  * A video structure always holds
27  * an array of characters whose length is equal to
28  * the longest line possible. v_text is allocated
29  * dynamically to fit the screen width.
30  */
31 struct video {
32 	short	v_hash;		/* Hash code, for compares.	 */
33 	short	v_flag;		/* Flag word.			 */
34 	short	v_color;	/* Color of the line.		 */
35 	int	v_cost;		/* Cost of display.		 */
36 	char	*v_text;	/* The actual characters.	 */
37 };
38 
39 #define VFCHG	0x0001			/* Changed.			 */
40 #define VFHBAD	0x0002			/* Hash and cost are bad.	 */
41 #define VFEXT	0x0004			/* extended line (beond ncol)	 */
42 
43 /*
44  * SCORE structures hold the optimal
45  * trace trajectory, and the cost of redisplay, when
46  * the dynamic programming redisplay code is used.
47  */
48 struct score {
49 	int	s_itrace;	/* "i" index for track back.	 */
50 	int	s_jtrace;	/* "j" index for trace back.	 */
51 	int	s_cost;		/* Display cost.		 */
52 };
53 
54 void	vtmove(int, int);
55 void	vtputc(int);
56 void	vtpute(int);
57 int	vtputs(const char *);
58 void	vteeol(void);
59 void	updext(int, int);
60 void	modeline(struct mgwin *, int);
61 void	setscores(int, int);
62 void	traceback(int, int, int, int);
63 void	ucopy(struct video *, struct video *);
64 void	uline(int, struct video *, struct video *);
65 void	hash(struct video *);
66 
67 
68 int	sgarbf = TRUE;		/* TRUE if screen is garbage.	 */
69 int	vtrow = HUGE;		/* Virtual cursor row.		 */
70 int	vtcol = HUGE;		/* Virtual cursor column.	 */
71 int	tthue = CNONE;		/* Current color.		 */
72 int	ttrow = HUGE;		/* Physical cursor row.		 */
73 int	ttcol = HUGE;		/* Physical cursor column.	 */
74 int	tttop = HUGE;		/* Top of scroll region.	 */
75 int	ttbot = HUGE;		/* Bottom of scroll region.	 */
76 int	lbound = 0;		/* leftmost bound of the current */
77 				/* line being displayed		 */
78 
79 struct video	**vscreen;		/* Edge vector, virtual.	 */
80 struct video	**pscreen;		/* Edge vector, physical.	 */
81 struct video	 *video;		/* Actual screen data.		 */
82 struct video	  blanks;		/* Blank line image.		 */
83 
84 /*
85  * This matrix is written as an array because
86  * we do funny things in the "setscores" routine, which
87  * is very compute intensive, to make the subscripts go away.
88  * It would be "SCORE	score[NROW][NROW]" in old speak.
89  * Look at "setscores" to understand what is up.
90  */
91 struct score *score;			/* [NROW * NROW] */
92 
93 static int	 linenos = TRUE;
94 static int	 colnos = FALSE;
95 
96 /* Is macro recording enabled? */
97 extern int macrodef;
98 /* Is working directory global? */
99 extern int globalwd;
100 
101 /*
102  * Since we don't have variables (we probably should) these are command
103  * processors for changing the values of mode flags.
104  */
105 /* ARGSUSED */
106 int
107 linenotoggle(int f, int n)
108 {
109 	if (f & FFARG)
110 		linenos = n > 0;
111 	else
112 		linenos = !linenos;
113 
114 	sgarbf = TRUE;
115 
116 	return (TRUE);
117 }
118 
119 /* ARGSUSED */
120 int
121 colnotoggle(int f, int n)
122 {
123 	if (f & FFARG)
124 		colnos = n > 0;
125 	else
126 		colnos = !colnos;
127 
128 	sgarbf = TRUE;
129 
130 	return (TRUE);
131 }
132 
133 /*
134  * Reinit the display data structures, this is called when the terminal
135  * size changes.
136  */
137 int
138 vtresize(int force, int newrow, int newcol)
139 {
140 	int	 i;
141 	int	 rowchanged, colchanged;
142 	static	 int first_run = 1;
143 	struct video	*vp;
144 
145 	if (newrow < 1 || newcol < 1)
146 		return (FALSE);
147 
148 	rowchanged = (newrow != nrow);
149 	colchanged = (newcol != ncol);
150 
151 #define TRYREALLOC(a, n) do {					\
152 		void *tmp;					\
153 		if ((tmp = realloc((a), (n))) == NULL) {	\
154 			panic("out of memory in display code");	\
155 		}						\
156 		(a) = tmp;					\
157 	} while (0)
158 
159 #define TRYREALLOCARRAY(a, n, m) do {				\
160 		void *tmp;					\
161 		if ((tmp = reallocarray((a), (n), (m))) == NULL) {\
162 			panic("out of memory in display code");	\
163 		}						\
164 		(a) = tmp;					\
165 	} while (0)
166 
167 	/* No update needed */
168 	if (!first_run && !force && !rowchanged && !colchanged)
169 		return (TRUE);
170 
171 	if (first_run)
172 		memset(&blanks, 0, sizeof(blanks));
173 
174 	if (rowchanged || first_run) {
175 		int vidstart;
176 
177 		/*
178 		 * This is not pretty.
179 		 */
180 		if (nrow == 0)
181 			vidstart = 0;
182 		else
183 			vidstart = 2 * (nrow - 1);
184 
185 		/*
186 		 * We're shrinking, free some internal data.
187 		 */
188 		if (newrow < nrow) {
189 			for (i = 2 * (newrow - 1); i < 2 * (nrow - 1); i++) {
190 				free(video[i].v_text);
191 				video[i].v_text = NULL;
192 			}
193 		}
194 
195 		TRYREALLOCARRAY(score, newrow, newrow * sizeof(struct score));
196 		TRYREALLOCARRAY(vscreen, (newrow - 1), sizeof(struct video *));
197 		TRYREALLOCARRAY(pscreen, (newrow - 1), sizeof(struct video *));
198 		TRYREALLOCARRAY(video, (newrow - 1), 2 * sizeof(struct video));
199 
200 		/*
201 		 * Zero-out the entries we just allocated.
202 		 */
203 		for (i = vidstart; i < 2 * (newrow - 1); i++)
204 			memset(&video[i], 0, sizeof(struct video));
205 
206 		/*
207 		 * Reinitialize vscreen and pscreen arrays completely.
208 		 */
209 		vp = &video[0];
210 		for (i = 0; i < newrow - 1; ++i) {
211 			vscreen[i] = vp;
212 			++vp;
213 			pscreen[i] = vp;
214 			++vp;
215 		}
216 	}
217 	if (rowchanged || colchanged || first_run) {
218 		for (i = 0; i < 2 * (newrow - 1); i++)
219 			TRYREALLOC(video[i].v_text, newcol);
220 		TRYREALLOC(blanks.v_text, newcol);
221 	}
222 
223 	nrow = newrow;
224 	ncol = newcol;
225 
226 	if (ttrow > nrow)
227 		ttrow = nrow;
228 	if (ttcol > ncol)
229 		ttcol = ncol;
230 
231 	first_run = 0;
232 	return (TRUE);
233 }
234 
235 #undef TRYREALLOC
236 #undef TRYREALLOCARRAY
237 
238 /*
239  * Initialize the data structures used
240  * by the display code. The edge vectors used
241  * to access the screens are set up. The operating
242  * system's terminal I/O channel is set up. Fill the
243  * "blanks" array with ASCII blanks. The rest is done
244  * at compile time. The original window is marked
245  * as needing full update, and the physical screen
246  * is marked as garbage, so all the right stuff happens
247  * on the first call to redisplay.
248  */
249 void
250 vtinit(void)
251 {
252 	int	i;
253 
254 	ttopen();
255 	ttinit();
256 
257 	/*
258 	 * ttinit called ttresize(), which called vtresize(), so our data
259 	 * structures are setup correctly.
260 	 */
261 
262 	blanks.v_color = CTEXT;
263 	for (i = 0; i < ncol; ++i)
264 		blanks.v_text[i] = ' ';
265 }
266 
267 /*
268  * Tidy up the virtual display system
269  * in anticipation of a return back to the host
270  * operating system. Right now all we do is position
271  * the cursor to the last line, erase the line, and
272  * close the terminal channel.
273  */
274 void
275 vttidy(void)
276 {
277 	ttcolor(CTEXT);
278 	ttnowindow();		/* No scroll window.	 */
279 	ttmove(nrow - 1, 0);	/* Echo line.		 */
280 	tteeol();
281 	tttidy();
282 	ttflush();
283 	ttclose();
284 }
285 
286 /*
287  * Move the virtual cursor to an origin
288  * 0 spot on the virtual display screen. I could
289  * store the column as a character pointer to the spot
290  * on the line, which would make "vtputc" a little bit
291  * more efficient. No checking for errors.
292  */
293 void
294 vtmove(int row, int col)
295 {
296 	vtrow = row;
297 	vtcol = col;
298 }
299 
300 /*
301  * Write a character to the virtual display,
302  * dealing with long lines and the display of unprintable
303  * things like control characters. Also expand tabs every 8
304  * columns. This code only puts printing characters into
305  * the virtual display image. Special care must be taken when
306  * expanding tabs. On a screen whose width is not a multiple
307  * of 8, it is possible for the virtual cursor to hit the
308  * right margin before the next tab stop is reached. This
309  * makes the tab code loop if you are not careful.
310  * Three guesses how we found this.
311  */
312 void
313 vtputc(int c)
314 {
315 	struct video	*vp;
316 
317 	c &= 0xff;
318 
319 	vp = vscreen[vtrow];
320 	if (vtcol >= ncol)
321 		vp->v_text[ncol - 1] = '$';
322 	else if (c == '\t'
323 #ifdef	NOTAB
324 	    && !(curbp->b_flag & BFNOTAB)
325 #endif
326 	    ) {
327 		do {
328 			vtputc(' ');
329 		} while (vtcol < ncol && (vtcol & 0x07) != 0);
330 	} else if (ISCTRL(c)) {
331 		vtputc('^');
332 		vtputc(CCHR(c));
333 	} else if (isprint(c))
334 		vp->v_text[vtcol++] = c;
335 	else {
336 		char bf[5];
337 
338 		snprintf(bf, sizeof(bf), "\\%o", c);
339 		vtputs(bf);
340 	}
341 }
342 
343 /*
344  * Put a character to the virtual screen in an extended line.  If we are not
345  * yet on left edge, don't print it yet.  Check for overflow on the right
346  * margin.
347  */
348 void
349 vtpute(int c)
350 {
351 	struct video *vp;
352 
353 	c &= 0xff;
354 
355 	vp = vscreen[vtrow];
356 	if (vtcol >= ncol)
357 		vp->v_text[ncol - 1] = '$';
358 	else if (c == '\t'
359 #ifdef	NOTAB
360 	    && !(curbp->b_flag & BFNOTAB)
361 #endif
362 	    ) {
363 		do {
364 			vtpute(' ');
365 		} while (((vtcol + lbound) & 0x07) != 0 && vtcol < ncol);
366 	} else if (ISCTRL(c) != FALSE) {
367 		vtpute('^');
368 		vtpute(CCHR(c));
369 	} else if (isprint(c)) {
370 		if (vtcol >= 0)
371 			vp->v_text[vtcol] = c;
372 		++vtcol;
373 	} else {
374 		char bf[5], *cp;
375 
376 		snprintf(bf, sizeof(bf), "\\%o", c);
377 		for (cp = bf; *cp != '\0'; cp++)
378 			vtpute(*cp);
379 	}
380 }
381 
382 /*
383  * Erase from the end of the software cursor to the end of the line on which
384  * the software cursor is located. The display routines will decide if a
385  * hardware erase to end of line command should be used to display this.
386  */
387 void
388 vteeol(void)
389 {
390 	struct video *vp;
391 
392 	vp = vscreen[vtrow];
393 	while (vtcol < ncol)
394 		vp->v_text[vtcol++] = ' ';
395 }
396 
397 /*
398  * Make sure that the display is
399  * right. This is a three part process. First,
400  * scan through all of the windows looking for dirty
401  * ones. Check the framing, and refresh the screen.
402  * Second, make sure that "currow" and "curcol" are
403  * correct for the current window. Third, make the
404  * virtual and physical screens the same.
405  */
406 void
407 update(int modelinecolor)
408 {
409 	struct line	*lp;
410 	struct mgwin	*wp;
411 	struct video	*vp1;
412 	struct video	*vp2;
413 	int	 c, i, j;
414 	int	 hflag;
415 	int	 currow, curcol;
416 	int	 offs, size;
417 
418 	if (charswaiting())
419 		return;
420 	if (sgarbf) {		/* must update everything */
421 		wp = wheadp;
422 		while (wp != NULL) {
423 			wp->w_rflag |= WFMODE | WFFULL;
424 			wp = wp->w_wndp;
425 		}
426 	}
427 	if (linenos || colnos) {
428 		wp = wheadp;
429 		while (wp != NULL) {
430 			wp->w_rflag |= WFMODE;
431 			wp = wp->w_wndp;
432 		}
433 	}
434 	hflag = FALSE;			/* Not hard. */
435 	for (wp = wheadp; wp != NULL; wp = wp->w_wndp) {
436 		/*
437 		 * Nothing to be done.
438 		 */
439 		if (wp->w_rflag == 0)
440 			continue;
441 
442 		if ((wp->w_rflag & WFFRAME) == 0) {
443 			lp = wp->w_linep;
444 			for (i = 0; i < wp->w_ntrows; ++i) {
445 				if (lp == wp->w_dotp)
446 					goto out;
447 				if (lp == wp->w_bufp->b_headp)
448 					break;
449 				lp = lforw(lp);
450 			}
451 		}
452 		/*
453 		 * Put the middle-line in place.
454 		 */
455 		i = wp->w_frame;
456 		if (i > 0) {
457 			--i;
458 			if (i >= wp->w_ntrows)
459 				i = wp->w_ntrows - 1;
460 		} else if (i < 0) {
461 			i += wp->w_ntrows;
462 			if (i < 0)
463 				i = 0;
464 		} else
465 			i = wp->w_ntrows / 2; /* current center, no change */
466 
467 		/*
468 		 * Find the line.
469 		 */
470 		lp = wp->w_dotp;
471 		while (i != 0 && lback(lp) != wp->w_bufp->b_headp) {
472 			--i;
473 			lp = lback(lp);
474 		}
475 		wp->w_linep = lp;
476 		wp->w_rflag |= WFFULL;	/* Force full.		 */
477 	out:
478 		lp = wp->w_linep;	/* Try reduced update.	 */
479 		i = wp->w_toprow;
480 		if ((wp->w_rflag & ~WFMODE) == WFEDIT) {
481 			while (lp != wp->w_dotp) {
482 				++i;
483 				lp = lforw(lp);
484 			}
485 			vscreen[i]->v_color = CTEXT;
486 			vscreen[i]->v_flag |= (VFCHG | VFHBAD);
487 			vtmove(i, 0);
488 			for (j = 0; j < llength(lp); ++j)
489 				vtputc(lgetc(lp, j));
490 			vteeol();
491 		} else if ((wp->w_rflag & (WFEDIT | WFFULL)) != 0) {
492 			hflag = TRUE;
493 			while (i < wp->w_toprow + wp->w_ntrows) {
494 				vscreen[i]->v_color = CTEXT;
495 				vscreen[i]->v_flag |= (VFCHG | VFHBAD);
496 				vtmove(i, 0);
497 				if (lp != wp->w_bufp->b_headp) {
498 					for (j = 0; j < llength(lp); ++j)
499 						vtputc(lgetc(lp, j));
500 					lp = lforw(lp);
501 				}
502 				vteeol();
503 				++i;
504 			}
505 		}
506 		if ((wp->w_rflag & WFMODE) != 0)
507 			modeline(wp, modelinecolor);
508 		wp->w_rflag = 0;
509 		wp->w_frame = 0;
510 	}
511 	lp = curwp->w_linep;	/* Cursor location. */
512 	currow = curwp->w_toprow;
513 	while (lp != curwp->w_dotp) {
514 		++currow;
515 		lp = lforw(lp);
516 	}
517 	curcol = 0;
518 	i = 0;
519 	while (i < curwp->w_doto) {
520 		c = lgetc(lp, i++);
521 		if (c == '\t'
522 #ifdef	NOTAB
523 		    && !(curbp->b_flag & BFNOTAB)
524 #endif
525 			) {
526 			curcol |= 0x07;
527 			curcol++;
528 		} else if (ISCTRL(c) != FALSE)
529 			curcol += 2;
530 		else if (isprint(c))
531 			curcol++;
532 		else {
533 			char bf[5];
534 
535 			snprintf(bf, sizeof(bf), "\\%o", c);
536 			curcol += strlen(bf);
537 		}
538 	}
539 	if (curcol >= ncol - 1) {	/* extended line. */
540 		/* flag we are extended and changed */
541 		vscreen[currow]->v_flag |= VFEXT | VFCHG;
542 		updext(currow, curcol);	/* and output extended line */
543 	} else
544 		lbound = 0;	/* not extended line */
545 
546 	/*
547 	 * Make sure no lines need to be de-extended because the cursor is no
548 	 * longer on them.
549 	 */
550 	wp = wheadp;
551 	while (wp != NULL) {
552 		lp = wp->w_linep;
553 		i = wp->w_toprow;
554 		while (i < wp->w_toprow + wp->w_ntrows) {
555 			if (vscreen[i]->v_flag & VFEXT) {
556 				/* always flag extended lines as changed */
557 				vscreen[i]->v_flag |= VFCHG;
558 				if ((wp != curwp) || (lp != wp->w_dotp) ||
559 				    (curcol < ncol - 1)) {
560 					vtmove(i, 0);
561 					for (j = 0; j < llength(lp); ++j)
562 						vtputc(lgetc(lp, j));
563 					vteeol();
564 					/* this line no longer is extended */
565 					vscreen[i]->v_flag &= ~VFEXT;
566 				}
567 			}
568 			lp = lforw(lp);
569 			++i;
570 		}
571 		/* if garbaged then fix up mode lines */
572 		if (sgarbf != FALSE)
573 			vscreen[i]->v_flag |= VFCHG;
574 		/* and onward to the next window */
575 		wp = wp->w_wndp;
576 	}
577 
578 	if (sgarbf != FALSE) {	/* Screen is garbage.	 */
579 		sgarbf = FALSE;	/* Erase-page clears.	 */
580 		epresf = FALSE;	/* The message area.	 */
581 		tttop = HUGE;	/* Forget where you set. */
582 		ttbot = HUGE;	/* scroll region.	 */
583 		tthue = CNONE;	/* Color unknown.	 */
584 		ttmove(0, 0);
585 		tteeop();
586 		for (i = 0; i < nrow - 1; ++i) {
587 			uline(i, vscreen[i], &blanks);
588 			ucopy(vscreen[i], pscreen[i]);
589 		}
590 		ttmove(currow, curcol - lbound);
591 		ttflush();
592 		return;
593 	}
594 	if (hflag != FALSE) {			/* Hard update?		*/
595 		for (i = 0; i < nrow - 1; ++i) {/* Compute hash data.	*/
596 			hash(vscreen[i]);
597 			hash(pscreen[i]);
598 		}
599 		offs = 0;			/* Get top match.	*/
600 		while (offs != nrow - 1) {
601 			vp1 = vscreen[offs];
602 			vp2 = pscreen[offs];
603 			if (vp1->v_color != vp2->v_color
604 			    || vp1->v_hash != vp2->v_hash)
605 				break;
606 			uline(offs, vp1, vp2);
607 			ucopy(vp1, vp2);
608 			++offs;
609 		}
610 		if (offs == nrow - 1) {		/* Might get it all.	*/
611 			ttmove(currow, curcol - lbound);
612 			ttflush();
613 			return;
614 		}
615 		size = nrow - 1;		/* Get bottom match.	*/
616 		while (size != offs) {
617 			vp1 = vscreen[size - 1];
618 			vp2 = pscreen[size - 1];
619 			if (vp1->v_color != vp2->v_color
620 			    || vp1->v_hash != vp2->v_hash)
621 				break;
622 			uline(size - 1, vp1, vp2);
623 			ucopy(vp1, vp2);
624 			--size;
625 		}
626 		if ((size -= offs) == 0)	/* Get screen size.	*/
627 			panic("Illegal screen size in update");
628 		setscores(offs, size);		/* Do hard update.	*/
629 		traceback(offs, size, size, size);
630 		for (i = 0; i < size; ++i)
631 			ucopy(vscreen[offs + i], pscreen[offs + i]);
632 		ttmove(currow, curcol - lbound);
633 		ttflush();
634 		return;
635 	}
636 	for (i = 0; i < nrow - 1; ++i) {	/* Easy update.		*/
637 		vp1 = vscreen[i];
638 		vp2 = pscreen[i];
639 		if ((vp1->v_flag & VFCHG) != 0) {
640 			uline(i, vp1, vp2);
641 			ucopy(vp1, vp2);
642 		}
643 	}
644 	ttmove(currow, curcol - lbound);
645 	ttflush();
646 }
647 
648 /*
649  * Update a saved copy of a line,
650  * kept in a video structure. The "vvp" is
651  * the one in the "vscreen". The "pvp" is the one
652  * in the "pscreen". This is called to make the
653  * virtual and physical screens the same when
654  * display has done an update.
655  */
656 void
657 ucopy(struct video *vvp, struct video *pvp)
658 {
659 	vvp->v_flag &= ~VFCHG;		/* Changes done.	 */
660 	pvp->v_flag = vvp->v_flag;	/* Update model.	 */
661 	pvp->v_hash = vvp->v_hash;
662 	pvp->v_cost = vvp->v_cost;
663 	pvp->v_color = vvp->v_color;
664 	bcopy(vvp->v_text, pvp->v_text, ncol);
665 }
666 
667 /*
668  * updext: update the extended line which the cursor is currently on at a
669  * column greater than the terminal width. The line will be scrolled right or
670  * left to let the user see where the cursor is.
671  */
672 void
673 updext(int currow, int curcol)
674 {
675 	struct line	*lp;			/* pointer to current line */
676 	int	 j;			/* index into line */
677 
678 	if (ncol < 2)
679 		return;
680 
681 	/*
682 	 * calculate what column the left bound should be
683 	 * (force cursor into middle half of screen)
684 	 */
685 	lbound = curcol - (curcol % (ncol >> 1)) - (ncol >> 2);
686 
687 	/*
688 	 * scan through the line outputing characters to the virtual screen
689 	 * once we reach the left edge
690 	 */
691 	vtmove(currow, -lbound);		/* start scanning offscreen */
692 	lp = curwp->w_dotp;			/* line to output */
693 	for (j = 0; j < llength(lp); ++j)	/* until the end-of-line */
694 		vtpute(lgetc(lp, j));
695 	vteeol();				/* truncate the virtual line */
696 	vscreen[currow]->v_text[0] = '$';	/* and put a '$' in column 1 */
697 }
698 
699 /*
700  * Update a single line. This routine only
701  * uses basic functionality (no insert and delete character,
702  * but erase to end of line). The "vvp" points at the video
703  * structure for the line on the virtual screen, and the "pvp"
704  * is the same for the physical screen. Avoid erase to end of
705  * line when updating CMODE color lines, because of the way that
706  * reverse video works on most terminals.
707  */
708 void
709 uline(int row, struct video *vvp, struct video *pvp)
710 {
711 	char  *cp1;
712 	char  *cp2;
713 	char  *cp3;
714 	char  *cp4;
715 	char  *cp5;
716 	int    nbflag;
717 
718 	if (vvp->v_color != pvp->v_color) {	/* Wrong color, do a	 */
719 		ttmove(row, 0);			/* full redraw.		 */
720 #ifdef	STANDOUT_GLITCH
721 		if (pvp->v_color != CTEXT && magic_cookie_glitch >= 0)
722 			tteeol();
723 #endif
724 		ttcolor(vvp->v_color);
725 #ifdef	STANDOUT_GLITCH
726 		cp1 = &vvp->v_text[magic_cookie_glitch > 0 ? magic_cookie_glitch : 0];
727 		/*
728 		 * The odd code for magic_cookie_glitch==0 is to avoid
729 		 * putting the invisible glitch character on the next line.
730 		 * (Hazeltine executive 80 model 30)
731 		 */
732 		cp2 = &vvp->v_text[ncol - (magic_cookie_glitch >= 0 ?
733 		    (magic_cookie_glitch != 0 ? magic_cookie_glitch : 1) : 0)];
734 #else
735 		cp1 = &vvp->v_text[0];
736 		cp2 = &vvp->v_text[ncol];
737 #endif
738 		while (cp1 != cp2) {
739 			ttputc(*cp1++);
740 			++ttcol;
741 		}
742 		ttcolor(CTEXT);
743 		return;
744 	}
745 	cp1 = &vvp->v_text[0];		/* Compute left match.	 */
746 	cp2 = &pvp->v_text[0];
747 	while (cp1 != &vvp->v_text[ncol] && cp1[0] == cp2[0]) {
748 		++cp1;
749 		++cp2;
750 	}
751 	if (cp1 == &vvp->v_text[ncol])	/* All equal.		 */
752 		return;
753 	nbflag = FALSE;
754 	cp3 = &vvp->v_text[ncol];	/* Compute right match.  */
755 	cp4 = &pvp->v_text[ncol];
756 	while (cp3[-1] == cp4[-1]) {
757 		--cp3;
758 		--cp4;
759 		if (cp3[0] != ' ')	/* Note non-blanks in	 */
760 			nbflag = TRUE;	/* the right match.	 */
761 	}
762 	cp5 = cp3;			/* Is erase good?	 */
763 	if (nbflag == FALSE && vvp->v_color == CTEXT) {
764 		while (cp5 != cp1 && cp5[-1] == ' ')
765 			--cp5;
766 		/* Alcyon hack */
767 		if ((int) (cp3 - cp5) <= tceeol)
768 			cp5 = cp3;
769 	}
770 	/* Alcyon hack */
771 	ttmove(row, (int) (cp1 - &vvp->v_text[0]));
772 #ifdef	STANDOUT_GLITCH
773 	if (vvp->v_color != CTEXT && magic_cookie_glitch > 0) {
774 		if (cp1 < &vvp->v_text[magic_cookie_glitch])
775 			cp1 = &vvp->v_text[magic_cookie_glitch];
776 		if (cp5 > &vvp->v_text[ncol - magic_cookie_glitch])
777 			cp5 = &vvp->v_text[ncol - magic_cookie_glitch];
778 	} else if (magic_cookie_glitch < 0)
779 #endif
780 		ttcolor(vvp->v_color);
781 	while (cp1 != cp5) {
782 		ttputc(*cp1++);
783 		++ttcol;
784 	}
785 	if (cp5 != cp3)			/* Do erase.		 */
786 		tteeol();
787 }
788 
789 /*
790  * Redisplay the mode line for the window pointed to by the "wp".
791  * This is the only routine that has any idea of how the mode line is
792  * formatted. You can change the modeline format by hacking at this
793  * routine. Called by "update" any time there is a dirty window.  Note
794  * that if STANDOUT_GLITCH is defined, first and last magic_cookie_glitch
795  * characters may never be seen.
796  */
797 void
798 modeline(struct mgwin *wp, int modelinecolor)
799 {
800 	int	n, md;
801 	struct buffer *bp;
802 	char sl[21];		/* Overkill. Space for 2^64 in base 10. */
803 	int len;
804 
805 	n = wp->w_toprow + wp->w_ntrows;	/* Location.		 */
806 	vscreen[n]->v_color = modelinecolor;	/* Mode line color.	 */
807 	vscreen[n]->v_flag |= (VFCHG | VFHBAD);	/* Recompute, display.	 */
808 	vtmove(n, 0);				/* Seek to right line.	 */
809 	bp = wp->w_bufp;
810 	vtputc('-');
811 	vtputc('-');
812 	if ((bp->b_flag & BFREADONLY) != 0) {
813 		vtputc('%');
814 		if ((bp->b_flag & BFCHG) != 0)
815 			vtputc('*');
816 		else
817 			vtputc('%');
818 	} else if ((bp->b_flag & BFCHG) != 0) {	/* "*" if changed.	 */
819 		vtputc('*');
820 		vtputc('*');
821 	} else {
822 		vtputc('-');
823 		vtputc('-');
824 	}
825 	vtputc('-');
826 	n = 5;
827 	n += vtputs("Mg: ");
828 	if (bp->b_bname[0] != '\0')
829 		n += vtputs(&(bp->b_bname[0]));
830 	while (n < 42) {			/* Pad out with blanks.	 */
831 		vtputc(' ');
832 		++n;
833 	}
834 	vtputc('(');
835 	++n;
836 	for (md = 0; ; ) {
837 		n += vtputs(bp->b_modes[md]->p_name);
838 		if (++md > bp->b_nmodes)
839 			break;
840 		vtputc('-');
841 		++n;
842 	}
843 	/* XXX These should eventually move to a real mode */
844 	if (macrodef == TRUE)
845 		n += vtputs("-def");
846 	if (globalwd == TRUE)
847 		n += vtputs("-gwd");
848 	vtputc(')');
849 	++n;
850 
851 	if (linenos && colnos)
852 		len = snprintf(sl, sizeof(sl), "--L%d--C%d", wp->w_dotline,
853 		    getcolpos(wp));
854 	else if (linenos)
855 		len = snprintf(sl, sizeof(sl), "--L%d", wp->w_dotline);
856 	else if (colnos)
857 		len = snprintf(sl, sizeof(sl), "--C%d", getcolpos(wp));
858 	if ((linenos || colnos) && len < sizeof(sl) && len != -1)
859 		n += vtputs(sl);
860 
861 	while (n < ncol) {			/* Pad out.		 */
862 		vtputc('-');
863 		++n;
864 	}
865 }
866 
867 /*
868  * Output a string to the mode line, report how long it was.
869  */
870 int
871 vtputs(const char *s)
872 {
873 	int n = 0;
874 
875 	while (*s != '\0') {
876 		vtputc(*s++);
877 		++n;
878 	}
879 	return (n);
880 }
881 
882 /*
883  * Compute the hash code for the line pointed to by the "vp".
884  * Recompute it if necessary. Also set the approximate redisplay
885  * cost. The validity of the hash code is marked by a flag bit.
886  * The cost understand the advantages of erase to end of line.
887  * Tuned for the VAX by Bob McNamara; better than it used to be on
888  * just about any machine.
889  */
890 void
891 hash(struct video *vp)
892 {
893 	int	i, n;
894 	char   *s;
895 
896 	if ((vp->v_flag & VFHBAD) != 0) {	/* Hash bad.		 */
897 		s = &vp->v_text[ncol - 1];
898 		for (i = ncol; i != 0; --i, --s)
899 			if (*s != ' ')
900 				break;
901 		n = ncol - i;			/* Erase cheaper?	 */
902 		if (n > tceeol)
903 			n = tceeol;
904 		vp->v_cost = i + n;		/* Bytes + blanks.	 */
905 		for (n = 0; i != 0; --i, --s)
906 			n = (n << 5) + n + *s;
907 		vp->v_hash = n;			/* Hash code.		 */
908 		vp->v_flag &= ~VFHBAD;		/* Flag as all done.	 */
909 	}
910 }
911 
912 /*
913  * Compute the Insert-Delete
914  * cost matrix. The dynamic programming algorithm
915  * described by James Gosling is used. This code assumes
916  * that the line above the echo line is the last line involved
917  * in the scroll region. This is easy to arrange on the VT100
918  * because of the scrolling region. The "offs" is the origin 0
919  * offset of the first row in the virtual/physical screen that
920  * is being updated; the "size" is the length of the chunk of
921  * screen being updated. For a full screen update, use offs=0
922  * and size=nrow-1.
923  *
924  * Older versions of this code implemented the score matrix by
925  * a two dimensional array of SCORE nodes. This put all kinds of
926  * multiply instructions in the code! This version is written to
927  * use a linear array and pointers, and contains no multiplication
928  * at all. The code has been carefully looked at on the VAX, with
929  * only marginal checking on other machines for efficiency. In
930  * fact, this has been tuned twice! Bob McNamara tuned it even
931  * more for the VAX, which is a big issue for him because of
932  * the 66 line X displays.
933  *
934  * On some machines, replacing the "for (i=1; i<=size; ++i)" with
935  * i = 1; do { } while (++i <=size)" will make the code quite a
936  * bit better; but it looks ugly.
937  */
938 void
939 setscores(int offs, int size)
940 {
941 	struct score	 *sp;
942 	struct score	 *sp1;
943 	struct video	**vp, **pp;
944 	struct video	**vbase, **pbase;
945 	int	  tempcost;
946 	int	  bestcost;
947 	int	  j, i;
948 
949 	vbase = &vscreen[offs - 1];	/* By hand CSE's.	 */
950 	pbase = &pscreen[offs - 1];
951 	score[0].s_itrace = 0;		/* [0, 0]		 */
952 	score[0].s_jtrace = 0;
953 	score[0].s_cost = 0;
954 	sp = &score[1];			/* Row 0, inserts.	 */
955 	tempcost = 0;
956 	vp = &vbase[1];
957 	for (j = 1; j <= size; ++j) {
958 		sp->s_itrace = 0;
959 		sp->s_jtrace = j - 1;
960 		tempcost += tcinsl;
961 		tempcost += (*vp)->v_cost;
962 		sp->s_cost = tempcost;
963 		++vp;
964 		++sp;
965 	}
966 	sp = &score[nrow];		/* Column 0, deletes.	 */
967 	tempcost = 0;
968 	for (i = 1; i <= size; ++i) {
969 		sp->s_itrace = i - 1;
970 		sp->s_jtrace = 0;
971 		tempcost += tcdell;
972 		sp->s_cost = tempcost;
973 		sp += nrow;
974 	}
975 	sp1 = &score[nrow + 1];		/* [1, 1].		 */
976 	pp = &pbase[1];
977 	for (i = 1; i <= size; ++i) {
978 		sp = sp1;
979 		vp = &vbase[1];
980 		for (j = 1; j <= size; ++j) {
981 			sp->s_itrace = i - 1;
982 			sp->s_jtrace = j;
983 			bestcost = (sp - nrow)->s_cost;
984 			if (j != size)	/* Cd(A[i])=0 @ Dis.	 */
985 				bestcost += tcdell;
986 			tempcost = (sp - 1)->s_cost;
987 			tempcost += (*vp)->v_cost;
988 			if (i != size)	/* Ci(B[j])=0 @ Dsj.	 */
989 				tempcost += tcinsl;
990 			if (tempcost < bestcost) {
991 				sp->s_itrace = i;
992 				sp->s_jtrace = j - 1;
993 				bestcost = tempcost;
994 			}
995 			tempcost = (sp - nrow - 1)->s_cost;
996 			if ((*pp)->v_color != (*vp)->v_color
997 			    || (*pp)->v_hash != (*vp)->v_hash)
998 				tempcost += (*vp)->v_cost;
999 			if (tempcost < bestcost) {
1000 				sp->s_itrace = i - 1;
1001 				sp->s_jtrace = j - 1;
1002 				bestcost = tempcost;
1003 			}
1004 			sp->s_cost = bestcost;
1005 			++sp;		/* Next column.		 */
1006 			++vp;
1007 		}
1008 		++pp;
1009 		sp1 += nrow;		/* Next row.		 */
1010 	}
1011 }
1012 
1013 /*
1014  * Trace back through the dynamic programming cost
1015  * matrix, and update the screen using an optimal sequence
1016  * of redraws, insert lines, and delete lines. The "offs" is
1017  * the origin 0 offset of the chunk of the screen we are about to
1018  * update. The "i" and "j" are always started in the lower right
1019  * corner of the matrix, and imply the size of the screen.
1020  * A full screen traceback is called with offs=0 and i=j=nrow-1.
1021  * There is some do-it-yourself double subscripting here,
1022  * which is acceptable because this routine is much less compute
1023  * intensive then the code that builds the score matrix!
1024  */
1025 void
1026 traceback(int offs, int size, int i, int j)
1027 {
1028 	int	itrace, jtrace;
1029 	int	k;
1030 	int	ninsl, ndraw, ndell;
1031 
1032 	if (i == 0 && j == 0)	/* End of update.	 */
1033 		return;
1034 	itrace = score[(nrow * i) + j].s_itrace;
1035 	jtrace = score[(nrow * i) + j].s_jtrace;
1036 	if (itrace == i) {	/* [i, j-1]		 */
1037 		ninsl = 0;	/* Collect inserts.	 */
1038 		if (i != size)
1039 			ninsl = 1;
1040 		ndraw = 1;
1041 		while (itrace != 0 || jtrace != 0) {
1042 			if (score[(nrow * itrace) + jtrace].s_itrace != itrace)
1043 				break;
1044 			jtrace = score[(nrow * itrace) + jtrace].s_jtrace;
1045 			if (i != size)
1046 				++ninsl;
1047 			++ndraw;
1048 		}
1049 		traceback(offs, size, itrace, jtrace);
1050 		if (ninsl != 0) {
1051 			ttcolor(CTEXT);
1052 			ttinsl(offs + j - ninsl, offs + size - 1, ninsl);
1053 		}
1054 		do {		/* B[j], A[j] blank.	 */
1055 			k = offs + j - ndraw;
1056 			uline(k, vscreen[k], &blanks);
1057 		} while (--ndraw);
1058 		return;
1059 	}
1060 	if (jtrace == j) {	/* [i-1, j]		 */
1061 		ndell = 0;	/* Collect deletes.	 */
1062 		if (j != size)
1063 			ndell = 1;
1064 		while (itrace != 0 || jtrace != 0) {
1065 			if (score[(nrow * itrace) + jtrace].s_jtrace != jtrace)
1066 				break;
1067 			itrace = score[(nrow * itrace) + jtrace].s_itrace;
1068 			if (j != size)
1069 				++ndell;
1070 		}
1071 		if (ndell != 0) {
1072 			ttcolor(CTEXT);
1073 			ttdell(offs + i - ndell, offs + size - 1, ndell);
1074 		}
1075 		traceback(offs, size, itrace, jtrace);
1076 		return;
1077 	}
1078 	traceback(offs, size, itrace, jtrace);
1079 	k = offs + j - 1;
1080 	uline(k, vscreen[k], pscreen[offs + i - 1]);
1081 }
1082