/**************************************************************************
 * This program is Copyright (C) 1986-2002 by Jonathan Payne.  JOVE is    *
 * provided by Jonathan and Jovehacks without charge and without          *
 * warranty.  You may copy, modify, and/or distribute JOVE, provided that *
 * this notice is included in all the source files and documentation.     *
 **************************************************************************/

#include "jove.h"
#include "fp.h"
#include "chars.h"
#include "jctype.h"
#include "disp.h"
#include "extend.h"
#include "fmt.h"
#include "term.h"
#include "mac.h"
#include "screen.h"
#include "wind.h"

int	AbortCnt,
	tabstop = 8;	/* VAR: expand tabs to this number of spaces */

struct scrimage
	*DesiredScreen = NULL,
	*PhysScreen = NULL;

private struct screenline	*Savelines = NULL;	/* scratch entries (LI of them) */

private void LEclear proto((struct screenline *));	/* free s_effects component */

private char	*cursor;			/* offset into current Line */

char	*cursend;

/* Position in tercap screen.  INFINITY means "don't know". */
int	CapCol,
	CapLine;

private int
	i_line,
	i_col;

void
make_scr()
{
	register int	i;
	register struct screenline	*ns;
	register char	*nsp;
	static char	*screenchars = NULL;
	static volatile int	oldLI = 0;

	/* In case we are RESHAPING the window! */
	if (DesiredScreen != NULL)
		free((UnivPtr) DesiredScreen);
	if (PhysScreen != NULL)
		free((UnivPtr) PhysScreen);
	i = oldLI;
	oldLI = 0;
	if (Savelines != NULL) {
		/* Note: each screenline in Savelines has a null s_effects
		 * (or is uninitialized).  LEclear must not be applied.
		 */
		free((UnivPtr) Savelines);
	}
	if (Screen != NULL) {
#ifdef HIGHLIGHTING
		for (ns = Screen; ns != &Screen[i]; ns++)
			LEclear(ns);
#endif
		free((UnivPtr) Screen);
	}
	if (screenchars != NULL)
		free((UnivPtr) screenchars);	/* free all the screen data */

	DesiredScreen = (struct scrimage *) malloc((unsigned) LI * sizeof (struct scrimage));
	PhysScreen = (struct scrimage *) malloc((unsigned) LI * sizeof (struct scrimage));

	Savelines = (struct screenline *)
			malloc((unsigned) LI * sizeof(struct screenline));
	ns = Screen = (struct screenline *)
			malloc((unsigned) LI * sizeof(struct screenline));

	nsp = screenchars = (char *) malloc((size_t)CO * LI);

	if (DesiredScreen == NULL
	|| PhysScreen == NULL
	|| Savelines == NULL
	|| ns == NULL
	|| nsp == NULL)
	{
		writef("\n\rCannot malloc screen!\n");
		finish(-1);	/* die! */
	}

	for (i = 0; i < LI; i++) {
		ns->s_line = nsp;
		/* End of Line (nsp[CO-1] is never used) */
		ns->s_roof = nsp + CO - 1;
		ns->s_effects = NOEFFECT;
		nsp += CO;
		ns += 1;

		/* ??? The following is a fudge to placate Purify.
		 * There is a real bug here, so we squash it with
		 * a sledge hammer.  What is the correct fix?
		 */
		{
			register struct scrimage	*p;

			p = &PhysScreen[i];
			p->s_offset = 0;
			p->s_flags = 0;
			p->s_vln = 0;
			p->s_id = NULL_DADDR;
			p->s_lp = NULL;
			p->s_window = NULL;

			p = &DesiredScreen[i];
			p->s_offset = 0;
			p->s_flags = 0;
			p->s_vln = 0;
			p->s_id = NULL_DADDR;
			p->s_lp = NULL;
			p->s_window = NULL;
		}
	}
	oldLI = LI;
	SO_off();
#ifdef HIGHLIGHTING
	US_effect(NO);
#endif
	cl_scr(NO);
}

void
clrline(cp1, cp2)
register char	*cp1,
		*cp2;
{
	while (cp1 < cp2)
		*cp1++ = ' ';
}


/* Output one character (if necessary) at the current position */

#ifdef MAC

