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