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