/* Character output to bit-mapped screen is very expensive. It makes
 * much more sense to write the entire line at once. So, we print all
 * the characters, whether already there or not, once the line is
 * complete.
 */

private unsigned char sput_buf[255];
private size_t sput_len = 0;

private void
sput_start()
{
/*	if (i_line != CapLine || i_col != CapCol) */
		NPlacur(i_line, i_col);
	sput_len = 0;
}

private void
sput_end()
{
	if (sput_len != 0) {
		writetext(sput_buf, sput_len);
		sput_len = 0;
	}
}

private void
sputc(c)
register char c;
{
	/* if line gets too long for sput_buf, ignore subsequent chars */
	if (sput_len < sizeof(sput_buf)) {
		*cursor++ = c;
		sput_buf[sput_len++] = (c == '0')? 0xAF /* slashed zero */ : c;
		CapCol++;
		i_col++;
	}
}

#else /* !MAC */

# ifdef HIGHLIGHTING
#  define	CharChanged(c)	(*cursor != (char) (c))
# else /* !HIGHLIGHTING */
private bool	ChangeEffect = NO;
#  define	CharChanged(c)	(ChangeEffect || *cursor != (char) (c))
# endif /* !HIGHLIGHTING */

# ifdef IBMPCDOS
/* On PC, we think that trying to avoid painting the character
 * is slower than just doing it.  I wonder if this is true.
 */
#  define	sputc(c)	do_sputc(c)
# else /* !IBMPCDOS */
#  define sputc(c)	{ \
	if (CharChanged(c)) { \
		do_sputc(c); \
	} else { \
		cursor += 1; \
		i_col += 1; \
	} \
}
# endif /* !IBMPCDOS */

private void
do_sputc(c)
register char	c;
{
	if (CharChanged(c)) {
# ifdef ID_CHAR
		INSmode(NO);
# endif
		if (i_line != CapLine || i_col != CapCol)
			Placur(i_line, i_col);
		*cursor++ = c;
# ifdef TERMCAP
		if (UL && c == '_' && *cursor != ' ')
			putstr(" \b");		/* Erase so '_' looks right. */
# endif
		scr_putchar(c);
		AbortCnt -= 1;
		CapCol += 1;
	} else {
		cursor += 1;
	}
	i_col += 1;
}
#endif /* !MAC */

#ifdef HIGHLIGHTING

private void	(*real_effect) ptrproto((bool));

private void
do_hlsputc(hl, oldhl, c)
register const struct LErange *hl;	/* desired highlighting */
register const struct LErange *oldhl;	/* previous highlighting */
char c;
{
	/* assert: hl != NULL && oldhl != NULL
	 * In other words, hl and oldhl must point to real LErange structs.
	 */

	/* The following two initializing expressions use the peculiar
	 * properties of unsigneds to make an efficient range test.
	 */
	void
		(*virtual_effect) ptrproto((bool)) =
			(unsigned)i_col - hl->start < hl->width? hl->high : hl->norm,
		(*underlying_effect) ptrproto((bool)) =
			(unsigned)i_col - oldhl->start < oldhl->width? oldhl->high : oldhl->norm;

	if (*cursor != c || virtual_effect != underlying_effect) {
# ifdef ID_CHAR
		INSmode(NO);
# endif
		if (i_line != CapLine || i_col != CapCol)
			Placur(i_line, i_col);
		if (virtual_effect != real_effect) {
			if (real_effect != NULL)
				real_effect(NO);
			/* instantaneously in neutral state */
			if (virtual_effect != NULL)
				virtual_effect(YES);
			real_effect = virtual_effect;
		}
# ifdef TERMCAP
		if (UL && c == '_' && *cursor != ' ')
			putstr(" \b");		/* Erase so '_' looks right. */
# endif
		*cursor++ = c;
		scr_putchar(c);
		AbortCnt -= 1;
		CapCol += 1;
	} else {
		cursor += 1;
	}
	i_col += 1;
}

#endif /* HIGHLIGHTING */

