/* Navigation functions.

   Copyright (C) 1993 Sebastiano Vigna

    This file is part of ne, the nice editor.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 1, or (at your option)
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */


#include "ne.h"

/* The functions in this file move the cursor. They also update the screen
accordingly. There are some assumptions which are made in order to simplify
the code: the TAB size has to be less than half the number of columns; and
win_x has to be a multiple of the TAB size.

The function themselves are very simple; unfortunately, they are the kind of
code filled up with +1 and -1 whose nature is not always obvious. Most
function do not have a description, because their name suggests immediately
their behaviour. */



/* This functions "resyncs" cur_pos (the current character the cursor is on)
with cur_x and win_x. It has to take into account the TAB expansion, and can
cause left/right movement in order to properly land on a real character. It
assumes that tab_size < columns/2. Note that this function has to be called
whenever the cursor is moved on a different line, keeping the x position
constant. The only way of avoiding this problem is not supporting TABs,
which is of course unacceptable. Note that if x_wanted is TRUE, then the
wanted x position is used rather tham cur_x+win_x. */

static void resync_pos(buffer *b) {

	line_desc *ld = b->cur_line_desc;

	int i, len, last_tab_skip, x = b->win_x+b->cur_x;

	if (b->x_wanted) x = b->wanted_x;

	assert(b->tab_size < columns/2);

	if (x == 0) {
		b->cur_pos = 0;
		return;
	}

	for(i=len=0; i<ld->line_len; i++) {

		if (ld->line[i] != '\t') len++;

		else len += (last_tab_skip = b->tab_size - len%b->tab_size);

		if (len == x) {
			b->cur_pos = i+1;
			if (b->x_wanted) {

				b->x_wanted = 0;

				if (x-b->win_x < columns) b->cur_x = x-b->win_x;
				else {
					b->win_x = x-columns;
					b->win_x += b->tab_size - b->win_x % b->tab_size;
					b->cur_x = x-b->win_x;
					update_window(b);
				}
			}
			return;
		}

		if (len > x) {

			b->cur_pos = i;

			len -= last_tab_skip;
			b->x_wanted = 1;
			b->wanted_x = x;

			if (len-b->win_x < columns) b->cur_x = len-b->win_x;
			else {
				b->win_x = len-columns;
				b->win_x += b->tab_size - b->win_x % b->tab_size;
				b->cur_x = len-b->win_x;
				update_window(b);
			}
			return;
		}
	}

	if (b->free_form) {
		b->cur_pos = ld->line_len + x-len;
		b->cur_x = x-b->win_x;
		b->x_wanted = 0;
	}
	else {
		b->wanted_x = x;
		move_to_eol(b);
		b->x_wanted = 1;
	}
}



int line_up(buffer *b) {

	if (b->cur_y>0) {
		b->cur_y--;
		b->cur_line--;
		b->cur_line_desc = (line_desc *)b->cur_line_desc->ld_node.prev;
		resync_pos(b);
		return(OK);
	}
	else {
		if (b->win_y>0) {
			b->win_y--;
			b->cur_line--;
			b->cur_line_desc = (line_desc *)b->cur_line_desc->ld_node.prev;
			b->top_line_desc = (line_desc *)b->top_line_desc->ld_node.prev;
			scroll_window(b, 0, 1);
			resync_pos(b);
			return(OK);
		}
	}
	return(ERROR);
}



int line_down(buffer *b) {

	if (b->cur_y<lines-2 && b->cur_line<b->line_num-1) {
		b->cur_y++;
		b->cur_line++;
		b->cur_line_desc = (line_desc *)b->cur_line_desc->ld_node.next;
		resync_pos(b);
		return(OK);
	}
	else {
		if (b->win_y<b->line_num-lines+1) {
			b->win_y++;
			b->cur_line++;
			b->cur_line_desc = (line_desc *)b->cur_line_desc->ld_node.next;
			b->top_line_desc = (line_desc *)b->top_line_desc->ld_node.next;
			scroll_window(b, 0, -1);
			resync_pos(b);
			return(OK);
		}
	}
	return(ERROR);
}



