/* Copyright (c) 1990,1991,1992 Chris and John Downey */
#ifndef lint
static char *sccsid = "@(#)movement.c	2.6 (Chris & John Downey) 11/30/92";
#endif

/***

* program name:
    xvi
* function:
    PD version of UNIX "vi" editor, with extensions.
* module name:
    movement.c
* module function:
    Movement of the cursor and the window into the buffer.
* history:
    STEVIE - ST Editor for VI Enthusiasts, Version 3.10
    Originally by Tim Thompson (twitch!tjt)
    Extensive modifications by Tony Andrews (onecom!wldrdg!tony)
    Heavily modified by Chris & John Downey

***/

#include "xvi.h"

/*
 * Shift a buffer's contents down relative to its window,
 * but don't change the display.
 *
 * Return number of physical lines shifted.
 */
int
shiftdown(win, nlines)
register Xviwin	*win;
unsigned	nlines;
{
    register unsigned	k;		/* loop counter */
    int			done = 0;	/* # of physical lines done */

    for (k = 0; k < nlines; k++) {
	register Line	*p;
	register long	physlines;

	/*
	 * Set the top screen line to the previous one.
	 */
	p = win->w_topline->l_prev;
	if (p == win->w_buffer->b_line0)
	    break;

	physlines = plines(win, p);
	done += LONG2INT(physlines);

	win->w_topline = p;
    }

    win->w_curs_new = TRUE;

    return(done);
}

/*
 * Shift a buffer's contents up relative to its window,
 * but don't change the display.
 *
 * Return number of physical lines shifted.
 */
int
shiftup(win, nlines)
register Xviwin	*win;
unsigned	nlines;
{
    register unsigned	k;		/* loop counter */
    int			done = 0;	/* # of physical lines done */

    for (k = 0; k < nlines; k++) {
	register Line	*p;
	register long	physlines;

	/*
	 * Set the top screen line to the next one.
	 */
	p = win->w_topline->l_next;
	if (p == win->w_buffer->b_lastline)
	    break;

	physlines = plines(win, win->w_topline);
	done += LONG2INT(physlines);

	win->w_topline = p;
    }

    win->w_curs_new = TRUE;

    return(done);
}

/*
 * Scroll the screen down 'nlines' lines.
 */
void
scrolldown(win, nlines)
register Xviwin	*win;
unsigned	nlines;
{
    s_ins(win, 0, shiftdown(win, nlines));
}

/*
 * Scroll the screen up 'nlines' lines.
 */
void
scrollup(win, nlines)
register Xviwin	*win;
unsigned	nlines;
{
    s_del(win, 0, shiftup(win, nlines));
}

/*
 * oneup
 * onedown
 * one_left
 * one_right
 *
 * Move cursor one char {left,right} or one line {up,down}.
 *
 * Return TRUE when successful, FALSE when we hit a boundary
 * (of a line, or the file).
 */

/*
 * Move the cursor up 'nlines' lines.
 *
 * Returns TRUE if we moved at all.
 */
bool_t
oneup(win, nlines)
register Xviwin	*win;
long		nlines;
{
    Posn		*pp;
    register Line	*curr_line;
    long		k;

    pp = win->w_cursor;
    curr_line = pp->p_line;

    for (k = 0; k < nlines; k++) {
	/*
	 * Look for the previous line.
	 */
	if (curr_line == win->w_buffer->b_file) {
	    /*
	     * If we've at least backed up a little ...
	     */
	    if (k > 0) {
		break;	/* to update the cursor, etc. */
	    } else {
		return(FALSE);
	    }
	}
	curr_line = curr_line->l_prev;
    }

    pp->p_line = curr_line;
    pp->p_index = 0;
    win->w_curs_new = TRUE;

    info_update(win);

    /*
     * Try to advance to the column we want to be at.
     */
    coladvance(win, win->w_curswant);
    return(TRUE);
}

/*
 * Move the cursor down 'nlines' lines.
 *
 * Returns TRUE if we moved at all.
 */
bool_t
onedown(win, nlines)
register Xviwin	*win;
long		nlines;
{
    Posn		*pp;
    register Line	*curr_line;
    long		k;

    pp = win->w_cursor;
    curr_line = pp->p_line;

    for (k = 0; k < nlines; k++) {
	/*
	 * Look for the next line.
	 */
	if (curr_line->l_next == win->w_buffer->b_lastline) {
	    if (k > 0) {
		break;
	    } else {
		return(FALSE);
	    }
	}
	curr_line = curr_line->l_next;
    }

    pp->p_line = curr_line;
    pp->p_index = 0;
    win->w_curs_new = TRUE;

    info_update(win);

    /*
     * Try to advance to the column we want to be at.
     */
    coladvance(win, win->w_curswant);

    return(TRUE);
}

/*ARGSUSED*/
bool_t
one_left(window, unused)
Xviwin	*window;
bool_t	unused;
{
    Posn	*p;

    window->w_set_want_col = TRUE;
    p = window->w_cursor;

    if (p->p_index > 0) {
	p->p_index--;
	curs_horiz(window, -1);
	return(TRUE);
    } else {
	return(FALSE);
    }
}