void
cl_eol()
{
	if (cursor == Curline->s_line)
		LEclear(Curline);	/* in case swrite was not called (hack!) */

	if (cursor < Curline->s_roof) {
#ifdef TERMCAP
		if (CE) {
			Placur(i_line, i_col);
			putpad(CE, 1);
			clrline(cursor, Curline->s_roof);
		} else {
			/* Ugh.  The slow way for dumb terminals. */
			register char *savecp = cursor;

			while (cursor < Curline->s_roof)
				sputc(' ');
			cursor = savecp;
		}
#else /* !TERMCAP */
		Placur(i_line, i_col);
		clr_eoln();	/* MAC and PCSCR define this */
		clrline(cursor, Curline->s_roof);
#endif /* !TERMCAP */
		Curline->s_roof = cursor;
	}
}

void
cl_scr(doit)
bool doit;
{
	register int	i;
	register struct screenline	*sp = Screen;

	for (i = 0; i < LI; i++, sp++) {
		LEclear(sp);
		clrline(sp->s_line, sp->s_roof);
		sp->s_roof = sp->s_line;
		PhysScreen[i].s_id = NULL_DADDR;
	}
	if (doit) {
		clr_page();
		CapCol = CapLine = 0;
		UpdMesg = YES;
	}
}

/* routines to manage a pool of LErange structs */

#ifdef HIGHLIGHTING

union LEspace {
	struct LErange	le;
	union LEspace	*next;
};

private union LEspace *LEfreeHead = NULL;

private struct LErange *
LEnew()
{
	struct LErange	*ret;

	if (LEfreeHead == NULL) {
		LEfreeHead = (union LEspace *) emalloc(sizeof(union LEspace));
		LEfreeHead->next = NULL;
	}
	ret = &LEfreeHead->le;
	LEfreeHead = LEfreeHead->next;
	return ret;
}

#endif /* HIGHLIGHTING */

private void
LEclear(sl)
struct screenline *sl;
{
#ifdef HIGHLIGHTING
	if (sl->s_effects != NOEFFECT) {
		((union LEspace *) sl->s_effects)->next = LEfreeHead;
		LEfreeHead = (union LEspace *) sl->s_effects;
	}
#endif /* HIGHLIGHTING */
	sl->s_effects = NOEFFECT;
}

/* Write `line' at the current position of `cursor'.  Stop when we
 * reach the end of the screen.  Aborts if there is a character
 * waiting.
 *
 * Note: All callers must have "DeTabed" "line", or processed
 * it equivalently -- it is presumed that line contains only
 * displayable characters.
 */