/* This function moves win_x of n bytes to the left (n *has* to be a
multiple of the current TAB size). It is used by char_left(). cur_x
is moved, too. */

static void block_left(buffer *b, int n) {

	int t = b->win_x;

	assert(n <= columns);
	assert(!(n%b->tab_size));

	if ((b->win_x -= n) < 0) b->win_x = 0;
	b->cur_x += t-b->win_x;
	update_window(b);
}



int char_left(buffer *b) {

	line_desc *ld = b->cur_line_desc;

	assert(ld != NULL);
	assert_line_desc(ld);

	b->x_wanted = 0;

	if (b->cur_pos > 0) {

		int disp = 1;

		if (b->cur_pos <= ld->line_len && ld->line[b->cur_pos-1] == 0x9)
			disp = b->tab_size - calc_len(ld, b->cur_pos-1, b->tab_size)%b->tab_size;

		if (b->cur_x < disp) block_left(b, b->tab_size*2);
		b->cur_x -= disp;
		b->cur_pos--;
		return(OK);
	}
	else if (b->cur_line>0) {
		line_up(b);
		move_to_eol(b);
		return(OK);
	}
	return(ERROR);
}


/* Same as block_left(), but to the right. */

static void block_right(buffer *b, int n) {

	assert(n <= columns);
	assert(!(n%b->tab_size));

	b->win_x += n;
	b->cur_x -= n;
	update_window(b);
}

int char_right(buffer *b) {

	line_desc *ld = b->cur_line_desc;
	int disp = 1;

	assert(ld != NULL);
	assert_line_desc(ld);

	if (ld->line && b->cur_pos < ld->line_len && ld->line[b->cur_pos] == 0x9)
		disp = b->tab_size - calc_len(ld, b->cur_pos, b->tab_size)%b->tab_size;

	b->x_wanted = 0;

	if (b->cur_pos == ld->line_len && !b->free_form) {
		if (!ld->ld_node.next->next) return(ERROR);
		move_to_sol(b);
		line_down(b);
		return(OK);
	}

	if (b->cur_x+disp >= columns) block_right(b, b->tab_size*2);
	b->cur_x += disp;
	b->cur_pos++;
	return(OK);
}



int page_up(buffer *b) {

	int i;
	line_desc *ld_top, *ld_cur;

	if (b->cur_y > 0) {
		b->cur_line -= b->cur_y;
		b->cur_y = 0;
		b->cur_line_desc = b->top_line_desc;
		resync_pos(b);
		return(OK);
	}

	if (b->win_y == 0) return(ERROR);

	if ((b->win_y -= lines-2)<0) b->win_y = 0;

	ld_top = b->top_line_desc;
	ld_cur = b->cur_line_desc;

	for(i=0; i<lines-2 && ld_top->ld_node.prev->prev; i++) {
		ld_top = (line_desc *)ld_top->ld_node.prev;
		ld_cur = (line_desc *)ld_cur->ld_node.prev;
		b->cur_line--;
	}

	b->top_line_desc = ld_top;
	b->cur_line_desc = ld_cur;

	update_window(b);
	resync_pos(b);
	return(ERROR);
}



int page_down(buffer *b) {
	int i, disp;
	line_desc *ld_top, *ld_cur;

	if (b->cur_y < lines-2) {
		if (b->win_y >= b->line_num - (lines-1)) {
			ld_cur = b->top_line_desc;
			for(i=0; i<lines-2 && ld_cur->ld_node.next->next; i++) ld_cur = (line_desc *)ld_cur->ld_node.next;
			b->cur_line += (i - b->cur_y);
			b->cur_y = i;
		}
		else {
			b->cur_line += (lines - 2 - b->cur_y);
			b->cur_y = lines-2;
			ld_cur = b->top_line_desc;
			for(i=0; i<lines-2; i++) ld_cur = (line_desc *)ld_cur->ld_node.next;
		}
		b->cur_line_desc = ld_cur;
		resync_pos(b);
		return(OK);
	}

	if (b->win_y >= b->line_num-(lines-1)) return(ERROR);

	disp = lines-2;

	if (b->win_y + disp > b->line_num-(lines-1))
		disp = b->line_num-(lines-1) - b->win_y;

	b->win_y += disp;
	b->cur_line += disp;

	ld_top = b->top_line_desc;
	ld_cur = b->cur_line_desc;

	for(i=0; i<disp && ld_top->ld_node.next->next; i++) {
		ld_top = (line_desc *)ld_top->ld_node.next;
		ld_cur = (line_desc *)ld_cur->ld_node.next;
	}

	b->top_line_desc = ld_top;
	b->cur_line_desc = ld_cur;

	update_window(b);
	resync_pos(b);
	return(OK);
}



