tty_update.c revision 66963
1/****************************************************************************
2 * Copyright (c) 1998,1999,2000 Free Software Foundation, Inc.              *
3 *                                                                          *
4 * Permission is hereby granted, free of charge, to any person obtaining a  *
5 * copy of this software and associated documentation files (the            *
6 * "Software"), to deal in the Software without restriction, including      *
7 * without limitation the rights to use, copy, modify, merge, publish,      *
8 * distribute, distribute with modifications, sublicense, and/or sell       *
9 * copies of the Software, and to permit persons to whom the Software is    *
10 * furnished to do so, subject to the following conditions:                 *
11 *                                                                          *
12 * The above copyright notice and this permission notice shall be included  *
13 * in all copies or substantial portions of the Software.                   *
14 *                                                                          *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22 *                                                                          *
23 * Except as contained in this notice, the name(s) of the above copyright   *
24 * holders shall not be used in advertising or otherwise to promote the     *
25 * sale, use or other dealings in this Software without prior written       *
26 * authorization.                                                           *
27 ****************************************************************************/
28
29/****************************************************************************
30 *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31 *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32 ****************************************************************************/
33
34/*-----------------------------------------------------------------
35 *
36 *	lib_doupdate.c
37 *
38 *	The routine doupdate() and its dependents.  Also _nc_outstr(),
39 *	so all physical output is concentrated here (except _nc_outch()
40 *	in lib_tputs.c).
41 *
42 *-----------------------------------------------------------------*/
43
44#ifdef __BEOS__
45#include <OS.h>
46#endif
47
48#include <curses.priv.h>
49
50#if defined(TRACE) && HAVE_SYS_TIMES_H && HAVE_TIMES
51#define USE_TRACE_TIMES 1
52#else
53#define USE_TRACE_TIMES 0
54#endif
55
56#if HAVE_SYS_TIME_H && HAVE_SYS_TIME_SELECT
57#include <sys/time.h>
58#endif
59
60#if USE_TRACE_TIMES
61#include <sys/times.h>
62#endif
63
64#if USE_FUNC_POLL
65#elif HAVE_SELECT
66#if HAVE_SYS_SELECT_H
67#include <sys/select.h>
68#endif
69#endif
70
71#include <term.h>
72
73MODULE_ID("$Id: tty_update.c,v 1.146 2000/10/07 01:11:44 tom Exp $")
74
75/*
76 * This define controls the line-breakout optimization.  Every once in a
77 * while during screen refresh, we want to check for input and abort the
78 * update if there's some waiting.  CHECK_INTERVAL controls the number of
79 * changed lines to be emitted between input checks.
80 *
81 * Note: Input-check-and-abort is no longer done if the screen is being
82 * updated from scratch.  This is a feature, not a bug.
83 */
84#define CHECK_INTERVAL	5
85
86#define FILL_BCE() (SP->_coloron && !SP->_default_color && !back_color_erase)
87
88/*
89 * Enable checking to see if doupdate and friends are tracking the true
90 * cursor position correctly.  NOTE: this is a debugging hack which will
91 * work ONLY on ANSI-compatible terminals!
92 */
93/* #define POSITION_DEBUG */
94
95static inline chtype ClrBlank(WINDOW *win);
96static int ClrBottom(int total);
97static void ClearScreen(chtype blank);
98static void ClrUpdate(void);
99static void DelChar(int count);
100static void InsStr(chtype * line, int count);
101static void TransformLine(int const lineno);
102
103#ifdef POSITION_DEBUG
104/****************************************************************************
105 *
106 * Debugging code.  Only works on ANSI-standard terminals.
107 *
108 ****************************************************************************/
109
110static void
111position_check(int expected_y, int expected_x, char *legend)
112/* check to see if the real cursor position matches the virtual */
113{
114    char buf[20];
115    char *s;
116    int y, x;
117
118    if (!_nc_tracing || (expected_y < 0 && expected_x < 0))
119	return;
120
121    _nc_flush();
122    memset(buf, '\0', sizeof(buf));
123    putp("\033[6n");		/* only works on ANSI-compatibles */
124    _nc_flush();
125    *(s = buf) = 0;
126    do {
127	int ask = sizeof(buf) - 1 - (s - buf);
128	int got = read(0, s, ask);
129	if (got == 0)
130	    break;
131	s += got;
132    } while (strchr(buf, 'R') == 0);
133    _tracef("probe returned %s", _nc_visbuf(buf));
134
135    /* try to interpret as a position report */
136    if (sscanf(buf, "\033[%d;%dR", &y, &x) != 2) {
137	_tracef("position probe failed in %s", legend);
138    } else {
139	if (expected_x < 0)
140	    expected_x = x - 1;
141	if (expected_y < 0)
142	    expected_y = y - 1;
143	if (y - 1 != expected_y || x - 1 != expected_x) {
144	    beep();
145	    tputs(tparm("\033[%d;%dH", expected_y + 1, expected_x + 1), 1, _nc_outch);
146	    _tracef("position seen (%d, %d) doesn't match expected one (%d, %d) in %s",
147		    y - 1, x - 1, expected_y, expected_x, legend);
148	} else {
149	    _tracef("position matches OK in %s", legend);
150	}
151    }
152}
153#else
154#define position_check(expected_y, expected_x, legend)	/* nothing */
155#endif /* POSITION_DEBUG */
156
157/****************************************************************************
158 *
159 * Optimized update code
160 *
161 ****************************************************************************/
162
163static inline void
164GoTo(int const row, int const col)
165{
166    chtype oldattr = SP->_current_attr;
167
168    TR(TRACE_MOVE, ("GoTo(%d, %d) from (%d, %d)",
169		    row, col, SP->_cursrow, SP->_curscol));
170
171    position_check(SP->_cursrow, SP->_curscol, "GoTo");
172
173    /*
174     * Force restore even if msgr is on when we're in an alternate
175     * character set -- these have a strong tendency to screw up the
176     * CR & LF used for local character motions!
177     */
178    if ((oldattr & A_ALTCHARSET)
179	|| (oldattr && !move_standout_mode)) {
180	TR(TRACE_CHARPUT, ("turning off (%#lx) %s before move",
181			   oldattr, _traceattr(oldattr)));
182	vidattr(A_NORMAL);
183    }
184
185    mvcur(SP->_cursrow, SP->_curscol, row, col);
186    SP->_cursrow = row;
187    SP->_curscol = col;
188    position_check(SP->_cursrow, SP->_curscol, "GoTo2");
189}
190
191static inline void
192PutAttrChar(chtype ch)
193{
194    int data;
195
196    if (tilde_glitch && (TextOf(ch) == '~'))
197	ch = ('`' | AttrOf(ch));
198
199    TR(TRACE_CHARPUT, ("PutAttrChar(%s) at (%d, %d)",
200		       _tracechtype(ch),
201		       SP->_cursrow, SP->_curscol));
202    UpdateAttrs(ch);
203    data = TextOf(ch);
204    if (SP->_outch != 0) {
205	SP->_outch(data);
206    } else {
207	putc(data, SP->_ofp);	/* macro's fastest... */
208#ifdef TRACE
209	_nc_outchars++;
210#endif /* TRACE */
211    }
212    SP->_curscol++;
213    if (char_padding) {
214	TPUTS_TRACE("char_padding");
215	putp(char_padding);
216    }
217}
218
219static bool
220check_pending(void)
221/* check for pending input */
222{
223    bool have_pending = FALSE;
224
225    /*
226     * Only carry out this check when the flag is zero, otherwise we'll
227     * have the refreshing slow down drastically (or stop) if there's an
228     * unread character available.
229     */
230    if (SP->_fifohold != 0)
231	return FALSE;
232
233    if (SP->_checkfd >= 0) {
234#if USE_FUNC_POLL
235	struct pollfd fds[1];
236	fds[0].fd = SP->_checkfd;
237	fds[0].events = POLLIN;
238	if (poll(fds, 1, 0) > 0) {
239	    have_pending = TRUE;
240	}
241#elif defined(__BEOS__)
242	/*
243	 * BeOS's select() is declared in socket.h, so the configure script does
244	 * not see it.  That's just as well, since that function works only for
245	 * sockets.  This (using snooze and ioctl) was distilled from Be's patch
246	 * for ncurses which uses a separate thread to simulate select().
247	 *
248	 * FIXME: the return values from the ioctl aren't very clear if we get
249	 * interrupted.
250	 */
251	int n = 0;
252	int howmany = ioctl(0, 'ichr', &n);
253	if (howmany >= 0 && n > 0) {
254	    have_pending = TRUE;
255	}
256#elif HAVE_SELECT
257	fd_set fdset;
258	struct timeval ktimeout;
259
260	ktimeout.tv_sec =
261	    ktimeout.tv_usec = 0;
262
263	FD_ZERO(&fdset);
264	FD_SET(SP->_checkfd, &fdset);
265	if (select(SP->_checkfd + 1, &fdset, NULL, NULL, &ktimeout) != 0) {
266	    have_pending = TRUE;
267	}
268#endif
269    }
270    if (have_pending) {
271	SP->_fifohold = 5;
272	_nc_flush();
273    }
274    return FALSE;
275}
276
277/*
278 * No one supports recursive inline functions.  However, gcc is quieter if we
279 * instantiate the recursive part separately.
280 */
281#if CC_HAS_INLINE_FUNCS
282static void callPutChar(chtype const);
283#else
284#define callPutChar(ch) PutChar(ch)
285#endif
286
287static inline void PutChar(chtype const ch);	/* forward declaration */
288
289/* put char at lower right corner */
290static void
291PutCharLR(chtype const ch)
292{
293    if (!auto_right_margin) {
294	/* we can put the char directly */
295	PutAttrChar(ch);
296    } else if (enter_am_mode && exit_am_mode) {
297	/* we can suppress automargin */
298	TPUTS_TRACE("exit_am_mode");
299	putp(exit_am_mode);
300
301	PutAttrChar(ch);
302	SP->_curscol--;
303	position_check(SP->_cursrow, SP->_curscol, "exit_am_mode");
304
305	TPUTS_TRACE("enter_am_mode");
306	putp(enter_am_mode);
307    } else if ((enter_insert_mode && exit_insert_mode)
308	       || insert_character || parm_ich) {
309	GoTo(screen_lines - 1, screen_columns - 2);
310	callPutChar(ch);
311	GoTo(screen_lines - 1, screen_columns - 2);
312	InsStr(newscr->_line[screen_lines - 1].text + screen_columns - 2, 1);
313    }
314}
315
316static void
317wrap_cursor(void)
318{
319    if (eat_newline_glitch) {
320	/*
321	 * xenl can manifest two different ways.  The vt100
322	 * way is that, when you'd expect the cursor to wrap,
323	 * it stays hung at the right margin (on top of the
324	 * character just emitted) and doesn't wrap until the
325	 * *next* graphic char is emitted.  The c100 way is
326	 * to ignore LF received just after an am wrap.
327	 *
328	 * An aggressive way to handle this would be to
329	 * emit CR/LF after the char and then assume the wrap
330	 * is done, you're on the first position of the next
331	 * line, and the terminal out of its weird state.
332	 * Here it's safe to just tell the code that the
333	 * cursor is in hyperspace and let the next mvcur()
334	 * call straighten things out.
335	 */
336	SP->_curscol = -1;
337	SP->_cursrow = -1;
338    } else if (auto_right_margin) {
339	SP->_curscol = 0;
340	SP->_cursrow++;
341    } else {
342	SP->_curscol--;
343    }
344    position_check(SP->_cursrow, SP->_curscol, "wrap_cursor");
345}
346
347static inline void
348PutChar(chtype const ch)
349/* insert character, handling automargin stuff */
350{
351    if (SP->_cursrow == screen_lines - 1 && SP->_curscol == screen_columns - 1)
352	PutCharLR(ch);
353    else
354	PutAttrChar(ch);
355
356    if (SP->_curscol >= screen_columns)
357	wrap_cursor();
358
359    position_check(SP->_cursrow, SP->_curscol, "PutChar");
360}
361
362/*
363 * Check whether the given character can be output by clearing commands.  This
364 * includes test for being a space and not including any 'bad' attributes, such
365 * as A_REVERSE.  All attribute flags which don't affect appearance of a space
366 * or can be output by clearing (A_COLOR in case of bce-terminal) are excluded.
367 */
368static inline bool
369can_clear_with(chtype ch)
370{
371    if (!back_color_erase && SP->_coloron) {
372	if (ch & A_COLOR)
373	    return FALSE;
374#if NCURSES_EXT_FUNCS
375	if (!SP->_default_color)
376	    return FALSE;
377	if (SP->_default_fg != C_MASK || SP->_default_bg != C_MASK)
378	    return FALSE;
379#endif
380    }
381    return ((ch & ~(NONBLANK_ATTR | A_COLOR)) == BLANK);
382}
383
384/*
385 * Issue a given span of characters from an array.
386 * Must be functionally equivalent to:
387 *	for (i = 0; i < num; i++)
388 *	    PutChar(ntext[i]);
389 * but can leave the cursor positioned at the middle of the interval.
390 *
391 * Returns: 0 - cursor is at the end of interval
392 *	    1 - cursor is somewhere in the middle
393 *
394 * This code is optimized using ech and rep.
395 */
396static int
397EmitRange(const chtype * ntext, int num)
398{
399    int i;
400
401    if (erase_chars || repeat_char) {
402	while (num > 0) {
403	    int runcount;
404	    chtype ntext0;
405
406	    while (num > 1 && ntext[0] != ntext[1]) {
407		PutChar(ntext[0]);
408		ntext++;
409		num--;
410	    }
411	    ntext0 = ntext[0];
412	    if (num == 1) {
413		PutChar(ntext0);
414		return 0;
415	    }
416	    runcount = 2;
417
418	    while (runcount < num && ntext[runcount] == ntext0)
419		runcount++;
420
421	    /*
422	     * The cost expression in the middle isn't exactly right.
423	     * _cup_ch_cost is an upper bound on the cost for moving to the
424	     * end of the erased area, but not the cost itself (which we
425	     * can't compute without emitting the move).  This may result
426	     * in erase_chars not getting used in some situations for
427	     * which it would be marginally advantageous.
428	     */
429	    if (erase_chars
430		&& runcount > SP->_ech_cost + SP->_cup_ch_cost
431		&& can_clear_with(ntext0)) {
432		UpdateAttrs(ntext0);
433		putp(tparm(erase_chars, runcount));
434
435		/*
436		 * If this is the last part of the given interval,
437		 * don't bother moving cursor, since it can be the
438		 * last update on the line.
439		 */
440		if (runcount < num) {
441		    GoTo(SP->_cursrow, SP->_curscol + runcount);
442		} else {
443		    return 1;	/* cursor stays in the middle */
444		}
445	    } else if (repeat_char && runcount > SP->_rep_cost) {
446		bool wrap_possible = (SP->_curscol + runcount >= screen_columns);
447		int rep_count = runcount;
448
449		if (wrap_possible)
450		    rep_count--;
451
452		UpdateAttrs(ntext0);
453		putp(tparm(repeat_char, TextOf(ntext0), rep_count));
454		SP->_curscol += rep_count;
455
456		if (wrap_possible)
457		    PutChar(ntext0);
458	    } else {
459		for (i = 0; i < runcount; i++)
460		    PutChar(ntext[i]);
461	    }
462	    ntext += runcount;
463	    num -= runcount;
464	}
465	return 0;
466    }
467
468    for (i = 0; i < num; i++)
469	PutChar(ntext[i]);
470    return 0;
471}
472
473/*
474 * Output the line in the given range [first .. last]
475 *
476 * If there's a run of identical characters that's long enough to justify
477 * cursor movement, use that also.
478 *
479 * Returns: same as EmitRange
480 */
481static int
482PutRange(
483	    const chtype * otext,
484	    const chtype * ntext,
485	    int row,
486	    int first, int last)
487{
488    int j, run;
489
490    TR(TRACE_CHARPUT, ("PutRange(%p, %p, %d, %d, %d)",
491		       otext, ntext, row, first, last));
492
493    if (otext != ntext
494	&& (last - first + 1) > SP->_inline_cost) {
495	for (j = first, run = 0; j <= last; j++) {
496	    if (otext[j] == ntext[j]) {
497		run++;
498	    } else {
499		if (run > SP->_inline_cost) {
500		    int before_run = (j - run);
501		    EmitRange(ntext + first, before_run - first);
502		    GoTo(row, first = j);
503		}
504		run = 0;
505	    }
506	}
507    }
508    return EmitRange(ntext + first, last - first + 1);
509}
510
511#if CC_HAS_INLINE_FUNCS
512static void
513callPutChar(chtype const ch)
514{
515    PutChar(ch);
516}
517#endif
518
519/* leave unbracketed here so 'indent' works */
520#define MARK_NOCHANGE(win,row) \
521		win->_line[row].firstchar = _NOCHANGE; \
522		win->_line[row].lastchar = _NOCHANGE; \
523		if_USE_SCROLL_HINTS(win->_line[row].oldindex = row)
524
525int
526doupdate(void)
527{
528    int i;
529    int nonempty;
530#if USE_TRACE_TIMES
531    struct tms before, after;
532#endif /* USE_TRACE_TIMES */
533
534    T((T_CALLED("doupdate()")));
535
536#ifdef TRACE
537    if (_nc_tracing & TRACE_UPDATE) {
538	if (curscr->_clear)
539	    _tracef("curscr is clear");
540	else
541	    _tracedump("curscr", curscr);
542	_tracedump("newscr", newscr);
543    }
544#endif /* TRACE */
545
546    _nc_signal_handler(FALSE);
547
548    if (SP->_fifohold)
549	SP->_fifohold--;
550
551#if USE_SIZECHANGE
552    if (SP->_endwin || SP->_sig_winch) {
553	/*
554	 * This is a transparent extension:  XSI does not address it,
555	 * and applications need not know that ncurses can do it.
556	 *
557	 * Check if the terminal size has changed while curses was off
558	 * (this can happen in an xterm, for example), and resize the
559	 * ncurses data structures accordingly.
560	 */
561	_nc_update_screensize();
562    }
563#endif
564
565    if (SP->_endwin) {
566
567	T(("coming back from shell mode"));
568	reset_prog_mode();
569
570	_nc_mvcur_resume();
571	_nc_screen_resume();
572	SP->_mouse_resume(SP);
573
574	SP->_endwin = FALSE;
575    }
576#if USE_TRACE_TIMES
577    /* zero the metering machinery */
578    _nc_outchars = 0;
579    (void) times(&before);
580#endif /* USE_TRACE_TIMES */
581
582    /*
583     * This is the support for magic-cookie terminals.  The
584     * theory: we scan the virtual screen looking for attribute
585     * turnons.  Where we find one, check to make sure it's
586     * realizable by seeing if the required number of
587     * un-attributed blanks are present before and after the
588     * attributed range; try to shift the range boundaries over
589     * blanks (not changing the screen display) so this becomes
590     * true.  If it is, shift the beginning attribute change
591     * appropriately (the end one, if we've gotten this far, is
592     * guaranteed room for its cookie). If not, nuke the added
593     * attributes out of the span.
594     */
595#if USE_XMC_SUPPORT
596    if (magic_cookie_glitch > 0) {
597	int j, k;
598	attr_t rattr = A_NORMAL;
599
600	for (i = 0; i < screen_lines; i++) {
601	    for (j = 0; j < screen_columns; j++) {
602		bool failed = FALSE;
603		chtype turnon = AttrOf(newscr->_line[i].text[j]) & ~rattr;
604
605		/* is an attribute turned on here? */
606		if (turnon == 0) {
607		    rattr = AttrOf(newscr->_line[i].text[j]);
608		    continue;
609		}
610
611		TR(TRACE_ATTRS, ("At (%d, %d): from %s...", i, j, _traceattr(rattr)));
612		TR(TRACE_ATTRS, ("...to %s", _traceattr(turnon)));
613
614		/*
615		 * If the attribute change location is a blank with a
616		 * "safe" attribute, undo the attribute turnon.  This may
617		 * ensure there's enough room to set the attribute before
618		 * the first non-blank in the run.
619		 */
620#define SAFE(a)	(!((a) & (chtype)~NONBLANK_ATTR))
621		if (TextOf(newscr->_line[i].text[j]) == ' ' && SAFE(turnon)) {
622		    newscr->_line[i].text[j] &= ~turnon;
623		    continue;
624		}
625
626		/* check that there's enough room at start of span */
627		for (k = 1; k <= magic_cookie_glitch; k++) {
628		    if (j - k < 0
629			|| TextOf(newscr->_line[i].text[j - k]) != ' '
630			|| !SAFE(AttrOf(newscr->_line[i].text[j - k])))
631			failed = TRUE;
632		}
633		if (!failed) {
634		    bool end_onscreen = FALSE;
635		    int m, n = j;
636
637		    /* find end of span, if it's onscreen */
638		    for (m = i; m < screen_lines; m++) {
639			for (; n < screen_columns; n++) {
640			    if (AttrOf(newscr->_line[m].text[n]) == rattr) {
641				end_onscreen = TRUE;
642				TR(TRACE_ATTRS,
643				   ("Range attributed with %s ends at (%d, %d)",
644				    _traceattr(turnon), m, n));
645				goto foundit;
646			    }
647			}
648			n = 0;
649		    }
650		    TR(TRACE_ATTRS,
651		       ("Range attributed with %s ends offscreen",
652			_traceattr(turnon)));
653		  foundit:;
654
655		    if (end_onscreen) {
656			chtype *lastline = newscr->_line[m].text;
657
658			/*
659			 * If there are safely-attributed blanks at the
660			 * end of the range, shorten the range.  This will
661			 * help ensure that there is enough room at end
662			 * of span.
663			 */
664			while (n >= 0
665			       && TextOf(lastline[n]) == ' '
666			       && SAFE(AttrOf(lastline[n])))
667			    lastline[n--] &= ~turnon;
668
669			/* check that there's enough room at end of span */
670			for (k = 1; k <= magic_cookie_glitch; k++)
671			    if (n + k >= screen_columns
672				|| TextOf(lastline[n + k]) != ' '
673				|| !SAFE(AttrOf(lastline[n + k])))
674				failed = TRUE;
675		    }
676		}
677
678		if (failed) {
679		    int p, q = j;
680
681		    TR(TRACE_ATTRS,
682		       ("Clearing %s beginning at (%d, %d)",
683			_traceattr(turnon), i, j));
684
685		    /* turn off new attributes over span */
686		    for (p = i; p < screen_lines; p++) {
687			for (; q < screen_columns; q++) {
688			    if (AttrOf(newscr->_line[p].text[q]) == rattr)
689				goto foundend;
690			    newscr->_line[p].text[q] &= ~turnon;
691			}
692			q = 0;
693		    }
694		  foundend:;
695		} else {
696		    TR(TRACE_ATTRS,
697		       ("Cookie space for %s found before (%d, %d)",
698			_traceattr(turnon), i, j));
699
700		    /*
701		     * back up the start of range so there's room
702		     * for cookies before the first nonblank character
703		     */
704		    for (k = 1; k <= magic_cookie_glitch; k++)
705			newscr->_line[i].text[j - k] |= turnon;
706		}
707
708		rattr = AttrOf(newscr->_line[i].text[j]);
709	    }
710	}
711
712#ifdef TRACE
713	/* show altered highlights after magic-cookie check */
714	if (_nc_tracing & TRACE_UPDATE) {
715	    _tracef("After magic-cookie check...");
716	    _tracedump("newscr", newscr);
717	}
718#endif /* TRACE */
719    }
720#endif /* USE_XMC_SUPPORT */
721
722    nonempty = 0;
723    if (curscr->_clear || newscr->_clear) {	/* force refresh ? */
724	TR(TRACE_UPDATE, ("clearing and updating from scratch"));
725	ClrUpdate();
726	curscr->_clear = FALSE;	/* reset flag */
727	newscr->_clear = FALSE;	/* reset flag */
728    } else {
729	int changedlines = CHECK_INTERVAL;
730
731	if (check_pending())
732	    goto cleanup;
733
734	nonempty = min(screen_lines, newscr->_maxy + 1);
735
736	if (SP->_scrolling) {
737	    _nc_scroll_optimize();
738	}
739
740	nonempty = ClrBottom(nonempty);
741
742	TR(TRACE_UPDATE, ("Transforming lines, nonempty %d", nonempty));
743	for (i = 0; i < nonempty; i++) {
744	    /*
745	     * Here is our line-breakout optimization.
746	     */
747	    if (changedlines == CHECK_INTERVAL) {
748		if (check_pending())
749		    goto cleanup;
750		changedlines = 0;
751	    }
752
753	    /*
754	     * newscr->line[i].firstchar is normally set
755	     * by wnoutrefresh.  curscr->line[i].firstchar
756	     * is normally set by _nc_scroll_window in the
757	     * vertical-movement optimization code,
758	     */
759	    if (newscr->_line[i].firstchar != _NOCHANGE
760		|| curscr->_line[i].firstchar != _NOCHANGE) {
761		TransformLine(i);
762		changedlines++;
763	    }
764
765	    /* mark line changed successfully */
766	    if (i <= newscr->_maxy) {
767		MARK_NOCHANGE(newscr, i)
768	    }
769	    if (i <= curscr->_maxy) {
770		MARK_NOCHANGE(curscr, i)
771	    }
772	}
773    }
774
775    /* put everything back in sync */
776    for (i = nonempty; i <= newscr->_maxy; i++) {
777	MARK_NOCHANGE(newscr, i)
778    }
779    for (i = nonempty; i <= curscr->_maxy; i++) {
780	MARK_NOCHANGE(curscr, i)
781    }
782
783    if (!newscr->_leaveok) {
784	curscr->_curx = newscr->_curx;
785	curscr->_cury = newscr->_cury;
786
787	GoTo(curscr->_cury, curscr->_curx);
788    }
789
790  cleanup:
791    /*
792     * Keep the physical screen in normal mode in case we get other
793     * processes writing to the screen.
794     */
795    UpdateAttrs(A_NORMAL);
796
797    _nc_flush();
798    curscr->_attrs = newscr->_attrs;
799
800#if USE_TRACE_TIMES
801    (void) times(&after);
802    TR(TRACE_TIMES,
803       ("Update cost: %ld chars, %ld clocks system time, %ld clocks user time",
804	_nc_outchars,
805	after.tms_stime - before.tms_stime,
806	after.tms_utime - before.tms_utime));
807#endif /* USE_TRACE_TIMES */
808
809    _nc_signal_handler(TRUE);
810
811    returnCode(OK);
812}
813
814/*
815 *	ClrBlank(win)
816 *
817 *	Returns the attributed character that corresponds to the "cleared"
818 *	screen.  If the terminal has the back-color-erase feature, this will be
819 *	colored according to the wbkgd() call.
820 *
821 *	We treat 'curscr' specially because it isn't supposed to be set directly
822 *	in the wbkgd() call.  Assume 'stdscr' for this case.
823 */
824#define BCE_ATTRS (A_NORMAL|A_COLOR)
825#define BCE_BKGD(win) (((win) == curscr ? stdscr : (win))->_bkgd)
826
827static inline chtype
828ClrBlank(WINDOW *win)
829{
830    chtype blank = BLANK;
831    if (back_color_erase)
832	blank |= (BCE_BKGD(win) & BCE_ATTRS);
833    return blank;
834}
835
836/*
837**	ClrUpdate()
838**
839**	Update by clearing and redrawing the entire screen.
840**
841*/
842
843static void
844ClrUpdate(void)
845{
846    int i;
847    chtype blank = ClrBlank(stdscr);
848    int nonempty = min(screen_lines, newscr->_maxy + 1);
849
850    TR(TRACE_UPDATE, ("ClrUpdate() called"));
851
852    ClearScreen(blank);
853
854    TR(TRACE_UPDATE, ("updating screen from scratch"));
855
856    nonempty = ClrBottom(nonempty);
857
858    for (i = 0; i < nonempty; i++)
859	TransformLine(i);
860}
861
862/*
863**	ClrToEOL(blank)
864**
865**	Clear to end of current line, starting at the cursor position
866*/
867
868static void
869ClrToEOL(chtype blank, bool needclear)
870{
871    int j;
872
873    if (curscr != 0
874	&& SP->_cursrow >= 0) {
875	for (j = SP->_curscol; j < screen_columns; j++) {
876	    if (j >= 0) {
877		chtype *cp = &(curscr->_line[SP->_cursrow].text[j]);
878
879		if (*cp != blank) {
880		    *cp = blank;
881		    needclear = TRUE;
882		}
883	    }
884	}
885    } else {
886	needclear = TRUE;
887    }
888
889    if (needclear) {
890	UpdateAttrs(blank);
891	TPUTS_TRACE("clr_eol");
892	if (SP->_el_cost > (screen_columns - SP->_curscol)) {
893	    int count = (screen_columns - SP->_curscol);
894	    while (count-- > 0)
895		PutChar(blank);
896	} else {
897	    putp(clr_eol);
898	}
899    }
900}
901
902/*
903**	ClrToEOS(blank)
904**
905**	Clear to end of screen, starting at the cursor position
906*/
907
908static void
909ClrToEOS(chtype blank)
910{
911    int row, col;
912
913    row = SP->_cursrow;
914    col = SP->_curscol;
915
916    UpdateAttrs(blank);
917    TPUTS_TRACE("clr_eos");
918    tputs(clr_eos, screen_lines - row, _nc_outch);
919
920    while (col < screen_columns)
921	curscr->_line[row].text[col++] = blank;
922
923    for (row++; row < screen_lines; row++) {
924	for (col = 0; col < screen_columns; col++)
925	    curscr->_line[row].text[col] = blank;
926    }
927}
928
929/*
930 *	ClrBottom(total)
931 *
932 *	Test if clearing the end of the screen would satisfy part of the
933 *	screen-update.  Do this by scanning backwards through the lines in the
934 *	screen, checking if each is blank, and one or more are changed.
935 */
936static int
937ClrBottom(int total)
938{
939    int row;
940    int col;
941    int top = total;
942    int last = min(screen_columns, newscr->_maxx + 1);
943    chtype blank = ClrBlank(stdscr);
944    bool ok;
945
946    if (clr_eos && can_clear_with(blank)) {
947
948	for (row = total - 1; row >= 0; row--) {
949	    for (col = 0, ok = TRUE; ok && col < last; col++) {
950		ok = (newscr->_line[row].text[col] == blank);
951	    }
952	    if (!ok)
953		break;
954
955	    for (col = 0; ok && col < last; col++) {
956		ok = (curscr->_line[row].text[col] == blank);
957	    }
958	    if (!ok)
959		top = row;
960	}
961
962	/* don't use clr_eos for just one line if clr_eol available */
963	if (top < total - 1 || (top < total && !clr_eol && !clr_bol)) {
964	    GoTo(top, 0);
965	    ClrToEOS(blank);
966	    total = top;
967	    if (SP->oldhash && SP->newhash) {
968		for (row = top; row < screen_lines; row++)
969		    SP->oldhash[row] = SP->newhash[row];
970	    }
971	}
972    }
973    return total;
974}
975
976/*
977**	TransformLine(lineno)
978**
979**	Transform the given line in curscr to the one in newscr, using
980**	Insert/Delete Character if _nc_idcok && has_ic().
981**
982**		firstChar = position of first different character in line
983**		oLastChar = position of last different character in old line
984**		nLastChar = position of last different character in new line
985**
986**		move to firstChar
987**		overwrite chars up to min(oLastChar, nLastChar)
988**		if oLastChar < nLastChar
989**			insert newLine[oLastChar+1..nLastChar]
990**		else
991**			delete oLastChar - nLastChar spaces
992*/
993
994static void
995TransformLine(int const lineno)
996{
997    int firstChar, oLastChar, nLastChar;
998    chtype *newLine = newscr->_line[lineno].text;
999    chtype *oldLine = curscr->_line[lineno].text;
1000    int n;
1001    bool attrchanged = FALSE;
1002
1003    TR(TRACE_UPDATE, ("TransformLine(%d) called", lineno));
1004
1005    /* copy new hash value to old one */
1006    if (SP->oldhash && SP->newhash)
1007	SP->oldhash[lineno] = SP->newhash[lineno];
1008
1009#define ColorOf(n) ((n) & A_COLOR)
1010#define unColor(n) ((n) & ALL_BUT_COLOR)
1011    /*
1012     * If we have colors, there is the possibility of having two color pairs
1013     * that display as the same colors.  For instance, Lynx does this.  Check
1014     * for this case, and update the old line with the new line's colors when
1015     * they are equivalent.
1016     */
1017    if (SP->_coloron) {
1018	chtype oldColor;
1019	chtype newColor;
1020	int oldPair;
1021	int newPair;
1022
1023	for (n = 0; n < screen_columns; n++) {
1024	    if (newLine[n] != oldLine[n]) {
1025		oldColor = ColorOf(oldLine[n]);
1026		newColor = ColorOf(newLine[n]);
1027		if (oldColor != newColor
1028		    && unColor(oldLine[n]) == unColor(newLine[n])) {
1029		    oldPair = PAIR_NUMBER(oldColor);
1030		    newPair = PAIR_NUMBER(newColor);
1031		    if (oldPair < COLOR_PAIRS
1032			&& newPair < COLOR_PAIRS
1033			&& SP->_color_pairs[oldPair] == SP->_color_pairs[newPair]) {
1034			oldLine[n] &= ~A_COLOR;
1035			oldLine[n] |= ColorOf(newLine[n]);
1036		    }
1037		}
1038	    }
1039	}
1040    }
1041
1042    if (ceol_standout_glitch && clr_eol) {
1043	firstChar = 0;
1044	while (firstChar < screen_columns) {
1045	    if (AttrOf(newLine[firstChar]) != AttrOf(oldLine[firstChar]))
1046		attrchanged = TRUE;
1047	    firstChar++;
1048	}
1049    }
1050
1051    firstChar = 0;
1052
1053    if (attrchanged) {		/* we may have to disregard the whole line */
1054	GoTo(lineno, firstChar);
1055	ClrToEOL(ClrBlank(curscr), FALSE);
1056	PutRange(oldLine, newLine, lineno, 0, (screen_columns - 1));
1057#if USE_XMC_SUPPORT
1058
1059#define NEW(r,c) newscr->_line[r].text[c]
1060#define xmc_turn_on(a,b) ((((a)^(b)) & ~(a) & SP->_xmc_triggers) != 0)
1061#define xmc_turn_off(a,b) xmc_turn_on(b,a)
1062
1063	/*
1064	 * This is a very simple loop to paint characters which may have the
1065	 * magic cookie glitch embedded.  It doesn't know much about video
1066	 * attributes which are continued from one line to the next.  It
1067	 * assumes that we have filtered out requests for attribute changes
1068	 * that do not get mapped to blank positions.
1069	 *
1070	 * FIXME: we are not keeping track of where we put the cookies, so this
1071	 * will work properly only once, since we may overwrite a cookie in a
1072	 * following operation.
1073	 */
1074    } else if (magic_cookie_glitch > 0) {
1075	GoTo(lineno, firstChar);
1076	for (n = 0; n < screen_columns; n++) {
1077	    int m = n + magic_cookie_glitch;
1078
1079	    /* check for turn-on:
1080	     * If we are writing an attributed blank, where the
1081	     * previous cell is not attributed.
1082	     */
1083	    if (TextOf(newLine[n]) == ' '
1084		&& ((n > 0
1085		     && xmc_turn_on(newLine[n - 1], newLine[n]))
1086		    || (n == 0
1087			&& lineno > 0
1088			&& xmc_turn_on(NEW(lineno - 1, screen_columns - 1),
1089				       newLine[n])))) {
1090		n = m;
1091	    }
1092
1093	    PutChar(newLine[n]);
1094
1095	    /* check for turn-off:
1096	     * If we are writing an attributed non-blank, where the
1097	     * next cell is blank, and not attributed.
1098	     */
1099	    if (TextOf(newLine[n]) != ' '
1100		&& ((n + 1 < screen_columns
1101		     && xmc_turn_off(newLine[n], newLine[n + 1]))
1102		    || (n + 1 >= screen_columns
1103			&& lineno + 1 < screen_lines
1104			&& xmc_turn_off(newLine[n], NEW(lineno + 1, 0))))) {
1105		n = m;
1106	    }
1107
1108	}
1109#undef NEW
1110#endif
1111    } else {
1112	chtype blank;
1113
1114	/* find the first differing character */
1115	while (firstChar < screen_columns &&
1116	       newLine[firstChar] == oldLine[firstChar])
1117	    firstChar++;
1118
1119	/* if there wasn't one, we're done */
1120	if (firstChar >= screen_columns)
1121	    return;
1122
1123	/* it may be cheap to clear leading whitespace with clr_bol */
1124	if (clr_bol && can_clear_with(blank = newLine[0])) {
1125	    int oFirstChar, nFirstChar;
1126
1127	    for (oFirstChar = 0; oFirstChar < screen_columns; oFirstChar++)
1128		if (oldLine[oFirstChar] != blank)
1129		    break;
1130	    for (nFirstChar = 0; nFirstChar < screen_columns; nFirstChar++)
1131		if (newLine[nFirstChar] != blank)
1132		    break;
1133
1134	    if (nFirstChar > oFirstChar + SP->_el1_cost) {
1135		if (nFirstChar >= screen_columns && SP->_el_cost <= SP->_el1_cost) {
1136		    GoTo(lineno, 0);
1137		    UpdateAttrs(blank);
1138		    TPUTS_TRACE("clr_eol");
1139		    putp(clr_eol);
1140		} else {
1141		    GoTo(lineno, nFirstChar - 1);
1142		    UpdateAttrs(blank);
1143		    TPUTS_TRACE("clr_bol");
1144		    putp(clr_bol);
1145		}
1146
1147		while (firstChar < nFirstChar)
1148		    oldLine[firstChar++] = blank;
1149
1150		if (firstChar >= screen_columns)
1151		    return;
1152	    }
1153	}
1154
1155	blank = newLine[screen_columns - 1];
1156
1157	if (!can_clear_with(blank)) {
1158	    /* find the last differing character */
1159	    nLastChar = screen_columns - 1;
1160
1161	    while (nLastChar > firstChar
1162		   && newLine[nLastChar] == oldLine[nLastChar])
1163		nLastChar--;
1164
1165	    if (nLastChar >= firstChar) {
1166		GoTo(lineno, firstChar);
1167		PutRange(oldLine, newLine, lineno, firstChar, nLastChar);
1168		memcpy(oldLine + firstChar,
1169		       newLine + firstChar,
1170		       (nLastChar - firstChar + 1) * sizeof(chtype));
1171	    }
1172	    return;
1173	}
1174
1175	/* find last non-blank character on old line */
1176	oLastChar = screen_columns - 1;
1177	while (oLastChar > firstChar && oldLine[oLastChar] == blank)
1178	    oLastChar--;
1179
1180	/* find last non-blank character on new line */
1181	nLastChar = screen_columns - 1;
1182	while (nLastChar > firstChar && newLine[nLastChar] == blank)
1183	    nLastChar--;
1184
1185	if ((nLastChar == firstChar)
1186	    && (SP->_el_cost < (oLastChar - nLastChar))) {
1187	    GoTo(lineno, firstChar);
1188	    if (newLine[firstChar] != blank)
1189		PutChar(newLine[firstChar]);
1190	    ClrToEOL(blank, FALSE);
1191	} else if ((nLastChar != oLastChar)
1192		   && (newLine[nLastChar] != oldLine[oLastChar]
1193		       || !(_nc_idcok && has_ic()))) {
1194	    GoTo(lineno, firstChar);
1195	    if ((oLastChar - nLastChar) > SP->_el_cost) {
1196		if (PutRange(oldLine, newLine, lineno, firstChar, nLastChar))
1197		    GoTo(lineno, nLastChar + 1);
1198		ClrToEOL(blank, FALSE);
1199	    } else {
1200		n = max(nLastChar, oLastChar);
1201		PutRange(oldLine, newLine, lineno, firstChar, n);
1202	    }
1203	} else {
1204	    int nLastNonblank = nLastChar;
1205	    int oLastNonblank = oLastChar;
1206
1207	    /* find the last characters that really differ */
1208	    while (newLine[nLastChar] == oldLine[oLastChar]) {
1209		if (nLastChar != 0
1210		    && oLastChar != 0) {
1211		    nLastChar--;
1212		    oLastChar--;
1213		} else {
1214		    break;
1215		}
1216	    }
1217
1218	    n = min(oLastChar, nLastChar);
1219	    if (n >= firstChar) {
1220		GoTo(lineno, firstChar);
1221		PutRange(oldLine, newLine, lineno, firstChar, n);
1222	    }
1223
1224	    if (oLastChar < nLastChar) {
1225		int m = max(nLastNonblank, oLastNonblank);
1226		GoTo(lineno, n + 1);
1227		if (InsCharCost(nLastChar - oLastChar)
1228		    > (m - n)) {
1229		    PutRange(oldLine, newLine, lineno, n + 1, m);
1230		} else {
1231		    InsStr(&newLine[n + 1], nLastChar - oLastChar);
1232		}
1233	    } else if (oLastChar > nLastChar) {
1234		GoTo(lineno, n + 1);
1235		if (DelCharCost(oLastChar - nLastChar)
1236		    > SP->_el_cost + nLastNonblank - (n + 1)) {
1237		    if (PutRange(oldLine, newLine, lineno,
1238				 n + 1, nLastNonblank))
1239			GoTo(lineno, nLastNonblank + 1);
1240		    ClrToEOL(blank, FALSE);
1241		} else {
1242		    /*
1243		     * The delete-char sequence will
1244		     * effectively shift in blanks from the
1245		     * right margin of the screen.  Ensure
1246		     * that they are the right color by
1247		     * setting the video attributes from
1248		     * the last character on the row.
1249		     */
1250		    UpdateAttrs(blank);
1251		    DelChar(oLastChar - nLastChar);
1252		}
1253	    }
1254	}
1255    }
1256
1257    /* update the code's internal representation */
1258    if (screen_columns > firstChar)
1259	memcpy(oldLine + firstChar,
1260	       newLine + firstChar,
1261	       (screen_columns - firstChar) * sizeof(chtype));
1262}
1263
1264/*
1265**	ClearScreen(blank)
1266**
1267**	Clear the physical screen and put cursor at home
1268**
1269*/
1270
1271static void
1272ClearScreen(chtype blank)
1273{
1274    int i, j;
1275    bool fast_clear = (clear_screen || clr_eos || clr_eol);
1276
1277    TR(TRACE_UPDATE, ("ClearScreen() called"));
1278
1279#if NCURSES_EXT_FUNCS
1280    if (SP->_coloron
1281	&& !SP->_default_color) {
1282	_nc_do_color(COLOR_PAIR(SP->_current_attr), 0, FALSE, _nc_outch);
1283	if (!back_color_erase) {
1284	    fast_clear = FALSE;
1285	}
1286    }
1287#endif
1288
1289    if (fast_clear) {
1290	if (clear_screen) {
1291	    UpdateAttrs(blank);
1292	    TPUTS_TRACE("clear_screen");
1293	    putp(clear_screen);
1294	    SP->_cursrow = SP->_curscol = 0;
1295	    position_check(SP->_cursrow, SP->_curscol, "ClearScreen");
1296	} else if (clr_eos) {
1297	    SP->_cursrow = SP->_curscol = -1;
1298	    GoTo(0, 0);
1299
1300	    UpdateAttrs(blank);
1301	    TPUTS_TRACE("clr_eos");
1302	    putp(clr_eos);
1303	} else if (clr_eol) {
1304	    SP->_cursrow = SP->_curscol = -1;
1305
1306	    UpdateAttrs(blank);
1307	    for (i = 0; i < screen_lines; i++) {
1308		GoTo(i, 0);
1309		TPUTS_TRACE("clr_eol");
1310		putp(clr_eol);
1311	    }
1312	    GoTo(0, 0);
1313	}
1314    } else {
1315	UpdateAttrs(blank);
1316	for (i = 0; i < screen_lines; i++) {
1317	    GoTo(i, 0);
1318	    for (j = 0; j < screen_columns; j++)
1319		PutChar(blank);
1320	}
1321	GoTo(0, 0);
1322    }
1323
1324    for (i = 0; i < screen_lines; i++) {
1325	for (j = 0; j < screen_columns; j++)
1326	    curscr->_line[i].text[j] = blank;
1327    }
1328
1329    TR(TRACE_UPDATE, ("screen cleared"));
1330}
1331
1332/*
1333**	InsStr(line, count)
1334**
1335**	Insert the count characters pointed to by line.
1336**
1337*/
1338
1339static void
1340InsStr(chtype * line, int count)
1341{
1342    TR(TRACE_UPDATE, ("InsStr(%p,%d) called", line, count));
1343
1344    /* Prefer parm_ich as it has the smallest cost - no need to shift
1345     * the whole line on each character. */
1346    /* The order must match that of InsCharCost. */
1347    if (parm_ich) {
1348	TPUTS_TRACE("parm_ich");
1349	tputs(tparm(parm_ich, count), count, _nc_outch);
1350	while (count) {
1351	    PutAttrChar(*line);
1352	    line++;
1353	    count--;
1354	}
1355    } else if (enter_insert_mode && exit_insert_mode) {
1356	TPUTS_TRACE("enter_insert_mode");
1357	putp(enter_insert_mode);
1358	while (count) {
1359	    PutAttrChar(*line);
1360	    if (insert_padding) {
1361		TPUTS_TRACE("insert_padding");
1362		putp(insert_padding);
1363	    }
1364	    line++;
1365	    count--;
1366	}
1367	TPUTS_TRACE("exit_insert_mode");
1368	putp(exit_insert_mode);
1369    } else {
1370	while (count) {
1371	    TPUTS_TRACE("insert_character");
1372	    putp(insert_character);
1373	    PutAttrChar(*line);
1374	    if (insert_padding) {
1375		TPUTS_TRACE("insert_padding");
1376		putp(insert_padding);
1377	    }
1378	    line++;
1379	    count--;
1380	}
1381    }
1382    position_check(SP->_cursrow, SP->_curscol, "InsStr");
1383}
1384
1385/*
1386**	DelChar(count)
1387**
1388**	Delete count characters at current position
1389**
1390*/
1391
1392static void
1393DelChar(int count)
1394{
1395    int n;
1396
1397    TR(TRACE_UPDATE, ("DelChar(%d) called, position = (%d,%d)", count,
1398		      newscr->_cury, newscr->_curx));
1399
1400    if (parm_dch) {
1401	TPUTS_TRACE("parm_dch");
1402	tputs(tparm(parm_dch, count), count, _nc_outch);
1403    } else {
1404	for (n = 0; n < count; n++) {
1405	    TPUTS_TRACE("delete_character");
1406	    putp(delete_character);
1407	}
1408    }
1409}
1410
1411/*
1412**	_nc_outstr(char *str)
1413**
1414**	Emit a string without waiting for update.
1415*/
1416
1417void
1418_nc_outstr(const char *str)
1419{
1420    (void) putp(str);
1421    _nc_flush();
1422}
1423
1424/*
1425 * Physical-scrolling support
1426 *
1427 * This code was adapted from Keith Bostic's hardware scrolling
1428 * support for 4.4BSD curses.  I (esr) translated it to use terminfo
1429 * capabilities, narrowed the call interface slightly, and cleaned
1430 * up some convoluted tests.  I also added support for the memory_above
1431 * memory_below, and non_dest_scroll_region capabilities.
1432 *
1433 * For this code to work, we must have either
1434 * change_scroll_region and scroll forward/reverse commands, or
1435 * insert and delete line capabilities.
1436 * When the scrolling region has been set, the cursor has to
1437 * be at the last line of the region to make the scroll up
1438 * happen, or on the first line of region to scroll down.
1439 *
1440 * This code makes one aesthetic decision in the opposite way from
1441 * BSD curses.  BSD curses preferred pairs of il/dl operations
1442 * over scrolls, allegedly because il/dl looked faster.  We, on
1443 * the other hand, prefer scrolls because (a) they're just as fast
1444 * on many terminals and (b) using them avoids bouncing an
1445 * unchanged bottom section of the screen up and down, which is
1446 * visually nasty.
1447 *
1448 * (lav): added more cases, used dl/il when bot==maxy and in csr case.
1449 *
1450 * I used assumption that capabilities il/il1/dl/dl1 work inside
1451 * changed scroll region not shifting screen contents outside of it.
1452 * If there are any terminals behaving different way, it would be
1453 * necessary to add some conditions to scroll_csr_forward/backward.
1454 */
1455
1456/* Try to scroll up assuming given csr (miny, maxy). Returns ERR on failure */
1457static int
1458scroll_csr_forward(int n, int top, int bot, int miny, int maxy, chtype blank)
1459{
1460    int i, j;
1461
1462    if (n == 1 && scroll_forward && top == miny && bot == maxy) {
1463	GoTo(bot, 0);
1464	UpdateAttrs(blank);
1465	TPUTS_TRACE("scroll_forward");
1466	tputs(scroll_forward, 0, _nc_outch);
1467    } else if (n == 1 && delete_line && bot == maxy) {
1468	GoTo(top, 0);
1469	UpdateAttrs(blank);
1470	TPUTS_TRACE("delete_line");
1471	tputs(delete_line, 0, _nc_outch);
1472    } else if (parm_index && top == miny && bot == maxy) {
1473	GoTo(bot, 0);
1474	UpdateAttrs(blank);
1475	TPUTS_TRACE("parm_index");
1476	tputs(tparm(parm_index, n, 0), n, _nc_outch);
1477    } else if (parm_delete_line && bot == maxy) {
1478	GoTo(top, 0);
1479	UpdateAttrs(blank);
1480	TPUTS_TRACE("parm_delete_line");
1481	tputs(tparm(parm_delete_line, n, 0), n, _nc_outch);
1482    } else if (scroll_forward && top == miny && bot == maxy) {
1483	GoTo(bot, 0);
1484	UpdateAttrs(blank);
1485	for (i = 0; i < n; i++) {
1486	    TPUTS_TRACE("scroll_forward");
1487	    tputs(scroll_forward, 0, _nc_outch);
1488	}
1489    } else if (delete_line && bot == maxy) {
1490	GoTo(top, 0);
1491	UpdateAttrs(blank);
1492	for (i = 0; i < n; i++) {
1493	    TPUTS_TRACE("delete_line");
1494	    tputs(delete_line, 0, _nc_outch);
1495	}
1496    } else
1497	return ERR;
1498
1499#if NCURSES_EXT_FUNCS
1500    if (FILL_BCE()) {
1501	for (i = 0; i < n; i++) {
1502	    GoTo(bot - i, 0);
1503	    for (j = 0; j < screen_columns; j++)
1504		PutChar(blank);
1505	}
1506    }
1507#endif
1508    return OK;
1509}
1510
1511/* Try to scroll down assuming given csr (miny, maxy). Returns ERR on failure */
1512/* n > 0 */
1513static int
1514scroll_csr_backward(int n, int top, int bot, int miny, int maxy, chtype blank)
1515{
1516    int i, j;
1517
1518    if (n == 1 && scroll_reverse && top == miny && bot == maxy) {
1519	GoTo(top, 0);
1520	UpdateAttrs(blank);
1521	TPUTS_TRACE("scroll_reverse");
1522	tputs(scroll_reverse, 0, _nc_outch);
1523    } else if (n == 1 && insert_line && bot == maxy) {
1524	GoTo(top, 0);
1525	UpdateAttrs(blank);
1526	TPUTS_TRACE("insert_line");
1527	tputs(insert_line, 0, _nc_outch);
1528    } else if (parm_rindex && top == miny && bot == maxy) {
1529	GoTo(top, 0);
1530	UpdateAttrs(blank);
1531	TPUTS_TRACE("parm_rindex");
1532	tputs(tparm(parm_rindex, n, 0), n, _nc_outch);
1533    } else if (parm_insert_line && bot == maxy) {
1534	GoTo(top, 0);
1535	UpdateAttrs(blank);
1536	TPUTS_TRACE("parm_insert_line");
1537	tputs(tparm(parm_insert_line, n, 0), n, _nc_outch);
1538    } else if (scroll_reverse && top == miny && bot == maxy) {
1539	GoTo(top, 0);
1540	UpdateAttrs(blank);
1541	for (i = 0; i < n; i++) {
1542	    TPUTS_TRACE("scroll_reverse");
1543	    tputs(scroll_reverse, 0, _nc_outch);
1544	}
1545    } else if (insert_line && bot == maxy) {
1546	GoTo(top, 0);
1547	UpdateAttrs(blank);
1548	for (i = 0; i < n; i++) {
1549	    TPUTS_TRACE("insert_line");
1550	    tputs(insert_line, 0, _nc_outch);
1551	}
1552    } else
1553	return ERR;
1554
1555#if NCURSES_EXT_FUNCS
1556    if (FILL_BCE()) {
1557	for (i = 0; i < n; i++) {
1558	    GoTo(top + i, 0);
1559	    for (j = 0; j < screen_columns; j++)
1560		PutChar(blank);
1561	}
1562    }
1563#endif
1564    return OK;
1565}
1566
1567/* scroll by using delete_line at del and insert_line at ins */
1568/* n > 0 */
1569static int
1570scroll_idl(int n, int del, int ins, chtype blank)
1571{
1572    int i;
1573
1574    if (!((parm_delete_line || delete_line) && (parm_insert_line || insert_line)))
1575	return ERR;
1576
1577    GoTo(del, 0);
1578    UpdateAttrs(blank);
1579    if (n == 1 && delete_line) {
1580	TPUTS_TRACE("delete_line");
1581	tputs(delete_line, 0, _nc_outch);
1582    } else if (parm_delete_line) {
1583	TPUTS_TRACE("parm_delete_line");
1584	tputs(tparm(parm_delete_line, n, 0), n, _nc_outch);
1585    } else {			/* if (delete_line) */
1586	for (i = 0; i < n; i++) {
1587	    TPUTS_TRACE("delete_line");
1588	    tputs(delete_line, 0, _nc_outch);
1589	}
1590    }
1591
1592    GoTo(ins, 0);
1593    UpdateAttrs(blank);
1594    if (n == 1 && insert_line) {
1595	TPUTS_TRACE("insert_line");
1596	tputs(insert_line, 0, _nc_outch);
1597    } else if (parm_insert_line) {
1598	TPUTS_TRACE("parm_insert_line");
1599	tputs(tparm(parm_insert_line, n, 0), n, _nc_outch);
1600    } else {			/* if (insert_line) */
1601	for (i = 0; i < n; i++) {
1602	    TPUTS_TRACE("insert_line");
1603	    tputs(insert_line, 0, _nc_outch);
1604	}
1605    }
1606
1607    return OK;
1608}
1609
1610int
1611_nc_scrolln(int n, int top, int bot, int maxy)
1612/* scroll region from top to bot by n lines */
1613{
1614    chtype blank = ClrBlank(stdscr);
1615    int i;
1616    bool cursor_saved = FALSE;
1617    int res;
1618
1619    TR(TRACE_MOVE, ("mvcur_scrolln(%d, %d, %d, %d)", n, top, bot, maxy));
1620
1621#if USE_XMC_SUPPORT
1622    /*
1623     * If we scroll, we might remove a cookie.
1624     */
1625    if (magic_cookie_glitch > 0) {
1626	return (ERR);
1627    }
1628#endif
1629
1630    if (n > 0) {		/* scroll up (forward) */
1631	/*
1632	 * Explicitly clear if stuff pushed off top of region might
1633	 * be saved by the terminal.
1634	 */
1635	res = scroll_csr_forward(n, top, bot, 0, maxy, blank);
1636
1637	if (res == ERR && change_scroll_region) {
1638	    if ((((n == 1 && scroll_forward) || parm_index)
1639		 && (SP->_cursrow == bot || SP->_cursrow == bot - 1))
1640		&& save_cursor && restore_cursor) {
1641		cursor_saved = TRUE;
1642		TPUTS_TRACE("save_cursor");
1643		tputs(save_cursor, 0, _nc_outch);
1644	    }
1645	    TPUTS_TRACE("change_scroll_region");
1646	    tputs(tparm(change_scroll_region, top, bot), 0, _nc_outch);
1647	    if (cursor_saved) {
1648		TPUTS_TRACE("restore_cursor");
1649		tputs(restore_cursor, 0, _nc_outch);
1650	    } else {
1651		SP->_cursrow = SP->_curscol = -1;
1652	    }
1653
1654	    res = scroll_csr_forward(n, top, bot, top, bot, blank);
1655
1656	    TPUTS_TRACE("change_scroll_region");
1657	    tputs(tparm(change_scroll_region, 0, maxy), 0, _nc_outch);
1658	    SP->_cursrow = SP->_curscol = -1;
1659	}
1660
1661	if (res == ERR && _nc_idlok)
1662	    res = scroll_idl(n, top, bot - n + 1, blank);
1663
1664	/*
1665	 * Clear the newly shifted-in text.
1666	 */
1667	if (res != ERR
1668	    && (non_dest_scroll_region || (memory_below && bot == maxy))) {
1669	    if (bot == maxy && clr_eos) {
1670		GoTo(bot - n, 0);
1671		ClrToEOS(BLANK);
1672	    } else {
1673		for (i = 0; i < n; i++) {
1674		    GoTo(bot - i, 0);
1675		    ClrToEOL(BLANK, FALSE);
1676		}
1677	    }
1678	}
1679
1680    } else {			/* (n < 0) - scroll down (backward) */
1681	res = scroll_csr_backward(-n, top, bot, 0, maxy, blank);
1682
1683	if (res == ERR && change_scroll_region) {
1684	    if (top != 0 && (SP->_cursrow == top || SP->_cursrow == top - 1)
1685		&& save_cursor && restore_cursor) {
1686		cursor_saved = TRUE;
1687		TPUTS_TRACE("save_cursor");
1688		tputs(save_cursor, 0, _nc_outch);
1689	    }
1690	    TPUTS_TRACE("change_scroll_region");
1691	    tputs(tparm(change_scroll_region, top, bot), 0, _nc_outch);
1692	    if (cursor_saved) {
1693		TPUTS_TRACE("restore_cursor");
1694		tputs(restore_cursor, 0, _nc_outch);
1695	    } else {
1696		SP->_cursrow = SP->_curscol = -1;
1697	    }
1698
1699	    res = scroll_csr_backward(-n, top, bot, top, bot, blank);
1700
1701	    TPUTS_TRACE("change_scroll_region");
1702	    tputs(tparm(change_scroll_region, 0, maxy), 0, _nc_outch);
1703	    SP->_cursrow = SP->_curscol = -1;
1704	}
1705
1706	if (res == ERR && _nc_idlok)
1707	    res = scroll_idl(-n, bot + n + 1, top, blank);
1708
1709	/*
1710	 * Clear the newly shifted-in text.
1711	 */
1712	if (res != ERR
1713	    && (non_dest_scroll_region || (memory_above && top == 0))) {
1714	    for (i = 0; i < -n; i++) {
1715		GoTo(i + top, 0);
1716		ClrToEOL(BLANK, FALSE);
1717	    }
1718	}
1719    }
1720
1721    if (res == ERR)
1722	return (ERR);
1723
1724    _nc_scroll_window(curscr, n, top, bot, blank);
1725
1726    /* shift hash values too - they can be reused */
1727    _nc_scroll_oldhash(n, top, bot);
1728
1729    return (OK);
1730}
1731
1732void
1733_nc_screen_resume(void)
1734{
1735    /* make sure terminal is in a sane known state */
1736    SP->_current_attr = A_NORMAL;
1737    newscr->_clear = TRUE;
1738
1739    if (SP->_coloron == TRUE && orig_pair)
1740	putp(orig_pair);
1741    if (exit_attribute_mode)
1742	putp(exit_attribute_mode);
1743    else {
1744	/* turn off attributes */
1745	if (exit_alt_charset_mode)
1746	    putp(exit_alt_charset_mode);
1747	if (exit_standout_mode)
1748	    putp(exit_standout_mode);
1749	if (exit_underline_mode)
1750	    putp(exit_underline_mode);
1751    }
1752    if (exit_insert_mode)
1753	putp(exit_insert_mode);
1754    if (enter_am_mode && exit_am_mode)
1755	putp(auto_right_margin ? enter_am_mode : exit_am_mode);
1756}
1757
1758void
1759_nc_screen_init(void)
1760{
1761    _nc_screen_resume();
1762}
1763
1764/* wrap up screen handling */
1765void
1766_nc_screen_wrap(void)
1767{
1768    UpdateAttrs(A_NORMAL);
1769#if NCURSES_EXT_FUNCS
1770    if (SP->_coloron
1771	&& !SP->_default_color) {
1772	SP->_default_color = TRUE;
1773	_nc_do_color(-1, 0, FALSE, _nc_outch);
1774	SP->_default_color = FALSE;
1775
1776	mvcur(SP->_cursrow, SP->_curscol, screen_lines - 1, 0);
1777	SP->_cursrow = screen_lines - 1;
1778	SP->_curscol = 0;
1779
1780	ClrToEOL(BLANK, TRUE);
1781    }
1782#endif
1783}
1784
1785#if USE_XMC_SUPPORT
1786void
1787_nc_do_xmc_glitch(attr_t previous)
1788{
1789    attr_t chg = XMC_CHANGES(previous ^ SP->_current_attr);
1790
1791    while (chg != 0) {
1792	if (chg & 1) {
1793	    SP->_curscol += magic_cookie_glitch;
1794	    if (SP->_curscol >= SP->_columns)
1795		wrap_cursor();
1796	    TR(TRACE_UPDATE, ("bumped to %d,%d after cookie", SP->_cursrow, SP->_curscol));
1797	}
1798	chg >>= 1;
1799    }
1800}
1801#endif /* USE_XMC_SUPPORT */
1802