ncurses 5.9 - patch 20120512
[ncurses.git] / ncurses / tty / tty_update.c
1 /****************************************************************************
2  * Copyright (c) 1998-2011,2012 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  *     and: Thomas E. Dickey                        1996-on                 *
33  *     and: Juergen Pfeifer                         2009                    *
34  ****************************************************************************/
35
36 /*-----------------------------------------------------------------
37  *
38  *      lib_doupdate.c
39  *
40  *      The routine doupdate() and its dependents.
41  *      All physical output is concentrated here (except _nc_outch()
42  *      in lib_tputs.c).
43  *
44  *-----------------------------------------------------------------*/
45
46 #include <curses.priv.h>
47
48 #ifndef CUR
49 #define CUR SP_TERMTYPE
50 #endif
51
52 #if defined __HAIKU__ && defined __BEOS__
53 #undef __BEOS__
54 #endif
55
56 #ifdef __BEOS__
57 #undef false
58 #undef true
59 #include <OS.h>
60 #endif
61
62 #if defined(TRACE) && HAVE_SYS_TIMES_H && HAVE_TIMES
63 #define USE_TRACE_TIMES 1
64 #else
65 #define USE_TRACE_TIMES 0
66 #endif
67
68 #if HAVE_SYS_TIME_H && HAVE_SYS_TIME_SELECT
69 #include <sys/time.h>
70 #endif
71
72 #if USE_TRACE_TIMES
73 #include <sys/times.h>
74 #endif
75
76 #if USE_FUNC_POLL
77 #elif HAVE_SELECT
78 #if HAVE_SYS_SELECT_H
79 #include <sys/select.h>
80 #endif
81 #endif
82
83 #include <ctype.h>
84
85 MODULE_ID("$Id: tty_update.c,v 1.268 2012/05/12 21:02:00 tom Exp $")
86
87 /*
88  * This define controls the line-breakout optimization.  Every once in a
89  * while during screen refresh, we want to check for input and abort the
90  * update if there's some waiting.  CHECK_INTERVAL controls the number of
91  * changed lines to be emitted between input checks.
92  *
93  * Note: Input-check-and-abort is no longer done if the screen is being
94  * updated from scratch.  This is a feature, not a bug.
95  */
96 #define CHECK_INTERVAL  5
97
98 #define FILL_BCE(sp) (sp->_coloron && !sp->_default_color && !back_color_erase)
99
100 static const NCURSES_CH_T blankchar = NewChar(BLANK_TEXT);
101 static NCURSES_CH_T normal = NewChar(BLANK_TEXT);
102
103 /*
104  * Enable checking to see if doupdate and friends are tracking the true
105  * cursor position correctly.  NOTE: this is a debugging hack which will
106  * work ONLY on ANSI-compatible terminals!
107  */
108 /* #define POSITION_DEBUG */
109
110 static NCURSES_INLINE NCURSES_CH_T ClrBlank(NCURSES_SP_DCLx WINDOW *win);
111
112 #if NCURSES_SP_FUNCS
113 static int ClrBottom(SCREEN *, int total);
114 static void ClearScreen(SCREEN *, NCURSES_CH_T blank);
115 static void ClrUpdate(SCREEN *);
116 static void DelChar(SCREEN *, int count);
117 static void InsStr(SCREEN *, NCURSES_CH_T * line, int count);
118 static void TransformLine(SCREEN *, int const lineno);
119 #else
120 static int ClrBottom(int total);
121 static void ClearScreen(NCURSES_CH_T blank);
122 static void ClrUpdate(void);
123 static void DelChar(int count);
124 static void InsStr(NCURSES_CH_T * line, int count);
125 static void TransformLine(int const lineno);
126 #endif
127
128 #ifdef POSITION_DEBUG
129 /****************************************************************************
130  *
131  * Debugging code.  Only works on ANSI-standard terminals.
132  *
133  ****************************************************************************/
134
135 static void
136 position_check(NCURSES_SP_DCLx int expected_y, int expected_x, char *legend)
137 /* check to see if the real cursor position matches the virtual */
138 {
139     char buf[20];
140     char *s;
141     int y, x;
142
143     if (!_nc_tracing || (expected_y < 0 && expected_x < 0))
144         return;
145
146     NCURSES_SP_NAME(_nc_flush) (NCURSES_SP_ARG);
147     memset(buf, '\0', sizeof(buf));
148     NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx "\033[6n");  /* only works on ANSI-compatibles */
149     NCURSES_SP_NAME(_nc_flush) (NCURSES_SP_ARG);
150     *(s = buf) = 0;
151     do {
152         int ask = sizeof(buf) - 1 - (s - buf);
153         int got = read(0, s, ask);
154         if (got == 0)
155             break;
156         s += got;
157     } while (strchr(buf, 'R') == 0);
158     _tracef("probe returned %s", _nc_visbuf(buf));
159
160     /* try to interpret as a position report */
161     if (sscanf(buf, "\033[%d;%dR", &y, &x) != 2) {
162         _tracef("position probe failed in %s", legend);
163     } else {
164         if (expected_x < 0)
165             expected_x = x - 1;
166         if (expected_y < 0)
167             expected_y = y - 1;
168         if (y - 1 != expected_y || x - 1 != expected_x) {
169             NCURSES_SP_NAME(beep) (NCURSES_SP_ARG);
170             NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
171                                     tparm("\033[%d;%dH",
172                                           expected_y + 1,
173                                           expected_x + 1),
174                                     1, NCURSES_SP_NAME(_nc_outch));
175             _tracef("position seen (%d, %d) doesn't match expected one (%d, %d) in %s",
176                     y - 1, x - 1, expected_y, expected_x, legend);
177         } else {
178             _tracef("position matches OK in %s", legend);
179         }
180     }
181 }
182 #else
183 #define position_check(sp, expected_y, expected_x, legend)      /* nothing */
184 #endif /* POSITION_DEBUG */
185
186 /****************************************************************************
187  *
188  * Optimized update code
189  *
190  ****************************************************************************/
191
192 static NCURSES_INLINE void
193 GoTo(NCURSES_SP_DCLx int const row, int const col)
194 {
195     TR(TRACE_MOVE, ("GoTo(%p, %d, %d) from (%d, %d)",
196                     (void *) SP_PARM, row, col, SP_PARM->_cursrow, SP_PARM->_curscol));
197
198     position_check(SP_PARM, SP_PARM->_cursrow, SP_PARM->_curscol, "GoTo");
199
200     TINFO_MVCUR(NCURSES_SP_ARGx
201                 SP_PARM->_cursrow,
202                 SP_PARM->_curscol,
203                 row, col);
204     position_check(SP_PARM, SP_PARM->_cursrow, SP_PARM->_curscol, "GoTo2");
205 }
206
207 static NCURSES_INLINE void
208 PutAttrChar(NCURSES_SP_DCLx CARG_CH_T ch)
209 {
210     int chlen = 1;
211     NCURSES_CH_T my_ch;
212     PUTC_DATA;
213     NCURSES_CH_T tilde;
214     NCURSES_CH_T attr = CHDEREF(ch);
215
216     TR(TRACE_CHARPUT, ("PutAttrChar(%s) at (%d, %d)",
217                        _tracech_t(ch),
218                        SP_PARM->_cursrow, SP_PARM->_curscol));
219 #if USE_WIDEC_SUPPORT
220     /*
221      * If this is not a valid character, there is nothing more to do.
222      */
223     if (isWidecExt(CHDEREF(ch))) {
224         TR(TRACE_CHARPUT, ("...skip"));
225         return;
226     }
227     /*
228      * Determine the number of character cells which the 'ch' value will use
229      * on the screen.  It should be at least one.
230      */
231     if ((chlen = wcwidth(CharOf(CHDEREF(ch)))) <= 0) {
232         static const NCURSES_CH_T blank = NewChar(BLANK_TEXT);
233
234         /*
235          * If the character falls into any of these special cases, do
236          * not force the result to a blank:
237          *
238          * a) it is printable (this works around a bug in wcwidth()).
239          * b) use_legacy_coding() has been called to modify the treatment
240          *    of codes 128-255.
241          * c) the acs_map[] has been initialized to allow codes 0-31
242          *    to be rendered.  This supports Linux console's "PC"
243          *    characters.  Codes 128-255 are allowed though this is
244          *    not checked.
245          */
246         if (is8bits(CharOf(CHDEREF(ch)))
247             && (isprint(CharOf(CHDEREF(ch)))
248                 || (SP_PARM->_legacy_coding > 0 && CharOf(CHDEREF(ch)) >= 160)
249                 || (SP_PARM->_legacy_coding > 1 && CharOf(CHDEREF(ch)) >= 128)
250                 || (AttrOf(attr) & A_ALTCHARSET
251                     && ((CharOfD(ch) < ACS_LEN
252                          && SP_PARM->_acs_map != 0
253                          && SP_PARM->_acs_map[CharOfD(ch)] != 0)
254                         || (CharOfD(ch) >= 128))))) {
255             ;
256         } else {
257             ch = CHREF(blank);
258             TR(TRACE_CHARPUT, ("forced to blank"));
259         }
260         chlen = 1;
261     }
262 #endif
263
264     if ((AttrOf(attr) & A_ALTCHARSET)
265         && SP_PARM->_acs_map != 0
266         && CharOfD(ch) < ACS_LEN) {
267         my_ch = CHDEREF(ch);    /* work around const param */
268 #if USE_WIDEC_SUPPORT
269         /*
270          * This is crude & ugly, but works most of the time.  It checks if the
271          * acs_chars string specified that we have a mapping for this
272          * character, and uses the wide-character mapping when we expect the
273          * normal one to be broken (by mis-design ;-).
274          */
275         if (SP_PARM->_screen_acs_fix
276             && SP_PARM->_screen_acs_map[CharOf(my_ch)]) {
277             RemAttr(attr, A_ALTCHARSET);
278             my_ch = _nc_wacs[CharOf(my_ch)];
279         }
280 #endif
281         /*
282          * If we (still) have alternate character set, it is the normal 8bit
283          * flavor.  The _screen_acs_map[] array tells if the character was
284          * really in acs_chars, needed because of the way wide/normal line
285          * drawing flavors are integrated.
286          */
287         if (AttrOf(attr) & A_ALTCHARSET) {
288             int j = CharOfD(ch);
289             chtype temp = UChar(SP_PARM->_acs_map[j]);
290
291             if (temp != 0) {
292                 SetChar(my_ch, temp, AttrOf(attr));
293             } else {
294                 my_ch = CHDEREF(ch);
295                 RemAttr(attr, A_ALTCHARSET);
296             }
297         }
298         ch = CHREF(my_ch);
299     }
300     if (tilde_glitch && (CharOfD(ch) == L('~'))) {
301         SetChar(tilde, L('`'), AttrOf(attr));
302         ch = CHREF(tilde);
303     }
304
305     UpdateAttrs(SP_PARM, attr);
306 #if !USE_WIDEC_SUPPORT
307     /* FIXME - we do this special case for signal handling, should see how to
308      * make it work for wide characters.
309      */
310     if (SP_PARM->_outch != 0) {
311         SP_PARM->_outch(NCURSES_SP_ARGx UChar(ch));
312     } else
313 #endif
314     {
315         PUTC(CHDEREF(ch), SP_PARM->_ofp);       /* macro's fastest... */
316         COUNT_OUTCHARS(1);
317     }
318     SP_PARM->_curscol += chlen;
319     if (char_padding) {
320         TPUTS_TRACE("char_padding");
321         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx char_padding);
322     }
323 }
324
325 static bool
326 check_pending(NCURSES_SP_DCL0)
327 /* check for pending input */
328 {
329     bool have_pending = FALSE;
330
331     /*
332      * Only carry out this check when the flag is zero, otherwise we'll
333      * have the refreshing slow down drastically (or stop) if there's an
334      * unread character available.
335      */
336     if (SP_PARM->_fifohold != 0)
337         return FALSE;
338
339     if (SP_PARM->_checkfd >= 0) {
340 #if USE_FUNC_POLL
341         struct pollfd fds[1];
342         fds[0].fd = SP_PARM->_checkfd;
343         fds[0].events = POLLIN;
344         if (poll(fds, (size_t) 1, 0) > 0) {
345             have_pending = TRUE;
346         }
347 #elif defined(__BEOS__)
348         /*
349          * BeOS's select() is declared in socket.h, so the configure script does
350          * not see it.  That's just as well, since that function works only for
351          * sockets.  This (using snooze and ioctl) was distilled from Be's patch
352          * for ncurses which uses a separate thread to simulate select().
353          *
354          * FIXME: the return values from the ioctl aren't very clear if we get
355          * interrupted.
356          */
357         int n = 0;
358         int howmany = ioctl(0, 'ichr', &n);
359         if (howmany >= 0 && n > 0) {
360             have_pending = TRUE;
361         }
362 #elif HAVE_SELECT
363         fd_set fdset;
364         struct timeval ktimeout;
365
366         ktimeout.tv_sec =
367             ktimeout.tv_usec = 0;
368
369         FD_ZERO(&fdset);
370         FD_SET(SP_PARM->_checkfd, &fdset);
371         if (select(SP_PARM->_checkfd + 1, &fdset, NULL, NULL, &ktimeout) != 0) {
372             have_pending = TRUE;
373         }
374 #endif
375     }
376     if (have_pending) {
377         SP_PARM->_fifohold = 5;
378         NCURSES_SP_NAME(_nc_flush) (NCURSES_SP_ARG);
379     }
380     return FALSE;
381 }
382
383 /* put char at lower right corner */
384 static void
385 PutCharLR(NCURSES_SP_DCLx const ARG_CH_T ch)
386 {
387     if (!auto_right_margin) {
388         /* we can put the char directly */
389         PutAttrChar(NCURSES_SP_ARGx ch);
390     } else if (enter_am_mode && exit_am_mode) {
391         /* we can suppress automargin */
392         TPUTS_TRACE("exit_am_mode");
393         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx exit_am_mode);
394
395         PutAttrChar(NCURSES_SP_ARGx ch);
396         SP_PARM->_curscol--;
397         position_check(SP_PARM,
398                        SP_PARM->_cursrow,
399                        SP_PARM->_curscol,
400                        "exit_am_mode");
401
402         TPUTS_TRACE("enter_am_mode");
403         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx enter_am_mode);
404     } else if ((enter_insert_mode && exit_insert_mode)
405                || insert_character || parm_ich) {
406         GoTo(NCURSES_SP_ARGx
407              screen_lines(SP_PARM) - 1,
408              screen_columns(SP_PARM) - 2);
409         PutAttrChar(NCURSES_SP_ARGx ch);
410         GoTo(NCURSES_SP_ARGx
411              screen_lines(SP_PARM) - 1,
412              screen_columns(SP_PARM) - 2);
413         InsStr(NCURSES_SP_ARGx
414                NewScreen(SP_PARM)->_line[screen_lines(SP_PARM) - 1].text +
415                screen_columns(SP_PARM) - 2, 1);
416     }
417 }
418
419 /*
420  * Wrap the cursor position, i.e., advance to the beginning of the next line.
421  */
422 static void
423 wrap_cursor(NCURSES_SP_DCL0)
424 {
425     if (eat_newline_glitch) {
426         /*
427          * xenl can manifest two different ways.  The vt100 way is that, when
428          * you'd expect the cursor to wrap, it stays hung at the right margin
429          * (on top of the character just emitted) and doesn't wrap until the
430          * *next* graphic char is emitted.  The c100 way is to ignore LF
431          * received just after an am wrap.
432          *
433          * An aggressive way to handle this would be to emit CR/LF after the
434          * char and then assume the wrap is done, you're on the first position
435          * of the next line, and the terminal out of its weird state.  Here
436          * it's safe to just tell the code that the cursor is in hyperspace and
437          * let the next mvcur() call straighten things out.
438          */
439         SP_PARM->_curscol = -1;
440         SP_PARM->_cursrow = -1;
441     } else if (auto_right_margin) {
442         SP_PARM->_curscol = 0;
443         SP_PARM->_cursrow++;
444         /*
445          * We've actually moved - but may have to work around problems with
446          * video attributes not working.
447          */
448         if (!move_standout_mode && AttrOf(SCREEN_ATTRS(SP_PARM))) {
449             TR(TRACE_CHARPUT, ("turning off (%#lx) %s before wrapping",
450                                (unsigned long) AttrOf(SCREEN_ATTRS(SP_PARM)),
451                                _traceattr(AttrOf(SCREEN_ATTRS(SP_PARM)))));
452             (void) VIDATTR(SP_PARM, A_NORMAL, 0);
453         }
454     } else {
455         SP_PARM->_curscol--;
456     }
457     position_check(SP_PARM,
458                    SP_PARM->_cursrow,
459                    SP_PARM->_curscol,
460                    "wrap_cursor");
461 }
462
463 static NCURSES_INLINE void
464 PutChar(NCURSES_SP_DCLx const ARG_CH_T ch)
465 /* insert character, handling automargin stuff */
466 {
467     if (SP_PARM->_cursrow == screen_lines(SP_PARM) - 1 &&
468         SP_PARM->_curscol == screen_columns(SP_PARM) - 1) {
469         PutCharLR(NCURSES_SP_ARGx ch);
470     } else {
471         PutAttrChar(NCURSES_SP_ARGx ch);
472     }
473
474     if (SP_PARM->_curscol >= screen_columns(SP_PARM))
475         wrap_cursor(NCURSES_SP_ARG);
476
477     position_check(SP_PARM, SP_PARM->_cursrow, SP_PARM->_curscol, "PutChar");
478 }
479
480 /*
481  * Check whether the given character can be output by clearing commands.  This
482  * includes test for being a space and not including any 'bad' attributes, such
483  * as A_REVERSE.  All attribute flags which don't affect appearance of a space
484  * or can be output by clearing (A_COLOR in case of bce-terminal) are excluded.
485  */
486 static NCURSES_INLINE bool
487 can_clear_with(NCURSES_SP_DCLx ARG_CH_T ch)
488 {
489     if (!back_color_erase && SP_PARM->_coloron) {
490 #if NCURSES_EXT_FUNCS
491         int pair;
492
493         if (!SP_PARM->_default_color)
494             return FALSE;
495         if (SP_PARM->_default_fg != C_MASK || SP_PARM->_default_bg != C_MASK)
496             return FALSE;
497         if ((pair = GetPair(CHDEREF(ch))) != 0) {
498             short fg, bg;
499             NCURSES_SP_NAME(pair_content) (NCURSES_SP_ARGx
500                                            (short) pair,
501                                            &fg, &bg);
502             if (fg != C_MASK || bg != C_MASK)
503                 return FALSE;
504         }
505 #else
506         if (AttrOfD(ch) & A_COLOR)
507             return FALSE;
508 #endif
509     }
510     return (ISBLANK(CHDEREF(ch)) &&
511             (AttrOfD(ch) & ~(NONBLANK_ATTR | A_COLOR)) == BLANK_ATTR);
512 }
513
514 /*
515  * Issue a given span of characters from an array.
516  * Must be functionally equivalent to:
517  *      for (i = 0; i < num; i++)
518  *          PutChar(ntext[i]);
519  * but can leave the cursor positioned at the middle of the interval.
520  *
521  * Returns: 0 - cursor is at the end of interval
522  *          1 - cursor is somewhere in the middle
523  *
524  * This code is optimized using ech and rep.
525  */
526 static int
527 EmitRange(NCURSES_SP_DCLx const NCURSES_CH_T * ntext, int num)
528 {
529     int i;
530
531     TR(TRACE_CHARPUT, ("EmitRange %d:%s", num, _nc_viscbuf(ntext, num)));
532
533     if (erase_chars || repeat_char) {
534         while (num > 0) {
535             int runcount;
536             NCURSES_CH_T ntext0;
537
538             while (num > 1 && !CharEq(ntext[0], ntext[1])) {
539                 PutChar(NCURSES_SP_ARGx CHREF(ntext[0]));
540                 ntext++;
541                 num--;
542             }
543             ntext0 = ntext[0];
544             if (num == 1) {
545                 PutChar(NCURSES_SP_ARGx CHREF(ntext0));
546                 return 0;
547             }
548             runcount = 2;
549
550             while (runcount < num && CharEq(ntext[runcount], ntext0))
551                 runcount++;
552
553             /*
554              * The cost expression in the middle isn't exactly right.
555              * _cup_ch_cost is an upper bound on the cost for moving to the
556              * end of the erased area, but not the cost itself (which we
557              * can't compute without emitting the move).  This may result
558              * in erase_chars not getting used in some situations for
559              * which it would be marginally advantageous.
560              */
561             if (erase_chars
562                 && runcount > SP_PARM->_ech_cost + SP_PARM->_cup_ch_cost
563                 && can_clear_with(NCURSES_SP_ARGx CHREF(ntext0))) {
564                 UpdateAttrs(SP_PARM, ntext0);
565                 NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx
566                                        TPARM_1(erase_chars, runcount));
567
568                 /*
569                  * If this is the last part of the given interval,
570                  * don't bother moving cursor, since it can be the
571                  * last update on the line.
572                  */
573                 if (runcount < num) {
574                     GoTo(NCURSES_SP_ARGx
575                          SP_PARM->_cursrow,
576                          SP_PARM->_curscol + runcount);
577                 } else {
578                     return 1;   /* cursor stays in the middle */
579                 }
580             } else if (repeat_char && runcount > SP_PARM->_rep_cost) {
581                 bool wrap_possible = (SP_PARM->_curscol + runcount >=
582                                       screen_columns(SP_PARM));
583                 int rep_count = runcount;
584
585                 if (wrap_possible)
586                     rep_count--;
587
588                 UpdateAttrs(SP_PARM, ntext0);
589                 NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
590                                         TPARM_2(repeat_char,
591                                                 CharOf(ntext0),
592                                                 rep_count),
593                                         rep_count,
594                                         NCURSES_SP_NAME(_nc_outch));
595                 SP_PARM->_curscol += rep_count;
596
597                 if (wrap_possible)
598                     PutChar(NCURSES_SP_ARGx CHREF(ntext0));
599             } else {
600                 for (i = 0; i < runcount; i++)
601                     PutChar(NCURSES_SP_ARGx CHREF(ntext[i]));
602             }
603             ntext += runcount;
604             num -= runcount;
605         }
606         return 0;
607     }
608
609     for (i = 0; i < num; i++)
610         PutChar(NCURSES_SP_ARGx CHREF(ntext[i]));
611     return 0;
612 }
613
614 /*
615  * Output the line in the given range [first .. last]
616  *
617  * If there's a run of identical characters that's long enough to justify
618  * cursor movement, use that also.
619  *
620  * Returns: same as EmitRange
621  */
622 static int
623 PutRange(NCURSES_SP_DCLx
624          const NCURSES_CH_T * otext,
625          const NCURSES_CH_T * ntext,
626          int row,
627          int first, int last)
628 {
629     int i, j, same;
630
631     TR(TRACE_CHARPUT, ("PutRange(%p, %p, %p, %d, %d, %d)",
632                        (void *) SP_PARM,
633                        (const void *) otext,
634                        (const void *) ntext,
635                        row, first, last));
636
637     if (otext != ntext
638         && (last - first + 1) > SP_PARM->_inline_cost) {
639         for (j = first, same = 0; j <= last; j++) {
640             if (!same && isWidecExt(otext[j]))
641                 continue;
642             if (CharEq(otext[j], ntext[j])) {
643                 same++;
644             } else {
645                 if (same > SP_PARM->_inline_cost) {
646                     EmitRange(NCURSES_SP_ARGx ntext + first, j - same - first);
647                     GoTo(NCURSES_SP_ARGx row, first = j);
648                 }
649                 same = 0;
650             }
651         }
652         i = EmitRange(NCURSES_SP_ARGx ntext + first, j - same - first);
653         /*
654          * Always return 1 for the next GoTo() after a PutRange() if we found
655          * identical characters at end of interval
656          */
657         return (same == 0 ? i : 1);
658     }
659     return EmitRange(NCURSES_SP_ARGx ntext + first, last - first + 1);
660 }
661
662 /* leave unbracketed here so 'indent' works */
663 #define MARK_NOCHANGE(win,row) \
664                 win->_line[row].firstchar = _NOCHANGE; \
665                 win->_line[row].lastchar = _NOCHANGE; \
666                 if_USE_SCROLL_HINTS(win->_line[row].oldindex = row)
667
668 NCURSES_EXPORT(int)
669 TINFO_DOUPDATE(NCURSES_SP_DCL0)
670 {
671     int i;
672     int nonempty;
673 #if USE_TRACE_TIMES
674     struct tms before, after;
675 #endif /* USE_TRACE_TIMES */
676
677     T((T_CALLED("_nc_tinfo:doupdate(%p)"), (void *) SP_PARM));
678
679     if (SP_PARM == 0)
680         returnCode(ERR);
681
682 #if !USE_REENTRANT
683     /*
684      * It is "legal" but unlikely that an application could assign a new
685      * value to one of the standard windows.  Check for that possibility
686      * and try to recover.
687      *
688      * We do not allow applications to assign new values in the reentrant
689      * model.
690      */
691 #define SyncScreens(internal,exported) \
692         if (internal == 0) internal = exported; \
693         if (internal != exported) exported = internal
694
695     SyncScreens(CurScreen(SP_PARM), curscr);
696     SyncScreens(NewScreen(SP_PARM), newscr);
697     SyncScreens(StdScreen(SP_PARM), stdscr);
698 #endif
699
700     if (CurScreen(SP_PARM) == 0
701         || NewScreen(SP_PARM) == 0
702         || StdScreen(SP_PARM) == 0)
703         returnCode(ERR);
704
705 #ifdef TRACE
706     if (USE_TRACEF(TRACE_UPDATE)) {
707         if (CurScreen(SP_PARM)->_clear)
708             _tracef("curscr is clear");
709         else
710             _tracedump("curscr", CurScreen(SP_PARM));
711         _tracedump("newscr", NewScreen(SP_PARM));
712         _nc_unlock_global(tracef);
713     }
714 #endif /* TRACE */
715
716     _nc_signal_handler(FALSE);
717
718     if (SP_PARM->_fifohold)
719         SP_PARM->_fifohold--;
720
721 #if USE_SIZECHANGE
722     if (SP_PARM->_endwin || _nc_handle_sigwinch(SP_PARM)) {
723         /*
724          * This is a transparent extension:  XSI does not address it,
725          * and applications need not know that ncurses can do it.
726          *
727          * Check if the terminal size has changed while curses was off
728          * (this can happen in an xterm, for example), and resize the
729          * ncurses data structures accordingly.
730          */
731         _nc_update_screensize(SP_PARM);
732     }
733 #endif
734
735     if (SP_PARM->_endwin) {
736
737         T(("coming back from shell mode"));
738         NCURSES_SP_NAME(reset_prog_mode) (NCURSES_SP_ARG);
739
740         NCURSES_SP_NAME(_nc_mvcur_resume) (NCURSES_SP_ARG);
741         NCURSES_SP_NAME(_nc_screen_resume) (NCURSES_SP_ARG);
742         SP_PARM->_mouse_resume(SP_PARM);
743
744         SP_PARM->_endwin = FALSE;
745     }
746 #if USE_TRACE_TIMES
747     /* zero the metering machinery */
748     RESET_OUTCHARS();
749     (void) times(&before);
750 #endif /* USE_TRACE_TIMES */
751
752     /*
753      * This is the support for magic-cookie terminals.  The theory:  we scan
754      * the virtual screen looking for attribute turnons.  Where we find one,
755      * check to make sure it's realizable by seeing if the required number of
756      * un-attributed blanks are present before and after the attributed range;
757      * try to shift the range boundaries over blanks (not changing the screen
758      * display) so this becomes true.  If it is, shift the beginning attribute
759      * change appropriately (the end one, if we've gotten this far, is
760      * guaranteed room for its cookie).  If not, nuke the added attributes out
761      * of the span.
762      */
763 #if USE_XMC_SUPPORT
764     if (magic_cookie_glitch > 0) {
765         int j, k;
766         attr_t rattr = A_NORMAL;
767
768         for (i = 0; i < screen_lines(SP_PARM); i++) {
769             for (j = 0; j < screen_columns(SP_PARM); j++) {
770                 bool failed = FALSE;
771                 NCURSES_CH_T *thisline = NewScreen(SP_PARM)->_line[i].text;
772                 attr_t thisattr = AttrOf(thisline[j]) & SP_PARM->_xmc_triggers;
773                 attr_t turnon = thisattr & ~rattr;
774
775                 /* is an attribute turned on here? */
776                 if (turnon == 0) {
777                     rattr = thisattr;
778                     continue;
779                 }
780
781                 TR(TRACE_ATTRS, ("At (%d, %d): from %s...", i, j, _traceattr(rattr)));
782                 TR(TRACE_ATTRS, ("...to %s", _traceattr(turnon)));
783
784                 /*
785                  * If the attribute change location is a blank with a "safe"
786                  * attribute, undo the attribute turnon.  This may ensure
787                  * there's enough room to set the attribute before the first
788                  * non-blank in the run.
789                  */
790 #define SAFE(scr,a)     (!((a) & (scr)->_xmc_triggers))
791                 if (ISBLANK(thisline[j]) && SAFE(SP_PARM, turnon)) {
792                     RemAttr(thisline[j], turnon);
793                     continue;
794                 }
795
796                 /* check that there's enough room at start of span */
797                 for (k = 1; k <= magic_cookie_glitch; k++) {
798                     if (j - k < 0
799                         || !ISBLANK(thisline[j - k])
800                         || !SAFE(SP_PARM, AttrOf(thisline[j - k]))) {
801                         failed = TRUE;
802                         TR(TRACE_ATTRS, ("No room at start in %d,%d%s%s",
803                                          i, j - k,
804                                          (ISBLANK(thisline[j - k])
805                                           ? ""
806                                           : ":nonblank"),
807                                          (SAFE(SP_PARM, AttrOf(thisline[j - k]))
808                                           ? ""
809                                           : ":unsafe")));
810                         break;
811                     }
812                 }
813                 if (!failed) {
814                     bool end_onscreen = FALSE;
815                     int m, n = j;
816
817                     /* find end of span, if it's onscreen */
818                     for (m = i; m < screen_lines(SP_PARM); m++) {
819                         for (; n < screen_columns(SP_PARM); n++) {
820                             attr_t testattr =
821                             AttrOf(NewScreen(SP_PARM)->_line[m].text[n]);
822                             if ((testattr & SP_PARM->_xmc_triggers) == rattr) {
823                                 end_onscreen = TRUE;
824                                 TR(TRACE_ATTRS,
825                                    ("Range attributed with %s ends at (%d, %d)",
826                                     _traceattr(turnon), m, n));
827                                 goto foundit;
828                             }
829                         }
830                         n = 0;
831                     }
832                     TR(TRACE_ATTRS,
833                        ("Range attributed with %s ends offscreen",
834                         _traceattr(turnon)));
835                   foundit:;
836
837                     if (end_onscreen) {
838                         NCURSES_CH_T *lastline =
839                         NewScreen(SP_PARM)->_line[m].text;
840
841                         /*
842                          * If there are safely-attributed blanks at the end of
843                          * the range, shorten the range.  This will help ensure
844                          * that there is enough room at end of span.
845                          */
846                         while (n >= 0
847                                && ISBLANK(lastline[n])
848                                && SAFE(SP_PARM, AttrOf(lastline[n]))) {
849                             RemAttr(lastline[n--], turnon);
850                         }
851
852                         /* check that there's enough room at end of span */
853                         for (k = 1; k <= magic_cookie_glitch; k++) {
854                             if (n + k >= screen_columns(SP_PARM)
855                                 || !ISBLANK(lastline[n + k])
856                                 || !SAFE(SP_PARM, AttrOf(lastline[n + k]))) {
857                                 failed = TRUE;
858                                 TR(TRACE_ATTRS,
859                                    ("No room at end in %d,%d%s%s",
860                                     i, j - k,
861                                     (ISBLANK(lastline[n + k])
862                                      ? ""
863                                      : ":nonblank"),
864                                     (SAFE(SP_PARM, AttrOf(lastline[n + k]))
865                                      ? ""
866                                      : ":unsafe")));
867                                 break;
868                             }
869                         }
870                     }
871                 }
872
873                 if (failed) {
874                     int p, q = j;
875
876                     TR(TRACE_ATTRS,
877                        ("Clearing %s beginning at (%d, %d)",
878                         _traceattr(turnon), i, j));
879
880                     /* turn off new attributes over span */
881                     for (p = i; p < screen_lines(SP_PARM); p++) {
882                         for (; q < screen_columns(SP_PARM); q++) {
883                             attr_t testattr = AttrOf(newscr->_line[p].text[q]);
884                             if ((testattr & SP_PARM->_xmc_triggers) == rattr)
885                                 goto foundend;
886                             RemAttr(NewScreen(SP_PARM)->_line[p].text[q], turnon);
887                         }
888                         q = 0;
889                     }
890                   foundend:;
891                 } else {
892                     TR(TRACE_ATTRS,
893                        ("Cookie space for %s found before (%d, %d)",
894                         _traceattr(turnon), i, j));
895
896                     /*
897                      * Back up the start of range so there's room for cookies
898                      * before the first nonblank character.
899                      */
900                     for (k = 1; k <= magic_cookie_glitch; k++)
901                         AddAttr(thisline[j - k], turnon);
902                 }
903
904                 rattr = thisattr;
905             }
906         }
907
908 #ifdef TRACE
909         /* show altered highlights after magic-cookie check */
910         if (USE_TRACEF(TRACE_UPDATE)) {
911             _tracef("After magic-cookie check...");
912             _tracedump("newscr", NewScreen(SP_PARM));
913             _nc_unlock_global(tracef);
914         }
915 #endif /* TRACE */
916     }
917 #endif /* USE_XMC_SUPPORT */
918
919     nonempty = 0;
920     if (CurScreen(SP_PARM)->_clear || NewScreen(SP_PARM)->_clear) {     /* force refresh ? */
921         ClrUpdate(NCURSES_SP_ARG);
922         CurScreen(SP_PARM)->_clear = FALSE;     /* reset flag */
923         NewScreen(SP_PARM)->_clear = FALSE;     /* reset flag */
924     } else {
925         int changedlines = CHECK_INTERVAL;
926
927         if (check_pending(NCURSES_SP_ARG))
928             goto cleanup;
929
930         nonempty = min(screen_lines(SP_PARM), NewScreen(SP_PARM)->_maxy + 1);
931
932         if (SP_PARM->_scrolling) {
933             NCURSES_SP_NAME(_nc_scroll_optimize) (NCURSES_SP_ARG);
934         }
935
936         nonempty = ClrBottom(NCURSES_SP_ARGx nonempty);
937
938         TR(TRACE_UPDATE, ("Transforming lines, nonempty %d", nonempty));
939         for (i = 0; i < nonempty; i++) {
940             /*
941              * Here is our line-breakout optimization.
942              */
943             if (changedlines == CHECK_INTERVAL) {
944                 if (check_pending(NCURSES_SP_ARG))
945                     goto cleanup;
946                 changedlines = 0;
947             }
948
949             /*
950              * newscr->line[i].firstchar is normally set
951              * by wnoutrefresh.  curscr->line[i].firstchar
952              * is normally set by _nc_scroll_window in the
953              * vertical-movement optimization code,
954              */
955             if (NewScreen(SP_PARM)->_line[i].firstchar != _NOCHANGE
956                 || CurScreen(SP_PARM)->_line[i].firstchar != _NOCHANGE) {
957                 TransformLine(NCURSES_SP_ARGx i);
958                 changedlines++;
959             }
960
961             /* mark line changed successfully */
962             if (i <= NewScreen(SP_PARM)->_maxy) {
963                 MARK_NOCHANGE(NewScreen(SP_PARM), i);
964             }
965             if (i <= CurScreen(SP_PARM)->_maxy) {
966                 MARK_NOCHANGE(CurScreen(SP_PARM), i);
967             }
968         }
969     }
970
971     /* put everything back in sync */
972     for (i = nonempty; i <= NewScreen(SP_PARM)->_maxy; i++) {
973         MARK_NOCHANGE(NewScreen(SP_PARM), i);
974     }
975     for (i = nonempty; i <= CurScreen(SP_PARM)->_maxy; i++) {
976         MARK_NOCHANGE(CurScreen(SP_PARM), i);
977     }
978
979     if (!NewScreen(SP_PARM)->_leaveok) {
980         CurScreen(SP_PARM)->_curx = NewScreen(SP_PARM)->_curx;
981         CurScreen(SP_PARM)->_cury = NewScreen(SP_PARM)->_cury;
982
983         GoTo(NCURSES_SP_ARGx CurScreen(SP_PARM)->_cury, CurScreen(SP_PARM)->_curx);
984     }
985
986   cleanup:
987     /*
988      * We would like to keep the physical screen in normal mode in case we get
989      * other processes writing to the screen.  This goal cannot be met for
990      * magic cookies since it interferes with attributes that may propagate
991      * past the current position.
992      */
993 #if USE_XMC_SUPPORT
994     if (magic_cookie_glitch != 0)
995 #endif
996         UpdateAttrs(SP_PARM, normal);
997
998     NCURSES_SP_NAME(_nc_flush) (NCURSES_SP_ARG);
999     WINDOW_ATTRS(CurScreen(SP_PARM)) = WINDOW_ATTRS(NewScreen(SP_PARM));
1000
1001 #if USE_TRACE_TIMES
1002     (void) times(&after);
1003     TR(TRACE_TIMES,
1004        ("Update cost: %ld chars, %ld clocks system time, %ld clocks user time",
1005         _nc_outchars,
1006         (long) (after.tms_stime - before.tms_stime),
1007         (long) (after.tms_utime - before.tms_utime)));
1008 #endif /* USE_TRACE_TIMES */
1009
1010     _nc_signal_handler(TRUE);
1011
1012     returnCode(OK);
1013 }
1014
1015 #if NCURSES_SP_FUNCS && !defined(USE_TERM_DRIVER)
1016 NCURSES_EXPORT(int)
1017 doupdate(void)
1018 {
1019     return TINFO_DOUPDATE(CURRENT_SCREEN);
1020 }
1021 #endif
1022
1023 /*
1024  *      ClrBlank(win)
1025  *
1026  *      Returns the attributed character that corresponds to the "cleared"
1027  *      screen.  If the terminal has the back-color-erase feature, this will be
1028  *      colored according to the wbkgd() call.
1029  *
1030  *      We treat 'curscr' specially because it isn't supposed to be set directly
1031  *      in the wbkgd() call.  Assume 'stdscr' for this case.
1032  */
1033 #define BCE_ATTRS (A_NORMAL|A_COLOR)
1034 #define BCE_BKGD(sp,win) (((win) == CurScreen(sp) ? StdScreen(sp) : (win))->_nc_bkgd)
1035
1036 static NCURSES_INLINE NCURSES_CH_T
1037 ClrBlank(NCURSES_SP_DCLx WINDOW *win)
1038 {
1039     NCURSES_CH_T blank = blankchar;
1040     if (back_color_erase)
1041         AddAttr(blank, (AttrOf(BCE_BKGD(SP_PARM, win)) & BCE_ATTRS));
1042     return blank;
1043 }
1044
1045 /*
1046 **      ClrUpdate()
1047 **
1048 **      Update by clearing and redrawing the entire screen.
1049 **
1050 */
1051
1052 static void
1053 ClrUpdate(NCURSES_SP_DCL0)
1054 {
1055     TR(TRACE_UPDATE, (T_CALLED("ClrUpdate")));
1056     if (0 != SP_PARM) {
1057         int i;
1058         NCURSES_CH_T blank = ClrBlank(NCURSES_SP_ARGx StdScreen(SP_PARM));
1059         int nonempty = min(screen_lines(SP_PARM),
1060                            NewScreen(SP_PARM)->_maxy + 1);
1061
1062         ClearScreen(NCURSES_SP_ARGx blank);
1063
1064         TR(TRACE_UPDATE, ("updating screen from scratch"));
1065
1066         nonempty = ClrBottom(NCURSES_SP_ARGx nonempty);
1067
1068         for (i = 0; i < nonempty; i++)
1069             TransformLine(NCURSES_SP_ARGx i);
1070     }
1071     TR(TRACE_UPDATE, (T_RETURN("")));
1072 }
1073
1074 /*
1075 **      ClrToEOL(blank)
1076 **
1077 **      Clear to end of current line, starting at the cursor position
1078 */
1079
1080 static void
1081 ClrToEOL(NCURSES_SP_DCLx NCURSES_CH_T blank, int needclear)
1082 {
1083     int j;
1084
1085     if (SP_PARM != 0) {
1086         if (CurScreen(SP_PARM) != 0
1087             && SP_PARM->_cursrow >= 0) {
1088             for (j = SP_PARM->_curscol; j < screen_columns(SP_PARM); j++) {
1089                 if (j >= 0) {
1090                     NCURSES_CH_T *cp =
1091                     &(CurScreen(SP_PARM)->_line[SP_PARM->_cursrow].text[j]);
1092
1093                     if (!CharEq(*cp, blank)) {
1094                         *cp = blank;
1095                         needclear = TRUE;
1096                     }
1097                 }
1098             }
1099         }
1100     }
1101
1102     if (needclear && (SP_PARM != 0)) {
1103         UpdateAttrs(SP_PARM, blank);
1104         TPUTS_TRACE("clr_eol");
1105         if (clr_eol && SP_PARM->_el_cost <= (screen_columns(SP_PARM) - SP_PARM->_curscol)) {
1106             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx clr_eol);
1107         } else {
1108             int count = (screen_columns(SP_PARM) - SP_PARM->_curscol);
1109             while (count-- > 0)
1110                 PutChar(NCURSES_SP_ARGx CHREF(blank));
1111         }
1112     }
1113 }
1114
1115 /*
1116 **      ClrToEOS(blank)
1117 **
1118 **      Clear to end of screen, starting at the cursor position
1119 */
1120
1121 static void
1122 ClrToEOS(NCURSES_SP_DCLx NCURSES_CH_T blank)
1123 {
1124     int row, col;
1125
1126     if (0 == SP_PARM)
1127         return;
1128
1129     row = SP_PARM->_cursrow;
1130     col = SP_PARM->_curscol;
1131
1132     UpdateAttrs(SP_PARM, blank);
1133     TPUTS_TRACE("clr_eos");
1134     NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1135                             clr_eos,
1136                             screen_lines(SP_PARM) - row,
1137                             NCURSES_SP_NAME(_nc_outch));
1138
1139     while (col < screen_columns(SP_PARM))
1140         CurScreen(SP_PARM)->_line[row].text[col++] = blank;
1141
1142     for (row++; row < screen_lines(SP_PARM); row++) {
1143         for (col = 0; col < screen_columns(SP_PARM); col++)
1144             CurScreen(SP_PARM)->_line[row].text[col] = blank;
1145     }
1146 }
1147
1148 /*
1149  *      ClrBottom(total)
1150  *
1151  *      Test if clearing the end of the screen would satisfy part of the
1152  *      screen-update.  Do this by scanning backwards through the lines in the
1153  *      screen, checking if each is blank, and one or more are changed.
1154  */
1155 static int
1156 ClrBottom(NCURSES_SP_DCLx int total)
1157 {
1158     int row;
1159     int col;
1160     int top = total;
1161     int last = min(screen_columns(SP_PARM), NewScreen(SP_PARM)->_maxx + 1);
1162     NCURSES_CH_T blank = NewScreen(SP_PARM)->_line[total - 1].text[last - 1];
1163     bool ok;
1164
1165     if (clr_eos && can_clear_with(NCURSES_SP_ARGx CHREF(blank))) {
1166
1167         for (row = total - 1; row >= 0; row--) {
1168             for (col = 0, ok = TRUE; ok && col < last; col++) {
1169                 ok = (CharEq(NewScreen(SP_PARM)->_line[row].text[col], blank));
1170             }
1171             if (!ok)
1172                 break;
1173
1174             for (col = 0; ok && col < last; col++) {
1175                 ok = (CharEq(CurScreen(SP_PARM)->_line[row].text[col], blank));
1176             }
1177             if (!ok)
1178                 top = row;
1179         }
1180
1181         /* don't use clr_eos for just one line if clr_eol available */
1182         if (top < total) {
1183             GoTo(NCURSES_SP_ARGx top, 0);
1184             ClrToEOS(NCURSES_SP_ARGx blank);
1185             if (SP_PARM->oldhash && SP_PARM->newhash) {
1186                 for (row = top; row < screen_lines(SP_PARM); row++)
1187                     SP_PARM->oldhash[row] = SP_PARM->newhash[row];
1188             }
1189         }
1190     }
1191     return top;
1192 }
1193
1194 #if USE_XMC_SUPPORT
1195 #if USE_WIDEC_SUPPORT
1196 #define check_xmc_transition(sp, a, b)                                  \
1197     ((((a)->attr ^ (b)->attr) & ~((a)->attr) & (sp)->_xmc_triggers) != 0)
1198 #define xmc_turn_on(sp,a,b) check_xmc_transition(sp,&(a), &(b))
1199 #else
1200 #define xmc_turn_on(sp,a,b) ((((a)^(b)) & ~(a) & (sp)->_xmc_triggers) != 0)
1201 #endif
1202
1203 #define xmc_new(sp,r,c) NewScreen(sp)->_line[r].text[c]
1204 #define xmc_turn_off(sp,a,b) xmc_turn_on(sp,b,a)
1205 #endif /* USE_XMC_SUPPORT */
1206
1207 /*
1208 **      TransformLine(lineno)
1209 **
1210 **      Transform the given line in curscr to the one in newscr, using
1211 **      Insert/Delete Character if idcok && has_ic().
1212 **
1213 **              firstChar = position of first different character in line
1214 **              oLastChar = position of last different character in old line
1215 **              nLastChar = position of last different character in new line
1216 **
1217 **              move to firstChar
1218 **              overwrite chars up to min(oLastChar, nLastChar)
1219 **              if oLastChar < nLastChar
1220 **                      insert newLine[oLastChar+1..nLastChar]
1221 **              else
1222 **                      delete oLastChar - nLastChar spaces
1223 */
1224
1225 static void
1226 TransformLine(NCURSES_SP_DCLx int const lineno)
1227 {
1228     int firstChar, oLastChar, nLastChar;
1229     NCURSES_CH_T *newLine = NewScreen(SP_PARM)->_line[lineno].text;
1230     NCURSES_CH_T *oldLine = CurScreen(SP_PARM)->_line[lineno].text;
1231     int n;
1232     bool attrchanged = FALSE;
1233
1234     TR(TRACE_UPDATE, (T_CALLED("TransformLine(%p, %d)"), (void *) SP_PARM, lineno));
1235
1236     /* copy new hash value to old one */
1237     if (SP_PARM->oldhash && SP_PARM->newhash)
1238         SP_PARM->oldhash[lineno] = SP_PARM->newhash[lineno];
1239
1240     /*
1241      * If we have colors, there is the possibility of having two color pairs
1242      * that display as the same colors.  For instance, Lynx does this.  Check
1243      * for this case, and update the old line with the new line's colors when
1244      * they are equivalent.
1245      */
1246     if (SP_PARM->_coloron) {
1247         int oldPair;
1248         int newPair;
1249
1250         for (n = 0; n < screen_columns(SP_PARM); n++) {
1251             if (!CharEq(newLine[n], oldLine[n])) {
1252                 oldPair = GetPair(oldLine[n]);
1253                 newPair = GetPair(newLine[n]);
1254                 if (oldPair != newPair
1255                     && unColor(oldLine[n]) == unColor(newLine[n])) {
1256                     if (oldPair < SP_PARM->_pair_limit
1257                         && newPair < SP_PARM->_pair_limit
1258                         && (SP_PARM->_color_pairs[oldPair] ==
1259                             SP_PARM->_color_pairs[newPair])) {
1260                         SetPair(oldLine[n], GetPair(newLine[n]));
1261                     }
1262                 }
1263             }
1264         }
1265     }
1266
1267     if (ceol_standout_glitch && clr_eol) {
1268         firstChar = 0;
1269         while (firstChar < screen_columns(SP_PARM)) {
1270             if (!SameAttrOf(newLine[firstChar], oldLine[firstChar])) {
1271                 attrchanged = TRUE;
1272                 break;
1273             }
1274             firstChar++;
1275         }
1276     }
1277
1278     firstChar = 0;
1279
1280     if (attrchanged) {          /* we may have to disregard the whole line */
1281         GoTo(NCURSES_SP_ARGx lineno, firstChar);
1282         ClrToEOL(NCURSES_SP_ARGx
1283                  ClrBlank(NCURSES_SP_ARGx
1284                           CurScreen(SP_PARM)), FALSE);
1285         PutRange(NCURSES_SP_ARGx
1286                  oldLine, newLine, lineno, 0,
1287                  screen_columns(SP_PARM) - 1);
1288 #if USE_XMC_SUPPORT
1289
1290         /*
1291          * This is a very simple loop to paint characters which may have the
1292          * magic cookie glitch embedded.  It doesn't know much about video
1293          * attributes which are continued from one line to the next.  It
1294          * assumes that we have filtered out requests for attribute changes
1295          * that do not get mapped to blank positions.
1296          *
1297          * FIXME: we are not keeping track of where we put the cookies, so this
1298          * will work properly only once, since we may overwrite a cookie in a
1299          * following operation.
1300          */
1301     } else if (magic_cookie_glitch > 0) {
1302         GoTo(NCURSES_SP_ARGx lineno, firstChar);
1303         for (n = 0; n < screen_columns(SP_PARM); n++) {
1304             int m = n + magic_cookie_glitch;
1305
1306             /* check for turn-on:
1307              * If we are writing an attributed blank, where the
1308              * previous cell is not attributed.
1309              */
1310             if (ISBLANK(newLine[n])
1311                 && ((n > 0
1312                      && xmc_turn_on(SP_PARM, newLine[n - 1], newLine[n]))
1313                     || (n == 0
1314                         && lineno > 0
1315                         && xmc_turn_on(SP_PARM,
1316                                        xmc_new(SP_PARM, lineno - 1,
1317                                                screen_columns(SP_PARM) - 1),
1318                                        newLine[n])))) {
1319                 n = m;
1320             }
1321
1322             PutChar(NCURSES_SP_ARGx CHREF(newLine[n]));
1323
1324             /* check for turn-off:
1325              * If we are writing an attributed non-blank, where the
1326              * next cell is blank, and not attributed.
1327              */
1328             if (!ISBLANK(newLine[n])
1329                 && ((n + 1 < screen_columns(SP_PARM)
1330                      && xmc_turn_off(SP_PARM, newLine[n], newLine[n + 1]))
1331                     || (n + 1 >= screen_columns(SP_PARM)
1332                         && lineno + 1 < screen_lines(SP_PARM)
1333                         && xmc_turn_off(SP_PARM,
1334                                         newLine[n],
1335                                         xmc_new(SP_PARM, lineno + 1, 0))))) {
1336                 n = m;
1337             }
1338
1339         }
1340 #endif
1341     } else {
1342         NCURSES_CH_T blank;
1343
1344         /* it may be cheap to clear leading whitespace with clr_bol */
1345         blank = newLine[0];
1346         if (clr_bol && can_clear_with(NCURSES_SP_ARGx CHREF(blank))) {
1347             int oFirstChar, nFirstChar;
1348
1349             for (oFirstChar = 0;
1350                  oFirstChar < screen_columns(SP_PARM);
1351                  oFirstChar++)
1352                 if (!CharEq(oldLine[oFirstChar], blank))
1353                     break;
1354             for (nFirstChar = 0;
1355                  nFirstChar < screen_columns(SP_PARM);
1356                  nFirstChar++)
1357                 if (!CharEq(newLine[nFirstChar], blank))
1358                     break;
1359
1360             if (nFirstChar == oFirstChar) {
1361                 firstChar = nFirstChar;
1362                 /* find the first differing character */
1363                 while (firstChar < screen_columns(SP_PARM)
1364                        && CharEq(newLine[firstChar], oldLine[firstChar]))
1365                     firstChar++;
1366             } else if (oFirstChar > nFirstChar) {
1367                 firstChar = nFirstChar;
1368             } else {            /* oFirstChar < nFirstChar */
1369                 firstChar = oFirstChar;
1370                 if (SP_PARM->_el1_cost < nFirstChar - oFirstChar) {
1371                     if (nFirstChar >= screen_columns(SP_PARM)
1372                         && SP_PARM->_el_cost <= SP_PARM->_el1_cost) {
1373                         GoTo(NCURSES_SP_ARGx lineno, 0);
1374                         UpdateAttrs(SP_PARM, blank);
1375                         TPUTS_TRACE("clr_eol");
1376                         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx clr_eol);
1377                     } else {
1378                         GoTo(NCURSES_SP_ARGx lineno, nFirstChar - 1);
1379                         UpdateAttrs(SP_PARM, blank);
1380                         TPUTS_TRACE("clr_bol");
1381                         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx clr_bol);
1382                     }
1383
1384                     while (firstChar < nFirstChar)
1385                         oldLine[firstChar++] = blank;
1386                 }
1387             }
1388         } else {
1389             /* find the first differing character */
1390             while (firstChar < screen_columns(SP_PARM)
1391                    && CharEq(newLine[firstChar], oldLine[firstChar]))
1392                 firstChar++;
1393         }
1394         /* if there wasn't one, we're done */
1395         if (firstChar >= screen_columns(SP_PARM)) {
1396             TR(TRACE_UPDATE, (T_RETURN("")));
1397             return;
1398         }
1399
1400         blank = newLine[screen_columns(SP_PARM) - 1];
1401
1402         if (!can_clear_with(NCURSES_SP_ARGx CHREF(blank))) {
1403             /* find the last differing character */
1404             nLastChar = screen_columns(SP_PARM) - 1;
1405
1406             while (nLastChar > firstChar
1407                    && CharEq(newLine[nLastChar], oldLine[nLastChar]))
1408                 nLastChar--;
1409
1410             if (nLastChar >= firstChar) {
1411                 GoTo(NCURSES_SP_ARGx lineno, firstChar);
1412                 PutRange(NCURSES_SP_ARGx
1413                          oldLine,
1414                          newLine,
1415                          lineno,
1416                          firstChar,
1417                          nLastChar);
1418                 memcpy(oldLine + firstChar,
1419                        newLine + firstChar,
1420                        (unsigned) (nLastChar - firstChar + 1) * sizeof(NCURSES_CH_T));
1421             }
1422             TR(TRACE_UPDATE, (T_RETURN("")));
1423             return;
1424         }
1425
1426         /* find last non-blank character on old line */
1427         oLastChar = screen_columns(SP_PARM) - 1;
1428         while (oLastChar > firstChar && CharEq(oldLine[oLastChar], blank))
1429             oLastChar--;
1430
1431         /* find last non-blank character on new line */
1432         nLastChar = screen_columns(SP_PARM) - 1;
1433         while (nLastChar > firstChar && CharEq(newLine[nLastChar], blank))
1434             nLastChar--;
1435
1436         if ((nLastChar == firstChar)
1437             && (SP_PARM->_el_cost < (oLastChar - nLastChar))) {
1438             GoTo(NCURSES_SP_ARGx lineno, firstChar);
1439             if (!CharEq(newLine[firstChar], blank))
1440                 PutChar(NCURSES_SP_ARGx CHREF(newLine[firstChar]));
1441             ClrToEOL(NCURSES_SP_ARGx blank, FALSE);
1442         } else if ((nLastChar != oLastChar)
1443                    && (!CharEq(newLine[nLastChar], oldLine[oLastChar])
1444                        || !(SP_PARM->_nc_sp_idcok
1445                             && NCURSES_SP_NAME(has_ic) (NCURSES_SP_ARG)))) {
1446             GoTo(NCURSES_SP_ARGx lineno, firstChar);
1447             if ((oLastChar - nLastChar) > SP_PARM->_el_cost) {
1448                 if (PutRange(NCURSES_SP_ARGx
1449                              oldLine,
1450                              newLine,
1451                              lineno,
1452                              firstChar,
1453                              nLastChar)) {
1454                     GoTo(NCURSES_SP_ARGx lineno, nLastChar + 1);
1455                 }
1456                 ClrToEOL(NCURSES_SP_ARGx blank, FALSE);
1457             } else {
1458                 n = max(nLastChar, oLastChar);
1459                 PutRange(NCURSES_SP_ARGx
1460                          oldLine,
1461                          newLine,
1462                          lineno,
1463                          firstChar,
1464                          n);
1465             }
1466         } else {
1467             int nLastNonblank = nLastChar;
1468             int oLastNonblank = oLastChar;
1469
1470             /* find the last characters that really differ */
1471             /* can be -1 if no characters differ */
1472             while (CharEq(newLine[nLastChar], oldLine[oLastChar])) {
1473                 /* don't split a wide char */
1474                 if (isWidecExt(newLine[nLastChar]) &&
1475                     !CharEq(newLine[nLastChar - 1], oldLine[oLastChar - 1]))
1476                     break;
1477                 nLastChar--;
1478                 oLastChar--;
1479                 if (nLastChar == -1 || oLastChar == -1)
1480                     break;
1481             }
1482
1483             n = min(oLastChar, nLastChar);
1484             if (n >= firstChar) {
1485                 GoTo(NCURSES_SP_ARGx lineno, firstChar);
1486                 PutRange(NCURSES_SP_ARGx
1487                          oldLine,
1488                          newLine,
1489                          lineno,
1490                          firstChar,
1491                          n);
1492             }
1493
1494             if (oLastChar < nLastChar) {
1495                 int m = max(nLastNonblank, oLastNonblank);
1496 #if USE_WIDEC_SUPPORT
1497                 while (isWidecExt(newLine[n + 1]) && n) {
1498                     --n;
1499                     --oLastChar;
1500                 }
1501 #endif
1502                 GoTo(NCURSES_SP_ARGx lineno, n + 1);
1503                 if ((nLastChar < nLastNonblank)
1504                     || InsCharCost(SP_PARM, nLastChar - oLastChar) > (m - n)) {
1505                     PutRange(NCURSES_SP_ARGx
1506                              oldLine,
1507                              newLine,
1508                              lineno,
1509                              n + 1,
1510                              m);
1511                 } else {
1512                     InsStr(NCURSES_SP_ARGx &newLine[n + 1], nLastChar - oLastChar);
1513                 }
1514             } else if (oLastChar > nLastChar) {
1515                 GoTo(NCURSES_SP_ARGx lineno, n + 1);
1516                 if (DelCharCost(SP_PARM, oLastChar - nLastChar)
1517                     > SP_PARM->_el_cost + nLastNonblank - (n + 1)) {
1518                     if (PutRange(NCURSES_SP_ARGx oldLine, newLine, lineno,
1519                                  n + 1, nLastNonblank))
1520                           GoTo(NCURSES_SP_ARGx lineno, nLastNonblank + 1);
1521                     ClrToEOL(NCURSES_SP_ARGx blank, FALSE);
1522                 } else {
1523                     /*
1524                      * The delete-char sequence will
1525                      * effectively shift in blanks from the
1526                      * right margin of the screen.  Ensure
1527                      * that they are the right color by
1528                      * setting the video attributes from
1529                      * the last character on the row.
1530                      */
1531                     UpdateAttrs(SP_PARM, blank);
1532                     DelChar(NCURSES_SP_ARGx oLastChar - nLastChar);
1533                 }
1534             }
1535         }
1536     }
1537
1538     /* update the code's internal representation */
1539     if (screen_columns(SP_PARM) > firstChar)
1540         memcpy(oldLine + firstChar,
1541                newLine + firstChar,
1542                (unsigned) (screen_columns(SP_PARM) - firstChar) * sizeof(NCURSES_CH_T));
1543     TR(TRACE_UPDATE, (T_RETURN("")));
1544     return;
1545 }
1546
1547 /*
1548 **      ClearScreen(blank)
1549 **
1550 **      Clear the physical screen and put cursor at home
1551 **
1552 */
1553
1554 static void
1555 ClearScreen(NCURSES_SP_DCLx NCURSES_CH_T blank)
1556 {
1557     int i, j;
1558     bool fast_clear = (clear_screen || clr_eos || clr_eol);
1559
1560     TR(TRACE_UPDATE, ("ClearScreen() called"));
1561
1562 #if NCURSES_EXT_FUNCS
1563     if (SP_PARM->_coloron
1564         && !SP_PARM->_default_color) {
1565         NCURSES_SP_NAME(_nc_do_color) (NCURSES_SP_ARGx
1566                                        (short) GET_SCREEN_PAIR(SP_PARM),
1567                                        0,
1568                                        FALSE,
1569                                        NCURSES_SP_NAME(_nc_outch));
1570         if (!back_color_erase) {
1571             fast_clear = FALSE;
1572         }
1573     }
1574 #endif
1575
1576     if (fast_clear) {
1577         if (clear_screen) {
1578             UpdateAttrs(SP_PARM, blank);
1579             TPUTS_TRACE("clear_screen");
1580             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx clear_screen);
1581             SP_PARM->_cursrow = SP_PARM->_curscol = 0;
1582             position_check(SP_PARM,
1583                            SP_PARM->_cursrow,
1584                            SP_PARM->_curscol,
1585                            "ClearScreen");
1586         } else if (clr_eos) {
1587             SP_PARM->_cursrow = SP_PARM->_curscol = -1;
1588             GoTo(NCURSES_SP_ARGx 0, 0);
1589             UpdateAttrs(SP_PARM, blank);
1590             TPUTS_TRACE("clr_eos");
1591             NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1592                                     clr_eos,
1593                                     screen_lines(SP_PARM),
1594                                     NCURSES_SP_NAME(_nc_outch));
1595         } else if (clr_eol) {
1596             SP_PARM->_cursrow = SP_PARM->_curscol = -1;
1597             UpdateAttrs(SP_PARM, blank);
1598             for (i = 0; i < screen_lines(SP_PARM); i++) {
1599                 GoTo(NCURSES_SP_ARGx i, 0);
1600                 TPUTS_TRACE("clr_eol");
1601                 NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx clr_eol);
1602             }
1603             GoTo(NCURSES_SP_ARGx 0, 0);
1604         }
1605     } else {
1606         UpdateAttrs(SP_PARM, blank);
1607         for (i = 0; i < screen_lines(SP_PARM); i++) {
1608             GoTo(NCURSES_SP_ARGx i, 0);
1609             for (j = 0; j < screen_columns(SP_PARM); j++)
1610                 PutChar(NCURSES_SP_ARGx CHREF(blank));
1611         }
1612         GoTo(NCURSES_SP_ARGx 0, 0);
1613     }
1614
1615     for (i = 0; i < screen_lines(SP_PARM); i++) {
1616         for (j = 0; j < screen_columns(SP_PARM); j++)
1617             CurScreen(SP_PARM)->_line[i].text[j] = blank;
1618     }
1619
1620     TR(TRACE_UPDATE, ("screen cleared"));
1621 }
1622
1623 /*
1624 **      InsStr(line, count)
1625 **
1626 **      Insert the count characters pointed to by line.
1627 **
1628 */
1629
1630 static void
1631 InsStr(NCURSES_SP_DCLx NCURSES_CH_T * line, int count)
1632 {
1633     TR(TRACE_UPDATE, ("InsStr(%p, %p,%d) called",
1634                       (void *) SP_PARM,
1635                       (void *) line, count));
1636
1637     /* Prefer parm_ich as it has the smallest cost - no need to shift
1638      * the whole line on each character. */
1639     /* The order must match that of InsCharCost. */
1640     if (parm_ich) {
1641         TPUTS_TRACE("parm_ich");
1642         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1643                                 TPARM_1(parm_ich, count),
1644                                 count,
1645                                 NCURSES_SP_NAME(_nc_outch));
1646         while (count) {
1647             PutAttrChar(NCURSES_SP_ARGx CHREF(*line));
1648             line++;
1649             count--;
1650         }
1651     } else if (enter_insert_mode && exit_insert_mode) {
1652         TPUTS_TRACE("enter_insert_mode");
1653         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx enter_insert_mode);
1654         while (count) {
1655             PutAttrChar(NCURSES_SP_ARGx CHREF(*line));
1656             if (insert_padding) {
1657                 TPUTS_TRACE("insert_padding");
1658                 NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx insert_padding);
1659             }
1660             line++;
1661             count--;
1662         }
1663         TPUTS_TRACE("exit_insert_mode");
1664         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx exit_insert_mode);
1665     } else {
1666         while (count) {
1667             TPUTS_TRACE("insert_character");
1668             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx insert_character);
1669             PutAttrChar(NCURSES_SP_ARGx CHREF(*line));
1670             if (insert_padding) {
1671                 TPUTS_TRACE("insert_padding");
1672                 NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx insert_padding);
1673             }
1674             line++;
1675             count--;
1676         }
1677     }
1678     position_check(SP_PARM, SP_PARM->_cursrow, SP_PARM->_curscol, "InsStr");
1679 }
1680
1681 /*
1682 **      DelChar(count)
1683 **
1684 **      Delete count characters at current position
1685 **
1686 */
1687
1688 static void
1689 DelChar(NCURSES_SP_DCLx int count)
1690 {
1691     int n;
1692
1693     TR(TRACE_UPDATE, ("DelChar(%p, %d) called, position = (%ld,%ld)",
1694                       (void *) SP_PARM, count,
1695                       (long) NewScreen(SP_PARM)->_cury,
1696                       (long) NewScreen(SP_PARM)->_curx));
1697
1698     if (parm_dch) {
1699         TPUTS_TRACE("parm_dch");
1700         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1701                                 TPARM_1(parm_dch, count),
1702                                 count,
1703                                 NCURSES_SP_NAME(_nc_outch));
1704     } else {
1705         for (n = 0; n < count; n++) {
1706             TPUTS_TRACE("delete_character");
1707             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx delete_character);
1708         }
1709     }
1710 }
1711
1712 /*
1713  * Physical-scrolling support
1714  *
1715  * This code was adapted from Keith Bostic's hardware scrolling
1716  * support for 4.4BSD curses.  I (esr) translated it to use terminfo
1717  * capabilities, narrowed the call interface slightly, and cleaned
1718  * up some convoluted tests.  I also added support for the memory_above
1719  * memory_below, and non_dest_scroll_region capabilities.
1720  *
1721  * For this code to work, we must have either
1722  * change_scroll_region and scroll forward/reverse commands, or
1723  * insert and delete line capabilities.
1724  * When the scrolling region has been set, the cursor has to
1725  * be at the last line of the region to make the scroll up
1726  * happen, or on the first line of region to scroll down.
1727  *
1728  * This code makes one aesthetic decision in the opposite way from
1729  * BSD curses.  BSD curses preferred pairs of il/dl operations
1730  * over scrolls, allegedly because il/dl looked faster.  We, on
1731  * the other hand, prefer scrolls because (a) they're just as fast
1732  * on many terminals and (b) using them avoids bouncing an
1733  * unchanged bottom section of the screen up and down, which is
1734  * visually nasty.
1735  *
1736  * (lav): added more cases, used dl/il when bot==maxy and in csr case.
1737  *
1738  * I used assumption that capabilities il/il1/dl/dl1 work inside
1739  * changed scroll region not shifting screen contents outside of it.
1740  * If there are any terminals behaving different way, it would be
1741  * necessary to add some conditions to scroll_csr_forward/backward.
1742  */
1743
1744 /* Try to scroll up assuming given csr (miny, maxy). Returns ERR on failure */
1745 static int
1746 scroll_csr_forward(NCURSES_SP_DCLx
1747                    int n,
1748                    int top,
1749                    int bot,
1750                    int miny,
1751                    int maxy,
1752                    NCURSES_CH_T blank)
1753 {
1754     int i;
1755
1756     if (n == 1 && scroll_forward && top == miny && bot == maxy) {
1757         GoTo(NCURSES_SP_ARGx bot, 0);
1758         UpdateAttrs(SP_PARM, blank);
1759         TPUTS_TRACE("scroll_forward");
1760         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx scroll_forward);
1761     } else if (n == 1 && delete_line && bot == maxy) {
1762         GoTo(NCURSES_SP_ARGx top, 0);
1763         UpdateAttrs(SP_PARM, blank);
1764         TPUTS_TRACE("delete_line");
1765         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx delete_line);
1766     } else if (parm_index && top == miny && bot == maxy) {
1767         GoTo(NCURSES_SP_ARGx bot, 0);
1768         UpdateAttrs(SP_PARM, blank);
1769         TPUTS_TRACE("parm_index");
1770         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1771                                 TPARM_2(parm_index, n, 0),
1772                                 n,
1773                                 NCURSES_SP_NAME(_nc_outch));
1774     } else if (parm_delete_line && bot == maxy) {
1775         GoTo(NCURSES_SP_ARGx top, 0);
1776         UpdateAttrs(SP_PARM, blank);
1777         TPUTS_TRACE("parm_delete_line");
1778         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1779                                 TPARM_2(parm_delete_line, n, 0),
1780                                 n,
1781                                 NCURSES_SP_NAME(_nc_outch));
1782     } else if (scroll_forward && top == miny && bot == maxy) {
1783         GoTo(NCURSES_SP_ARGx bot, 0);
1784         UpdateAttrs(SP_PARM, blank);
1785         for (i = 0; i < n; i++) {
1786             TPUTS_TRACE("scroll_forward");
1787             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx scroll_forward);
1788         }
1789     } else if (delete_line && bot == maxy) {
1790         GoTo(NCURSES_SP_ARGx top, 0);
1791         UpdateAttrs(SP_PARM, blank);
1792         for (i = 0; i < n; i++) {
1793             TPUTS_TRACE("delete_line");
1794             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx delete_line);
1795         }
1796     } else
1797         return ERR;
1798
1799 #if NCURSES_EXT_FUNCS
1800     if (FILL_BCE(SP_PARM)) {
1801         int j;
1802         for (i = 0; i < n; i++) {
1803             GoTo(NCURSES_SP_ARGx bot - i, 0);
1804             for (j = 0; j < screen_columns(SP_PARM); j++)
1805                 PutChar(NCURSES_SP_ARGx CHREF(blank));
1806         }
1807     }
1808 #endif
1809     return OK;
1810 }
1811
1812 /* Try to scroll down assuming given csr (miny, maxy). Returns ERR on failure */
1813 /* n > 0 */
1814 static int
1815 scroll_csr_backward(NCURSES_SP_DCLx
1816                     int n,
1817                     int top,
1818                     int bot,
1819                     int miny,
1820                     int maxy,
1821                     NCURSES_CH_T blank)
1822 {
1823     int i;
1824
1825     if (n == 1 && scroll_reverse && top == miny && bot == maxy) {
1826         GoTo(NCURSES_SP_ARGx top, 0);
1827         UpdateAttrs(SP_PARM, blank);
1828         TPUTS_TRACE("scroll_reverse");
1829         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx scroll_reverse);
1830     } else if (n == 1 && insert_line && bot == maxy) {
1831         GoTo(NCURSES_SP_ARGx top, 0);
1832         UpdateAttrs(SP_PARM, blank);
1833         TPUTS_TRACE("insert_line");
1834         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx insert_line);
1835     } else if (parm_rindex && top == miny && bot == maxy) {
1836         GoTo(NCURSES_SP_ARGx top, 0);
1837         UpdateAttrs(SP_PARM, blank);
1838         TPUTS_TRACE("parm_rindex");
1839         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1840                                 TPARM_2(parm_rindex, n, 0),
1841                                 n,
1842                                 NCURSES_SP_NAME(_nc_outch));
1843     } else if (parm_insert_line && bot == maxy) {
1844         GoTo(NCURSES_SP_ARGx top, 0);
1845         UpdateAttrs(SP_PARM, blank);
1846         TPUTS_TRACE("parm_insert_line");
1847         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1848                                 TPARM_2(parm_insert_line, n, 0),
1849                                 n,
1850                                 NCURSES_SP_NAME(_nc_outch));
1851     } else if (scroll_reverse && top == miny && bot == maxy) {
1852         GoTo(NCURSES_SP_ARGx top, 0);
1853         UpdateAttrs(SP_PARM, blank);
1854         for (i = 0; i < n; i++) {
1855             TPUTS_TRACE("scroll_reverse");
1856             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx scroll_reverse);
1857         }
1858     } else if (insert_line && bot == maxy) {
1859         GoTo(NCURSES_SP_ARGx top, 0);
1860         UpdateAttrs(SP_PARM, blank);
1861         for (i = 0; i < n; i++) {
1862             TPUTS_TRACE("insert_line");
1863             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx insert_line);
1864         }
1865     } else
1866         return ERR;
1867
1868 #if NCURSES_EXT_FUNCS
1869     if (FILL_BCE(SP_PARM)) {
1870         int j;
1871         for (i = 0; i < n; i++) {
1872             GoTo(NCURSES_SP_ARGx top + i, 0);
1873             for (j = 0; j < screen_columns(SP_PARM); j++)
1874                 PutChar(NCURSES_SP_ARGx CHREF(blank));
1875         }
1876     }
1877 #endif
1878     return OK;
1879 }
1880
1881 /* scroll by using delete_line at del and insert_line at ins */
1882 /* n > 0 */
1883 static int
1884 scroll_idl(NCURSES_SP_DCLx int n, int del, int ins, NCURSES_CH_T blank)
1885 {
1886     int i;
1887
1888     if (!((parm_delete_line || delete_line) && (parm_insert_line || insert_line)))
1889         return ERR;
1890
1891     GoTo(NCURSES_SP_ARGx del, 0);
1892     UpdateAttrs(SP_PARM, blank);
1893     if (n == 1 && delete_line) {
1894         TPUTS_TRACE("delete_line");
1895         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx delete_line);
1896     } else if (parm_delete_line) {
1897         TPUTS_TRACE("parm_delete_line");
1898         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1899                                 TPARM_2(parm_delete_line, n, 0),
1900                                 n,
1901                                 NCURSES_SP_NAME(_nc_outch));
1902     } else {                    /* if (delete_line) */
1903         for (i = 0; i < n; i++) {
1904             TPUTS_TRACE("delete_line");
1905             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx delete_line);
1906         }
1907     }
1908
1909     GoTo(NCURSES_SP_ARGx ins, 0);
1910     UpdateAttrs(SP_PARM, blank);
1911     if (n == 1 && insert_line) {
1912         TPUTS_TRACE("insert_line");
1913         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx insert_line);
1914     } else if (parm_insert_line) {
1915         TPUTS_TRACE("parm_insert_line");
1916         NCURSES_SP_NAME(tputs) (NCURSES_SP_ARGx
1917                                 TPARM_2(parm_insert_line, n, 0),
1918                                 n,
1919                                 NCURSES_SP_NAME(_nc_outch));
1920     } else {                    /* if (insert_line) */
1921         for (i = 0; i < n; i++) {
1922             TPUTS_TRACE("insert_line");
1923             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx insert_line);
1924         }
1925     }
1926
1927     return OK;
1928 }
1929
1930 /*
1931  * Note:  some terminals require the cursor to be within the scrolling margins
1932  * before setting them.  Generally, the cursor must be at the appropriate end
1933  * of the scrolling margins when issuing an indexing operation (it is not
1934  * apparent whether it must also be at the left margin; we do this just to be
1935  * safe).  To make the related cursor movement a little faster, we use the
1936  * save/restore cursor capabilities if the terminal has them.
1937  */
1938 NCURSES_EXPORT(int)
1939 NCURSES_SP_NAME(_nc_scrolln) (NCURSES_SP_DCLx
1940                               int n,
1941                               int top,
1942                               int bot,
1943                               int maxy)
1944 /* scroll region from top to bot by n lines */
1945 {
1946     NCURSES_CH_T blank;
1947     int i;
1948     bool cursor_saved = FALSE;
1949     int res;
1950
1951     TR(TRACE_MOVE, ("_nc_scrolln(%p, %d, %d, %d, %d)",
1952                     (void *) SP_PARM, n, top, bot, maxy));
1953
1954     if (!IsValidScreen(SP_PARM))
1955         return (ERR);
1956
1957     blank = ClrBlank(NCURSES_SP_ARGx StdScreen(SP_PARM));
1958
1959 #if USE_XMC_SUPPORT
1960     /*
1961      * If we scroll, we might remove a cookie.
1962      */
1963     if (magic_cookie_glitch > 0) {
1964         return (ERR);
1965     }
1966 #endif
1967
1968     if (n > 0) {                /* scroll up (forward) */
1969         /*
1970          * Explicitly clear if stuff pushed off top of region might
1971          * be saved by the terminal.
1972          */
1973         res = scroll_csr_forward(NCURSES_SP_ARGx n, top, bot, 0, maxy, blank);
1974
1975         if (res == ERR && change_scroll_region) {
1976             if ((((n == 1 && scroll_forward) || parm_index)
1977                  && (SP_PARM->_cursrow == bot || SP_PARM->_cursrow == bot - 1))
1978                 && save_cursor && restore_cursor) {
1979                 cursor_saved = TRUE;
1980                 TPUTS_TRACE("save_cursor");
1981                 NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx save_cursor);
1982             }
1983             TPUTS_TRACE("change_scroll_region");
1984             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx
1985                                    TPARM_2(change_scroll_region, top, bot));
1986             if (cursor_saved) {
1987                 TPUTS_TRACE("restore_cursor");
1988                 NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx restore_cursor);
1989             } else {
1990                 SP_PARM->_cursrow = SP_PARM->_curscol = -1;
1991             }
1992
1993             res = scroll_csr_forward(NCURSES_SP_ARGx n, top, bot, top, bot, blank);
1994
1995             TPUTS_TRACE("change_scroll_region");
1996             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx
1997                                    TPARM_2(change_scroll_region, 0, maxy));
1998             SP_PARM->_cursrow = SP_PARM->_curscol = -1;
1999         }
2000
2001         if (res == ERR && SP_PARM->_nc_sp_idlok)
2002             res = scroll_idl(NCURSES_SP_ARGx n, top, bot - n + 1, blank);
2003
2004         /*
2005          * Clear the newly shifted-in text.
2006          */
2007         if (res != ERR
2008             && (non_dest_scroll_region || (memory_below && bot == maxy))) {
2009             static const NCURSES_CH_T blank2 = NewChar(BLANK_TEXT);
2010             if (bot == maxy && clr_eos) {
2011                 GoTo(NCURSES_SP_ARGx bot - n + 1, 0);
2012                 ClrToEOS(NCURSES_SP_ARGx blank2);
2013             } else {
2014                 for (i = 0; i < n; i++) {
2015                     GoTo(NCURSES_SP_ARGx bot - i, 0);
2016                     ClrToEOL(NCURSES_SP_ARGx blank2, FALSE);
2017                 }
2018             }
2019         }
2020
2021     } else {                    /* (n < 0) - scroll down (backward) */
2022         res = scroll_csr_backward(NCURSES_SP_ARGx -n, top, bot, 0, maxy, blank);
2023
2024         if (res == ERR && change_scroll_region) {
2025             if (top != 0
2026                 && (SP_PARM->_cursrow == top ||
2027                     SP_PARM->_cursrow == top - 1)
2028                 && save_cursor && restore_cursor) {
2029                 cursor_saved = TRUE;
2030                 TPUTS_TRACE("save_cursor");
2031                 NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx save_cursor);
2032             }
2033             TPUTS_TRACE("change_scroll_region");
2034             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx
2035                                    TPARM_2(change_scroll_region, top, bot));
2036             if (cursor_saved) {
2037                 TPUTS_TRACE("restore_cursor");
2038                 NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx restore_cursor);
2039             } else {
2040                 SP_PARM->_cursrow = SP_PARM->_curscol = -1;
2041             }
2042
2043             res = scroll_csr_backward(NCURSES_SP_ARGx
2044                                       -n, top, bot, top, bot, blank);
2045
2046             TPUTS_TRACE("change_scroll_region");
2047             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx
2048                                    TPARM_2(change_scroll_region, 0, maxy));
2049             SP_PARM->_cursrow = SP_PARM->_curscol = -1;
2050         }
2051
2052         if (res == ERR && SP_PARM->_nc_sp_idlok)
2053             res = scroll_idl(NCURSES_SP_ARGx -n, bot + n + 1, top, blank);
2054
2055         /*
2056          * Clear the newly shifted-in text.
2057          */
2058         if (res != ERR
2059             && (non_dest_scroll_region || (memory_above && top == 0))) {
2060             static const NCURSES_CH_T blank2 = NewChar(BLANK_TEXT);
2061             for (i = 0; i < -n; i++) {
2062                 GoTo(NCURSES_SP_ARGx i + top, 0);
2063                 ClrToEOL(NCURSES_SP_ARGx blank2, FALSE);
2064             }
2065         }
2066     }
2067
2068     if (res == ERR)
2069         return (ERR);
2070
2071     _nc_scroll_window(CurScreen(SP_PARM), n,
2072                       (NCURSES_SIZE_T) top,
2073                       (NCURSES_SIZE_T) bot,
2074                       blank);
2075
2076     /* shift hash values too - they can be reused */
2077     NCURSES_SP_NAME(_nc_scroll_oldhash) (NCURSES_SP_ARGx n, top, bot);
2078
2079     return (OK);
2080 }
2081
2082 #if NCURSES_SP_FUNCS
2083 NCURSES_EXPORT(int)
2084 _nc_scrolln(int n, int top, int bot, int maxy)
2085 {
2086     return NCURSES_SP_NAME(_nc_scrolln) (CURRENT_SCREEN, n, top, bot, maxy);
2087 }
2088 #endif
2089
2090 NCURSES_EXPORT(void)
2091 NCURSES_SP_NAME(_nc_screen_resume) (NCURSES_SP_DCL0)
2092 {
2093     assert(SP_PARM);
2094
2095     /* make sure terminal is in a sane known state */
2096     SetAttr(SCREEN_ATTRS(SP_PARM), A_NORMAL);
2097     NewScreen(SP_PARM)->_clear = TRUE;
2098
2099     /* reset color pairs and definitions */
2100     if (SP_PARM->_coloron || SP_PARM->_color_defs)
2101         NCURSES_SP_NAME(_nc_reset_colors) (NCURSES_SP_ARG);
2102
2103     /* restore user-defined colors, if any */
2104     if (SP_PARM->_color_defs < 0) {
2105         int n;
2106         SP_PARM->_color_defs = -(SP_PARM->_color_defs);
2107         for (n = 0; n < SP_PARM->_color_defs; ++n) {
2108             if (SP_PARM->_color_table[n].init) {
2109                 NCURSES_SP_NAME(init_color) (NCURSES_SP_ARGx
2110                                              (short) n,
2111                                              SP_PARM->_color_table[n].r,
2112                                              SP_PARM->_color_table[n].g,
2113                                              SP_PARM->_color_table[n].b);
2114             }
2115         }
2116     }
2117
2118     if (exit_attribute_mode)
2119         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx exit_attribute_mode);
2120     else {
2121         /* turn off attributes */
2122         if (exit_alt_charset_mode)
2123             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx exit_alt_charset_mode);
2124         if (exit_standout_mode)
2125             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx exit_standout_mode);
2126         if (exit_underline_mode)
2127             NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx exit_underline_mode);
2128     }
2129     if (exit_insert_mode)
2130         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx exit_insert_mode);
2131     if (enter_am_mode && exit_am_mode)
2132         NCURSES_SP_NAME(putp) (NCURSES_SP_ARGx
2133                                (auto_right_margin
2134                                 ? enter_am_mode
2135                                 : exit_am_mode));
2136 }
2137
2138 #if NCURSES_SP_FUNCS
2139 NCURSES_EXPORT(void)
2140 _nc_screen_resume(void)
2141 {
2142     NCURSES_SP_NAME(_nc_screen_resume) (CURRENT_SCREEN);
2143 }
2144 #endif
2145
2146 NCURSES_EXPORT(void)
2147 NCURSES_SP_NAME(_nc_screen_init) (NCURSES_SP_DCL0)
2148 {
2149     NCURSES_SP_NAME(_nc_screen_resume) (NCURSES_SP_ARG);
2150 }
2151
2152 #if NCURSES_SP_FUNCS
2153 NCURSES_EXPORT(void)
2154 _nc_screen_init(void)
2155 {
2156     NCURSES_SP_NAME(_nc_screen_init) (CURRENT_SCREEN);
2157 }
2158 #endif
2159
2160 /* wrap up screen handling */
2161 NCURSES_EXPORT(void)
2162 NCURSES_SP_NAME(_nc_screen_wrap) (NCURSES_SP_DCL0)
2163 {
2164     if (SP_PARM == 0)
2165         return;
2166
2167     UpdateAttrs(SP_PARM, normal);
2168 #if NCURSES_EXT_FUNCS
2169     if (SP_PARM->_coloron
2170         && !SP_PARM->_default_color) {
2171         static const NCURSES_CH_T blank = NewChar(BLANK_TEXT);
2172         SP_PARM->_default_color = TRUE;
2173         NCURSES_SP_NAME(_nc_do_color) (NCURSES_SP_ARGx
2174                                        -1,
2175                                        0,
2176                                        FALSE,
2177                                        NCURSES_SP_NAME(_nc_outch));
2178         SP_PARM->_default_color = FALSE;
2179
2180         TINFO_MVCUR(NCURSES_SP_ARGx
2181                     SP_PARM->_cursrow,
2182                     SP_PARM->_curscol,
2183                     screen_lines(SP_PARM) - 1,
2184                     0);
2185
2186         ClrToEOL(NCURSES_SP_ARGx blank, TRUE);
2187     }
2188 #endif
2189     if (SP_PARM->_color_defs) {
2190         NCURSES_SP_NAME(_nc_reset_colors) (NCURSES_SP_ARG);
2191     }
2192 }
2193
2194 #if NCURSES_SP_FUNCS
2195 NCURSES_EXPORT(void)
2196 _nc_screen_wrap(void)
2197 {
2198     NCURSES_SP_NAME(_nc_screen_wrap) (CURRENT_SCREEN);
2199 }
2200 #endif
2201
2202 #if USE_XMC_SUPPORT
2203 NCURSES_EXPORT(void)
2204 NCURSES_SP_NAME(_nc_do_xmc_glitch) (NCURSES_SP_DCLx attr_t previous)
2205 {
2206     if (SP_PARM != 0) {
2207         attr_t chg = XMC_CHANGES(previous ^ AttrOf(SCREEN_ATTRS(SP_PARM)));
2208
2209         while (chg != 0) {
2210             if (chg & 1) {
2211                 SP_PARM->_curscol += magic_cookie_glitch;
2212                 if (SP_PARM->_curscol >= SP_PARM->_columns)
2213                     wrap_cursor(NCURSES_SP_ARG);
2214                 TR(TRACE_UPDATE, ("bumped to %d,%d after cookie",
2215                                   SP_PARM->_cursrow, SP_PARM->_curscol));
2216             }
2217             chg >>= 1;
2218         }
2219     }
2220 }
2221
2222 #if NCURSES_SP_FUNCS
2223 NCURSES_EXPORT(void)
2224 _nc_do_xmc_glitch(attr_t previous)
2225 {
2226     NCURSES_SP_NAME(_nc_do_xmc_glitch) (CURRENT_SCREEN, previous);
2227 }
2228 #endif
2229
2230 #endif /* USE_XMC_SUPPORT */