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