1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1995-1999 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /* LINTLIBRARY */
30 
31 /*
32  * doupdate.c
33  *
34  * XCurses Library
35  *
36  * Copyright 1990, 1995 by Mortice Kern Systems Inc.  All rights reserved.
37  *
38  */
39 
40 #ifdef M_RCSID
41 #ifndef lint
42 static char const rcsID[] =
43 "$Header: /team/ps/sun_xcurses/archive/local_changes/xcurses/src/lib/"
44 "libxcurses/src/libc/xcurses/rcs/doupdate.c 1.22 1998/06/04 12:13:38 "
45 "cbates Exp $";
46 #endif
47 #endif
48 
49 #include <sys/isa_defs.h>
50 #include <private.h>
51 #include <string.h>
52 #include <signal.h>
53 
54 #undef SIGTSTP
55 
56 /*
57  * This value is the ideal length for the cursor addressing sequence
58  * being four bytes long, ie. "<escape><cursor addressing code><row><col>".
59  * eg. VT52 - "\EYrc" or ADM3A - "\E=rc"
60  */
61 #define	JUMP_SIZE	4
62 
63 /*
64  * This value is the ideal length for the clear-to-eol sequence
65  * being two bytes long, ie "<escape><clear eol code>".
66  */
67 #define	CEOL_SIZE	2
68 
69 #define	GOTO(r, c)	((void) __m_mvcur(curscr->_cury, curscr->_curx,\
70 	r, c, __m_outc), curscr->_cury = r, curscr->_curx = c)
71 
72 typedef struct cost_op {
73 	short	cost;
74 	short	op;
75 } lcost;
76 
77 typedef void (*t_action)(int, int);
78 
79 
80 #define	LC(i, j) 	(lc[(i) * (LINES + 1) + (j)])
81 
82 static lcost *lc = NULL;
83 #if defined(_LP64)
84 static unsigned int	*nhash = NULL;
85 #else
86 static unsigned long	*nhash = NULL;
87 #endif
88 static t_action *del = NULL;
89 static t_action *ins_rep = NULL;
90 
91 static WINDOW	*newscr;
92 
93 static void erase_bottom(int, int);
94 static void clear_bottom(int);
95 static void complex(void);
96 static int cost(int, int);
97 static void lines_delete(int, int);
98 static void lines_insert(int, int);
99 static void lines_replace(int, int);
100 static void script(int, int);
101 static int scroll_up(int);
102 static void simple(void);
103 static void text_replace(int);
104 #if 0
105 static int scroll_dn(int);
106 #endif
107 
108 
109 /*
110  * Wrapper that streams Curses output.
111  *
112  * All escape sequences going to the screen come through here.
113  * All ordinary characters go to the screen via the putc in doupdate.c
114  */
115 int
116 __m_outc(int ch)
117 {
118 	return (putc(ch, __m_screen->_of));
119 }
120 
121 /*
122  * Allocate or grow doupdate() structures.
123  */
124 int
125 __m_doupdate_init(void)
126 {
127 	void	*new;
128 	static short	nlines = 0;
129 
130 	if (lines <= 0)
131 		return (-1);
132 
133 	if (lines <= nlines)
134 		return (0);
135 
136 	new = malloc((lines + 1) * (lines + 1) * sizeof (*lc));
137 	if (new == NULL)
138 		return (-1);
139 	if (lc != NULL)
140 		free(lc);
141 	lc = (lcost *) new;
142 
143 	new = malloc((lines + lines) * sizeof (*del));
144 	if (new == NULL)
145 		return (-1);
146 	if (del != NULL)
147 		free(del);
148 	del = (t_action *) new;
149 	ins_rep = del + lines;
150 
151 	new = malloc(lines * sizeof (*nhash));
152 	if (new == NULL)
153 		return (-1);
154 	if (nhash != NULL)
155 		free(nhash);
156 #if defined(_LP64)
157 	nhash = (unsigned int *) new;
158 #else
159 	nhash = (unsigned long *) new;
160 #endif
161 
162 	nlines = lines;
163 
164 	return (0);
165 }
166 
167 static void
168 erase_bottom(int start, int end)
169 {
170 	int i;
171 
172 	for (i = start; i < end; ++i) {
173 		(void) __m_cc_erase(curscr, i, 0, i, curscr->_maxx - 1);
174 		__m_cc_hash(curscr, __m_screen->_hash, i);
175 	}
176 }
177 
178 /*
179  *  Clear from the start of the current row to bottom of screen.
180  */
181 static void
182 clear_bottom(int y)
183 {
184 	/* Restore default color pair before doing area clears. */
185 	if (back_color_erase)
186 		(void) vid_puts(WA_NORMAL, 0, (void *) 0, __m_outc);
187 
188 	if (y == 0 && clear_screen != NULL) {
189 		(void) TPUTS(clear_screen, 1, __m_outc);
190 	} else {
191 		(void) __m_mvcur(-1, -1, y, 0, __m_outc);
192 		if (clr_eos != NULL) {
193 			(void) TPUTS(clr_eos, 1, __m_outc);
194 		} else if (clr_eol != NULL) {
195 			for (;;) {
196 				(void) TPUTS(clr_eol, 1, __m_outc);
197 				if (LINES <= y)
198 					break;
199 				(void) __m_mvcur(y, 0, y + 1, 0, __m_outc);
200 				++y;
201 			}
202 		}
203 	}
204 
205 	curscr->_cury = y;
206 	curscr->_curx = 0;
207 }
208 
209 
210 
211 /*
212  * Rewrite of text_replace() implementation by C. Bates of MKS
213  *
214  * This code creates a list of 'regions' for each test line which
215  * is to be replaced. Each region describes a portion of the line.
216  * Logic is performed on the list of regions, then the regions
217  * are used to generate output.
218  */
219 typedef	struct LineRegion {
220 	int	col;	/* Starting column of region */
221 	int	size;	/* Size of region */
222 	/* 0: Difference Region, 1: Common Region, 2: Delete Region */
223 	int	type;
224 } LineRegion;
225 #define	REGION_DIFFERENT	0
226 #define	REGION_COMMON		1
227 #define	REGION_DELETE		2
228 
229 #define	DELETE_SEARCH_LIMIT	4
230 #define	DELETE_THRESHOLD	10
231 
232 static LineRegion	regions[1024];
233 int	nRegions = 0;
234 
235 /*
236  * Return the first column of the completely blank End-of-line
237  */
238 static int
239 _find_blank_tail(int row)
240 {
241 	cchar_t	*nptr;
242 	int	tail = COLS;
243 
244 	if (!clr_eol)
245 		return (COLS);
246 	/*
247 	 * Find start of blank tail region.
248 	 */
249 	nptr = &newscr->_line[row][COLS];
250 	for (; 0 < tail; --tail) {
251 		if (!__m_cc_compare(--nptr, &newscr->_bg, 1))
252 			break;
253 	}
254 	return (tail);
255 }
256 
257 /*
258  * Send all the characters in the region to the terminal
259  */
260 static void
261 _writeRegion(int row, LineRegion region)
262 {
263 	short	npair;
264 	attr_t	nattr;
265 	int	i;
266 	cchar_t	*optr = &curscr->_line[row][region.col];
267 	cchar_t	*nptr = &newscr->_line[row][region.col];
268 
269 	for (i = 0; i < region.size; i++, nptr++, optr++) {
270 		nattr = nptr->_at;
271 		npair = nptr->_co;
272 
273 		/*
274 		 * Change attribute state.
275 		 */
276 		if ((ATTR_STATE != nattr) || (optr->_at != nattr) ||
277 			(cur_term->_co != npair)) {
278 			(void) vid_puts(nattr, npair, NULL, __m_outc);
279 		}
280 		/*
281 		 * Don't display internal characters.
282 		 */
283 		if (nptr->_f)
284 			(void) __m_cc_write(nptr);
285 
286 		/*
287 		 * Update copy of screen image.
288 		 */
289 		*optr = *nptr;
290 		curscr->_curx = region.col + i + 1;
291 	}
292 }
293 
294 /*
295  * Delete some characters from the terminal for this region
296  */
297 static void
298 _deleteRegion(int row, LineRegion region)
299 {
300 	int	i;
301 	cchar_t	*optr = &curscr->_line[row][region.col];
302 
303 	if ((region.size <= 1) || !parm_dch) {
304 		for (i = 0; i < region.size; i++)
305 			(void) TPUTS(delete_character, 1, __m_outc);
306 	} else {
307 		(void) TPUTS(tparm(parm_dch, (long)region.size,
308 			0, 0, 0, 0, 0, 0, 0, 0), region.size, __m_outc);
309 	}
310 	for (i = region.col; i < COLS - region.size; i++) {
311 		/*
312 		 * Delete the chars in the image of the real screen
313 		 */
314 		*optr = *(optr + region.size);
315 		optr++;
316 	}
317 }
318 
319 /*
320  * Use clr_eol control if possible
321  */
322 static void
323 _clearToEOL(int row, int tail)
324 {
325 	if (tail < COLS) {
326 		GOTO(row, tail);
327 		/*
328 		 * Restore default color pair before area clear.
329 		 */
330 		if (back_color_erase)
331 			(void) vid_puts(WA_NORMAL, 0, NULL, __m_outc);
332 
333 		(void) TPUTS(clr_eol, 1, __m_outc);
334 		(void) __m_cc_erase(curscr, row, tail, row, COLS - 1);
335 	}
336 }
337 
338 /*
339  * Delete leading common region
340  */
341 static void
342 _normalizeRegions1(void)
343 {
344 	int	iRegion;
345 
346 	/*
347 	 * Delete leading common region
348 	 */
349 	if (regions[0].type == REGION_COMMON) {
350 		nRegions--;
351 		for (iRegion = 0; iRegion < nRegions; iRegion++) {
352 			regions[iRegion] = regions[iRegion + 1];
353 		}
354 	}
355 }
356 
357 /*
358  * Give each region a size, then delete all trailing common regions
359  */
360 static void
361 _normalizeRegions2(void)
362 {
363 	int	iRegion;
364 
365 	for (iRegion = 0; iRegion < nRegions - 1; iRegion++) {
366 		regions[iRegion].size = regions[iRegion + 1].col -
367 			regions[iRegion].col;
368 	}
369 	regions[nRegions - 1].size = COLS - regions[nRegions - 1].col;
370 
371 	/*
372 	 * Delete trailing common regions
373 	 */
374 	while (regions[nRegions - 1].type == REGION_COMMON)
375 		nRegions--;
376 }
377 
378 /*
379  * Tiny common regions are merged into adjacent difference regions
380  */
381 static void
382 _mergeTinyRegions(void)
383 {
384 	int	from;
385 	int	to;
386 	for (from = 1, to = 1; from < nRegions; ) {
387 		if ((regions[from].type == REGION_COMMON) &&
388 			(regions[from].size < JUMP_SIZE)) {
389 			/*
390 			 * Merge out tiny common regions
391 			 */
392 			regions[to - 1].size += regions[from].size;
393 			/*
394 			 * Now join adjacent non-common regions
395 			 */
396 			if (++from < nRegions)
397 				regions[to - 1].size += regions[from++].size;
398 		} else {
399 			regions[to++] = regions[from++];
400 		}
401 	}
402 	nRegions = to;
403 }
404 
405 /*
406  * Create the initial list of regions for this row
407  */
408 static int
409 _findRegions(int row)
410 {
411 	int	cmp;
412 	int	old_cmp;
413 	int	col;
414 	int	bestDeleteCount;
415 	cchar_t	*nptr = &newscr->_line[row][0];
416 	cchar_t	*optr = &curscr->_line[row][0];
417 
418 	col = 0;
419 	nRegions = 0;
420 	bestDeleteCount = 0;
421 	if ((__m_screen->_flags & S_INS_DEL_CHAR) &&
422 		(parm_dch || delete_character)) {
423 		int	bestFit = 0;
424 		int	deletePoint;
425 		int	deleteCount;
426 		int	matches;
427 
428 		/*
429 		 * Skip to first difference
430 		 */
431 		for (col = 0; col < COLS; col++) {
432 			if (!__m_cc_compare(&optr[col], &nptr[col], 1))
433 				break;
434 		}
435 		deletePoint = col;
436 		for (deleteCount = 1; deleteCount < DELETE_SEARCH_LIMIT;
437 			deleteCount++) {
438 			matches = 0;
439 			for (col = deletePoint; col < COLS - deleteCount;
440 				col++) {
441 				if (__m_cc_compare(&optr[col + deleteCount],
442 					&nptr[col], 1))
443 					matches++;
444 				else
445 					break;
446 			}
447 			if (matches > bestFit) {
448 				bestFit = matches;
449 				bestDeleteCount = deleteCount;
450 			}
451 		}
452 		if (bestFit > DELETE_THRESHOLD) {
453 			regions[nRegions].type = REGION_DELETE;
454 			regions[nRegions].col = deletePoint;
455 			regions[nRegions].size = bestDeleteCount;
456 			nRegions++;
457 			col = deletePoint + bestDeleteCount;
458 		} else {
459 			col = 0;
460 			nRegions = 0;
461 			/* Forget trying to use character delete */
462 			bestDeleteCount = 0;
463 		}
464 	}
465 	for (old_cmp = -1; col + bestDeleteCount < COLS; col++) {
466 		cmp = __m_cc_compare(&optr[col + bestDeleteCount],
467 			&nptr[col], 1);
468 		if (cmp != old_cmp) {
469 			regions[nRegions].type = cmp ? REGION_COMMON :
470 				REGION_DIFFERENT;
471 			regions[nRegions].col = col;
472 			regions[nRegions].size = 0;	/* Determine later */
473 			nRegions++;
474 			old_cmp = cmp;
475 		}
476 	}
477 	if (bestDeleteCount) {
478 		/*
479 		 * Force update of end-of-line if delete is to be used
480 		 */
481 		regions[nRegions].type = REGION_DIFFERENT;
482 		regions[nRegions].col = col;
483 		regions[nRegions].size = 0;	/* Determine later */
484 		nRegions++;
485 	}
486 	_normalizeRegions1();
487 	if (nRegions == 0)
488 		return (0);		/* No difference regions */
489 
490 	_normalizeRegions2();
491 	return (1);
492 }
493 
494 /*
495  * Determine if Clr-EOL optimization can be used, and
496  * adjust regions accordingly
497  */
498 static int
499 _ceolAdjustRegions(int row)
500 {
501 	int	iRegion;
502 	int	blankEolStart = _find_blank_tail(row);
503 
504 	for (iRegion = 0; iRegion < nRegions; iRegion++) {
505 		switch (regions[iRegion].type) {
506 		case REGION_DIFFERENT:
507 			if (regions[iRegion].col >= blankEolStart) {
508 				/*
509 				 * Delete this and all following regions
510 				 */
511 				nRegions = iRegion;
512 				return (blankEolStart);
513 			}
514 			if (regions[iRegion].col + regions[iRegion].size >
515 				blankEolStart) {
516 				/*
517 				 * Truncate this region to end
518 				 * where blank EOL starts
519 				 */
520 				regions[iRegion].size = blankEolStart -
521 					regions[iRegion].col;
522 				/*
523 				 * Delete all following regions
524 				 */
525 				nRegions = iRegion + 1;
526 				return (blankEolStart);
527 			}
528 			break;
529 		case REGION_COMMON:
530 			break;
531 		case REGION_DELETE:		/* Scrap the whole thing */
532 			return (COLS);
533 		}
534 	}
535 	return (COLS);	/* Couldn't use Clear EOL optimization */
536 }
537 
538 /*
539  * Generate output, based on region list
540  */
541 static void
542 _updateRegions(int row)
543 {
544 	int	ceolStart;
545 	int	iRegion;
546 
547 	ceolStart = _ceolAdjustRegions(row);
548 
549 	/*
550 	 * regions are guaranteed to start with a non-common region.
551 	 * tiny common regions have also been merged into
552 	 * bracketting common-regions.
553 	 */
554 	if (nRegions) {
555 		for (iRegion = 0; iRegion < nRegions; iRegion++) {
556 			switch (regions[iRegion].type) {
557 			case REGION_COMMON:
558 				break;
559 			case REGION_DELETE:
560 				/*
561 				 * Start of non-common region
562 				 */
563 				GOTO(row, regions[iRegion].col);
564 				_deleteRegion(row, regions[iRegion]);
565 				break;
566 			case REGION_DIFFERENT:
567 				/*
568 				 * Star of non-common region
569 				 */
570 				GOTO(row, regions[iRegion].col);
571 				_writeRegion(row, regions[iRegion]);
572 				break;
573 			}
574 		}
575 	}
576 	if (ceolStart != COLS) {
577 		_clearToEOL(row, ceolStart);
578 	}
579 }
580 
581 /*
582  * The new text_replace algorithm, which uses regions
583  */
584 static void
585 text_replace(int row)
586 {
587 	if (!_findRegions(row))
588 		return;
589 	_mergeTinyRegions();
590 	_updateRegions(row);
591 
592 	/*
593 	 * Line wrapping checks.
594 	 */
595 	if (COLS <= curscr->_curx) {
596 		--curscr->_curx;
597 		if (auto_right_margin && (row < LINES - 1)) {
598 			if (eat_newline_glitch) {
599 				(void) __m_outc('\r');
600 				(void) __m_outc('\n');
601 			}
602 			++curscr->_cury;
603 			curscr->_curx = 0;
604 		}
605 	}
606 }
607 
608 /*
609  * Replace a block of lines.
610  * Only ever used for complex().
611  */
612 static void
613 lines_replace(int from, int to_1)
614 {
615 	for (; from < to_1; ++from)
616 		text_replace(from);
617 }
618 
619 /*
620  * Delete a block of lines.
621  * Only ever used for complex().
622  */
623 static void
624 lines_delete(int from, int to_1)
625 {
626 	int count = to_1 - from;
627 
628 	if (LINES <= to_1) {
629 		erase_bottom(from, LINES);
630 		clear_bottom(from);
631 	} else {
632 		GOTO(from, 0);
633 		(void) winsdelln(curscr, -count);
634 
635 		if (parm_delete_line != NULL) {
636 			/*
637 			 * Assume that the sequence to delete more than one
638 			 * line is faster than repeated single delete_lines.
639 			 */
640 			(void) TPUTS(tparm(parm_delete_line, (long)count,
641 				0, 0, 0, 0, 0, 0, 0, 0), count, __m_outc);
642 		} else if (delete_line != NULL) {
643 			while (from++ < to_1)
644 				(void) TPUTS(delete_line, 1, __m_outc);
645 		} else  {
646 			/* Error -- what to do. */
647 			return;
648 		}
649 	}
650 }
651 
652 /*
653  * Insert a block of lines.
654  * Only ever used for complex().
655  *
656  * We must assume that insert_line and parm_insert_line reset the
657  * cursor column to zero.  Therefore it is text_replace() responsiblity
658  * to move the cursor to the correct column to begin the update.
659  */
660 static void
661 lines_insert(int from, int to_1)
662 {
663 	int	row;
664 	int	count = to_1 - from;
665 
666 	/*
667 	 * Position the cursor and insert a block of lines into the screen
668 	 * image now, insert lines into the physical screen, then draw the
669 	 * new screen lines.
670 	 */
671 	GOTO(from, 0);
672 	(void) winsdelln(curscr, count);
673 
674 	if (parm_insert_line != NULL) {
675 		/*
676 		 * Assume that the sequence to insert more than one line is
677 		 * faster than repeated single insert_lines.
678 		 */
679 		(void) TPUTS(tparm(parm_insert_line, (long)count,
680 			0, 0, 0, 0, 0, 0, 0, 0), count, __m_outc);
681 	} else if (insert_line != NULL) {
682 		/*
683 		 * For the single line insert we use to iterate moving
684 		 * the cursor, inserting, and then drawing a line.  That
685 		 * would appear to be slow but visually appealing.  However,
686 		 * people on slow terminals want speed and those on fast
687 		 * terminal won't see it.
688 		 */
689 		for (row = from; row < to_1; ++row)
690 			(void) TPUTS(insert_line, 1, __m_outc);
691 	} else {
692 		/* Error -- what to do. */
693 		return;
694 	}
695 
696 	for (row = from; row < to_1; ++row)
697 		text_replace(row);
698 }
699 
700 static int
701 scroll_up(int n)
702 {
703 	int	count = n;
704 	int	start, finish, to;
705 
706 	if (scroll_forward != NULL) {
707 		GOTO(LINES-1, 0);
708 		while (0 < n--)
709 			(void) TPUTS(scroll_forward, 1, __m_outc);
710 	} else if (parm_delete_line != NULL && 1 < n) {
711 		GOTO(0, 0);
712 		(void) TPUTS(tparm(parm_delete_line, (long)n,
713 			0, 0, 0, 0, 0, 0, 0, 0), n, __m_outc);
714 	} else if (delete_line != NULL) {
715 		GOTO(0, 0);
716 		while (0 < n--)
717 			(void) TPUTS(delete_line, 1, __m_outc);
718 	} else {
719 		return (0);
720 	}
721 
722 	/* Scroll recorded image. */
723 	start = 0;
724 	finish = count-1;
725 	to = lines;
726 
727 	(void) __m_cc_erase(curscr, start, 0, finish, curscr->_maxx-1);
728 	(void) __m_ptr_move((void **) curscr->_line,
729 		curscr->_maxy, start, finish, to);
730 
731 	simple();
732 
733 	return (1);
734 }
735 
736 #if 0
737 static int
738 scroll_dn(int n)
739 {
740 	int	count = n;
741 	int	start, finish, to;
742 
743 	if (LINES < n)
744 		return (0);
745 
746 	if (scroll_reverse != NULL) {
747 		GOTO(0, 0);
748 		while (0 < n--)
749 			(void) TPUTS(scroll_reverse, 1, __m_outc);
750 	} else if (parm_insert_line != NULL && 1 < n) {
751 		GOTO(0, 0);
752 		(void) TPUTS(tparm(parm_insert_line, (long)n,
753 			0, 0, 0, 0, 0, 0, 0, 0), n, __m_outc);
754 	} else if (insert_line != NULL) {
755 		GOTO(0, 0);
756 		while (0 < n--)
757 			(void) TPUTS(insert_line, 1, __m_outc);
758 	} else {
759 		return (0);
760 	}
761 
762 	/* Scroll recorded image. */
763 	start = lines - count;
764 	finish = lines - 1;
765 	to = 0;
766 
767 	(void) __m_cc_erase(curscr, start, 0, finish, curscr->_maxx-1);
768 	(void) __m_ptr_move((void **) curscr->_line,
769 		curscr->_maxy, start, finish, to);
770 
771 	simple();
772 
773 	return (1);
774 }
775 #endif
776 
777 /*
778  * Dynamic programming algorithm for the string edit problem.
779  *
780  * This is a modified Gosling cost algorithm that takes into account
781  * null/move operations.
782  *
783  * Costs for move, delete, replace, and insert are 0, 1, 2, and 3
784  * repectively.
785  */
786 #define	MOVE_COST	0
787 #define	REPLACE_COST	10
788 #define	INSERT_COST	12
789 #define	DELETE_COST	1
790 
791 static int
792 cost(int fr, int lr)
793 {
794 	lcost	*lcp;
795 	int	or, nr, cc;
796 #if defined(_LP64)
797 	unsigned int	*ohash = __m_screen->_hash;
798 #else
799 	unsigned long	*ohash = __m_screen->_hash;
800 #endif
801 
802 	/*
803 	 * Prepare initial row and column of cost matrix.
804 	 *
805 	 *	0 3 6 9 ...
806 	 *	1
807 	 *	2
808 	 *	3
809 	 *	:
810 	 */
811 	LC(fr, fr).cost = MOVE_COST;
812 	for (cc = 1, ++lr, nr = fr+1; nr <= lr; ++nr, ++cc) {
813 		/* Top row is 3, 6, 9, ... */
814 		LC(fr, nr).cost = cc * INSERT_COST;
815 		LC(fr, nr).op = 'i';
816 
817 		/* Left column is 1, 2, 3, ... */
818 		LC(nr, fr).cost = cc * DELETE_COST;
819 		LC(nr, fr).op = 'd';
820 	}
821 
822 	for (--lr, or = fr; or <= lr; ++or) {
823 		for (nr = fr; nr <= lr; ++nr) {
824 			lcp = &LC(or + 1, nr + 1);
825 
826 			/* Assume move op. */
827 			lcp->cost = LC(or, nr).cost;
828 			lcp->op = 'm';
829 
830 			if (ohash[or] != nhash[nr]) {
831 				/* Lines are different, assume replace op. */
832 				lcp->cost += REPLACE_COST;
833 				lcp->op = 'r';
834 			}
835 
836 			/* Compare insert op. */
837 			if ((cc = LC(or + 1, nr).cost + INSERT_COST) <
838 				lcp->cost) {
839 				lcp->cost = cc;
840 				lcp->op = 'i';
841 			}
842 
843 			/* Compare delete op. */
844 			if ((cc = LC(or, nr + 1).cost + DELETE_COST) <
845 				lcp->cost) {
846 				lcp->cost = cc;
847 				lcp->op = 'd';
848 			}
849 		}
850 	}
851 
852 	return (LC(lr + 1, lr + 1).cost);
853 }
854 
855 /*
856  * Build edit script.
857  *
858  * Normally this would be a recursve routine doing the deletes, inserts,
859  * and replaces on individual lines. Instead we build the script so that
860  * we can later do the operations on a block basis.  For terminals with
861  * parm_delete or parm_insert strings this will be better in terms of the
862  * number of characters sent to delete and insert a block of lines.
863  *
864  * Also we can optimize the script so that tail inserts become replaces.
865  * This saves unnecessary inserts operations when the tail can just be
866  * overwritten.
867  */
868 static void
869 script(int fr, int lr)
870 {
871 	int	i, j;
872 	cchar_t	*cp;
873 
874 	i = j = lr + 1;
875 
876 	(void) memset(del, 0, sizeof (*del) * LINES);
877 	(void) memset(ins_rep, 0, sizeof (*ins_rep) * LINES);
878 
879 	do {
880 		/*
881 		 * We don't have to bounds check i or j becuase row fr and
882 		 * column fr of lc have been preset in order to guarantee the
883 		 * correct motion.
884 		 */
885 		switch (LC(i, j).op) {
886 		case 'i':
887 			--j;
888 			ins_rep[j] = lines_insert;
889 			break;
890 		case 'd':
891 			--i;
892 			del[i] = lines_delete;
893 			break;
894 		case 'm':
895 			--i;
896 			--j;
897 			break;
898 		case 'r':
899 			--i;
900 			--j;
901 			ins_rep[j] = lines_replace;
902 			break;
903 		}
904 	} while (fr < i || fr < j);
905 
906 	/* Optimize Tail Inserts */
907 	for (i = LINES-1; 0 <= i && ins_rep[i] == lines_insert; --i) {
908 		/* Make each character in the screen line image invalid. */
909 		for (cp = curscr->_line[i], j = 0; j < COLS; ++j, ++cp)
910 			cp->_n = -1;
911 		ins_rep[i] = lines_replace;
912 	}
913 }
914 
915 /*
916  * Complex update algorithm using insert/delete line operations.
917  *
918  * References:
919  * [MyM86]	E.W. Myers & W. Miller, Row Replacement Algorithms for
920  *		Screen Editors, TR 86-19, Dept. Computer Science, U. of Arizona
921  * [MyM87]	E.W. Myers & W. Miller, A Simple Row Replacement Method,
922  *		TR 86-28, Dept. Computer Science, U. of Arizona
923  * [Mil87]	W. Miller, A Software Tools Sampler, Prentice-Hall, 1987
924  * [Gos81]	James Gosling, A redisplay algorithm, Proceedings of the
925  *		ACM Symposium on Text Manipulation, SIGPLAN Notices,
926  *		16(6) June 1981, pg 123-129
927  *
928  * All the above were reviewed and experimented with.  Due to the nature of
929  * Curses' having to handling overlapping WINDOWs, the only suitable
930  * algorithum is [Gos81].  The others are better suited to editor type
931  * applications that have one window being the entire terminal screen.
932  *
933  */
934 static void
935 complex(void)
936 {
937 	int	fr = -1;
938 	int	i, j, lr;
939 	t_action	func;
940 
941 	/* Find block of lines to change */
942 	for (i = 0; i < LINES; ++i) {
943 		if (newscr->_first[i] < newscr->_last[i]) {
944 			/* Compute new hash. */
945 			__m_cc_hash(newscr, nhash, i);
946 			if (fr == -1)
947 				fr = i;
948 			lr = i;
949 		} else {
950 			/* Line not dirty so hash same as before. */
951 			nhash[i] = __m_screen->_hash[i];
952 		}
953 	}
954 
955 	if (fr != -1) {
956 		/* Gosling */
957 		(void) cost(fr, lr);
958 		script(fr, lr);
959 
960 		/* Do deletes first in reverse order. */
961 		for (j = lr; fr <= j; --j) {
962 			if (del[j] != (t_action) 0) {
963 				for (i = j-1; fr <= i; --i)
964 					if (del[i] == (t_action) 0)
965 						break;
966 
967 				lines_delete(i+1, j+1);
968 				j = i;
969 			}
970 		}
971 
972 		/* Do insert/replace in forward order. */
973 		for (i = fr; i <= lr; ++i) {
974 			if ((func = ins_rep[i]) != (t_action) 0) {
975 				/* Find size of block */
976 				for (j = i; j <= lr && ins_rep[j] == func; ++j)
977 					;
978 				(*func)(i, j);
979 				i = j - 1;
980 			}
981 		}
982 		/*
983 		 * _line[], which contains pointers to screen lines,
984 		 * may be shuffled.
985 		 */
986 		for (i = fr; i <= lr; ++i) {
987 			/* Save new hash for next update. */
988 			__m_screen->_hash[i] = nhash[i];
989 
990 			/* Mark line as untouched. */
991 			newscr->_first[i] = newscr->_maxx;
992 			newscr->_last[i] = -1;
993 		}
994 	}
995 }
996 
997 /*
998  * Simple screen update algorithm
999  *
1000  * We perform a simple incremental update of the terminal screen.
1001  * Only the segment of a line that was touched is replaced on the
1002  * line.
1003  */
1004 static void
1005 simple(void)
1006 {
1007 	int row;
1008 
1009 	for (row = 0; row < newscr->_maxy; ++row) {
1010 		if (newscr->_first[row] < newscr->_last[row]) {
1011 			text_replace(row);
1012 
1013 			/* Mark line as untouched. */
1014 			newscr->_first[row] = newscr->_maxx;
1015 			newscr->_last[row] = -1;
1016 			__m_cc_hash(curscr, __m_screen->_hash, row);
1017 		}
1018 	}
1019 
1020 	newscr->_flags &= ~W_REDRAW_WINDOW;
1021 }
1022 
1023 void
1024 wtouchln_hard(WINDOW *w, int y, int n)
1025 {
1026 	int	last;
1027 
1028 	last = w->_maxx;
1029 
1030 	for (; (y < w->_maxy) && (0 < n); ++y, --n) {
1031 		/*
1032 		 * Force compare in doupdate to fail.
1033 		 * Touch should be unconditional
1034 		 */
1035 (void) memset(&__m_screen->_curscr->_line[w->_begy + y][w->_begx],
1036 	0xff, last * sizeof (cchar_t));
1037 	}
1038 }
1039 /*
1040  * Send all changes made to _newscr to the physical terminal.
1041  *
1042  * If idlok() is set TRUE then doupdate will try and use hardware insert
1043  * and delete line sequences in an effort to optimize output.  idlok()
1044  * should really only be used in applications that want a proper scrolling
1045  * effect.
1046  *
1047  * Added scroll heuristic to handle special case where a full size window
1048  * with full size scroll region, will scroll the window and replace dirty
1049  * lines instead of performing usual cost/script operations.
1050  */
1051 int
1052 doupdate(void)
1053 {
1054 #ifdef SIGTSTP
1055 	int (*oldsig)(int) = signal(SIGTSTP, SIG_IGN);
1056 #endif
1057 
1058 	if (pollTypeahead()) {
1059 		return (OK);
1060 	}
1061 	newscr = __m_screen->_newscr;
1062 
1063 	if (__m_screen->_flags & S_ENDWIN) {
1064 		/* Return from temporary escape done with endwin(). */
1065 		__m_screen->_flags &= ~S_ENDWIN;
1066 
1067 		(void) reset_prog_mode();
1068 		if (enter_ca_mode != NULL)
1069 			(void) TPUTS(enter_ca_mode, 1, __m_outc);
1070 		if (keypad_xmit != NULL)
1071 			(void) TPUTS(keypad_xmit, 1, __m_outc);
1072 		if (ena_acs != NULL)
1073 			(void) TPUTS(ena_acs, 1, __m_outc);
1074 
1075 		/* Force redraw of screen. */
1076 		newscr->_flags |= W_CLEAR_WINDOW;
1077 	}
1078 	/*
1079 	 * When redrawwing a window, we not only assume that line
1080 	 * noise may have lost characters, but line noise may have
1081 	 * generated bogus characters on the screen outside the
1082 	 * the window in question, in which case redraw the entire
1083 	 * screen to be sure.
1084 	 */
1085 	if ((newscr->_flags & (W_CLEAR_WINDOW | W_REDRAW_WINDOW)) ||
1086 		(curscr->_flags & W_CLEAR_WINDOW)) {
1087 		erase_bottom(0, newscr->_maxy);
1088 		clear_bottom(0);
1089 		(void) wtouchln(newscr, 0, newscr->_maxy, 1);
1090 		newscr->_flags &= ~W_CLEAR_WINDOW;
1091 		curscr->_flags &= ~W_CLEAR_WINDOW;
1092 	}
1093 
1094 	/*
1095 	 * Scrolling heuristic should only be used if lines being
1096 	 * scrolled are clean because scrolling overrides updates
1097 	 *
1098 	 * Right now, the following code should always turn off
1099 	 * scrolling, because the internal scroll touches the
1100 	 * scrolled lines. This thing requires a lot more care
1101 	 * than I have right now...
1102 	 */
1103 	if (newscr->_scroll) {
1104 		int	y;
1105 		for (y = 0; y < newscr->_maxy; ++y) {
1106 			if (0 <= newscr->_last[y]) {
1107 				newscr->_scroll = 0;
1108 			}
1109 		}
1110 		newscr->_scroll = 0;	/* Just fudge it for now ... */
1111 	}
1112 	if (newscr->_flags & W_REDRAW_WINDOW) {
1113 		simple();
1114 	} else {
1115 		if (newscr->_scroll == 0) {
1116 			if (__m_screen->_flags & S_INS_DEL_LINE) {
1117 				complex();
1118 			} else {
1119 				simple();
1120 			}
1121 		} else {
1122 			if (!scroll_up(newscr->_scroll)) {
1123 				if (__m_screen->_flags & S_INS_DEL_LINE) {
1124 					complex();
1125 				} else {
1126 					simple();
1127 				}
1128 			}
1129 		}
1130 	}
1131 
1132 	if (!(newscr->_flags & W_LEAVE_CURSOR))	{
1133 		GOTO(newscr->_cury, newscr->_curx);
1134 	}
1135 
1136 	if (!(curscr->_flags & W_FLUSH)) {
1137 		(void) fflush(__m_screen->_of);
1138 	}
1139 
1140 	newscr->_scroll = curscr->_scroll = 0;
1141 
1142 	/* Send labels to terminal that supports them. */
1143 	__m_slk_doupdate();
1144 #ifdef SIGTSTP
1145 	signal(SIGTSTP, oldsig);
1146 #endif
1147 
1148 	return (OK);
1149 }
1150 
1151 /*
1152  * If true, the implementation may use hardware insert and delete,
1153  * character features of the terminal.  The window parameter
1154  * is ignored.
1155  */
1156 /* ARGSUSED */
1157 void
1158 idcok(WINDOW *w, bool bf)
1159 {
1160 	__m_screen->_flags &= ~S_INS_DEL_CHAR;
1161 	if (bf)
1162 		__m_screen->_flags |= S_INS_DEL_CHAR;
1163 }
1164 
1165 /*
1166  * If true, the implementation may use hardware insert, delete,
1167  * and scroll line features of the terminal.  The window parameter
1168  * is ignored.
1169  */
1170 /* ARGSUSED */
1171 int
1172 idlok(WINDOW *w, bool bf)
1173 {
1174 	__m_screen->_flags &= ~S_INS_DEL_LINE;
1175 	if (bf && has_il())
1176 		__m_screen->_flags |= S_INS_DEL_LINE;
1177 
1178 	return (OK);
1179 }
1180 
1181 /*
1182  * Use the POSIX 32-bit CRC function to compute a hash value
1183  * for the window line.
1184  */
1185 void
1186 #if defined(_LP64)
1187 __m_cc_hash(WINDOW *w, unsigned int *array, int y)
1188 #else
1189 __m_cc_hash(WINDOW *w, unsigned long *array, int y)
1190 #endif
1191 {
1192 	array[y] = 0;
1193 	m_crcposix(&array[y], (unsigned char *) w->_line[y],
1194 		(size_t)(w->_maxx * sizeof (**w->_line)));
1195 }
1196