void goto_line(buffer *b, int n) {
	int i;
	line_desc *ld;

	if (n >= b->line_num || n == b->cur_line) return;

	if (n >= b->win_y && n < b->win_y+lines-1) {
		b->cur_y = n-b->win_y;
		b->cur_line = n;
		ld = b->top_line_desc;
		for(i=0; i<b->cur_y; i++) ld = (line_desc *)ld->ld_node.next;
		b->cur_line_desc = ld;
		resync_pos(b);
		return;
	}

	b->win_y = n - (lines-1)/2;

	if (b->win_y > b->line_num-(lines-1)) b->win_y = b->line_num-(lines-1);
	if (b->win_y < 0) b->win_y = 0;

	b->cur_y = n - b->win_y;
	b->cur_line = n;

	if (n<b->line_num/2) {
		ld = (line_desc *)b->line_desc_list.head;
		for(i=0; i<b->win_y; i++) ld = (line_desc *)ld->ld_node.next;
	}
	else {
		ld = (line_desc *)b->line_desc_list.tail_pred;
		for(i=b->line_num-1; i>b->win_y; i--) ld = (line_desc *)ld->ld_node.prev;
	}

	b->top_line_desc = ld;
	for(i=0; i<b->cur_y; i++) ld = (line_desc *)ld->ld_node.next;
	b->cur_line_desc = ld;

	update_window(b);
	resync_pos(b);
}



void goto_column(buffer *b, int n) {

	b->x_wanted = 0;

	if (n == b->win_x+b->cur_x) return;

	if (n >= b->win_x && n < b->win_x+columns) {
		b->cur_x = n-b->win_x;
		resync_pos(b);
		return;
	}

	if ((b->win_x = n - columns/2)<0) b->win_x = 0;
	b->win_x -= b->win_x%b->tab_size;
	b->cur_x = n-b->win_x;

	resync_pos(b);
	update_window(b);
}



/* This is like a goto_column(), but you specify a position (i.e.,
a character offset) instead. */

void goto_pos(buffer *b, int pos) {

	goto_column(b, calc_len(b->cur_line_desc, pos, b->tab_size));
}



void move_to_sol(buffer *b) {

	int t;

	b->x_wanted = 0;

	t = b->win_x;
	b->win_x =
	b->cur_x =
	b->cur_pos = 0;

	if (t) update_window(b);
}


void move_to_eol(buffer *b) {
	int i, len, total_len;
	line_desc *ld = b->cur_line_desc;

	assert(ld->ld_node.next != NULL);
	assert((ld->line != NULL) == (ld->line_len != 0));

	b->x_wanted = 0;

	if (!ld->line) {
		move_to_sol(b);
		return;
	}

	total_len = calc_len(ld, ld->line_len, b->tab_size);

	if (total_len >= b->win_x && total_len < b->win_x+columns) {
		b->cur_x = total_len - b->win_x;
		b->cur_pos = ld->line_len;
		return;
	}

	for(len=0, i=0; i<ld->line_len; i++)  {
		if (ld->line[i] != '\t') len++;
		else len += b->tab_size - len%b->tab_size;

		if (total_len-len < columns - b->tab_size) {
			int t = b->win_x;
			b->win_x = len-len%b->tab_size;
			b->cur_x = total_len - b->win_x;
			b->cur_pos = ld->line_len;
			if (t != b->win_x) update_window(b);
			return;
		}
	}

	assert(FALSE);
}



/* This function sets the variables like a move_to_sof(), but does not
perform any update. This is required in several places. */