bool
swrite(line, hl, abortable)
register char	*line;
LineEffects	hl;
bool	abortable;
{
	register int	n = cursend - cursor;
	bool	aborted = NO;
	/* Unfortunately, neither of our LineEffects representation
	 * is suitable for representing the state of a partially
	 * updated line.  Consequently, this routine unconditionally
	 * replaces the old hl with the new.  To ensure that the new
	 * hl is correct, we compute MinCol to indicate how far in
	 * the line we must get, and will not abort until we have
	 * reached at least that column.
	 *
	 * This is unacceptably ugly.  We really must switch to a better
	 * representation
	 */
	int	MinCol = 0;
#ifdef HIGHLIGHTING
	/* If either the old line or the new line has effects,
	 * we know that some effects processing is necessary.
	 * If so, we ensure that the old line has an effect
	 * by adding a no-op effect if necessary: this is needed
	 * to ensure Placur does not get into trouble.  (UGLY!)
	 */
	struct LErange	*oldhl = Curline->s_effects;
	static const struct LErange	nohl = { 0, 0, LENULLPROC, LENULLPROC };

	if (oldhl != NOEFFECT) {
		int	w = Curline->s_roof - Curline->s_line;

		if (oldhl->norm != NULL)
			MinCol = w;
		if (oldhl->high != NULL && w > (int)oldhl->start)
			MinCol = jmax(MinCol, jmin(w, (int) (oldhl->start + oldhl->width)));
	}
	/* If either the old line or the new line has effects,
	 * we know that some effects processing is necessary.
	 * If so, we ensure that the old line has an effect
	 * by adding a no-op effect if necessary: this is needed
	 * to ensure Placur does not get into trouble.  (UGLY!)
	 */
	if (hl != NOEFFECT) {
		if (hl->high != NULL)
			MinCol = jmax(MinCol, (int) (hl->start + hl->width));
		if (oldhl == NOEFFECT) {
			oldhl = Curline->s_effects = LEnew();	/* keep Placur on-track */
			*oldhl = nohl;
		}
	}
	real_effect = LENULLPROC;
#else /* !HIGHLIGHTING */
	if (Curline->s_effects != hl)
		MinCol = Curline->s_roof - Curline->s_line;	/* must obliterate old */
#endif /* !HIGHLIGHTING */

	if (n > 0) {
		register ZXchar	c;

#ifdef HIGHLIGHTING
		/* nnhl: non-NULL version of hl (possibly
		 * a no-op) to reduce the cases handled.
		 */
		const struct LErange	*nnhl = hl == NOEFFECT? &nohl : hl;
#		define	spit(c)	{ if (oldhl != NULL) do_hlsputc(nnhl,oldhl,c); else sputc(c); }

#else /* !HIGHLIGHTING */

#		define	spit(c)	sputc(c)

# ifdef MAC
		sput_start();	/* Okay, because no interruption possible */
# else /* !MAC */
		if (hl != Curline->s_effects)
			ChangeEffect = YES;
# endif /* !MAC */

		if (hl != NOEFFECT)
			SO_effect(YES);
#endif /* !HIGHLIGHTING */

		while ((c = ZXC(*line++)) != '\0') {
			if (abortable && i_col >= MinCol && AbortCnt < 0) {
				AbortCnt = ScrBufSize;
				if (PreEmptOutput()) {
					aborted = YES;
					break;
				}
			}
#ifdef TERMCAP
			if (Hazeltine && c == '~')
				c = '`';
#endif
#ifdef CODEPAGE437
			/* ??? Some archane mapping of IBM PC characters.
			 * According to the appendix of the Microsoft MSDOS
			 * Operating System 5.0 User's Guide and Reference,
			 * in Code Page 437 (USA English) ' ', 0x00, and 0xFF are
			 * blank and 0x01 is a face.
			 */
			if (c == 0xFF)
				c = 1;
			else if (c == ' ' && hl != NOEFFECT)
				c = 0xFF;
#endif /* CODEPAGE437 */
			if (--n <= 0) {
				/* We've got one more column -- how will we spend it?
				 * ??? This is probably redundant -- callers do truncation.
				 */
				if (*line != '\0')
					c = '!';
				spit(c);
				break;
			}
			spit(c);
		}
#ifdef HIGHLIGHTING
		if (real_effect != NULL)
			real_effect(NO);
#else /* !HIGHLIGHTING */
# ifdef MAC
		sput_end();	/* flush before reverting SO */
# else /* !MAC */
		ChangeEffect = NO;
# endif /* !MAC */
		if (hl != NOEFFECT)
			SO_off();
#endif /* !HIGHLIGHTING */
		if (cursor > Curline->s_roof)
			Curline->s_roof = cursor;
#		undef	spit
	}
#ifdef HIGHLIGHTING
	if (hl == NOEFFECT)
		LEclear(Curline);
	else
		*(Curline->s_effects) = *hl;
#else /* !HIGHLIGHTING */
	Curline->s_effects = hl;
#endif /* !HIGHLIGHTING */
	return !aborted;
}

void
i_set(nline, ncol)
register int	nline,
		ncol;
{
	Curline = &Screen[nline];
	cursor = Curline->s_line + ncol;
	cursend = &Curline->s_line[CO - 1];
	i_line = nline;
	i_col = ncol;
}

void
SO_off()
{
	SO_effect(NO);
}

#ifdef TERMCAP

void
SO_effect(on)
bool	on;
{
	/* If there are magic cookies, then WHERE the SO string is
	 * printed decides where the SO actually starts on the screen.
	 * So it's important to make sure the cursor is positioned there
	 * anyway.  I think this is right.
	 */
	if (SG != 0) {
		Placur(i_line, i_col);
		i_col += SG;
		CapCol += SG;
		cursor += SG;
	}
	putpad(on? SO : SE, 1);
}

# ifdef HIGHLIGHTING
void
US_effect(on)
bool	on;
{
	if (UG == 0)	/* not used if magic cookies */
		putpad(on? US : UE, 1);
}
# endif /* HIGHLIGHTING */

#endif /* TERMCAP */

/* Insert `num' lines at top, but leave all the lines BELOW `bottom'
 * alone (at least they won't look any different when we are done).
 * This changes the screen array AND does the physical changes.
 */