/*
 * The move_past_end parameter will be TRUE if moving past the end
 * of the line (onto the first '\0' character) is allowed. We will
 * never move past this character.
 */
bool_t
one_right(window, move_past_end)
Xviwin	*window;
bool_t	move_past_end;
{
    Posn	*p;
    char	*txtp;

    window->w_set_want_col = TRUE;
    p = window->w_cursor;
    txtp = &p->p_line->l_text[p->p_index];

    if (txtp[0] != '\0' && (move_past_end || txtp[1] != '\0')) {
	p->p_index++;
	curs_horiz(window, 1);
	return(TRUE);
    } else {
	return(FALSE);
    }
}

void
begin_line(window, flag)
Xviwin	*window;
bool_t	flag;
{
    register Posn	*pos;
    register int	c;

    pos = window->w_cursor;

    if (flag) {
	char		*startp;
	register char	*p;

	startp = p = pos->p_line->l_text;
	while ((c = *p) != '\0' && p[1] != '\0' && is_space(c)) {
	    p++;
	}
	pos->p_index = p - startp;
    } else {
	pos->p_index = 0;
    }

    window->w_set_want_col = TRUE;
    window->w_curs_new = TRUE;
}

/*
 * coladvance(win, col)
 *
 * Try to advance to the specified column, starting at p.
 */
void
coladvance(win, col)
register Xviwin	*win;
register int	col;
{
    register int	c;
    register char	*tstart, *tp;

    tp = tstart = win->w_cursor->p_line->l_text;
    /*
     * Try to advance to the specified column.
     */
    for (c = 0; c < col; ) {
	if (*tp == '\0') {
	    /*
	     * We're at the end of the line.
	     */
	    break;
	}
	c += vischar(*tp, (char **) NULL, (Pb(P_list) ? -1 : c));

	tp++;
    }

    /*
     * Move back onto last character if we have to.
     */
    if ((*tp == '\0' || c > col) && tp > tstart)
	--tp;
    win->w_cursor->p_index = tp - tstart;
    curwin->w_curs_new = TRUE;
}

/*
 * Go to the specified line number in the current buffer.
 * Position the cursor at the first non-white.
 */
void
do_goto(line)
long line;
{
    move_cursor(curwin, gotoline(curbuf, line), 0);
    begin_line(curwin, TRUE);
}

/*
 * Move the cursor to the specified line, at the specified position.
 */
void
move_cursor(win, lp, index)
Xviwin	*win;
Line	*lp;
int	index;
{
    Posn *p;
    Line *oldline;

    p = win->w_cursor;
    oldline = p->p_line;
    p->p_line = lp;
    p->p_index = index;
    win->w_curs_new = TRUE;
    if (oldline != lp)
	info_update(win);
}

/*
 * Adjust window so that currline is, as far as possible, in the
 * middle of it.
 *
 * Don't update the screen: move_window_to_cursor() does that.
 */
static void
jump(win, currline, halfwinsize)
Xviwin		*win;
Line		*currline;
int		halfwinsize;
{
    register int	count;
    register int	spare;
    register Line	*topline;
    register Line	*filestart = win->w_buffer->b_file;

    spare = win->w_nrows - (unsigned int) plines(win, topline = currline) - 1;
    for (count = 0; count < halfwinsize && topline != filestart;) {
	topline = topline->l_prev;
	count += (unsigned int) plines(win, topline);
	if (count >= spare) {
	    if (count > spare)
		topline = topline->l_next;
	    break;
	}
    }
    win->w_topline = topline;
    update_buffer(win->w_buffer);

    /*
     * The result of calling show_file_info here is that if the
     * cursor moves a long away - e.g. for a "G" command or a search
     * - the status line is updated with the correct line number.
     * This is a small cost compared to updating the whole window.
     */
    show_file_info(win, TRUE);

    win->w_curs_new = TRUE;
}

/*
 * Update the position of the window relative to the buffer, moving
 * the window if necessary to ensure that the cursor is inside the
 * window boundary. w_topline, w_botline and w_cursor must be set to
 * something reasonable for us to be able to test whether the cursor
 * is in the window or not, unless (as a special case) the buffer is
 * empty - in this case, the cursor sits at top left, despite there
 * being no character there for it to sit on.
 *
 * If we have to move the window only a small amount, we try to scroll
 * it rather than redrawing. If we have to redraw, we also rewrite the
 * status line on the principle that it's probably a negligible cost
 * compared to updating the whole window.
 */