void reset_position_to_sof(buffer *b) {
	b->x_wanted =
	b->win_x =
	b->win_y =
	b->cur_x =
	b->cur_y =
	b->cur_line =
	b->cur_pos = 0;
	b->cur_line_desc = b->top_line_desc = (line_desc *)b->line_desc_list.head;
}



void move_to_sof(buffer *b) {

	int old_win_x = b->win_x, old_win_y = b->win_y;

	reset_position_to_sof(b);

	if (old_win_x != b->win_x || old_win_y != b->win_y) update_window(b);
}



void move_to_eof(buffer *b) {
	int i;
	int old_win_x = b->win_x, old_win_y = b->win_y;
	line_desc *ld = (line_desc *)b->line_desc_list.tail_pred;

	b->x_wanted = 0;

	for(i=0; i<lines-2 && ld->ld_node.prev->prev; i++) ld = (line_desc *)ld->ld_node.prev;

	b->cur_line = b->line_num-1;

	if (!ld->ld_node.prev->prev) {
		b->win_x =
		b->win_y =
		b->cur_x =
		b->cur_pos = 0;
		b->cur_y = b->line_num-1;
		b->top_line_desc = (line_desc *)b->line_desc_list.head;
		b->cur_line_desc = (line_desc *)b->line_desc_list.tail_pred;
	}
	else {
		b->win_x =
		b->cur_x =
		b->cur_pos = 0;
		b->win_y = b->line_num - (lines-1);
		b->cur_y = lines-2;
		b->top_line_desc = ld;
		b->cur_line_desc = (line_desc *)b->line_desc_list.tail_pred;
	}

	if (old_win_x != b->win_x || old_win_y != b->win_y) update_window(b);
}



void toggle_sof_eof(buffer *b) {

	if (b->cur_line == 0 && b->cur_pos == 0) move_to_eof(b);
	else move_to_sof(b);
}



void toggle_sol_eol(buffer *b) {

	if (b->cur_pos == 0) move_to_eol(b);
	else move_to_sol(b);
}



/* This function searches for the start of the next or previous word, depending
on the value of dir. */

int search_word(buffer *b, int dir) {

	line_desc *ld;
   int y, pos, word_started = FALSE, space_skipped = FALSE;

	assert(dir == -1 || dir == 1);

   ld = b->cur_line_desc;
	pos = b->cur_pos+dir;
   y = b->cur_line;

   while(y<b->line_num && y>=0) {
		while(pos < ld->line_len && pos>=0) {
			if ((ispunct(ld->line[pos]) || isspace(ld->line[pos]))) space_skipped = TRUE;
			else word_started = TRUE;

			if (dir>0) {
				if (space_skipped && !(ispunct(ld->line[pos]) || isspace(ld->line[pos]))) {
					goto_line(b, y);
					goto_pos(b, pos);
					return(OK);
				}
			}
			else {
				if (word_started) {
					if (ispunct(ld->line[pos]) || isspace(ld->line[pos])) {
						goto_line(b, y);
						goto_pos(b, pos+1);
						return(OK);
					}
					else if (pos == 0) {
						goto_line(b, y);
						goto_pos(b, 0);
						return(0);
					}
				}
			}
			pos+=dir;
		}

		space_skipped = TRUE;

		if (dir>0) {
         ld = (line_desc *)ld->ld_node.next;
         y++;
			pos = 0;
      }
      else {
         ld = (line_desc *)ld->ld_node.prev;
         y--;
			if (ld->ld_node.prev) pos = ld->line_len-1;
      }
	}
	return(ERROR);
}



/* This function moves to the character after the end of the current word. It
doesn't move at all on spaces and punctuation. */

void move_to_eow(buffer *b) {

	line_desc *ld = b->cur_line_desc;
   int pos = b->cur_pos;

	if (pos >= ld->line_len  || ispunct(ld->line[pos]) || isspace(ld->line[pos])) return;

	while(pos < ld->line_len) {
		if (ispunct(ld->line[pos]) || isspace(ld->line[pos])) break;
		pos++;
	}

	goto_pos(b, pos);
}