void
v_ins_line(num, top, bottom)
int num,
    top,
    bottom;
{
	register int	i;

	/* assert(num <= bottom-top+1) */

	/* Blank and save the screen pointers that will fall off the end. */

	for(i = 0; i < num; i++) {
		struct screenline	*sp = &Screen[bottom - i];

		clrline(sp->s_line, sp->s_roof);
		sp->s_roof = sp->s_line;
		LEclear(sp);
		Savelines[i] = *sp;
	}

	/* Num number of bottom lines will be lost.
	 * Copy everything down num number of times.
	 */
	for (i = bottom-num; i >= top; i--)
		Screen[i + num] = Screen[i];

	/* Insert the now-blank saved ones at the top. */

	for (i = 0; i < num; i++)
		Screen[top + i] = Savelines[i];
	i_lines(top, bottom, num);
}

/* Delete `num' lines starting at `top' leaving the lines below `bottom'
 * alone.  This updates the internal image as well as the physical image.
 */
void
v_del_line(num, top, bottom)
int num,
    top,
    bottom;
{
	register int	i;

	/* assert(num <= bottom-top+1) */

	/* Blank and save the lines to be deleted from the top. */

	for (i = 0; i < num; i++) {
		struct screenline	*sp = &Screen[top + i];

		clrline(sp->s_line, sp->s_roof);
		sp->s_roof = sp->s_line;
		LEclear(sp);
		Savelines[i] = *sp;
	}

	/* Copy everything up num number of lines. */

	for (i = top; i + num <= bottom; i++)
		Screen[i] = Screen[i + num];

	/* Restore the now-blank lost lines */

	for (i = 0; i < num; i++)
		Screen[bottom - i] = Savelines[i];
	d_lines(top, bottom, num);
}

#ifdef TERMCAP	/* remainder of this file */

/* The cursor optimization happens here.  You may decide that this
 * is going too far with cursor optimization, or perhaps it should
 * limit the amount of checking to when the output speed is slow.
 * What ever turns you on ...
 */
struct cursaddr {
	int	cm_numchars;
	void	(*cm_proc) ();
};

private char	*Cmstr;
private struct cursaddr	*HorMin,
			*VertMin,
			*DirectMin;

private void
	ForTab proto((int)),
	RetTab proto((int)),
	DownMotion proto((int)),
	UpMotion proto((int)),
	GoDirect proto((int, int)),
	HomeGo proto((int, int)),
	BottomUp proto((int, int));


private struct cursaddr	WarpHor[] = {
	{ 0,	ForTab },
	{ 0,	RetTab }
};

private struct cursaddr	WarpVert[] = {
	{ 0,	DownMotion },
	{ 0,	UpMotion }
};

private struct cursaddr	WarpDirect[] = {
	{ 0,	GoDirect },
	{ 0,	HomeGo },
	{ 0,	BottomUp }
};

# define FORTAB		0	/* Forward using tabs */
# define RETFORTAB	1	/* Beginning of line and then tabs */
# define NUMHOR		2

# define DOWN		0	/* Move down */
# define UPMOVE		1	/* Move up */
# define NUMVERT		2

# define DIRECT		0	/* Using CM */
# define HOME		1	/* HOME	*/
# define LOWER		2	/* Lower Line */
# define NUMDIRECT	3

# define	home()		Placur(0, 0)
# define LowLine()	{ putpad(LL, 1); CapLine = ILI; CapCol = 0; }
# define PrintHo()	{ putpad(HO, 1); CapLine = CapCol = 0; }

private void
GoDirect(line, col)
register int	line,
		col;
{
	putpad(Cmstr, 1);
	CapLine = line;
	CapCol = col;
}

private void
RetTab(col)
register int	col;
{
	scr_putchar('\r');
	CapCol = 0;
	ForTab(col);
}

private void
HomeGo(line, col)
int line,
    col;
{
	PrintHo();
	DownMotion(line);
	ForTab(col);
}

private void
BottomUp(line, col)
register int	line,
		col;
{
	LowLine();
	UpMotion(line);
	ForTab(col);
}

/* Tries to move forward using tabs (if possible).  It tabs to the
 * closest tabstop which means it may go past 'destcol' and backspace
 * to it.
 * Note: changes to this routine must be matched by changes in ForNum.
 */
