2 /***************************************************************************
4 ****************************************************************************
5 * ncurses is copyright (C) 1992-1995 *
7 * zmbenhal@netcom.com *
9 * esr@snark.thyrsus.com *
11 * Permission is hereby granted to reproduce and distribute ncurses *
12 * by any means and for any fee, whether alone or as part of a *
13 * larger distribution, in source or in binary form, PROVIDED *
14 * this notice is included with any such distribution, and is not *
15 * removed from any of its header files. Mention of ncurses in any *
16 * applications linked with it is highly appreciated. *
18 * ncurses comes AS IS with no warranty, implied or expressed. *
20 ***************************************************************************/
26 ** The routines for moving the physical cursor and scrolling:
28 ** void _nc_mvcur_init(void), mvcur_wrap(void)
30 ** void _nc_mvcur_resume(void)
32 ** int mvcur(int old_y, int old_x, int new_y, int new_x)
34 ** void _nc_mvcur_wrap(void)
36 ** int _nc_mvcur_scrolln(int n, int top, int bot, int maxy)
38 ** Comparisons with older movement optimizers:
39 ** SVr3 curses mvcur() can't use cursor_to_ll or auto_left_margin.
40 ** 4.4BSD curses can't use cuu/cud/cuf/cub/hpa/vpa/tab/cbt for local
41 ** motions. It doesn't use tactics based on auto_left_margin. Weirdly
42 ** enough, it doesn't use its own hardware-scrolling routine to scroll up
43 ** destination lines for out-of-bounds addresses!
44 ** old ncurses optimizer: less accurate cost computations (in fact,
45 ** it was broken and had to be commented out!).
47 ** Compile with -DMAIN to build an interactive tester/timer for the movement
48 ** optimizer. You can use it to investigate the optimizer's behavior.
49 ** You can also use it for tuning the formulas used to determine whether
50 ** or not full optimization is attempted.
52 ** This code has a nasty tendency to find bugs in terminfo entries, because it
53 ** exercises the non-cup movement capabilities heavily. If you think you've
54 ** found a bug, try deleting subsets of the following capabilities (arranged
55 ** in decreasing order of suspiciousness): it, tab, cbt, hpa, vpa, cuu, cud,
56 ** cuf, cub, cuu1, cud1, cuf1, cub1. It may be that one or more are wrong.
58 ** Note: you should expect this code to look like a resource hog in a profile.
59 ** That's because it does a lot of I/O, through the tputs() calls. The I/O
60 ** cost swamps the computation overhead (and as machines get faster, this
61 ** will become even more true). Comments in the test exerciser at the end
62 ** go into detail about tuning and how you can gauge the optimizer's
66 /****************************************************************************
68 * Constants and macros for optimizer tuning.
70 ****************************************************************************/
73 * The average overhead of a full optimization computation in character
74 * transmission times. If it's too high, the algorithm will be a bit
75 * over-biased toward using cup rather than local motions; if it's too
76 * low, the algorithm may spend more time than is strictly optimal
77 * looking for non-cup motions. Profile the optimizer using the `t'
78 * command of the exerciser (see below), and round to the nearest integer.
80 * Yes, I (esr) thought about computing expected overhead dynamically, say
81 * by derivation from a running average of optimizer times. But the
82 * whole point of this optimization is to *decrease* the frequency of
85 #define COMPUTE_OVERHEAD 1 /* I use a 90MHz Pentium @ 9.6Kbps */
88 * LONG_DIST is the distance we consider to be just as costly to move over as a
89 * cup sequence is to emit. In other words, it's the length of a cup sequence
90 * adjusted for average computation overhead. The magic number is the length
91 * of "\033[yy;xxH", the typical cup sequence these days.
93 #define LONG_DIST (8 - COMPUTE_OVERHEAD)
96 * Tell whether a motion is optimizable by local motions. Needs to be cheap to
97 * compute. In general, all the fast moves go to either the right or left edge
98 * of the screen. So any motion to a location that is (a) further away than
99 * LONG_DIST and (b) further inward from the right or left edge than LONG_DIST,
100 * we'll consider nonlocal.
102 #define NOT_LOCAL(fy, fx, ty, tx) ((tx > LONG_DIST) && (tx < screen_lines - 1 - LONG_DIST) && (abs(ty-fy) + abs(tx-fx) > LONG_DIST))
104 /****************************************************************************
106 * External interfaces
108 ****************************************************************************/
111 * For this code to work OK, the following components must live in the
114 * int _char_padding; // cost of character put
115 * int _cr_cost; // cost of (carriage_return)
116 * int _cup_cost; // cost of (cursor_address)
117 * int _home_cost; // cost of (cursor_home)
118 * int _ll_cost; // cost of (cursor_to_ll)
120 * int _ht_cost; // cost of (tab)
121 * int _cbt_cost; // cost of (backtab)
123 * int _cub1_cost; // cost of (cursor_left)
124 * int _cuf1_cost; // cost of (cursor_right)
125 * int _cud1_cost; // cost of (cursor_down)
126 * int _cuu1_cost; // cost of (cursor_up)
127 * int _cub_cost; // cost of (parm_cursor_left)
128 * int _cuf_cost; // cost of (parm_cursor_right)
129 * int _cud_cost; // cost of (parm_cursor_down)
130 * int _cuu_cost; // cost of (parm_cursor_up)
131 * int _hpa_cost; // cost of (column_address)
132 * int _vpa_cost; // cost of (row_address)
133 * int _ech_cost; // cost of (erase_chars)
134 * int _rep_cost; // cost of (repeat_char)
136 * The TABS_OK switch controls whether it is reliable to use tab/backtabs
137 * for local motions. On many systems, it's not, due to uncertainties about
138 * tab delays and whether or not tabs will be expanded in raw mode. If you
139 * have parm_right_cursor, tab motions don't win you a lot anyhow.
142 #include <curses.priv.h>
146 MODULE_ID("$Id: lib_mvcur.c,v 1.37 1997/05/03 22:15:26 Peter.Wemm Exp $")
148 #define STRLEN(s) (s != 0) ? strlen(s) : 0
150 #define CURRENT_ATTR SP->_current_attr /* current phys attribute */
151 #define CURRENT_ROW SP->_cursrow /* phys cursor row */
152 #define CURRENT_COLUMN SP->_curscol /* phys cursor column */
153 #define REAL_ATTR SP->_current_attr /* phys current attribute */
154 #define WANT_CHAR(y, x) SP->_newscr->_line[y].text[x] /* desired state */
155 #define BAUDRATE SP->_baudrate /* bits per second */
158 #include <sys/time.h>
160 static bool profiling = FALSE;
166 static void save_curs(void);
167 static void restore_curs(void);
168 static int cost_of(const char *const cap, int affcnt);
169 static int normalized_cost(const char *const cap, int affcnt);
171 /****************************************************************************
173 * Initialization/wrapup (including cost pre-computation)
175 ****************************************************************************/
179 trace_cost_of(const char *capname, const char *cap, int affcnt)
181 int result = cost_of(cap,affcnt);
182 TR(TRACE_CHARPUT|TRACE_MOVE, ("CostOf %s %d", capname, result));
185 #define CostOf(cap,affcnt) trace_cost_of(#cap,cap,affcnt);
188 trace_normalized_cost(const char *capname, const char *cap, int affcnt)
190 int result = normalized_cost(cap,affcnt);
191 TR(TRACE_CHARPUT|TRACE_MOVE, ("NormalizedCost %s %d", capname, result));
194 #define NormalizedCost(cap,affcnt) trace_normalized_cost(#cap,cap,affcnt);
198 #define CostOf(cap,affcnt) cost_of(cap,affcnt);
199 #define NormalizedCost(cap,affcnt) normalized_cost(cap,affcnt);
203 static int cost_of(const char *const cap, int affcnt)
204 /* compute the cost of a given operation */
213 for (cp = cap; *cp; cp++)
215 /* extract padding, either mandatory or required */
216 if (cp[0] == '$' && cp[1] == '<' && strchr(cp, '>'))
220 for (cp += 2; *cp != '>'; cp++)
223 number = number * 10 + (*cp - '0');
225 number += (*++cp - 10) / 10.0;
230 cum_cost += number * 10;
233 cum_cost += SP->_char_padding;
236 return((int)cum_cost);
240 static int normalized_cost(const char *const cap, int affcnt)
241 /* compute the effective character-count for an operation (round up) */
243 int cost = cost_of(cap, affcnt);
244 if (cost != INFINITY)
245 cost = (cost + SP->_char_padding - 1) / SP->_char_padding;
249 static void reset_scroll_region(void)
250 /* Set the scroll-region to a known state (the default) */
252 if (change_scroll_region)
254 /* change_scroll_region may trash the cursor location */
256 TPUTS_TRACE("change_scroll_region");
257 putp(tparm(change_scroll_region, 0, screen_lines - 1));
262 void _nc_mvcur_resume(void)
263 /* what to do at initialization time and after each shellout */
265 /* initialize screen for cursor access */
268 TPUTS_TRACE("enter_ca_mode");
273 * Doing this here rather than in _nc_mvcur_wrap() ensures that
274 * ncurses programs will see a reset scroll region even if a
275 * program that messed with it died ungracefully.
277 * This also undoes the effects of terminal init strings that assume
278 * they know the screen size. This is useful when you're running
279 * a vt100 emulation through xterm.
281 reset_scroll_region();
284 void _nc_mvcur_init(void)
285 /* initialize the cost structure */
288 * 9 = 7 bits + 1 parity + 1 stop.
290 SP->_char_padding = (9 * 1000 * 10) / (BAUDRATE > 0 ? BAUDRATE : 9600);
291 if (SP->_char_padding <= 0)
292 SP->_char_padding = 1; /* must be nonzero */
293 TR(TRACE_CHARPUT|TRACE_MOVE, ("char_padding %d msecs", SP->_char_padding));
295 /* non-parameterized local-motion strings */
296 SP->_cr_cost = CostOf(carriage_return, 0);
297 SP->_home_cost = CostOf(cursor_home, 0);
298 SP->_ll_cost = CostOf(cursor_to_ll, 0);
300 SP->_ht_cost = CostOf(tab, 0);
301 SP->_cbt_cost = CostOf(back_tab, 0);
303 SP->_cub1_cost = CostOf(cursor_left, 0);
304 SP->_cuf1_cost = CostOf(cursor_right, 0);
305 SP->_cud1_cost = CostOf(cursor_down, 0);
306 SP->_cuu1_cost = CostOf(cursor_up, 0);
309 * Assumption: if the terminal has memory_relative addressing, the
310 * initialization strings or smcup will set single-page mode so we
311 * can treat it like absolute screen addressing. This seems to be true
312 * for all cursor_mem_address terminal types in the terminfo database.
314 SP->_address_cursor = cursor_address ? cursor_address : cursor_mem_address;
317 * Parametrized local-motion strings. This static cost computation
318 * depends on the following assumptions:
320 * (1) They never have * padding. In the entire master terminfo database
321 * as of March 1995, only the obsolete Zenith Z-100 pc violates this.
322 * (Proportional padding is found mainly in insert, delete and scroll
325 * (2) The average case of cup has two two-digit parameters. Strictly,
326 * the average case for a 24 * 80 screen has ((10*10*(1 + 1)) +
327 * (14*10*(1 + 2)) + (10*70*(2 + 1)) + (14*70*4)) / (24*80) = 3.458
328 * digits of parameters. On a 25x80 screen the average is 3.6197.
329 * On larger screens the value gets much closer to 4.
331 * (3) The average case of cub/cuf/hpa/ech/rep has 2 digits of parameters
332 * (strictly, (((10 * 1) + (70 * 2)) / 80) = 1.8750).
334 * (4) The average case of cud/cuu/vpa has 2 digits of parameters
335 * (strictly, (((10 * 1) + (14 * 2)) / 24) = 1.5833).
337 * All these averages depend on the assumption that all parameter values
338 * are equally probable.
340 SP->_cup_cost = CostOf(tparm(SP->_address_cursor, 23, 23), 1);
341 SP->_cub_cost = CostOf(tparm(parm_left_cursor, 23), 1);
342 SP->_cuf_cost = CostOf(tparm(parm_right_cursor, 23), 1);
343 SP->_cud_cost = CostOf(tparm(parm_down_cursor, 23), 1);
344 SP->_cuu_cost = CostOf(tparm(parm_up_cursor, 23), 1);
345 SP->_hpa_cost = CostOf(tparm(column_address, 23), 1);
346 SP->_vpa_cost = CostOf(tparm(row_address, 23), 1);
348 /* non-parameterized screen-update strings */
349 SP->_ed_cost = NormalizedCost(clr_eos, 1);
350 SP->_el_cost = NormalizedCost(clr_eol, 1);
351 SP->_el1_cost = NormalizedCost(clr_bol, 1);
352 SP->_dch1_cost = NormalizedCost(delete_character, 1);
353 SP->_ich1_cost = NormalizedCost(insert_character, 1);
355 /* parameterized screen-update strings */
356 SP->_dch_cost = NormalizedCost(tparm(parm_dch, 23), 1);
357 SP->_ich_cost = NormalizedCost(tparm(parm_ich, 23), 1);
358 SP->_ech_cost = NormalizedCost(tparm(erase_chars, 23), 1);
359 SP->_rep_cost = NormalizedCost(tparm(repeat_char, ' ', 23), 1);
361 SP->_cup_ch_cost = NormalizedCost(tparm(SP->_address_cursor, 23, 23), 1);
362 SP->_hpa_ch_cost = NormalizedCost(tparm(column_address, 23), 1);
364 /* pre-compute some capability lengths */
365 SP->_carriage_return_length = STRLEN(carriage_return);
366 SP->_cursor_home_length = STRLEN(cursor_home);
367 SP->_cursor_to_ll_length = STRLEN(cursor_to_ll);
370 * A different, possibly better way to arrange this would be to set
371 * SP->_endwin = TRUE at window initialization time and let this be
372 * called by doupdate's return-from-shellout code.
377 void _nc_mvcur_wrap(void)
378 /* wrap up cursor-addressing mode */
380 reset_scroll_region();
383 TPUTS_TRACE("exit_ca_mode");
388 /****************************************************************************
390 * Optimized cursor movement
392 ****************************************************************************/
395 * Perform repeated-append, returning cost
398 repeated_append (int total, int num, int repeat, char *dst, const char *src)
400 register size_t src_len = strlen(src);
401 register size_t dst_len = STRLEN(dst);
403 if ((dst_len + repeat * src_len) < OPT_SIZE-1) {
404 total += (num * repeat);
407 while (repeat-- > 0) {
408 (void) strcpy(dst, src);
419 #define NEXTTAB(fr) (fr + init_tabs - (fr % init_tabs))
420 #define LASTTAB(fr) (fr - init_tabs + (fr % init_tabs))
422 /* Note: we'd like to inline this for speed, but GNU C barfs on the attempt. */
425 relative_move(char *result, int from_y,int from_x,int to_y,int to_x, bool ovw)
426 /* move via local motions (cuu/cuu1/cud/cud1/cub1/cub/cuf1/cuf/vpa/hpa) */
428 int n, vcost = 0, hcost = 0;
440 (void) strcpy(result, tparm(row_address, to_y));
441 vcost = SP->_vpa_cost;
448 if (parm_down_cursor && SP->_cud_cost < vcost)
451 (void) strcpy(result, tparm(parm_down_cursor, n));
452 vcost = SP->_cud_cost;
455 if (cursor_down && (n * SP->_cud1_cost < vcost))
459 vcost = repeated_append(vcost, SP->_cud1_cost, n, result, cursor_down);
462 else /* (to_y < from_y) */
466 if (parm_up_cursor && SP->_cup_cost < vcost)
469 (void) strcpy(result, tparm(parm_up_cursor, n));
470 vcost = SP->_cup_cost;
473 if (cursor_up && (n * SP->_cuu1_cost < vcost))
477 vcost = repeated_append(vcost, SP->_cuu1_cost, n, result, cursor_up);
481 if (vcost == INFINITY)
486 result += strlen(result);
497 (void) strcpy(result, tparm(column_address, to_x));
498 hcost = SP->_hpa_cost;
505 if (parm_right_cursor && SP->_cuf_cost < hcost)
508 (void) strcpy(result, tparm(parm_right_cursor, n));
509 hcost = SP->_cuf_cost;
519 /* use hard tabs, if we have them, to do as much as possible */
520 if (init_tabs > 0 && tab)
524 for (fr = from_x; (nxt = NEXTTAB(fr)) <= to_x; fr = nxt)
526 lhcost = repeated_append(lhcost, SP->_ht_cost, 1, try, tab);
527 if (lhcost == INFINITY)
536 #if defined(REAL_ATTR) && defined(WANT_CHAR)
538 * If we have no attribute changes, overwrite is cheaper.
539 * Note: must suppress this by passing in ovw = FALSE whenever
540 * WANT_CHAR would return invalid data. In particular, this
541 * is true between the time a hardware scroll has been done
542 * and the time the structure WANT_CHAR would access has been
549 for (i = 0; i < n; i++)
550 if ((WANT_CHAR(to_y, from_x + i) & A_ATTRIBUTES) != CURRENT_ATTR)
561 sp = try + strlen(try);
563 for (i = 0; i < n; i++)
564 *sp++ = WANT_CHAR(to_y, from_x + i);
566 lhcost += n * SP->_char_padding;
569 #endif /* defined(REAL_ATTR) && defined(WANT_CHAR) */
571 lhcost = repeated_append(lhcost, SP->_cuf1_cost, n, try, cursor_right);
577 (void) strcpy(result, try);
582 else /* (to_x < from_x) */
586 if (parm_left_cursor && SP->_cub_cost < hcost)
589 (void) strcpy(result, tparm(parm_left_cursor, n));
590 hcost = SP->_cub_cost;
600 if (init_tabs > 0 && back_tab)
604 for (fr = from_x; (nxt = LASTTAB(fr)) >= to_x; fr = nxt)
606 lhcost = repeated_append(lhcost, SP->_cbt_cost, 1, try, back_tab);
607 if (lhcost == INFINITY)
615 lhcost = repeated_append(lhcost, SP->_cub1_cost, n, try, cursor_left);
620 (void) strcpy(result, try);
626 if (hcost == INFINITY)
630 return(vcost + hcost);
632 #endif /* !NO_OPTIMIZE */
635 * With the machinery set up above, it's conceivable that
636 * onscreen_mvcur could be modified into a recursive function that does
637 * an alpha-beta search of motion space, as though it were a chess
638 * move tree, with the weight function being boolean and the search
639 * depth equated to length of string. However, this would jack up the
640 * computation cost a lot, especially on terminals without a cup
641 * capability constraining the search tree depth. So we settle for
642 * the simpler method below.
646 onscreen_mvcur(int yold,int xold,int ynew,int xnew, bool ovw)
647 /* onscreen move from (yold, xold) to (ynew, xnew) */
649 char use[OPT_SIZE], *sp;
650 int tactic = 0, newcost, usecost = INFINITY;
653 struct timeval before, after;
655 gettimeofday(&before, NULL);
658 /* tactic #0: use direct cursor addressing */
659 sp = tparm(SP->_address_cursor, ynew, xnew);
663 (void) strcpy(use, sp);
664 usecost = SP->_cup_cost;
666 #if defined(TRACE) || defined(NCURSES_TEST)
667 if (!(_nc_optimize_enable & OPTIMIZE_MVCUR))
672 * We may be able to tell in advance that the full optimization
673 * will probably not be worth its overhead. Also, don't try to
674 * use local movement if the current attribute is anything but
675 * A_NORMAL...there are just too many ways this can screw up
676 * (like, say, local-movement \n getting mapped to some obscure
677 * character because A_ALTCHARSET is on).
679 if (yold == -1 || xold == -1 ||
680 REAL_ATTR != A_NORMAL || NOT_LOCAL(yold, xold, ynew, xnew))
685 (void) fputs("nonlocal\n", stderr);
686 goto nonlocal; /* always run the optimizer if profiling */
695 /* tactic #1: use local movement */
696 if (yold != -1 && xold != -1
697 && ((newcost=relative_move(NULL, yold, xold, ynew, xnew, ovw))!=INFINITY)
698 && newcost < usecost)
704 /* tactic #2: use carriage-return + local movement */
705 if (yold < screen_lines - 1 && xold < screen_columns - 1)
708 && ((newcost=relative_move(NULL, yold,0,ynew,xnew, ovw)) != INFINITY)
709 && SP->_cr_cost + newcost < usecost)
712 usecost = SP->_cr_cost + newcost;
716 /* tactic #3: use home-cursor + local movement */
718 && ((newcost=relative_move(NULL, 0, 0, ynew, xnew, ovw)) != INFINITY)
719 && SP->_home_cost + newcost < usecost)
722 usecost = SP->_home_cost + newcost;
725 /* tactic #4: use home-down + local movement */
727 && ((newcost=relative_move(NULL, screen_lines-1, 0, ynew, xnew, ovw)) != INFINITY)
728 && SP->_ll_cost + newcost < usecost)
731 usecost = SP->_ll_cost + newcost;
735 * tactic #5: use left margin for wrap to right-hand side,
736 * unless strange wrap behavior indicated by xenl might hose us.
738 if (auto_left_margin && !eat_newline_glitch
739 && yold > 0 && yold < screen_lines - 1 && cursor_left
740 && ((newcost=relative_move(NULL, yold-1, screen_columns-1, ynew, xnew, ovw)) != INFINITY)
741 && SP->_cr_cost + SP->_cub1_cost + newcost + newcost < usecost)
744 usecost = SP->_cr_cost + SP->_cub1_cost + newcost;
748 * These cases are ordered by estimated relative frequency.
753 (void) relative_move(use, yold, xold, ynew, xnew, ovw);
754 else if (tactic == 2)
756 (void) strcpy(use, carriage_return);
757 (void) relative_move(use + SP->_carriage_return_length,
758 yold,0,ynew,xnew, ovw);
760 else if (tactic == 3)
762 (void) strcpy(use, cursor_home);
763 (void) relative_move(use + SP->_cursor_home_length,
764 0, 0, ynew, xnew, ovw);
766 else if (tactic == 4)
768 (void) strcpy(use, cursor_to_ll);
769 (void) relative_move(use + SP->_cursor_to_ll_length,
770 screen_lines-1, 0, ynew, xnew, ovw);
772 else /* if (tactic == 5) */
776 (void) strcat(use, carriage_return);
777 (void) strcat(use, cursor_left);
778 (void) relative_move(use + strlen(use),
779 yold-1, screen_columns-1, ynew, xnew, ovw);
782 #endif /* !NO_OPTIMIZE */
785 gettimeofday(&after, NULL);
786 diff = after.tv_usec - before.tv_usec
787 + (after.tv_sec - before.tv_sec) * 1000000;
789 (void) fprintf(stderr, "onscreen: %d msec, %f 28.8Kbps char-equivalents\n",
790 (int)diff, diff/288);
794 if (usecost != INFINITY)
796 TPUTS_TRACE("mvcur");
797 tputs(use, 1, _nc_outch);
804 int mvcur(int yold, int xold, int ynew, int xnew)
805 /* optimized cursor move from (yold, xold) to (ynew, xnew) */
807 TR(TRACE_MOVE, ("mvcur(%d,%d,%d,%d) called", yold, xold, ynew, xnew));
809 if (yold == ynew && xold == xnew)
813 * Most work here is rounding for terminal boundaries getting the
814 * column position implied by wraparound or the lack thereof and
815 * rolling up the screen to get ynew on the screen.
818 if (xnew >= screen_columns)
820 ynew += xnew / screen_columns;
821 xnew %= screen_columns;
823 if (xold >= screen_columns)
827 l = (xold + 1) / screen_columns;
829 if (yold >= screen_lines)
830 l -= (yold - screen_lines - 1);
835 TPUTS_TRACE("newline");
836 tputs(newline, 0, _nc_outch);
845 TPUTS_TRACE("carriage_return");
846 tputs(carriage_return, 0, _nc_outch);
855 if (yold > screen_lines - 1)
856 yold = screen_lines - 1;
857 if (ynew > screen_lines - 1)
858 ynew = screen_lines - 1;
860 /* destination location is on screen now */
861 return(onscreen_mvcur(yold, xold, ynew, xnew, TRUE));
865 /****************************************************************************
867 * Cursor save_restore
869 ****************************************************************************/
871 /* assumption: sc/rc is faster than cursor addressing */
873 static int oy, ox; /* ugh, mvcur_scrolln() needs to see this */
875 static void save_curs(void)
877 if (save_cursor && restore_cursor)
879 TPUTS_TRACE("save_cursor");
887 static void restore_curs(void)
889 if (save_cursor && restore_cursor)
891 TPUTS_TRACE("restore_cursor");
892 putp(restore_cursor);
895 onscreen_mvcur(-1, -1, oy, ox, FALSE);
898 /****************************************************************************
900 * Physical-scrolling support
902 ****************************************************************************/
904 static int DoTheScrolling(int n, int top, int bot, int maxy)
905 /* scroll region from top to bot by n lines */
910 * This code was adapted from Keith Bostic's hardware scrolling
911 * support for 4.4BSD curses. I (esr) translated it to use terminfo
912 * capabilities, narrowed the call interface slightly, and cleaned
913 * up some convoluted tests. I also added support for the memory_above
914 * memory_below, and non_dest_scroll_region capabilities.
916 * For this code to work, we must have either
917 * change_scroll_region and scroll forward/reverse commands, or
918 * insert and delete line capabilities.
919 * When the scrolling region has been set, the cursor has to
920 * be at the last line of the region to make the scroll
923 * This code makes one aesthetic decision in the opposite way from
924 * BSD curses. BSD curses preferred pairs of il/dl operations
925 * over scrolls, allegedly because il/dl looked faster. We, on
926 * the other hand, prefer scrolls because (a) they're just as fast
927 * on many terminals and (b) using them avoids bouncing an
928 * unchanged bottom section of the screen up and down, which is
934 * Explicitly clear if stuff pushed off top of region might
935 * be saved by the terminal.
937 if (non_dest_scroll_region || (memory_above && top == 0)) {
938 for (i = 0; i < n; i++)
941 TPUTS_TRACE("clr_eol");
942 tputs(clr_eol, n, _nc_outch);
946 if (change_scroll_region && (scroll_forward || parm_index))
948 TPUTS_TRACE("change_scroll_region");
949 tputs(tparm(change_scroll_region, top, bot), 0, _nc_outch);
951 onscreen_mvcur(-1, -1, bot, 0, TRUE);
953 if (parm_index != NULL)
955 TPUTS_TRACE("parm_index");
956 tputs(tparm(parm_index, n, 0), n, _nc_outch);
960 for (i = 0; i < n; i++)
962 TPUTS_TRACE("scroll_forward");
963 tputs(scroll_forward, 0, _nc_outch);
966 TPUTS_TRACE("change_scroll_region");
967 tputs(tparm(change_scroll_region, 0, maxy), 0, _nc_outch);
969 else if (parm_index && top == 0 && bot == maxy)
971 onscreen_mvcur(oy, ox, bot, 0, TRUE);
972 TPUTS_TRACE("parm_index");
973 tputs(tparm(parm_index, n, 0), n, _nc_outch);
975 else if (scroll_forward && top == 0 && bot == maxy)
977 onscreen_mvcur(oy, ox, bot, 0, TRUE);
978 for (i = 0; i < n; i++)
980 TPUTS_TRACE("scroll_forward");
981 tputs(scroll_forward, 0, _nc_outch);
985 && (parm_delete_line || delete_line)
986 && (parm_insert_line || insert_line))
988 onscreen_mvcur(oy, ox, top, 0, TRUE);
990 if (parm_delete_line)
992 TPUTS_TRACE("parm_delete_line");
993 tputs(tparm(parm_delete_line, n, 0), n, _nc_outch);
997 for (i = 0; i < n; i++)
999 TPUTS_TRACE("parm_index");
1000 tputs(delete_line, 0, _nc_outch);
1004 onscreen_mvcur(top, 0, bot - n + 1, 0, FALSE);
1006 /* Push down the bottom region. */
1007 if (parm_insert_line)
1009 TPUTS_TRACE("parm_insert_line");
1010 tputs(tparm(parm_insert_line, n, 0), n, _nc_outch);
1014 for (i = 0; i < n; i++)
1016 TPUTS_TRACE("insert_line");
1017 tputs(insert_line, 0, _nc_outch);
1027 * Do explicit clear to end of region if it's possible that the
1028 * terminal might hold on to stuff we push off the end.
1030 if (non_dest_scroll_region || (memory_below && bot == maxy))
1032 if (bot == maxy && clr_eos)
1034 mvcur(-1, -1, lines + n, 0);
1035 TPUTS_TRACE("clr_eos");
1036 tputs(clr_eos, n, _nc_outch);
1040 for (i = 0; i < -n; i++)
1042 mvcur(-1, -1, lines + n + i, 0);
1043 TPUTS_TRACE("clr_eol");
1044 tputs(clr_eol, n, _nc_outch);
1049 if (change_scroll_region && (scroll_reverse || parm_rindex))
1051 TPUTS_TRACE("change_scroll_region");
1052 tputs(tparm(change_scroll_region, top, bot), 0, _nc_outch);
1054 onscreen_mvcur(-1, -1, top, 0, TRUE);
1058 TPUTS_TRACE("parm_rindex");
1059 tputs(tparm(parm_rindex, -n, 0), -n, _nc_outch);
1063 for (i = n; i < 0; i++)
1065 TPUTS_TRACE("scroll_reverse");
1066 tputs(scroll_reverse, 0, _nc_outch);
1069 TPUTS_TRACE("change_scroll_region");
1070 tputs(tparm(change_scroll_region, 0, maxy), 0, _nc_outch);
1072 else if (parm_rindex && top == 0 && bot == maxy)
1074 onscreen_mvcur(oy, ox, bot + n + 1, 0, TRUE);
1076 TPUTS_TRACE("parm_rindex");
1077 tputs(tparm(parm_rindex, -n, 0), -n, _nc_outch);
1079 else if (scroll_reverse && top == 0 && bot == maxy)
1081 onscreen_mvcur(-1, -1, 0, 0, TRUE);
1082 for (i = n; i < 0; i++)
1084 TPUTS_TRACE("scroll_reverse");
1085 tputs(scroll_reverse, 0, _nc_outch);
1089 && (parm_delete_line || delete_line)
1090 && (parm_insert_line || insert_line))
1092 onscreen_mvcur(oy, ox, bot + n + 1, 0, TRUE);
1094 if (parm_delete_line)
1096 TPUTS_TRACE("parm_delete_line");
1097 tputs(tparm(parm_delete_line, -n, 0), -n, _nc_outch);
1101 for (i = n; i < 0; i++)
1103 TPUTS_TRACE("delete_line");
1104 tputs(delete_line, 0, _nc_outch);
1108 onscreen_mvcur(bot + n + 1, 0, top, 0, FALSE);
1110 /* Scroll the block down. */
1111 if (parm_insert_line)
1113 TPUTS_TRACE("parm_insert_line");
1114 tputs(tparm(parm_insert_line, -n, 0), -n, _nc_outch);
1118 for (i = n; i < 0; i++)
1120 TPUTS_TRACE("insert_line");
1121 tputs(insert_line, 0, _nc_outch);
1132 int _nc_mvcur_scrolln(int n, int top, int bot, int maxy)
1133 /* scroll region from top to bot by n lines */
1137 TR(TRACE_MOVE, ("mvcur_scrolln(%d, %d, %d, %d)", n, top, bot, maxy));
1140 code = DoTheScrolling(n, top, bot, maxy);
1146 /****************************************************************************
1148 * Movement optimizer test code
1150 ****************************************************************************/
1153 #include <dump_entry.h>
1155 char *_nc_progname = "mvcur";
1157 static unsigned long xmits;
1159 int tputs(const char *string, int affcnt, int (*outc)(int))
1160 /* stub tputs() that dumps sequences in a visible form */
1163 xmits += strlen(string);
1165 (void) fputs(_nc_visbuf(string), stdout);
1169 int putp(const char *string)
1171 return(tputs(string, 1, _nc_outch));
1174 int _nc_outch(int ch)
1180 static char tname[BUFSIZ];
1182 static void load_term(void)
1184 (void) setupterm(tname, STDOUT_FILENO, NULL);
1187 static int roll(int n)
1191 i = (RAND_MAX / n) * n;
1192 while ((j = rand()) >= i)
1197 int main(int argc, char *argv[])
1199 (void) strcpy(tname, getenv("TERM"));
1201 _nc_setupscreen(lines, columns, stdout);
1205 #if HAVE_SETVBUF || HAVE_SETBUFFER
1207 * Undo the effects of our optimization hack, otherwise our interactive
1208 * prompts don't flush properly.
1211 (void) setvbuf(SP->_ofp, malloc(BUFSIZ), _IOLBF, BUFSIZ);
1212 #elif HAVE_SETBUFFER
1213 (void) setbuffer(SP->_ofp, malloc(BUFSIZ), BUFSIZ);
1215 #endif /* HAVE_SETVBUF || HAVE_SETBUFFER */
1217 (void) puts("The mvcur tester. Type ? for help");
1219 fputs("smcup:", stdout);
1224 int fy, fx, ty, tx, n, i;
1225 char buf[BUFSIZ], capname[BUFSIZ];
1227 (void) fputs("> ", stdout);
1228 (void) fgets(buf, sizeof(buf), stdin);
1232 (void) puts("? -- display this help message");
1233 (void) puts("fy fx ty tx -- (4 numbers) display (fy,fx)->(ty,tx) move");
1234 (void) puts("s[croll] n t b m -- display scrolling sequence");
1235 (void) printf("r[eload] -- reload terminal info for %s\n",
1237 (void) puts("l[oad] <term> -- load terminal info for type <term>");
1238 (void) puts("d[elete] <cap> -- delete named capability");
1239 (void) puts("i[nspect] -- display terminal capabilities");
1240 (void) puts("c[ost] -- dump cursor-optimization cost table");
1241 (void) puts("o[optimize] -- toggle movement optimization");
1242 (void) puts("t[orture] <num> -- torture-test with <num> random moves");
1243 (void) puts("q[uit] -- quit the program");
1245 else if (sscanf(buf, "%d %d %d %d", &fy, &fx, &ty, &tx) == 4)
1247 struct timeval before, after;
1251 gettimeofday(&before, NULL);
1252 mvcur(fy, fx, ty, tx);
1253 gettimeofday(&after, NULL);
1255 printf("\" (%ld msec)\n",
1256 after.tv_usec - before.tv_usec + (after.tv_sec - before.tv_sec) * 1000000);
1258 else if (sscanf(buf, "s %d %d %d %d", &fy, &fx, &ty, &tx) == 4)
1260 struct timeval before, after;
1264 gettimeofday(&before, NULL);
1265 _nc_mvcur_scrolln(fy, fx, ty, tx);
1266 gettimeofday(&after, NULL);
1268 printf("\" (%ld msec)\n",
1269 after.tv_usec - before.tv_usec + (after.tv_sec - before.tv_sec) * 1000000);
1271 else if (buf[0] == 'r')
1273 (void) strcpy(tname, getenv("TERM"));
1276 else if (sscanf(buf, "l %s", tname) == 1)
1280 else if (sscanf(buf, "d %s", capname) == 1)
1282 struct name_table_entry const *np = _nc_find_entry(capname,
1283 _nc_info_hash_table);
1286 (void) printf("No such capability as \"%s\"\n", capname);
1289 switch(np->nte_type)
1292 cur_term->type.Booleans[np->nte_index] = FALSE;
1293 (void) printf("Boolean capability `%s' (%d) turned off.\n",
1294 np->nte_name, np->nte_index);
1298 cur_term->type.Numbers[np->nte_index] = -1;
1299 (void) printf("Number capability `%s' (%d) set to -1.\n",
1300 np->nte_name, np->nte_index);
1304 cur_term->type.Strings[np->nte_index] = (char *)NULL;
1305 (void) printf("String capability `%s' (%d) deleted.\n",
1306 np->nte_name, np->nte_index);
1311 else if (buf[0] == 'i')
1313 dump_init((char *)NULL, F_TERMINFO, S_TERMINFO, 70, 0);
1314 dump_entry(&cur_term->type, NULL);
1317 else if (buf[0] == 'o')
1319 if (_nc_optime_enable & OPTIMIZE_MVCUR)
1321 _nc_optimize_enable &=~ OPTIMIZE_MVCUR;
1322 (void) puts("Optimization is now off.");
1326 _nc_optimize_enable |= OPTIMIZE_MVCUR;
1327 (void) puts("Optimization is now on.");
1331 * You can use the `t' test to profile and tune the movement
1332 * optimizer. Use iteration values in three digits or more.
1333 * At above 5000 iterations the profile timing averages are stable
1334 * to within a millisecond or three.
1336 * The `overhead' field of the report will help you pick a
1337 * COMPUTE_OVERHEAD figure appropriate for your processor and
1338 * expected line speed. The `total estimated time' is
1339 * computation time plus a character-transmission time
1340 * estimate computed from the number of transmits and the baud
1343 * Use this together with the `o' command to get a read on the
1344 * optimizer's effectiveness. Compare the total estimated times
1345 * for `t' runs of the same length in both optimized and un-optimized
1346 * modes. As long as the optimized times are less, the optimizer
1349 else if (sscanf(buf, "t %d", &n) == 1)
1351 float cumtime = 0, perchar;
1352 int speeds[] = {2400, 9600, 14400, 19200, 28800, 38400, 0};
1354 srand((unsigned)(getpid() + time((time_t *)0)));
1357 for (i = 0; i < n; i++)
1360 * This does a move test between two random locations,
1361 * Random moves probably short-change the optimizer,
1362 * which will work better on the short moves probably
1363 * typical of doupdate()'s usage pattern. Still,
1364 * until we have better data...
1366 #ifdef FIND_COREDUMP
1367 int from_y = roll(lines);
1368 int to_y = roll(lines);
1369 int from_x = roll(columns);
1370 int to_x = roll(columns);
1372 printf("(%d,%d) -> (%d,%d)\n", from_y, from_x, to_y, to_x);
1373 mvcur(from_y, from_x, to_y, to_x);
1375 mvcur(roll(lines), roll(columns), roll(lines), roll(columns));
1376 #endif /* FIND_COREDUMP */
1383 * Average milliseconds per character optimization time.
1384 * This is the key figure to watch when tuning the optimizer.
1386 perchar = cumtime / n;
1388 (void) printf("%d moves (%ld chars) in %d msec, %f msec each:\n",
1389 n, xmits, (int)cumtime, perchar);
1391 for (i = 0; speeds[i]; i++)
1394 * Total estimated time for the moves, computation and
1395 * transmission both. Transmission time is an estimate
1396 * assuming 9 bits/char, 8 bits + 1 stop bit.
1398 float totalest = cumtime + xmits * 9 * 1e6 / speeds[i];
1401 * Per-character optimization overhead in character transmits
1402 * at the current speed. Round this to the nearest integer
1403 * to figure COMPUTE_OVERHEAD for the speed.
1405 float overhead = speeds[i] * perchar / 1e6;
1407 (void) printf("%6d bps: %3.2f char-xmits overhead; total estimated time %15.2f\n",
1408 speeds[i], overhead, totalest);
1411 else if (buf[0] == 'c')
1413 (void) printf("char padding: %d\n", SP->_char_padding);
1414 (void) printf("cr cost: %d\n", SP->_cr_cost);
1415 (void) printf("cup cost: %d\n", SP->_cup_cost);
1416 (void) printf("home cost: %d\n", SP->_home_cost);
1417 (void) printf("ll cost: %d\n", SP->_ll_cost);
1419 (void) printf("ht cost: %d\n", SP->_ht_cost);
1420 (void) printf("cbt cost: %d\n", SP->_cbt_cost);
1421 #endif /* TABS_OK */
1422 (void) printf("cub1 cost: %d\n", SP->_cub1_cost);
1423 (void) printf("cuf1 cost: %d\n", SP->_cuf1_cost);
1424 (void) printf("cud1 cost: %d\n", SP->_cud1_cost);
1425 (void) printf("cuu1 cost: %d\n", SP->_cuu1_cost);
1426 (void) printf("cub cost: %d\n", SP->_cub_cost);
1427 (void) printf("cuf cost: %d\n", SP->_cuf_cost);
1428 (void) printf("cud cost: %d\n", SP->_cud_cost);
1429 (void) printf("cuu cost: %d\n", SP->_cuu_cost);
1430 (void) printf("hpa cost: %d\n", SP->_hpa_cost);
1431 (void) printf("vpa cost: %d\n", SP->_vpa_cost);
1433 else if (buf[0] == 'x' || buf[0] == 'q')
1436 (void) puts("Invalid command.");
1439 (void) fputs("rmcup:", stdout);
1448 /* lib_mvcur.c ends here */