/*	$OpenBSD: basic.c,v 1.51 2022/05/24 16:42:19 op Exp $	*/

/* This file is in the public domain */

/*
 *		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 <sys/queue.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#include "def.h"

#define percint(n1, n2)		((n1 * (int) n2) * 0.1)

/*
 * Go to beginning of line.
 */
/* ARGSUSED */
int
gotobol(int f, int n)
{
	if (n == 0)
		return (TRUE);

	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 */
int
backchar(int f, int n)
{
	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_headp) {
				if (!(f & FFRAND))
					(void)dobeep_msg("Beginning "
					    "of buffer");
				return (FALSE);
			}
			curwp->w_dotp = lp;
			curwp->w_doto = llength(lp);
			curwp->w_rflag |= WFMOVE;
			curwp->w_dotline--;
		} else
			curwp->w_doto--;
	}
	return (TRUE);
}

/*
 * Go to end of line.
 */
/* ARGSUSED */
int
gotoeol(int f, int n)
{
	if (n == 0)
		return (TRUE);

	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 */
int
forwchar(int f, 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_headp) {
				curwp->w_dotp = lback(curwp->w_dotp);
				if (!(f & FFRAND))
					(void)dobeep_msg("End of buffer");
				return (FALSE);
			}
			curwp->w_doto = 0;
			curwp->w_dotline++;
			curwp->w_rflag |= WFMOVE;
		} else
			curwp->w_doto++;
	}
	return (TRUE);
}

/*
 * Go to the beginning of the buffer. Setting WFFULL is conservative,
 * but almost always the case. A universal argument of higher than 9
 * puts the cursor back to the end of buffer.
 */
int
gotobob(int f, int n)
{
	if (!curwp->w_markp)
		(void) setmark(f, n);
	curwp->w_dotp = bfirstlp(curbp);
	curwp->w_doto = 0;
	curwp->w_rflag |= WFFULL;
	curwp->w_dotline = 1;
	if (f & FFOTHARG && n > 0) {
		if (n > 9)
			gotoeob(0, 0);
		else
			forwline(f, percint(curwp->w_bufp->b_lines, n) - 1);
	}
	return (TRUE);
}

/*
 * Go to the end of the buffer. Leave dot 3 lines from the bottom of the
 * window if buffer length is longer than window length; same as emacs.
 * Setting WFFULL is conservative, but almost always the case. A universal
 * argument of higher than 9 puts the cursor back to the start of buffer.
 */