private void
ForTab(to)
int	to;
{
	if ((to > CapCol+1) && TABS && (phystab > 0)) {
		register int	tabgoal,
				ntabs,
				pts = phystab;

		tabgoal = to + (pts / 2);
		tabgoal -= (tabgoal % pts);

		/* Don't tab to last place or else it is likely to screw up. */
		if (tabgoal >= CO)
			tabgoal -= pts;

		ntabs = (tabgoal / pts) - (CapCol / pts);
		/* If tabbing moves past goal, and goal is more cols back
		 * than we would have had to move forward from our original
		 * position, tab is counterproductive.  Notice that if our
		 * original motion would have been backwards, tab loses too,
		 * so we need not write abs(to-CapCol).
		 */
		if (tabgoal > to && tabgoal-to >= to-CapCol)
			ntabs = 0;
		while (--ntabs >= 0) {
			scr_putchar('\t');
			CapCol = tabgoal;	/* idempotent */
		}
	}

	if (to > CapCol) {
		register char	*cp = &Screen[CapLine].s_line[CapCol];

# ifdef ID_CHAR
		INSmode(NO);	/* we're not just a motion */
# endif
		while (to > CapCol) {
			scr_putchar(*cp++);
			CapCol++;
		}
	}

	while (to < CapCol) {
		putpad(BC, 1);
		CapCol--;
	}
}

private void
DownMotion(destline)
register int	destline;
{
	register int	nlines = destline - CapLine;

	while (--nlines >= 0) {
		putpad(DO, 1);
		CapLine = destline;	/* idempotent */
	}
}

private void
UpMotion(destline)
register int	destline;
{
	register int	nchars = CapLine - destline;

	while (--nchars >= 0) {
		putpad(UP, 1);
		CapLine = destline;	/* idempotent */
	}
}

private int ForNum proto((int from, int to));

void
Placur(line, col)
int line,
    col;
{
# define CursMin(which,addrs,max)	{ \
	register int	best = 0, \
			i; \
	register struct cursaddr	*cp; \
	for (cp = &(addrs)[1], i = 1; i < (max); i++, cp++) \
		if (cp->cm_numchars < (addrs)[best].cm_numchars) \
			best = i; \
	(which) = &(addrs)[best]; \
}

	if (line == CapLine && col == CapCol)
		return;		/* We are already there. */

	/* Number of characters to move horizontally for each case.
	 * 1: Try tabbing to the correct place.
	 * 2: Try going to the beginning of the line, and then tab.
	 */
	{
		int	dcol = col - CapCol;		/* Number of columns to move */
		int	xtracost = 0;	/* Misc addition to cost. */

# ifdef ID_CHAR
		if (IN_INSmode && MI)
			xtracost = IMEIlen;
		/* If we're already in insert mode, it is likely that we will
		 * want to be in insert mode again, after the insert.
		 */
# endif

		if (dcol == 1 || dcol == 0) {		/* Most common case. */
			HorMin = &WarpHor[FORTAB];
			HorMin->cm_numchars = dcol + xtracost;
		} else {
			/* if CapCol is unknown, FORTAB is impossible */
			WarpHor[FORTAB].cm_numchars = CapCol == INFINITY? INFINITY :
				xtracost + ForNum(CapCol, col);
			WarpHor[RETFORTAB].cm_numchars = xtracost + 1 + ForNum(0, col);

			/* Which is the shortest of the bunch */

			CursMin(HorMin, WarpHor, NUMHOR);
		}
	}

	/* Moving vertically is more simple. */
	{
		int	dline = line - CapLine;		/* Number of lines to move */

		WarpVert[DOWN].cm_numchars = dline >= 0 ? dline : INFINITY;
		WarpVert[UPMOVE].cm_numchars = dline < 0 ? ((-dline) * UPlen) : INFINITY;
	}

	/* Which of these is simpler */
	CursMin(VertMin, WarpVert, NUMVERT);

	/* Homing first and lowering first are considered
	 * direct motions.
	 * Homing first's total is the sum of the cost of homing
	 * and the sum of tabbing (if possible) to the right.
	 */
	if (Screen[line].s_effects != NOEFFECT && CM != NULL) {
		/* We are going to a line with inversion or underlining;
		 * Don't try any clever stuff
		 */
		DirectMin = &WarpDirect[DIRECT];
		DirectMin->cm_numchars = 0;
		Cmstr = targ2(CM, col, line);
	} else if (VertMin->cm_numchars + HorMin->cm_numchars <= 3) {
		/* Since no direct method is ever shorter than 3 chars, don't try it. */
		DirectMin = &WarpDirect[DIRECT];	/* A dummy ... */
		DirectMin->cm_numchars = INFINITY;
	} else {
		WarpDirect[DIRECT].cm_numchars = CM != NULL ?
				strlen(Cmstr = targ2(CM, col, line)) : INFINITY;
		WarpDirect[HOME].cm_numchars = HOlen + line +
				WarpHor[RETFORTAB].cm_numchars;
		WarpDirect[LOWER].cm_numchars = LLlen + ((ILI - line) * UPlen) +
				WarpHor[RETFORTAB].cm_numchars;
		CursMin(DirectMin, WarpDirect, NUMDIRECT);
	}

	if (HorMin->cm_numchars + VertMin->cm_numchars < DirectMin->cm_numchars) {
		if (line != CapLine)
			(*(void (*)ptrproto((int)))VertMin->cm_proc)(line);
		if (col != CapCol) {
# ifdef ID_CHAR
			INSmode(NO);	/* We may use real characters ... */
# endif
			(*(void (*)ptrproto((int)))HorMin->cm_proc)(col);
		}
	} else {
# ifdef ID_CHAR
		if (IN_INSmode && !MI)
			INSmode(NO);
# endif
		(*(void (*)ptrproto((int, int)))DirectMin->cm_proc)(line, col);
	}
}


