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