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