]> ncurses.scripts.mit.edu Git - ncurses.git/blob - ncurses/tty/lib_mvcur.c
ncurses 5.1
[ncurses.git] / ncurses / tty / lib_mvcur.c
1 /****************************************************************************
2  * Copyright (c) 1998,1999,2000 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  ****************************************************************************/
33
34 /*
35 **      lib_mvcur.c
36 **
37 **      The routines for moving the physical cursor and scrolling:
38 **
39 **              void _nc_mvcur_init(void)
40 **
41 **              void _nc_mvcur_resume(void)
42 **
43 **              int mvcur(int old_y, int old_x, int new_y, int new_x)
44 **
45 **              void _nc_mvcur_wrap(void)
46 **
47 ** Comparisons with older movement optimizers:
48 **    SVr3 curses mvcur() can't use cursor_to_ll or auto_left_margin.
49 **    4.4BSD curses can't use cuu/cud/cuf/cub/hpa/vpa/tab/cbt for local
50 ** motions.  It doesn't use tactics based on auto_left_margin.  Weirdly
51 ** enough, it doesn't use its own hardware-scrolling routine to scroll up
52 ** destination lines for out-of-bounds addresses!
53 **    old ncurses optimizer: less accurate cost computations (in fact,
54 ** it was broken and had to be commented out!).
55 **
56 ** Compile with -DMAIN to build an interactive tester/timer for the movement
57 ** optimizer.  You can use it to investigate the optimizer's behavior.
58 ** You can also use it for tuning the formulas used to determine whether
59 ** or not full optimization is attempted.
60 **
61 ** This code has a nasty tendency to find bugs in terminfo entries, because it
62 ** exercises the non-cup movement capabilities heavily.  If you think you've
63 ** found a bug, try deleting subsets of the following capabilities (arranged
64 ** in decreasing order of suspiciousness): it, tab, cbt, hpa, vpa, cuu, cud,
65 ** cuf, cub, cuu1, cud1, cuf1, cub1.  It may be that one or more are wrong.
66 **
67 ** Note: you should expect this code to look like a resource hog in a profile.
68 ** That's because it does a lot of I/O, through the tputs() calls.  The I/O
69 ** cost swamps the computation overhead (and as machines get faster, this
70 ** will become even more true).  Comments in the test exerciser at the end
71 ** go into detail about tuning and how you can gauge the optimizer's
72 ** effectiveness.
73 **/
74
75 /****************************************************************************
76  *
77  * Constants and macros for optimizer tuning.
78  *
79  ****************************************************************************/
80
81 /*
82  * The average overhead of a full optimization computation in character
83  * transmission times.  If it's too high, the algorithm will be a bit
84  * over-biased toward using cup rather than local motions; if it's too
85  * low, the algorithm may spend more time than is strictly optimal
86  * looking for non-cup motions.  Profile the optimizer using the `t'
87  * command of the exerciser (see below), and round to the nearest integer.
88  *
89  * Yes, I (esr) thought about computing expected overhead dynamically, say
90  * by derivation from a running average of optimizer times.  But the
91  * whole point of this optimization is to *decrease* the frequency of
92  * system calls. :-)
93  */
94 #define COMPUTE_OVERHEAD        1       /* I use a 90MHz Pentium @ 9.6Kbps */
95
96 /*
97  * LONG_DIST is the distance we consider to be just as costly to move over as a
98  * cup sequence is to emit.  In other words, it's the length of a cup sequence
99  * adjusted for average computation overhead.  The magic number is the length
100  * of "\033[yy;xxH", the typical cup sequence these days.
101  */
102 #define LONG_DIST               (8 - COMPUTE_OVERHEAD)
103
104 /*
105  * Tell whether a motion is optimizable by local motions.  Needs to be cheap to
106  * compute. In general, all the fast moves go to either the right or left edge
107  * of the screen.  So any motion to a location that is (a) further away than
108  * LONG_DIST and (b) further inward from the right or left edge than LONG_DIST,
109  * we'll consider nonlocal.
110  */
111 #define NOT_LOCAL(fy, fx, ty, tx)       ((tx > LONG_DIST) && (tx < screen_lines - 1 - LONG_DIST) && (abs(ty-fy) + abs(tx-fx) > LONG_DIST))
112
113 /****************************************************************************
114  *
115  * External interfaces
116  *
117  ****************************************************************************/
118
119 /*
120  * For this code to work OK, the following components must live in the
121  * screen structure:
122  *
123  *      int             _char_padding;  // cost of character put
124  *      int             _cr_cost;       // cost of (carriage_return)
125  *      int             _cup_cost;      // cost of (cursor_address)
126  *      int             _home_cost;     // cost of (cursor_home)
127  *      int             _ll_cost;       // cost of (cursor_to_ll)
128  *#if USE_HARD_TABS
129  *      int             _ht_cost;       // cost of (tab)
130  *      int             _cbt_cost;      // cost of (back_tab)
131  *#endif USE_HARD_TABS
132  *      int             _cub1_cost;     // cost of (cursor_left)
133  *      int             _cuf1_cost;     // cost of (cursor_right)
134  *      int             _cud1_cost;     // cost of (cursor_down)
135  *      int             _cuu1_cost;     // cost of (cursor_up)
136  *      int             _cub_cost;      // cost of (parm_cursor_left)
137  *      int             _cuf_cost;      // cost of (parm_cursor_right)
138  *      int             _cud_cost;      // cost of (parm_cursor_down)
139  *      int             _cuu_cost;      // cost of (parm_cursor_up)
140  *      int             _hpa_cost;      // cost of (column_address)
141  *      int             _vpa_cost;      // cost of (row_address)
142  *      int             _ech_cost;      // cost of (erase_chars)
143  *      int             _rep_cost;      // cost of (repeat_char)
144  *
145  * The USE_HARD_TABS switch controls whether it is reliable to use tab/backtabs
146  * for local motions.  On many systems, it's not, due to uncertainties about
147  * tab delays and whether or not tabs will be expanded in raw mode.  If you
148  * have parm_right_cursor, tab motions don't win you a lot anyhow.
149  */
150
151 #include <curses.priv.h>
152 #include <term.h>
153 #include <ctype.h>
154
155 MODULE_ID("$Id: lib_mvcur.c,v 1.67 2000/06/24 21:13:51 tom Exp $")
156
157 #define STRLEN(s)       (s != 0) ? strlen(s) : 0
158
159 #define CURRENT_ROW     SP->_cursrow    /* phys cursor row */
160 #define CURRENT_COLUMN  SP->_curscol    /* phys cursor column */
161 #define CURRENT_ATTR    SP->_current_attr       /* current phys attribute */
162 #define REAL_ATTR       SP->_current_attr       /* phys current attribute */
163 #define WANT_CHAR(y, x) SP->_newscr->_line[y].text[x]   /* desired state */
164 #define BAUDRATE        cur_term->_baudrate     /* bits per second */
165
166 #if defined(MAIN) || defined(NCURSES_TEST)
167 #include <sys/time.h>
168
169 static bool profiling = FALSE;
170 static float diff;
171 #endif /* MAIN */
172
173 #define OPT_SIZE 512
174
175 static int normalized_cost(const char *const cap, int affcnt);
176
177 #if !HAVE_STRSTR
178 char *
179 _nc_strstr(const char *haystack, const char *needle)
180 {
181     size_t len1 = strlen(haystack);
182     size_t len2 = strlen(needle);
183     char *result = 0;
184
185     while ((len1 != 0) && (len1-- >= len2)) {
186         if (!strncmp(haystack, needle, len2)) {
187             result = haystack;
188             break;
189         }
190         haystack++;
191     }
192     return result;
193 }
194 #endif
195
196 /****************************************************************************
197  *
198  * Initialization/wrapup (including cost pre-computation)
199  *
200  ****************************************************************************/
201
202 #ifdef TRACE
203 static int
204 trace_cost_of(const char *capname, const char *cap, int affcnt)
205 {
206     int result = _nc_msec_cost(cap, affcnt);
207     TR(TRACE_CHARPUT | TRACE_MOVE,
208         ("CostOf %s %d %s", capname, result, _nc_visbuf(cap)));
209     return result;
210 }
211 #define CostOf(cap,affcnt) trace_cost_of(#cap,cap,affcnt);
212
213 static int
214 trace_normalized_cost(const char *capname, const char *cap, int affcnt)
215 {
216     int result = normalized_cost(cap, affcnt);
217     TR(TRACE_CHARPUT | TRACE_MOVE,
218         ("NormalizedCost %s %d %s", capname, result, _nc_visbuf(cap)));
219     return result;
220 }
221 #define NormalizedCost(cap,affcnt) trace_normalized_cost(#cap,cap,affcnt);
222
223 #else
224
225 #define CostOf(cap,affcnt) _nc_msec_cost(cap,affcnt);
226 #define NormalizedCost(cap,affcnt) normalized_cost(cap,affcnt);
227
228 #endif
229
230 int
231 _nc_msec_cost(const char *const cap, int affcnt)
232 /* compute the cost of a given operation */
233 {
234     if (cap == 0)
235         return (INFINITY);
236     else {
237         const char *cp;
238         float cum_cost = 0;
239
240         for (cp = cap; *cp; cp++) {
241             /* extract padding, either mandatory or required */
242             if (cp[0] == '$' && cp[1] == '<' && strchr(cp, '>')) {
243                 float number = 0;
244
245                 for (cp += 2; *cp != '>'; cp++) {
246                     if (isdigit(*cp))
247                         number = number * 10 + (*cp - '0');
248                     else if (*cp == '*')
249                         number *= affcnt;
250                     else if (*cp == '.' && (*++cp != '>') && isdigit(*cp))
251                         number += (*cp - '0') / 10.0;
252                 }
253
254 #ifdef NCURSES_NO_PADDING
255                 if (!(SP->_no_padding))
256 #endif
257                     cum_cost += number * 10;
258             } else
259                 cum_cost += SP->_char_padding;
260         }
261
262         return ((int) cum_cost);
263     }
264 }
265
266 static int
267 normalized_cost(const char *const cap, int affcnt)
268 /* compute the effective character-count for an operation (round up) */
269 {
270     int cost = _nc_msec_cost(cap, affcnt);
271     if (cost != INFINITY)
272         cost = (cost + SP->_char_padding - 1) / SP->_char_padding;
273     return cost;
274 }
275
276 static void
277 reset_scroll_region(void)
278 /* Set the scroll-region to a known state (the default) */
279 {
280     if (change_scroll_region) {
281         TPUTS_TRACE("change_scroll_region");
282         putp(tparm(change_scroll_region, 0, screen_lines - 1));
283     }
284 }
285
286 void
287 _nc_mvcur_resume(void)
288 /* what to do at initialization time and after each shellout */
289 {
290     /* initialize screen for cursor access */
291     if (enter_ca_mode) {
292         TPUTS_TRACE("enter_ca_mode");
293         putp(enter_ca_mode);
294     }
295
296     /*
297      * Doing this here rather than in _nc_mvcur_wrap() ensures that
298      * ncurses programs will see a reset scroll region even if a
299      * program that messed with it died ungracefully.
300      *
301      * This also undoes the effects of terminal init strings that assume
302      * they know the screen size.  This is useful when you're running
303      * a vt100 emulation through xterm.
304      */
305     reset_scroll_region();
306     SP->_cursrow = SP->_curscol = -1;
307
308     /* restore cursor shape */
309     if (SP->_cursor != -1) {
310         int cursor = SP->_cursor;
311         SP->_cursor = -1;
312         curs_set(cursor);
313     }
314 }
315
316 void
317 _nc_mvcur_init(void)
318 /* initialize the cost structure */
319 {
320     /*
321      * 9 = 7 bits + 1 parity + 1 stop.
322      */
323     SP->_char_padding = (9 * 1000 * 10) / (BAUDRATE > 0 ? BAUDRATE : 9600);
324     if (SP->_char_padding <= 0)
325         SP->_char_padding = 1;  /* must be nonzero */
326     TR(TRACE_CHARPUT | TRACE_MOVE, ("char_padding %d msecs", SP->_char_padding));
327
328     /* non-parameterized local-motion strings */
329     SP->_cr_cost = CostOf(carriage_return, 0);
330     SP->_home_cost = CostOf(cursor_home, 0);
331     SP->_ll_cost = CostOf(cursor_to_ll, 0);
332 #if USE_HARD_TABS
333     SP->_ht_cost = CostOf(tab, 0);
334     SP->_cbt_cost = CostOf(back_tab, 0);
335 #endif /* USE_HARD_TABS */
336     SP->_cub1_cost = CostOf(cursor_left, 0);
337     SP->_cuf1_cost = CostOf(cursor_right, 0);
338     SP->_cud1_cost = CostOf(cursor_down, 0);
339     SP->_cuu1_cost = CostOf(cursor_up, 0);
340
341     SP->_smir_cost = CostOf(enter_insert_mode, 0);
342     SP->_rmir_cost = CostOf(exit_insert_mode, 0);
343     SP->_ip_cost = 0;
344     if (insert_padding) {
345         SP->_ip_cost = CostOf(insert_padding, 0);
346     }
347
348     /*
349      * Assumption: if the terminal has memory_relative addressing, the
350      * initialization strings or smcup will set single-page mode so we
351      * can treat it like absolute screen addressing.  This seems to be true
352      * for all cursor_mem_address terminal types in the terminfo database.
353      */
354     SP->_address_cursor = cursor_address ? cursor_address : cursor_mem_address;
355
356     /*
357      * Parametrized local-motion strings.  This static cost computation
358      * depends on the following assumptions:
359      *
360      * (1) They never have * padding.  In the entire master terminfo database
361      *     as of March 1995, only the obsolete Zenith Z-100 pc violates this.
362      *     (Proportional padding is found mainly in insert, delete and scroll
363      *     capabilities).
364      *
365      * (2) The average case of cup has two two-digit parameters.  Strictly,
366      *     the average case for a 24 * 80 screen has ((10*10*(1 + 1)) +
367      *     (14*10*(1 + 2)) + (10*70*(2 + 1)) + (14*70*4)) / (24*80) = 3.458
368      *     digits of parameters.  On a 25x80 screen the average is 3.6197.
369      *     On larger screens the value gets much closer to 4.
370      *
371      * (3) The average case of cub/cuf/hpa/ech/rep has 2 digits of parameters
372      *     (strictly, (((10 * 1) + (70 * 2)) / 80) = 1.8750).
373      *
374      * (4) The average case of cud/cuu/vpa has 2 digits of parameters
375      *     (strictly, (((10 * 1) + (14 * 2)) / 24) = 1.5833).
376      *
377      * All these averages depend on the assumption that all parameter values
378      * are equally probable.
379      */
380     SP->_cup_cost = CostOf(tparm(SP->_address_cursor, 23, 23), 1);
381     SP->_cub_cost = CostOf(tparm(parm_left_cursor, 23), 1);
382     SP->_cuf_cost = CostOf(tparm(parm_right_cursor, 23), 1);
383     SP->_cud_cost = CostOf(tparm(parm_down_cursor, 23), 1);
384     SP->_cuu_cost = CostOf(tparm(parm_up_cursor, 23), 1);
385     SP->_hpa_cost = CostOf(tparm(column_address, 23), 1);
386     SP->_vpa_cost = CostOf(tparm(row_address, 23), 1);
387
388     /* non-parameterized screen-update strings */
389     SP->_ed_cost = NormalizedCost(clr_eos, 1);
390     SP->_el_cost = NormalizedCost(clr_eol, 1);
391     SP->_el1_cost = NormalizedCost(clr_bol, 1);
392     SP->_dch1_cost = NormalizedCost(delete_character, 1);
393     SP->_ich1_cost = NormalizedCost(insert_character, 1);
394
395     /* parameterized screen-update strings */
396     SP->_dch_cost = NormalizedCost(tparm(parm_dch, 23), 1);
397     SP->_ich_cost = NormalizedCost(tparm(parm_ich, 23), 1);
398     SP->_ech_cost = NormalizedCost(tparm(erase_chars, 23), 1);
399     SP->_rep_cost = NormalizedCost(tparm(repeat_char, ' ', 23), 1);
400
401     SP->_cup_ch_cost = NormalizedCost(tparm(SP->_address_cursor, 23, 23), 1);
402     SP->_hpa_ch_cost = NormalizedCost(tparm(column_address, 23), 1);
403     SP->_cuf_ch_cost = NormalizedCost(tparm(parm_right_cursor, 23), 1);
404     SP->_inline_cost = min(SP->_cup_ch_cost,
405         min(SP->_hpa_ch_cost,
406             SP->_cuf_ch_cost));
407
408     /* pre-compute some capability lengths */
409     SP->_carriage_return_length = STRLEN(carriage_return);
410     SP->_cursor_home_length = STRLEN(cursor_home);
411     SP->_cursor_to_ll_length = STRLEN(cursor_to_ll);
412
413     /*
414      * If save_cursor is used within enter_ca_mode, we should not use it for
415      * scrolling optimization, since the corresponding restore_cursor is not
416      * nested on the various terminals (vt100, xterm, etc.) which use this
417      * feature.
418      */
419     if (save_cursor != 0
420         && enter_ca_mode != 0
421         && strstr(enter_ca_mode, save_cursor) != 0) {
422         T(("...suppressed sc/rc capability due to conflict with smcup/rmcup"));
423         save_cursor = 0;
424         restore_cursor = 0;
425     }
426
427     /*
428      * A different, possibly better way to arrange this would be to set
429      * SP->_endwin = TRUE at window initialization time and let this be
430      * called by doupdate's return-from-shellout code.
431      */
432     _nc_mvcur_resume();
433 }
434
435 void
436 _nc_mvcur_wrap(void)
437 /* wrap up cursor-addressing mode */
438 {
439     /* leave cursor at screen bottom */
440     mvcur(-1, -1, screen_lines - 1, 0);
441
442     /* set cursor to normal mode */
443     if (SP->_cursor != -1)
444         curs_set(1);
445
446     if (exit_ca_mode) {
447         TPUTS_TRACE("exit_ca_mode");
448         putp(exit_ca_mode);
449     }
450     /*
451      * Reset terminal's tab counter.  There's a long-time bug that
452      * if you exit a "curses" program such as vi or more, tab
453      * forward, and then backspace, the cursor doesn't go to the
454      * right place.  The problem is that the kernel counts the
455      * escape sequences that reset things as column positions.
456      * Utter a \r to reset this invisibly.
457      */
458     _nc_outch('\r');
459 }
460
461 /****************************************************************************
462  *
463  * Optimized cursor movement
464  *
465  ****************************************************************************/
466
467 /*
468  * Perform repeated-append, returning cost
469  */
470 static inline int
471 repeated_append(int total, int num, int repeat, char *dst, const char *src)
472 {
473     register size_t src_len = strlen(src);
474     register size_t dst_len = STRLEN(dst);
475
476     if ((dst_len + repeat * src_len) < OPT_SIZE - 1) {
477         total += (num * repeat);
478         if (dst) {
479             dst += dst_len;
480             while (repeat-- > 0) {
481                 (void) strcpy(dst, src);
482                 dst += src_len;
483             }
484         }
485     } else {
486         total = INFINITY;
487     }
488     return total;
489 }
490
491 #ifndef NO_OPTIMIZE
492 #define NEXTTAB(fr)     (fr + init_tabs - (fr % init_tabs))
493
494 /*
495  * Assume back_tab (CBT) does not wrap backwards at the left margin, return
496  * a negative value at that point to simplify the loop.
497  */
498 #define LASTTAB(fr)     ((fr > 0) ? ((fr - 1) / init_tabs) * init_tabs : -1)
499
500 /* Note: we'd like to inline this for speed, but GNU C barfs on the attempt. */
501
502 static int
503 relative_move(char *result, int from_y, int from_x, int to_y, int to_x, bool ovw)
504 /* move via local motions (cuu/cuu1/cud/cud1/cub1/cub/cuf1/cuf/vpa/hpa) */
505 {
506     int n, vcost = 0, hcost = 0;
507
508     if (result)
509         result[0] = '\0';
510
511     if (to_y != from_y) {
512         vcost = INFINITY;
513
514         if (row_address) {
515             if (result)
516                 (void) strcpy(result, tparm(row_address, to_y));
517             vcost = SP->_vpa_cost;
518         }
519
520         if (to_y > from_y) {
521             n = (to_y - from_y);
522
523             if (parm_down_cursor && SP->_cud_cost < vcost) {
524                 if (result)
525                     (void) strcpy(result, tparm(parm_down_cursor, n));
526                 vcost = SP->_cud_cost;
527             }
528
529             if (cursor_down && (n * SP->_cud1_cost < vcost)) {
530                 if (result)
531                     result[0] = '\0';
532                 vcost = repeated_append(0, SP->_cud1_cost, n, result, cursor_down);
533             }
534         } else {                /* (to_y < from_y) */
535             n = (from_y - to_y);
536
537             if (parm_up_cursor && SP->_cup_cost < vcost) {
538                 if (result)
539                     (void) strcpy(result, tparm(parm_up_cursor, n));
540                 vcost = SP->_cup_cost;
541             }
542
543             if (cursor_up && (n * SP->_cuu1_cost < vcost)) {
544                 if (result)
545                     result[0] = '\0';
546                 vcost = repeated_append(0, SP->_cuu1_cost, n, result, cursor_up);
547             }
548         }
549
550         if (vcost == INFINITY)
551             return (INFINITY);
552     }
553
554     if (result)
555         result += strlen(result);
556
557     if (to_x != from_x) {
558         char str[OPT_SIZE];
559
560         hcost = INFINITY;
561
562         if (column_address) {
563             if (result)
564                 (void) strcpy(result, tparm(column_address, to_x));
565             hcost = SP->_hpa_cost;
566         }
567
568         if (to_x > from_x) {
569             n = to_x - from_x;
570
571             if (parm_right_cursor && SP->_cuf_cost < hcost) {
572                 if (result)
573                     (void) strcpy(result, tparm(parm_right_cursor, n));
574                 hcost = SP->_cuf_cost;
575             }
576
577             if (cursor_right) {
578                 int lhcost = 0;
579
580                 str[0] = '\0';
581
582 #if USE_HARD_TABS
583                 /* use hard tabs, if we have them, to do as much as possible */
584                 if (init_tabs > 0 && tab) {
585                     int nxt, fr;
586
587                     for (fr = from_x; (nxt = NEXTTAB(fr)) <= to_x; fr = nxt) {
588                         lhcost = repeated_append(lhcost, SP->_ht_cost, 1,
589                             str, tab);
590                         if (lhcost == INFINITY)
591                             break;
592                     }
593
594                     n = to_x - fr;
595                     from_x = fr;
596                 }
597 #endif /* USE_HARD_TABS */
598
599 #if defined(REAL_ATTR) && defined(WANT_CHAR)
600 #ifdef BSD_TPUTS
601                 /*
602                  * If we're allowing BSD-style padding in tputs, don't generate
603                  * a string with a leading digit.  Otherwise, that will be
604                  * interpreted as a padding value rather than sent to the
605                  * screen.
606                  */
607                 if (ovw
608                     && n > 0
609                     && vcost == 0
610                     && str[0] == '\0'
611                     && isdigit(TextOf(WANT_CHAR(to_y, from_x))))
612                     ovw = FALSE;
613 #endif
614                 /*
615                  * If we have no attribute changes, overwrite is cheaper.
616                  * Note: must suppress this by passing in ovw = FALSE whenever
617                  * WANT_CHAR would return invalid data.  In particular, this
618                  * is true between the time a hardware scroll has been done
619                  * and the time the structure WANT_CHAR would access has been
620                  * updated.
621                  */
622                 if (ovw) {
623                     int i;
624
625                     for (i = 0; i < n; i++)
626                         if ((WANT_CHAR(to_y, from_x + i) & A_ATTRIBUTES) != CURRENT_ATTR) {
627                             ovw = FALSE;
628                             break;
629                         }
630                 }
631                 if (ovw) {
632                     char *sp;
633                     int i;
634
635                     sp = str + strlen(str);
636
637                     for (i = 0; i < n; i++)
638                         *sp++ = WANT_CHAR(to_y, from_x + i);
639                     *sp = '\0';
640                     lhcost += n * SP->_char_padding;
641                 } else
642 #endif /* defined(REAL_ATTR) && defined(WANT_CHAR) */
643                 {
644                     lhcost = repeated_append(lhcost, SP->_cuf1_cost, n, str, cursor_right);
645                 }
646
647                 if (lhcost < hcost) {
648                     if (result)
649                         (void) strcpy(result, str);
650                     hcost = lhcost;
651                 }
652             }
653         } else {                /* (to_x < from_x) */
654             n = from_x - to_x;
655
656             if (parm_left_cursor && SP->_cub_cost < hcost) {
657                 if (result)
658                     (void) strcpy(result, tparm(parm_left_cursor, n));
659                 hcost = SP->_cub_cost;
660             }
661
662             if (cursor_left) {
663                 int lhcost = 0;
664
665                 str[0] = '\0';
666
667 #if USE_HARD_TABS
668                 if (init_tabs > 0 && back_tab) {
669                     int nxt, fr;
670
671                     for (fr = from_x; (nxt = LASTTAB(fr)) >= to_x; fr = nxt) {
672                         lhcost = repeated_append(lhcost, SP->_cbt_cost, 1,
673                             str, back_tab);
674                         if (lhcost == INFINITY)
675                             break;
676                     }
677
678                     n = fr - to_x;
679                 }
680 #endif /* USE_HARD_TABS */
681
682                 lhcost = repeated_append(lhcost, SP->_cub1_cost, n, str, cursor_left);
683
684                 if (lhcost < hcost) {
685                     if (result)
686                         (void) strcpy(result, str);
687                     hcost = lhcost;
688                 }
689             }
690         }
691
692         if (hcost == INFINITY)
693             return (INFINITY);
694     }
695
696     return (vcost + hcost);
697 }
698 #endif /* !NO_OPTIMIZE */
699
700 /*
701  * With the machinery set up above, it's conceivable that
702  * onscreen_mvcur could be modified into a recursive function that does
703  * an alpha-beta search of motion space, as though it were a chess
704  * move tree, with the weight function being boolean and the search
705  * depth equated to length of string.  However, this would jack up the
706  * computation cost a lot, especially on terminals without a cup
707  * capability constraining the search tree depth.  So we settle for
708  * the simpler method below.
709  */
710
711 static inline int
712 onscreen_mvcur(int yold, int xold, int ynew, int xnew, bool ovw)
713 /* onscreen move from (yold, xold) to (ynew, xnew) */
714 {
715     char use[OPT_SIZE], *sp;
716     int tactic = 0, newcost, usecost = INFINITY;
717     int t5_cr_cost;
718
719 #if defined(MAIN) || defined(NCURSES_TEST)
720     struct timeval before, after;
721
722     gettimeofday(&before, NULL);
723 #endif /* MAIN */
724
725     /* tactic #0: use direct cursor addressing */
726     sp = tparm(SP->_address_cursor, ynew, xnew);
727     if (sp) {
728         tactic = 0;
729         (void) strcpy(use, sp);
730         usecost = SP->_cup_cost;
731
732 #if defined(TRACE) || defined(NCURSES_TEST)
733         if (!(_nc_optimize_enable & OPTIMIZE_MVCUR))
734             goto nonlocal;
735 #endif /* TRACE */
736
737         /*
738          * We may be able to tell in advance that the full optimization
739          * will probably not be worth its overhead.  Also, don't try to
740          * use local movement if the current attribute is anything but
741          * A_NORMAL...there are just too many ways this can screw up
742          * (like, say, local-movement \n getting mapped to some obscure
743          * character because A_ALTCHARSET is on).
744          */
745         if (yold == -1 || xold == -1 || NOT_LOCAL(yold, xold, ynew, xnew)) {
746 #if defined(MAIN) || defined(NCURSES_TEST)
747             if (!profiling) {
748                 (void) fputs("nonlocal\n", stderr);
749                 goto nonlocal;  /* always run the optimizer if profiling */
750             }
751 #else
752             goto nonlocal;
753 #endif /* MAIN */
754         }
755     }
756 #ifndef NO_OPTIMIZE
757     /* tactic #1: use local movement */
758     if (yold != -1 && xold != -1
759         && ((newcost = relative_move(NULL, yold, xold, ynew, xnew, ovw)) != INFINITY)
760         && newcost < usecost) {
761         tactic = 1;
762         usecost = newcost;
763     }
764
765     /* tactic #2: use carriage-return + local movement */
766     if (yold != -1 && carriage_return
767         && ((newcost = relative_move(NULL, yold, 0, ynew, xnew, ovw)) != INFINITY)
768         && SP->_cr_cost + newcost < usecost) {
769         tactic = 2;
770         usecost = SP->_cr_cost + newcost;
771     }
772
773     /* tactic #3: use home-cursor + local movement */
774     if (cursor_home
775         && ((newcost = relative_move(NULL, 0, 0, ynew, xnew, ovw)) != INFINITY)
776         && SP->_home_cost + newcost < usecost) {
777         tactic = 3;
778         usecost = SP->_home_cost + newcost;
779     }
780
781     /* tactic #4: use home-down + local movement */
782     if (cursor_to_ll
783         && ((newcost = relative_move(NULL, screen_lines - 1, 0, ynew, xnew,
784                     ovw)) != INFINITY)
785         && SP->_ll_cost + newcost < usecost) {
786         tactic = 4;
787         usecost = SP->_ll_cost + newcost;
788     }
789
790     /*
791      * tactic #5: use left margin for wrap to right-hand side,
792      * unless strange wrap behavior indicated by xenl might hose us.
793      */
794     t5_cr_cost = (xold > 0 ? SP->_cr_cost : 0);
795     if (auto_left_margin && !eat_newline_glitch
796         && yold > 0 && cursor_left
797         && ((newcost = relative_move(NULL, yold - 1, screen_columns - 1,
798                     ynew, xnew, ovw)) != INFINITY)
799         && t5_cr_cost + SP->_cub1_cost + newcost < usecost) {
800         tactic = 5;
801         usecost = t5_cr_cost + SP->_cub1_cost + newcost;
802     }
803
804     /*
805      * These cases are ordered by estimated relative frequency.
806      */
807     switch (tactic) {
808     case 1:
809         (void) relative_move(use, yold, xold, ynew, xnew, ovw);
810         break;
811     case 2:
812         (void) strcpy(use, carriage_return);
813         (void) relative_move(use + SP->_carriage_return_length,
814             yold, 0, ynew, xnew, ovw);
815         break;
816     case 3:
817         (void) strcpy(use, cursor_home);
818         (void) relative_move(use + SP->_cursor_home_length,
819             0, 0, ynew, xnew, ovw);
820         break;
821     case 4:
822         (void) strcpy(use, cursor_to_ll);
823         (void) relative_move(use + SP->_cursor_to_ll_length,
824             screen_lines - 1, 0, ynew, xnew, ovw);
825         break;
826     case 5:
827         use[0] = '\0';
828         if (xold > 0)
829             (void) strcat(use, carriage_return);
830         (void) strcat(use, cursor_left);
831         (void) relative_move(use + strlen(use),
832             yold - 1, screen_columns - 1, ynew, xnew, ovw);
833         break;
834     }
835 #endif /* !NO_OPTIMIZE */
836
837 #if defined(MAIN) || defined(NCURSES_TEST)
838     gettimeofday(&after, NULL);
839     diff = after.tv_usec - before.tv_usec
840         + (after.tv_sec - before.tv_sec) * 1000000;
841     if (!profiling)
842         (void) fprintf(stderr,
843             "onscreen: %d msec, %f 28.8Kbps char-equivalents\n",
844             (int) diff, diff / 288);
845 #endif /* MAIN */
846
847   nonlocal:
848     if (usecost != INFINITY) {
849         TPUTS_TRACE("mvcur");
850         tputs(use, 1, _nc_outch);
851         return (OK);
852     } else
853         return (ERR);
854 }
855
856 int
857 mvcur(int yold, int xold, int ynew, int xnew)
858 /* optimized cursor move from (yold, xold) to (ynew, xnew) */
859 {
860     TR(TRACE_MOVE, ("mvcur(%d,%d,%d,%d) called", yold, xold, ynew, xnew));
861
862     if (yold == ynew && xold == xnew)
863         return (OK);
864
865     /*
866      * Most work here is rounding for terminal boundaries getting the
867      * column position implied by wraparound or the lack thereof and
868      * rolling up the screen to get ynew on the screen.
869      */
870
871     if (xnew >= screen_columns) {
872         ynew += xnew / screen_columns;
873         xnew %= screen_columns;
874     }
875     if (xold >= screen_columns) {
876         int l;
877
878         l = (xold + 1) / screen_columns;
879         yold += l;
880         if (yold >= screen_lines)
881             l -= (yold - screen_lines - 1);
882
883         while (l > 0) {
884             if (newline) {
885                 TPUTS_TRACE("newline");
886                 tputs(newline, 0, _nc_outch);
887             } else
888                 putchar('\n');
889             l--;
890             if (xold > 0) {
891                 if (carriage_return) {
892                     TPUTS_TRACE("carriage_return");
893                     tputs(carriage_return, 0, _nc_outch);
894                 } else
895                     putchar('\r');
896                 xold = 0;
897             }
898         }
899     }
900
901     if (yold > screen_lines - 1)
902         yold = screen_lines - 1;
903     if (ynew > screen_lines - 1)
904         ynew = screen_lines - 1;
905
906     /* destination location is on screen now */
907     return (onscreen_mvcur(yold, xold, ynew, xnew, TRUE));
908 }
909
910 #if defined(TRACE) || defined(NCURSES_TEST)
911 int _nc_optimize_enable = OPTIMIZE_ALL;
912 #endif
913
914 #if defined(MAIN) || defined(NCURSES_TEST)
915 /****************************************************************************
916  *
917  * Movement optimizer test code
918  *
919  ****************************************************************************/
920
921 #include <tic.h>
922 #include <dump_entry.h>
923
924 const char *_nc_progname = "mvcur";
925
926 static unsigned long xmits;
927
928 /* these override lib_tputs.c */
929 int
930 tputs(const char *string, int affcnt GCC_UNUSED, int (*outc) (int) GCC_UNUSED)
931 /* stub tputs() that dumps sequences in a visible form */
932 {
933     if (profiling)
934         xmits += strlen(string);
935     else
936         (void) fputs(_nc_visbuf(string), stdout);
937     return (OK);
938 }
939
940 int
941 putp(const char *string)
942 {
943     return (tputs(string, 1, _nc_outch));
944 }
945
946 int
947 _nc_outch(int ch)
948 {
949     putc(ch, stdout);
950     return OK;
951 }
952
953 char PC = 0;                    /* used by termcap library */
954 speed_t ospeed = 0;             /* used by termcap library */
955 int _nc_nulls_sent = 0;         /* used by 'tack' program */
956
957 int
958 delay_output(int ms GCC_UNUSED)
959 {
960     return OK;
961 }
962
963 static char tname[MAX_ALIAS];
964
965 static void
966 load_term(void)
967 {
968     (void) setupterm(tname, STDOUT_FILENO, NULL);
969 }
970
971 static int
972 roll(int n)
973 {
974     int i, j;
975
976     i = (RAND_MAX / n) * n;
977     while ((j = rand()) >= i)
978         continue;
979     return (j % n);
980 }
981
982 int
983 main(int argc GCC_UNUSED, char *argv[]GCC_UNUSED)
984 {
985     (void) strcpy(tname, termname());
986     load_term();
987     _nc_setupscreen(lines, columns, stdout);
988     baudrate();
989
990     _nc_mvcur_init();
991     NC_BUFFERED(FALSE);
992
993     (void) puts("The mvcur tester.  Type ? for help");
994
995     fputs("smcup:", stdout);
996     putchar('\n');
997
998     for (;;) {
999         int fy, fx, ty, tx, n, i;
1000         char buf[BUFSIZ], capname[BUFSIZ];
1001
1002         (void) fputs("> ", stdout);
1003         (void) fgets(buf, sizeof(buf), stdin);
1004
1005         if (buf[0] == '?') {
1006             (void) puts("?                -- display this help message");
1007             (void)
1008                 puts("fy fx ty tx      -- (4 numbers) display (fy,fx)->(ty,tx) move");
1009             (void) puts("s[croll] n t b m -- display scrolling sequence");
1010             (void)
1011                 printf("r[eload]         -- reload terminal info for %s\n",
1012                 termname());
1013             (void)
1014                 puts("l[oad] <term>    -- load terminal info for type <term>");
1015             (void) puts("d[elete] <cap>   -- delete named capability");
1016             (void) puts("i[nspect]        -- display terminal capabilities");
1017             (void)
1018                 puts("c[ost]           -- dump cursor-optimization cost table");
1019             (void) puts("o[optimize]      -- toggle movement optimization");
1020             (void)
1021                 puts("t[orture] <num>  -- torture-test with <num> random moves");
1022             (void) puts("q[uit]           -- quit the program");
1023         } else if (sscanf(buf, "%d %d %d %d", &fy, &fx, &ty, &tx) == 4) {
1024             struct timeval before, after;
1025
1026             putchar('"');
1027
1028             gettimeofday(&before, NULL);
1029             mvcur(fy, fx, ty, tx);
1030             gettimeofday(&after, NULL);
1031
1032             printf("\" (%ld msec)\n",
1033                 (long) (after.tv_usec - before.tv_usec + (after.tv_sec -
1034                         before.tv_sec) * 1000000));
1035         } else if (sscanf(buf, "s %d %d %d %d", &fy, &fx, &ty, &tx) == 4) {
1036             struct timeval before, after;
1037
1038             putchar('"');
1039
1040             gettimeofday(&before, NULL);
1041             _nc_scrolln(fy, fx, ty, tx);
1042             gettimeofday(&after, NULL);
1043
1044             printf("\" (%ld msec)\n",
1045                 (long) (after.tv_usec - before.tv_usec + (after.tv_sec -
1046                         before.tv_sec) * 1000000));
1047         } else if (buf[0] == 'r') {
1048             (void) strcpy(tname, termname());
1049             load_term();
1050         } else if (sscanf(buf, "l %s", tname) == 1) {
1051             load_term();
1052         } else if (sscanf(buf, "d %s", capname) == 1) {
1053             struct name_table_entry const *np = _nc_find_entry(capname,
1054                 _nc_info_hash_table);
1055
1056             if (np == NULL)
1057                 (void) printf("No such capability as \"%s\"\n", capname);
1058             else {
1059                 switch (np->nte_type) {
1060                 case BOOLEAN:
1061                     cur_term->type.Booleans[np->nte_index] = FALSE;
1062                     (void)
1063                         printf("Boolean capability `%s' (%d) turned off.\n",
1064                         np->nte_name, np->nte_index);
1065                     break;
1066
1067                 case NUMBER:
1068                     cur_term->type.Numbers[np->nte_index] = ABSENT_NUMERIC;
1069                     (void) printf("Number capability `%s' (%d) set to -1.\n",
1070                         np->nte_name, np->nte_index);
1071                     break;
1072
1073                 case STRING:
1074                     cur_term->type.Strings[np->nte_index] = ABSENT_STRING;
1075                     (void) printf("String capability `%s' (%d) deleted.\n",
1076                         np->nte_name, np->nte_index);
1077                     break;
1078                 }
1079             }
1080         } else if (buf[0] == 'i') {
1081             dump_init((char *) NULL, F_TERMINFO, S_TERMINFO, 70, 0, FALSE);
1082             dump_entry(&cur_term->type, FALSE, TRUE, 0);
1083             putchar('\n');
1084         } else if (buf[0] == 'o') {
1085             if (_nc_optimize_enable & OPTIMIZE_MVCUR) {
1086                 _nc_optimize_enable &= ~OPTIMIZE_MVCUR;
1087                 (void) puts("Optimization is now off.");
1088             } else {
1089                 _nc_optimize_enable |= OPTIMIZE_MVCUR;
1090                 (void) puts("Optimization is now on.");
1091             }
1092         }
1093         /*
1094          * You can use the `t' test to profile and tune the movement
1095          * optimizer.  Use iteration values in three digits or more.
1096          * At above 5000 iterations the profile timing averages are stable
1097          * to within a millisecond or three.
1098          *
1099          * The `overhead' field of the report will help you pick a
1100          * COMPUTE_OVERHEAD figure appropriate for your processor and
1101          * expected line speed.  The `total estimated time' is
1102          * computation time plus a character-transmission time
1103          * estimate computed from the number of transmits and the baud
1104          * rate.
1105          *
1106          * Use this together with the `o' command to get a read on the
1107          * optimizer's effectiveness.  Compare the total estimated times
1108          * for `t' runs of the same length in both optimized and un-optimized
1109          * modes.  As long as the optimized times are less, the optimizer
1110          * is winning.
1111          */
1112         else if (sscanf(buf, "t %d", &n) == 1) {
1113             float cumtime = 0, perchar;
1114             int speeds[] =
1115             {2400, 9600, 14400, 19200, 28800, 38400, 0};
1116
1117             srand((unsigned) (getpid() + time((time_t *) 0)));
1118             profiling = TRUE;
1119             xmits = 0;
1120             for (i = 0; i < n; i++) {
1121                 /*
1122                  * This does a move test between two random locations,
1123                  * Random moves probably short-change the optimizer,
1124                  * which will work better on the short moves probably
1125                  * typical of doupdate()'s usage pattern.  Still,
1126                  * until we have better data...
1127                  */
1128 #ifdef FIND_COREDUMP
1129                 int from_y = roll(lines);
1130                 int to_y = roll(lines);
1131                 int from_x = roll(columns);
1132                 int to_x = roll(columns);
1133
1134                 printf("(%d,%d) -> (%d,%d)\n", from_y, from_x, to_y, to_x);
1135                 mvcur(from_y, from_x, to_y, to_x);
1136 #else
1137                 mvcur(roll(lines), roll(columns), roll(lines), roll(columns));
1138 #endif /* FIND_COREDUMP */
1139                 if (diff)
1140                     cumtime += diff;
1141             }
1142             profiling = FALSE;
1143
1144             /*
1145              * Average milliseconds per character optimization time.
1146              * This is the key figure to watch when tuning the optimizer.
1147              */
1148             perchar = cumtime / n;
1149
1150             (void) printf("%d moves (%ld chars) in %d msec, %f msec each:\n",
1151                 n, xmits, (int) cumtime, perchar);
1152
1153             for (i = 0; speeds[i]; i++) {
1154                 /*
1155                  * Total estimated time for the moves, computation and
1156                  * transmission both. Transmission time is an estimate
1157                  * assuming 9 bits/char, 8 bits + 1 stop bit.
1158                  */
1159                 float totalest = cumtime + xmits * 9 * 1e6 / speeds[i];
1160
1161                 /*
1162                  * Per-character optimization overhead in character transmits
1163                  * at the current speed.  Round this to the nearest integer
1164                  * to figure COMPUTE_OVERHEAD for the speed.
1165                  */
1166                 float overhead = speeds[i] * perchar / 1e6;
1167
1168                 (void)
1169                     printf("%6d bps: %3.2f char-xmits overhead; total estimated time %15.2f\n",
1170                     speeds[i], overhead, totalest);
1171             }
1172         } else if (buf[0] == 'c') {
1173             (void) printf("char padding: %d\n", SP->_char_padding);
1174             (void) printf("cr cost: %d\n", SP->_cr_cost);
1175             (void) printf("cup cost: %d\n", SP->_cup_cost);
1176             (void) printf("home cost: %d\n", SP->_home_cost);
1177             (void) printf("ll cost: %d\n", SP->_ll_cost);
1178 #if USE_HARD_TABS
1179             (void) printf("ht cost: %d\n", SP->_ht_cost);
1180             (void) printf("cbt cost: %d\n", SP->_cbt_cost);
1181 #endif /* USE_HARD_TABS */
1182             (void) printf("cub1 cost: %d\n", SP->_cub1_cost);
1183             (void) printf("cuf1 cost: %d\n", SP->_cuf1_cost);
1184             (void) printf("cud1 cost: %d\n", SP->_cud1_cost);
1185             (void) printf("cuu1 cost: %d\n", SP->_cuu1_cost);
1186             (void) printf("cub cost: %d\n", SP->_cub_cost);
1187             (void) printf("cuf cost: %d\n", SP->_cuf_cost);
1188             (void) printf("cud cost: %d\n", SP->_cud_cost);
1189             (void) printf("cuu cost: %d\n", SP->_cuu_cost);
1190             (void) printf("hpa cost: %d\n", SP->_hpa_cost);
1191             (void) printf("vpa cost: %d\n", SP->_vpa_cost);
1192         } else if (buf[0] == 'x' || buf[0] == 'q')
1193             break;
1194         else
1195             (void) puts("Invalid command.");
1196     }
1197
1198     (void) fputs("rmcup:", stdout);
1199     _nc_mvcur_wrap();
1200     putchar('\n');
1201
1202     return (0);
1203 }
1204
1205 #endif /* MAIN */
1206
1207 /* lib_mvcur.c ends here */