int
gotoeob(int f, int n)
{
	int		 ln;
	struct line	*lp;

	if (!curwp->w_markp)
		(void) setmark(f, n);
	curwp->w_dotp = blastlp(curbp);
	curwp->w_doto = llength(curwp->w_dotp);
	curwp->w_dotline = curwp->w_bufp->b_lines;

	lp = curwp->w_dotp;
	ln = curwp->w_ntrows - 3;

	if (ln < curwp->w_bufp->b_lines && ln >= 3) {
		while (ln--)
			curwp->w_dotp = lback(curwp->w_dotp);

		curwp->w_linep = curwp->w_dotp;
		curwp->w_dotp = lp;
	}
	if (f & FFOTHARG && n > 0) {
		if (n > 9)
			gotobob(0, 0);
		else
			backline(f, percint(curwp->w_bufp->b_lines, n));
	}

	curwp->w_rflag |= WFFULL;
	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 */
int
forwline(int f, int n)
{
	struct line  *dlp;

	if (n < 0)
		return (backline(f | FFRAND, -n));
	if ((dlp = curwp->w_dotp) == curbp->b_headp) {
		if (!(f & FFRAND))
			(void)dobeep_msg("End of buffer");
		return(TRUE);
	}
	if ((lastflag & CFCPCN) == 0)	/* Fix goal. */
		setgoal();
	thisflag |= CFCPCN;
	if (n == 0)
		return (TRUE);
	while (n--) {
		dlp = lforw(dlp);
		if (dlp == curbp->b_headp) {
			curwp->w_dotp = lback(dlp);
			curwp->w_doto = llength(curwp->w_dotp);
			curwp->w_rflag |= WFMOVE;
			if (!(f & FFRAND))
				(void)dobeep_msg("End of buffer");
			return (TRUE);
		}
		curwp->w_dotline++;
	}
	curwp->w_rflag |= WFMOVE;
	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 */
int
backline(int f, int n)
{
	struct line   *dlp;

	if (n < 0)
		return (forwline(f | FFRAND, -n));
	if ((lastflag & CFCPCN) == 0)	/* Fix goal. */
		setgoal();
	thisflag |= CFCPCN;
	dlp = curwp->w_dotp;
	if (lback(dlp) == curbp->b_headp)  {
		if (!(f & FFRAND))
			(void)dobeep_msg("Beginning of buffer");
		return(TRUE);
	}
	while (n-- && lback(dlp) != curbp->b_headp) {
		dlp = lback(dlp);
		curwp->w_dotline--;
	}
	if (n > 0 && !(f & FFRAND))
		(void)dobeep_msg("Beginning of buffer");
	curwp->w_dotp = dlp;
	curwp->w_doto = getgoal(dlp);
	curwp->w_rflag |= 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(void)
{
	curgoal = getcolpos(curwp);	/* 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.
 */
int
getgoal(struct line *dlp)
{
	int c, i, col = 0;
	char tmp[5];


	for (i = 0; i < llength(dlp); i++) {
		c = lgetc(dlp, i);
		if (c == '\t'
#ifdef	NOTAB
		    && !(curbp->b_flag & BFNOTAB)
#endif
			) {
			col |= 0x07;
			col++;
		} else if (ISCTRL(c) != FALSE) {
			col += 2;
		} else if (isprint(c))
			col++;
		else {
			col += snprintf(tmp, sizeof(tmp), "\\%o", c);
		}
		if (col > curgoal)
			break;
	}
	return (i);
}

/*
 * 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 */
int
forwpage(int f, int n)
{
	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));

	lp = curwp->w_linep;
	while (n--)
		if ((lp = lforw(lp)) == curbp->b_headp) {
			(void)dobeep_msg("End of buffer");
			return(TRUE);
		}
	curwp->w_linep = lp;
	curwp->w_rflag |= WFFULL;

	/* if in current window, don't move dot */
	for (n = curwp->w_ntrows; n-- && lp != curbp->b_headp; lp = lforw(lp))
		if (lp == curwp->w_dotp)
			return (TRUE);

	/* Advance the dot the slow way, for line nos */
	while (curwp->w_dotp != curwp->w_linep) {
		curwp->w_dotp = lforw(curwp->w_dotp);
		curwp->w_dotline++;
	}
	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 */
int
backpage(int f, int n)
{
	struct line  *lp, *lp2;

	if (!(f & FFARG)) {
		n = curwp->w_ntrows - 2;	/* Default scroll.	 */
		if (n <= 0)			/* Don't blow up if the  */
			return (backline(f, 1));/* window is tiny.	 */
	} else if (n < 0)
		return (forwpage(f | FFRAND, -n));

	lp = lp2 = curwp->w_linep;

	while (n-- && lback(lp) != curbp->b_headp) {
		lp = lback(lp);
	}
	if (lp == curwp->w_linep)
		(void)dobeep_msg("Beginning of buffer");

	curwp->w_linep = lp;
	curwp->w_rflag |= WFFULL;

	/* if in current window, don't move dot */
	for (n = curwp->w_ntrows; n-- && lp != curbp->b_headp; lp = lforw(lp))
		if (lp == curwp->w_dotp)
			return (TRUE);

        lp2 = lforw(lp2);

	/* Move the dot the slow way, for line nos */
	while (curwp->w_dotp != lp2) {
                if (curwp->w_dotline <= curwp->w_ntrows)
			goto out;
		curwp->w_dotp = lback(curwp->w_dotp);
		curwp->w_dotline--;
	}
out:
	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.
 */
int
forw1page(int f, int n)
{
	if (!(f & FFARG)) {
		n = 1;
		f = FFUNIV;
	}
	forwpage(f | FFRAND, n);
	return (TRUE);
}

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

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

	if (wheadp->w_wndp == NULL)
		return(dobeep_msg("No other window"));

	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(void)
{
	curwp->w_markp = curwp->w_dotp;
	curwp->w_marko = curwp->w_doto;
	curwp->w_markline = curwp->w_dotline;
}

/*
 * 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 */
int
setmark(int f, int n)
{
	isetmark();
	ewprintf("Mark set");
	return (TRUE);
}

/* Clear the mark, if set. */
/* ARGSUSED */
int
clearmark(int f, int n)
{
	if (!curwp->w_markp)
		return (FALSE);

	curwp->w_markp = NULL;
	curwp->w_marko = 0;
	curwp->w_markline = 0;

	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 */
int
swapmark(int f, int n)
{
	struct line  *odotp;
	int odoto, odotline;

	if (curwp->w_markp == NULL)
		return(dobeep_msg("No mark in this window"));

	odotp = curwp->w_dotp;
	odoto = curwp->w_doto;
	odotline = curwp->w_dotline;
	curwp->w_dotp = curwp->w_markp;
	curwp->w_doto = curwp->w_marko;
	curwp->w_dotline = curwp->w_markline;
	curwp->w_markp = odotp;
	curwp->w_marko = odoto;
	curwp->w_markline = odotline;
	curwp->w_rflag |= 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 */
int
gotoline(int f, int n)
{
	char   buf[32], *bufp;
	const char *err;

	if (!(f & FFARG)) {
		if ((bufp = eread("Goto line: ", buf, sizeof(buf),
		    EFNUL | EFNEW | EFCR)) == NULL)
			return (ABORT);
		if (bufp[0] == '\0')
			return (ABORT);
		n = (int)strtonum(buf, INT_MIN, INT_MAX, &err);
		if (err)
			return(dobeep_msgs("Line number", err));
	}
	return(setlineno(n));
}

/*
 * Set the line number and switch to it.
 */
int
setlineno(int n)
{
	struct line  *clp;

	if (n >= 0) {
		if (n == 0)
			n++;
		curwp->w_dotline = n;
		clp = lforw(curbp->b_headp);	/* "clp" is first line */
		while (--n > 0) {
			if (lforw(clp) == curbp->b_headp) {
				curwp->w_dotline = curwp->w_bufp->b_lines;
				break;
			}
			clp = lforw(clp);
		}
	} else {
		curwp->w_dotline = curwp->w_bufp->b_lines + n;
		clp = lback(curbp->b_headp);	/* "clp" is last line */
		while (n < 0) {
			if (lback(clp) == curbp->b_headp) {
				curwp->w_dotline = 1;
				break;
			}
			clp = lback(clp);
			n++;
		}
	}
	curwp->w_dotp = clp;
	curwp->w_doto = 0;
	curwp->w_rflag |= WFMOVE;
	return (TRUE);
}
