/*
 * Basic cursor motion commands.
 * 
 * The routines in this file are the basic command functions for moving the
 * cursor around on the screen, setting mark, and swapping dot with mark.
 * Only moves between lines, which might make the current buffer framing bad,
 * are hard.
 */
#include	"notab.h"
#include	"gosmacs.h"

#include	"def.h"
#include	"line.h"
#include	"buffer.h"
#include	"window.h"
#include	"no_macro.h"
#ifndef	NO_MACRO
#include	"macro.h"
#endif

#ifdef	ANSI
#include	<stdlib.h>
#endif

VOID setgoal    PROTO((VOID));

/*
 * Go to beginning of line.
 */
/* ARGSUSED */
gotobol(f, n)
{
	curwp->w_doto = 0;
	return (TRUE);
}

/*
 * Move cursor backwards. Do the right thing if the count is less than 0.
 * Error if you try to move back from the beginning of the buffer.
 */
/* ARGSUSED */
backchar(f, n)
	register int    n;
{
	register struct line *lp;

	if (n < 0)
		return forwchar(f, -n);
	while (n--) {
		if (curwp->w_doto == 0) {
			if ((lp = lback(curwp->w_dotp)) == curbp->b_linep) {
				if (!(f & FFRAND))
					ewprintf("Beginning of buffer");
				return (FALSE);
			}
			curwp->w_dotp = lp;
			curwp->w_doto = llength(lp);
			curwp->w_flag |= WFMOVE;
		} else
			curwp->w_doto--;
	}
	return TRUE;
}

/*
 * Go to end of line.
 */
/* ARGSUSED */
gotoeol(f, n)
{
	curwp->w_doto = llength(curwp->w_dotp);
	return (TRUE);
}

/*
 * Move cursor forwards. Do the right thing if the count is less than 0.
 * Error if you try to move forward from the end of the buffer.
 */
/* ARGSUSED */
forwchar(f, n)
	register int    n;
{
	if (n < 0)
		return backchar(f, -n);
	while (n--) {
		if (curwp->w_doto == llength(curwp->w_dotp)) {
			curwp->w_dotp = lforw(curwp->w_dotp);
			if (curwp->w_dotp == curbp->b_linep) {
				curwp->w_dotp = lback(curwp->w_dotp);
				if (!(f & FFRAND))
					ewprintf("End of buffer");
				return FALSE;
			}
			curwp->w_doto = 0;
			curwp->w_flag |= WFMOVE;
		} else
			curwp->w_doto++;
	}
	return TRUE;
}

/*
 * Go to the beginning of the buffer. Setting WFHARD is conservative, but
 * almost always the case.
 */
gotobob(f, n)
{
	(VOID) setmark(f, n);
	curwp->w_dotp = lforw(curbp->b_linep);
	curwp->w_doto = 0;
	curwp->w_flag |= WFHARD;
	return TRUE;
}

/*
 * Go to the end of the buffer. Setting WFHARD is conservative, but almost
 * always the case.
 */
gotoeob(f, n)
{
	(VOID) setmark(f, n);
	curwp->w_dotp = lback(curbp->b_linep);
	curwp->w_doto = llength(curwp->w_dotp);
	curwp->w_flag |= WFHARD;
	return TRUE;
}

/*
 * Move forward by full lines. If the number of lines to move is less than
 * zero, call the backward line function to actually do it. The last command
 * controls how the goal column is set.
 */
/* ARGSUSED */
forwline(f, n)
{
	register struct line *dlp;

	if (n < 0)
		return backline(f | FFRAND, -n);
	if ((lastflag & CFCPCN) == 0)	/* Fix goal.		 */
		setgoal();
	thisflag |= CFCPCN;
	if (n == 0)
		return TRUE;
	dlp = curwp->w_dotp;
	while (dlp != curbp->b_linep && n--)
		dlp = lforw(dlp);
	curwp->w_flag |= WFMOVE;
	if (dlp == curbp->b_linep) {	/* ^N at end of buffer creates lines
					 * (like gnu) */
#ifndef	NO_MACRO
		if (inmacro)
			return FALSE;
#endif
		if (!(curbp->b_flag & BFCHG)) {	/* first change */
			curbp->b_flag |= BFCHG;
			curwp->w_flag |= WFMODE;
		}
		curwp->w_doto = 0;
		while (n-- >= 0) {
			if ((dlp = lallocx(0)) == NULL)
				return FALSE;
			dlp->l_fp = curbp->b_linep;
			dlp->l_bp = lback(dlp->l_fp);
			dlp->l_bp->l_fp = dlp->l_fp->l_bp = dlp;
		}
		curwp->w_dotp = lback(curbp->b_linep);
	} else {
		curwp->w_dotp = dlp;
		curwp->w_doto = getgoal(dlp);
	}
	return TRUE;
}