/* Figures out how many characters ForTab() would use to move forward
 * using tabs (if possible).
 * Note: changes to this routine must be matched by changes in ForTab.
 * An exception is that any cost for leaving insert mode has been
 * accounted for by our caller.
 */
private int
ForNum(from, to)
register int	from;
int to;
{
	register int	tabgoal,
			pts = phystab;
	int		ntabs = 0;

	if ((to > from+1) && TABS && (pts > 0)) {
		tabgoal = to + (pts / 2);
		tabgoal -= (tabgoal % pts);
		if (tabgoal >= CO)
			tabgoal -= pts;
		ntabs = (tabgoal / pts) - (from / pts);
		/* If tabbing moves past goal, and goal is more cols back
		 * than we would have had to move forward from our original
		 * position, tab is counterproductive.  Notice that if our
		 * original motion would have been backwards, tab loses too,
		 * so we need not write abs(to-from).
		 */
		if (tabgoal > to && tabgoal-to >= to-from)
			ntabs = 0;
		if (ntabs != 0)
			from = tabgoal;
	}
	return ntabs + (from>to? from-to : to-from);
}

void
i_lines(top, bottom, num)
int top,
    bottom,
    num;
{
	if (CS) {
		putpad(targ2(CS, bottom, top), 1);
		CapCol = CapLine = INFINITY;	/* actually: unknown */
		Placur(top, 0);
		putmulti(SR, M_SR, num, bottom - top);
		putpad(targ2(CS, ILI, 0), 1);
		CapCol = CapLine = INFINITY;	/* actually: unknown */
	} else {
		Placur(bottom - num + 1, 0);
		putmulti(DL, M_DL, num, ILI - CapLine);
		Placur(top, 0);
		putmulti(AL, M_AL, num, ILI - CapLine);
	}
}

void
d_lines(top, bottom, num)
int top,
    bottom,
    num;
{
	if (CS) {
		putpad(targ2(CS, bottom, top), 1);
		CapCol = CapLine = INFINITY;	/* actually: unknown */
		Placur(bottom, 0);
		putmulti(SF, M_SF, num, bottom - top);
		putpad(targ2(CS, ILI, 0), 1);
		CapCol = CapLine = INFINITY;	/* actually: unknown */
	} else {
		Placur(top, 0);
		putmulti(DL, M_DL, num, ILI - top);
		Placur(bottom + 1 - num, 0);
		putmulti(AL, M_AL, num, ILI - CapLine);
	}
}

#endif /* TERMCAP */
