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