/*
 * This function is like "forwline", but goes backwards. The scheme is
 * exactly the same. Check for arguments that are less than zero and call
 * your alternate. Figure out the new line and call "movedot" to perform the
 * motion.
 */
/* ARGSUSED */
backline(f, n)
{
	register struct line *dlp;

	if (n < 0)
		return forwline(f | FFRAND, -n);
	if ((lastflag & CFCPCN) == 0)	/* Fix goal.		 */
		setgoal();
	thisflag |= CFCPCN;
	dlp = curwp->w_dotp;
	while (n-- && lback(dlp) != curbp->b_linep)
		dlp = lback(dlp);
	curwp->w_dotp = dlp;
	curwp->w_doto = getgoal(dlp);
	curwp->w_flag |= WFMOVE;
	return TRUE;
}

/*
 * Set the current goal column, which is saved in the external variable
 * "curgoal", to the current cursor column. The column is never off the edge
 * of the screen; it's more like display then show position.
 */
VOID
setgoal()
{

	curgoal = getcolpos() - 1;	/* Get the position.	 */
	/* we can now display past end of display, don't chop! */
}

/*
 * This routine looks at a line (pointed to by the LINE pointer "dlp") and
 * the current vertical motion goal column (set by the "setgoal" routine
 * above) and returns the best offset to use when a vertical motion is made
 * into the line.
 */
getgoal(dlp)
	register struct line *dlp;
{
	register int    c;
	register int    col;
	register int    newcol;
	register int    dbo;

	col = 0;
	dbo = 0;
	while (dbo != llength(dlp)) {
		c = lgetc(dlp, dbo);
		newcol = col;
		if (c == '\t'
#ifdef	NOTAB
		    && !(curbp->b_flag & BFNOTAB)
#endif
			)
			newcol |= 0x07;
		else if (ISCTRL(c) != FALSE)
			++newcol;
		++newcol;
		if (newcol > curgoal)
			break;
		col = newcol;
		++dbo;
	}
	return (dbo);
}

/*
 * Scroll forward by a specified number of lines, or by a full page if no
 * argument. The "2" is the window overlap (this is the default value from
 * ITS EMACS). Because the top line in the window is zapped, we have to do a
 * hard update and get it back.
 */
/* ARGSUSED */
forwpage(f, n)
	register int    n;
{
	register struct line *lp;

	if (!(f & FFARG)) {
		n = curwp->w_ntrows - 2;	/* Default scroll.	 */
		if (n <= 0)	/* Forget the overlap	 */
			n = 1;	/* if tiny window.	 */
	} else if (n < 0)
		return backpage(f | FFRAND, -n);
#ifdef	GOSMACS
	else			/* Convert from pages	 */
		n *= curwp->w_ntrows;	/* to lines.		 */
#endif
	lp = curwp->w_linep;
	while (n-- && lforw(lp) != curbp->b_linep)
		lp = lforw(lp);
	curwp->w_linep = lp;
	curwp->w_flag |= WFHARD;
	/* if in current window, don't move dot */
	for (n = curwp->w_ntrows; n-- && lp != curbp->b_linep; lp = lforw(lp))
		if (lp == curwp->w_dotp)
			return TRUE;
	curwp->w_dotp = curwp->w_linep;
	curwp->w_doto = 0;
	return TRUE;
}

/*
 * This command is like "forwpage", but it goes backwards. The "2", like
 * above, is the overlap between the two windows. The value is from the ITS
 * EMACS manual. The hard update is done because the top line in the window
 * is zapped.
 */