void
move_window_to_cursor(win)
register Xviwin		*win;
{
    register Line	*currline;
    int			halfwinsize;
    long		distance;

    currline = win->w_cursor->p_line;
    halfwinsize = (win->w_nrows - 1) / 2;

    /*
     * First stage: move window towards cursor.
     */
    if (bufempty(win->w_buffer)) {
	/*
	 * Special case - file is empty.
	 */
	win->w_topline = win->w_buffer->b_file;
	win->w_cursor->p_line = win->w_buffer->b_file;
	win->w_cursor->p_index = 0;
	win->w_curs_new = TRUE;

    } else if (earlier(currline, win->w_topline)) {
	long	nlines;

	/*
	 * The cursor is above the top of the window; move the
	 * window towards it.
	 */

	nlines = cntplines(win, currline, win->w_topline);

	/*
	 * Decide whether it's worthwhile - & possible - to
	 * scroll to get the window to the right place.
	 *
	 * It's possible if we can have scrolling regions, or
	 * we can insert screen lines & the window is at the
	 * bottom of the screen.
	 *
	 * The actual scrolling is done by s_ins(), in
	 * screen.c.
	 *
	 * If Pn(P_jumpscroll) is js_OFF, we don't use
	 * jumpscroll anyway.
	 */
	if (
	    nlines > halfwinsize
	    ||
	    Pn(P_jumpscroll) == js_ON
	    ||
	    (
		!can_scroll_area
		&&
		Pn(P_jumpscroll) == js_AUTO
		&&
		(
		    !can_ins_line
		    ||
		    win->w_cmdline < (Rows - 1)
		)
	    )
	) {
	    jump(win, currline, halfwinsize);
	} else {
	    s_ins(win, 0, (int) nlines);
	    win->w_topline = currline;
	    update_window(win);
	}
    } else {
	long	nlines;

	/*
	 * The cursor is on or after the last line of the screen,
	 * so we might have to move the screen to it. Check to see
	 * whether we are actually off the screen; if not, we don't
	 * have to do anything. We do this in a certain way so as
	 * not to do any unnecessary calculations; this routine is
	 * called very often, so we must not take too much time.
	 */
	if (earlier(currline, win->w_botline->l_prev) ||
			    (plines(win, currline) == 1 &&
			     earlier(currline, win->w_botline))) {
	    return;
	}
	distance = cntplines(win, win->w_topline, currline->l_next);
	if (distance <= win->w_nrows - 1) {
	    return;
	}

	/*
	 * The cursor is off the bottom of the window, or the
	 * line the cursor is on won't completely fit in the
	 * window.
	 */

	nlines = distance - (win->w_nrows - 1);

	/*
	 * Decide whether it's worthwhile - & possible - to
	 * scroll to get the window to the right place.
	 *
	 * It's possible if we can have scrolling regions, or
	 * we can delete screen lines & the window is at the
	 * bottom of the screen, or it's the only window on
	 * the screen.
	 *
	 * The actual scrolling is done by s_del(), in
	 * screen.c.
	 *
	 * If Pn(P_jumpscroll) is js_OFF, we don't use
	 * jumpscroll anyway.
	 */
	if (
	    nlines > halfwinsize
	    ||
	    Pn(P_jumpscroll) == js_ON
	    ||
	    (
		!can_scroll_area
		&&
		Pn(P_jumpscroll) == js_AUTO
		&&
		(
		    (
			!can_del_line
			&&
			win->w_winpos != 0
		    )
		    ||
		    win->w_cmdline < (Rows - 1)
		)
	    )
	) {
	    jump(win, currline, halfwinsize);
	} else {
	    long	done = 0;
	    Line	*l;
	    Line	*newtopline;

	    /*
	     * Work out where we should place topline in
	     * order that the cursor line is made the
	     * bottom line of the screen.
	     */
	    for (l = currline;; l = l->l_prev) {
		done += plines(win, l);
		if (done >= win->w_nrows - 1)
		    break;
		if (l == win->w_buffer->b_file)
		    break;
	    }
	    while (done > win->w_nrows - 1 && l != currline) {
		done -= plines(win, l);
		l = l->l_next;
	    }
	    newtopline = l;

	    /*
	     * Now work out how many screen lines we want
	     * to scroll the window by. This code assumes
	     * that the old value of topline is earlier
	     * in the buffer than the new, so check this
	     * first.
	     */
	    if (earlier(win->w_topline, newtopline)) {
		done = cntplines(win, win->w_topline, newtopline);

		if (done != 0) {
		    s_del(win, 0, (int) done);
		}
	    }

	    win->w_topline = newtopline;
	    win->w_curs_new = TRUE;
	    update_window(win);
	}
    }
}

/*
 * Make sure the cursor is within the given window.
 *
 * This is needed for commands like control-E & control-Y.
 */
void
move_cursor_to_window(win)
Xviwin		*win;
{
    Posn	*cp;

    cp = win->w_cursor;

    if (earlier(cp->p_line, win->w_topline)) {
	cp->p_line = win->w_topline;
	info_update(win);
	coladvance(win, win->w_curswant);
    } else if (!earlier(cp->p_line, win->w_botline)
	       && earlier(win->w_topline, win->w_botline)) {
	cp->p_line = win->w_botline->l_prev;
	info_update(win);
	coladvance(win, win->w_curswant);
    }
    win->w_curs_new = TRUE;
}