/* ARGSUSED */
backpage(f, n)
	register int    n;
{
	register struct line *lp;

	if (!(f & FFARG)) {
		n = curwp->w_ntrows - 2;	/* Default scroll.	 */
		if (n <= 0)	/* Don't blow up if the */
			n = 1;	/* window is tiny.	 */
	} else if (n < 0)
		return forwpage(f | FFRAND, -n);
#ifdef	GOSMACS
	else			/* Convert from pages	 */
		n *= curwp->w_ntrows;	/* to lines.		 */
#endif
	lp = curwp->w_linep;
	while (n-- && lback(lp) != curbp->b_linep)
		lp = lback(lp);
	curwp->w_linep = lp;
	curwp->w_flag |= WFHARD;
	/* if in current window, don't move dot */
	for (n = curwp->w_ntrows; n-- && lp != curbp->b_linep; lp = lforw(lp))
		if (lp == curwp->w_dotp)
			return TRUE;
	curwp->w_dotp = curwp->w_linep;
	curwp->w_doto = 0;
	return TRUE;
}

/*
 * These functions are provided for compatibility with Gosling's Emacs. They
 * are used to scroll the display up (or down) one line at a time.
 */

#ifdef GOSMACS
forw1page(f, n)
	int             f, n;
{
	if (!(f & FFARG)) {
		n = 1;
		f = FFUNIV;
	}
	return forwpage(f | FFRAND, n);
}

back1page(f, n)
	int             f, n;
{
	if (!(f & FFARG)) {
		n = 1;
		f = FFUNIV;
	}
	return backpage(f | FFRAND, n);
}
#endif

/*
 * Page the other window. Check to make sure it exists, then nextwind,
 * forwpage and restore window pointers.
 */
pagenext(f, n)
{
	register struct window *wp;

	if (wheadp->w_wndp == NULL) {
		ewprintf("No other window");
		return FALSE;
	}
	wp = curwp;
	(VOID) nextwind(f, n);
	(VOID) forwpage(f, n);
	curwp = wp;
	curbp = wp->w_bufp;
	return TRUE;
}

/*
 * Internal set mark routine, used by other functions (daveb).
 */
VOID
isetmark()
{
	curwp->w_markp = curwp->w_dotp;
	curwp->w_marko = curwp->w_doto;
}

/*
 * Set the mark in the current window to the value of dot. A message is
 * written to the echo line.  (ewprintf knows about macros)
 */
/* ARGSUSED */
setmark(f, n)
{
	if (f & FFARG)
		return swapmark(f, n);
	isetmark();
	ewprintf("Mark set");
	return TRUE;
}

/*
 * Swap the values of "dot" and "mark" in the current window. This is pretty
 * easy, because all of the hard work gets done by the standard routine that
 * moves the mark about. The only possible error is "no mark".
 */
/* ARGSUSED */
swapmark(f, n)
{
	register struct line *odotp;
	register int    odoto;

	if (curwp->w_markp == NULL) {
		ewprintf("No mark in this window");
		return FALSE;
	}
	odotp = curwp->w_dotp;
	odoto = curwp->w_doto;
	curwp->w_dotp = curwp->w_markp;
	curwp->w_doto = curwp->w_marko;
	curwp->w_markp = odotp;
	curwp->w_marko = odoto;
	curwp->w_flag |= WFMOVE;
	return TRUE;
}

/*
 * Go to a specific line, mostly for looking up errors in C programs, which
 * give the error a line number. If an argument is present, then it is the
 * line number, else prompt for a line number to use.
 */
/* ARGSUSED */
gotoline(f, n)
	register int    n;
{
	register struct line *clp;
	register int    s;
	char            buf[32];

	if (!(f & FFARG)) {
		if ((s = ereply("Goto line: ", buf, sizeof(buf))) != TRUE)
			return s;
		n = atoi(buf);
	}
	if (n > 0) {
		clp = lforw(curbp->b_linep);	/* "clp" is first line	 */
		while (--n > 0) {
			if (lforw(clp) == curbp->b_linep)
				break;
			clp = lforw(clp);
		}
	} else {
		clp = lback(curbp->b_linep);	/* clp is last line */
		while (n < 0) {
			if (lback(clp) == curbp->b_linep)
				break;
			clp = lback(clp);
			n++;
		}
	}
	curwp->w_dotp = clp;
	curwp->w_doto = 0;
	curwp->w_flag |= WFMOVE;
	return TRUE;
}
