/*	SC	A Spreadsheet Calculator
 *
 *	One line vi emulation
 *	$Revision: 7.16 $
 */

#include <sys/types.h>
#ifdef BSD42
#include <strings.h>
#else
#ifndef SYSIII
#include <string.h>
#endif
#endif

#include <signal.h>
#include <curses.h>
#include <ctype.h>
#include <stdlib.h>
#include "sc.h"

#if defined(REGCOMP)
#include <regex.h>
#endif
#if defined(RE_COMP)
extern char *re_comp(char *s);
extern char *re_exec(char *s);
#endif
#if defined(REGCMP)
char *regcmp();
char *regex();
#endif

void doshell();
void gohome();
void leftlimit();
void rightlimit();
void gototop();
void gotobottom();

#define istext(a) (isalnum(a) || ((a) == '_'))

#define bool	int
#define true	1
#define false	0

static void append_line();
static void back_hist();
static int  back_line(int arg);
static int  back_word(int arg, int big_word);
static void back_space();
static void change_case(int arg);
static void col_0();
static void cr_line();
static void del_in_line(int arg, int back_null);
static void del_to_end();
static void doabbrev();
static void dogoto();
static void dotab();
static void dotcmd();
static int  find_char(int arg, int n);
static void for_hist();
static int  for_line(int arg, int stop_null);
static int  for_word(int arg, int end_word, int big_word, int stop_null);
static int  istart;
static void last_col();
static void match_paren();
static void readhistfile(FILE *fp);
static void rep_char();
static void replace_in_line(int c);
static void replace_mode();
static void restore_it();
static void savedot(int c);
static void save_hist();
static void search_again(bool reverse);
static void search_hist();
static void search_mode(char sind);
static void stop_edit();
static int  to_char(int arg, int n);
static void u_save(int c);
static void yank_cmd(int delete, int change);
static void yank_chars(register int first, register int last, int delete);

extern int framerows;		/* Rows in current frame */
extern char mode_ind;		/* Mode indicator */
extern char search_ind;		/* Search indicator */
extern int lcols;		/* Spreadsheet Column the cursor was in last */
char	*completethis = NULL;
int	search_dir;		/* Search direction:  forward = 0; back = 1 */

/* values for mode below */

#define INSERT_MODE	0	/* Insert mode */
#define EDIT_MODE       1	/* Edit mode */
#define REP_MODE        2	/* Replace mode */
#define SEARCH_MODE	3	/* Get arguments for '/' command */
#define NAVIGATE_MODE	4	/* Navigate the spreadsheet while editing
					a line */

#define	DOTLEN		200

static int mode = INSERT_MODE;
struct	hist {
	unsigned int	len;
	char		*histline;
} history[HISTLEN + 1];

static int	histp = 0;
static int	lasthist = 0;
static int	endhist = -1;
static int	histsessionstart = 0;
static int	histsessionnew = 0;

#ifdef REGCOMP
static regex_t	preg;
static regex_t	*last_search = NULL;
static int	errcode;
#else
#ifndef RE_COMP
static char	*last_search = NULL;
#endif
#endif
static char	*undo_line = NULL;
static int	undo_lim;
static char	dotb[DOTLEN];
static int	doti = 0;
static int	do_dot = 0;
static int	nosavedot = 1;
static int	dotarg = 1;
static char	putbuf[FBUFLEN];
static int	findfunc = '\0';
static int	findchar = 1;
static int	finddir = 0;

void
write_line(int c)
{
    struct frange *fr;
    struct crange *cr;

    error("");
    if (c != ctl('i')) completethis = NULL;
    if (mode == EDIT_MODE) {
	nosavedot = 0;
	switch (c) {
	case KEY_BACKSPACE:
	case (ctl('h')):	linelim = back_line(arg);		break;
	case (ctl('i')):	dotab();				break;
	case (ctl('m')):	if (search_ind != ' ')
				    search_hist();
				else
				    cr_line();				break;
	case 'v':
	case (ctl('v')):	toggle_navigate_mode();			break;
	case ESC:	stop_edit();					break;
	case '+':	for_hist();					break;
	case '-':	back_hist();					break;
	case KEY_END:
	case (ctl('e')):
	case '$':	last_col();					break;
	case '.':	dotcmd();					break;
	case '!':	doshell();					break;
	case ';':	if (findfunc)
			    ungetch(findchar);
			else
			    break;
			findchar = 0;
			if (findfunc == 'f')
			    linelim = find_char(arg, finddir);
			else
			    linelim = to_char(arg, finddir);
									break;
	case ',':	if (findfunc)
			    ungetch(findchar);
			else
			    break;
			findchar = 0;
			if (findfunc == 'f')
			    linelim = find_char(arg, -(finddir));
			else
			    linelim = to_char(arg, -(finddir));
									break;
	case '~':	u_save(c); change_case(arg);			break;
	case '%':	match_paren();					break;
#ifdef KEY_FIND
	case KEY_FIND:
#endif
	case '?':
	case '/':	search_mode(c);					break;
	case KEY_HOME:
	case (ctl('a')):
	case '0':	col_0();					break;
	case 'A':	u_save(c); last_col(); append_line();		break;
	case 'B':	linelim = back_word(arg, 1);			break;
	case 'C':	u_save(c); del_to_end(); append_line();		break;
	case 'D':	u_save(c); del_to_end();			break;
	case 'E':	linelim = for_word(arg, 1, 1, 0);		break;
	case 'F':	linelim = find_char(arg, -1);			break;
	case 'G':	if (histp > 0) histp = lasthist; for_hist();	break;
	case 'I':	u_save(c); col_0(); insert_mode();		break;
	case 'N':	search_again(true);				break;
	case 'P':	u_save(c);
			ins_string(putbuf);
			linelim = back_line(1);				break;
	case 'R':	u_save(c); replace_mode();			break;
	case 'T':	linelim = to_char(arg, -1);			break;
	case 'W':	linelim = for_word(arg, 0, 1, 0);		break;
	case 'X':	u_save(c); back_space();			break;
	case 'Y':	yank_chars(linelim, strlen(line), 0);		break;
	case 'a':	u_save(c); append_line();			break;
	case 'b':	linelim = back_word(arg, 0);			break;
	case 'c':	u_save(c); yank_cmd(1, 1); insert_mode();	break;
	case 'd':	u_save(c); yank_cmd(1, 0);			break;
	case 'e':	linelim = for_word(arg, 1, 0, 0);		break;
	case 'f':	linelim = find_char(arg, 1);			break;
	case KEY_LEFT:
	case (ctl('b')):
	case 'h':	linelim = back_line(arg);			break;
	case KEY_IC:
	case 'i':	u_save(c); insert_mode();			break;
	case KEY_DOWN:
	case 'j':	for_hist();					break;
	case KEY_UP:
	case 'k':	back_hist();					break;
	case KEY_RIGHT:
	case (ctl('f')):
	case ' ':
	case 'l':	linelim = for_line(arg, 0);			break;
	case 'n':	search_again(false);				break;
	case 'p':	u_save(c);
			linelim = for_line(1, 1);
			ins_string(putbuf);
			linelim = back_line(1);				break;
	case 'q':	stop_edit();					break;
	case 'r':	u_save(c); rep_char();				break;
	case 's':	u_save(c); del_in_line(arg, 0); insert_mode();	break;
	case 't':	linelim = to_char(arg, 1);			break;
	case 'u':	restore_it();					break;
	case 'w':	linelim = for_word(arg, 0, 0, 0);		break;
	case KEY_DC:
	case 'x':	u_save(c); del_in_line(arg, 1);			break;
	case 'y':	yank_cmd(0, 0);					break;
	default:							break;
	}
    } else if (mode == INSERT_MODE) { 
	if (c == (ctl('m')))
	    savedot(ESC);
	else
	    savedot(c);
	switch (c) {
	case KEY_BACKSPACE:
	case (ctl('h')):	back_space();				break;
	case (ctl('i')):	dotab();				break;
	case (ctl('m')):	cr_line();				break;
	case (ctl('v')):	toggle_navigate_mode();			break;
	case KEY_LEFT:
	case (ctl('b')):	if (numeric_field) {
				    if (linelim == strlen(line) &&
					    (line[linelim - 1] == '+' ||
					     line[linelim - 1] == '-')) {
					toggle_navigate_mode();
					backcol(1);
				    } else {
					c = craction;
					craction = 0;
					cr_line();
					craction = c;
					backcol(1);
				    }
				} else {
				    linelim = back_line(arg);
				    istart = linelim;
				}   break;
	case KEY_RIGHT:
	case (ctl('f')):	if (numeric_field) {
				    if (linelim == strlen(line) &&
					    (line[linelim - 1] == '+' ||
					     line[linelim - 1] == '-')) {
					toggle_navigate_mode();
					forwcol(1);
				    } else {
					c = craction;
					craction = 0;
					cr_line();
					craction = c;
					forwcol(1);
				    }
				} else {
				    linelim = for_line(arg, 1);
				    istart = linelim;
				}   break;
	case KEY_DOWN:
	case (ctl('n')):	if (numeric_field) {
				    if (linelim == strlen(line) &&
					    (line[linelim - 1] == '+' ||
					     line[linelim - 1] == '-')) {
					toggle_navigate_mode();
					forwrow(1);
				    } else {
					c = craction;
					craction = 0;
					cr_line();
					craction = c;
					forwrow(1);
				    }
				} else {
				    for_hist();
				    istart = linelim;
				}   break;
	case KEY_UP:
	case (ctl('p')):	if (numeric_field) {
				    if (linelim == strlen(line) &&
					    (line[linelim - 1] == '+' ||
					     line[linelim - 1] == '-')) {
					toggle_navigate_mode();
					backrow(1);
				    } else {
					c = craction;
					craction = 0;
					cr_line();
					craction = c;
					backrow(1);
				    }
				} else {
				    back_hist();
				    istart = linelim;
				}   break;
	case KEY_HOME:
	case (ctl('a')):	col_0();				break;
	case KEY_END:
	case (ctl('e')):	last_col();				break;
	case ESC:		ins_in_line(0);
				edit_mode();				break;
	/* '\035' is ^], which expands abbreviations without inserting another
	 * character in the line
	 */
	case '\035':		if (linelim > 0) doabbrev();		break;
	default:		ins_in_line(c);				break;
	}
    } else if (mode == SEARCH_MODE) {
	switch (c) {
	case KEY_BACKSPACE:
	case (ctl('h')):	back_space();				break;
	case (ctl('m')):	search_hist();				break;
	case ESC:		ins_in_line(0);
				edit_mode();				break;
	/* '\035' is ^], which expands abbreviations without inserting another
	 * character in the line
	 */
	case '\035':		if (linelim > 0) doabbrev();		break;
	default:		ins_in_line(c);				break;
	}
    } else if (mode == REP_MODE) {
	savedot(c);
	switch (c) {
	case KEY_BACKSPACE:
	case (ctl('h')):	if (linelim > strlen(undo_line))
				    back_space();
				else {
				    linelim = back_line(1);
				    *(line+linelim) = *(undo_line+linelim);
				}					break;
	case (ctl('m')):	cr_line();				break;
	case ESC:		edit_mode();				break;
	default:		replace_in_line(c);			break;
	}
    } else if (mode == NAVIGATE_MODE) {
	switch (c) {
	case '.':
	case ':':
	case (ctl('i')):	if (!showrange) {
				    toggle_navigate_mode();
				    startshow();
				} else if (linelim == strlen(line) &&
					(line[linelim - 1] == '+' ||
					line[linelim - 1] == '-' ||
					(line[linelim - 1] == ' ' &&
					 line[linelim - 2] == '='))) {
				    ins_string("@sum(");
				    showdr();
				    ins_in_line(')');
				} else {
				    showdr();
				    ins_in_line(' ');
				} 					break;
	case ' ':		if (showrange) {
				    showdr();
				    ins_in_line(' ');
				    toggle_navigate_mode();
				} else {
				    forwcol(arg);
				} 					break;
	case '+':
	case '-':		if (!showrange) {
				    ins_string(v_name(currow, curcol));
				    ins_in_line(c);
				} else if (linelim == strlen(line) &&
					(line[linelim - 1] == '+' ||
					line[linelim - 1] == '-' ||
					(line[linelim - 1] == ' ' &&
					 line[linelim - 2] == '='))) {
				    ins_string("@sum(");
				    showdr();
				    ins_in_line(')');
				    ins_in_line(c);
				    toggle_navigate_mode();
				} else {
				    showdr();
				    ins_in_line(')');
				    ins_in_line(c);
				}					break;
	case (ctl('m')):	if (!showrange) {
				    ins_string(v_name(currow, curcol));
				    toggle_navigate_mode();
				} else {
				    toggle_navigate_mode();
				    cr_line();
				}					break;
	case 'v':	{   /* insert variable value */
				    struct ent *p = *ATBL(tbl, currow, curcol);
				    char temp[100];

				    if (p && p->flags & is_valid) {
					(void) sprintf(temp, "%.*f",
						precision[curcol], p->v);
					ins_string(temp);
					toggle_navigate_mode();
				    }
				}					break;
	case 'c':		if ((cr = find_crange(currow, curcol))) {
				    ins_string(r_name(cr->r_left->row,
					    cr->r_left->col,
					    cr->r_right->row,
					    cr->r_right->col));
				    toggle_navigate_mode();
				    ins_in_line(' ');
				    showrange = 0;
				}					break;
	case 'f':		if ((fr = find_frange(currow, curcol))) {
				    ins_string(r_name(fr->or_left->row,
					    fr->or_left->col,
					    fr->or_right->row,
					    fr->or_right->col));
				    toggle_navigate_mode();
				    ins_in_line(' ');
				    showrange = 0;
				}					break;
	case 'r':		if ((fr = find_frange(currow, curcol))) {
				    ins_string(r_name(fr->ir_left->row,
					    fr->ir_left->col,
					    fr->ir_right->row,
					    fr->ir_right->col));
				    toggle_navigate_mode();
				    ins_in_line(' ');
				    showrange = 0;
				}					break;
	case KEY_LEFT:
	case 'h':		backcol(arg);				break;
	case KEY_RIGHT:
	case 'l':		forwcol(arg);				break;
	case KEY_DOWN:
	case (ctl('n')):
	case 'j':		forwrow(arg);				break;
	case KEY_UP:
	case (ctl('p')):
	case 'k':		backrow(arg);				break;
	case 'q':
	case ctl('g'):
	case (ctl('v')):
	case ESC:		toggle_navigate_mode();
				showrange = 0;				break;
	case 'H':		backcol(curcol - stcol + 2);
									break;
	case KEY_NPAGE:			/* next page */
	case (ctl('f')):
	case 'J':
				{
				int ps;

				ps = pagesize ? pagesize :
				    (LINES - RESROW - framerows)/2;
				forwrow(arg * ps);
				strow = strow + (arg * ps);
				FullUpdate++;
				}
									break;
	case KEY_PPAGE:			/* previous page */
	case (ctl('b')):
	case 'K':
				{
				int ps;

				ps = pagesize ? pagesize :
				    (LINES - RESROW - framerows)/2;
				backrow(arg * ps);
				strow = strow - (arg * ps);
				if (strow < 0) strow = 0;
				FullUpdate++;
				}
									break;
	case 'L':		forwcol(lcols - (curcol - stcol) + 1);	break;
	case (ctl('a')):
	case KEY_HOME:		gohome();				break;
	case '0':		leftlimit();				break;
	case '$':		rightlimit();				break;
	case '^':		gototop();				break;
	case '#':		gotobottom();				break;
	case 'o':		if (showrange) {
				    int r = currow;
				    int c = curcol;

				    currow = showsr;
				    showsr = r;
				    curcol = showsc;
				    showsc = c;
				    rowsinrange = 1;
				    colsinrange = fwidth[curcol];
				} 					break;
	case 'm':		markcell();				break;
	case '`': case '\'':	dotick(c);				break;
	case '*':		if (nmgetch() == '*') gotonote();	break;
	case 'g':		dogoto();				break;
	case 'n':		go_last();				break;
	case 'w':		{
				register struct ent *p;

				while (--arg>=0) {
				    do {
					if (curcol < maxcols - 1)
					    curcol++;
					else {
					    if (currow < maxrows - 1) {
						while(++currow < maxrows - 1 &&
							row_hidden[currow])
						    ;
						curcol = 0;
					    } else {
						error("At end of table");
						break;
					    }
					}
				    } while(col_hidden[curcol] ||
					    !VALID_CELL(p, currow, curcol));
				}
				rowsinrange = 1;
				colsinrange = fwidth[curcol];
				break;
				}
	case 'b':		{
				register struct ent *p;

				while (--arg>=0) {
				    do {
					if (curcol) 
					    curcol--;
					else {
					    if (currow) {
						while(--currow && row_hidden[currow])
						    ;
						curcol = maxcols - 1;
					    } else {
						error("At start of table");
						break;
					    }
					}
				    } while (col_hidden[curcol] ||
					    !VALID_CELL(p, currow, curcol));
				}
				rowsinrange = 1;
				colsinrange = fwidth[curcol];
				break;
				}
	case 'C':		if (braille)
				    braillealt ^= 1;
				break;
	}
    }
}

void
edit_mode()
{
    mode_ind = 'e';
    mode = EDIT_MODE;
    if (linelim < 0)	/* -1 says stop editing, ...so we still aren't */
	return;
    numeric_field = 0;
    linelim = back_line(1);
}

void
insert_mode()
{
    mode_ind = 'i';
    mode = INSERT_MODE;
    istart = linelim;
}

static void
search_mode(char sind)
{
    if (search_ind == ' ') {
	/*
	 * This back and forth movement through the history is just a quick
	 * way to force the current command to be saved in history[0].histline,
	 * allocating space for it if necessary.  The command will be copied
	 * back into line by search_hist() before the search is done. - CRM
	 */
	back_hist();
	for_hist();
	line[0] = '\0';
	linelim = 0;
	mode_ind = 'i';
	search_ind = sind;
	search_dir = sind == '/' ? 1 : 0;
	mode = SEARCH_MODE;
	istart = linelim;
    }
}

static void
replace_mode()
{
    mode_ind = 'R';
    mode = REP_MODE;
}

void
toggle_navigate_mode()
{
    static char prev_mode = NAVIGATE_MODE;
    int limtmp;

    switch (prev_mode) {
	case INSERT_MODE:
				if (mode == NAVIGATE_MODE) {
				    prev_mode = NAVIGATE_MODE;
				    insert_mode();
				    break;
				} else
				    return;
	case EDIT_MODE:
				if (mode == NAVIGATE_MODE) {
				    prev_mode = NAVIGATE_MODE;
				    limtmp = linelim;
				    edit_mode();
				    linelim = limtmp;
				    break;
				} else
				    return;
	case NAVIGATE_MODE:
				prev_mode = mode;
				mode_ind = 'v';
				mode = NAVIGATE_MODE;
				break;
	default:
				prev_mode = NAVIGATE_MODE;
				break;
    }
}

void
dotab()
{
    static struct range *firstmatch;
    static struct range *lastmatch;
    static struct range *nextmatch;
    int len;

    if (linelim > 0 && isalnum(line[linelim-1]) || line[linelim-1] == '_' ||
	    (completethis && line[linelim-1] == ' ')) {
	if (!completethis) {
	    for (completethis = line + linelim - 1; isalnum(*completethis) ||
		    *completethis == '_'; completethis--) /* */;
	    completethis++;
	    len = line + linelim - completethis;
	    if (!find_range(completethis, -len, NULL, NULL, &lastmatch)) {
		firstmatch = lastmatch;
		while (firstmatch->r_next &&
			!strncmp(completethis, firstmatch->r_next->r_name, len))
		    firstmatch = firstmatch->r_next;
		nextmatch = firstmatch;
	    } else
		nextmatch = NULL;
	}
	if (nextmatch) {
	    len = line + linelim - completethis;
	    memmove(completethis, line + linelim, strlen(line) - linelim + 1);
	    linelim -= len;
	    ins_string(nextmatch->r_name);
	    if (*(completethis - 1) == ' ' && *(line + linelim) != ' ')
		ins_in_line(' ');
	    if (nextmatch == lastmatch)
		nextmatch = firstmatch;
	    else
		nextmatch = nextmatch->r_prev;
	}
    } else
	startshow();
}

/* show the current range (see ^I), we are moving around to define a range */
void
startshow()
{
    showrange = 1;
    showsr = currow;
    showsc = curcol;
    toggle_navigate_mode();
}

/* insert the range we defined by moving around the screen, see startshow() */

void
showdr()
{
    int			minsr, minsc, maxsr, maxsc;
    char		*p;
    char		r[12];
    struct frange	*fr = find_frange(currow, curcol);

    if (showrange == SHOWROWS) {
	minsr = showsr < currow ? showsr : currow;
	minsc = fr ? fr->or_left->col : 0;
	maxsr = showsr > currow ? showsr : currow;
	maxsc = fr ? fr->or_right->col : maxcols;

	toggle_navigate_mode();
	sprintf(r, "%d:%d", minsr, maxsr);
	ins_string(r);
    } else if (showrange == SHOWCOLS) {
	minsr = 0;
	minsc = showsc < curcol ? showsc : curcol;
	maxsr = maxrows;
	maxsc = showsc > curcol ? showsc : curcol;

	strcpy(r, coltoa(minsc));
	strcat(r, ":");
	strcat(r, coltoa(maxsc));

	toggle_navigate_mode();
	ins_string(r);
    } else {
	minsr = showsr < currow ? showsr : currow;
	minsc = showsc < curcol ? showsc : curcol;
	maxsr = showsr > currow ? showsr : currow;
	maxsc = showsc > curcol ? showsc : curcol;

	toggle_navigate_mode();
	ins_string(r_name(minsr, minsc, maxsr, maxsc));
    }
    showrange = 0;
}

/* dot command functions.  Saves info so we can redo on a '.' command */

static void
savedot(int c)
{
    if (do_dot || nosavedot || (c == '\n'))
	return;

    if (doti == 0) dotarg = arg;
    if (doti < DOTLEN-1) {
	if (c > 255) {
	    if (doti < DOTLEN-2) {
		dotb[doti++] = c/256;
		c %= 256;
	    } else
		return;
	}
	dotb[doti++] = c;
	dotb[doti] = '\0';
    }
}

static int dotcalled = 0;

static void
dotcmd()
{
    int c;

    if (dotcalled)	/* stop recursive calling of dotcmd() */
	return;
    do_dot = 1;
    doti = 0;
    if (arg == 1)
	arg = dotarg;
    else
	dotarg = arg;
    while (dotb[doti] != '\0') {
	if ((c = dotb[doti++]) < 4)
	    c = c * 256 + dotb[doti++];
	dotcalled = 1;
	write_line(c);
    }
    do_dot = 0;
    doti = 0;
    dotcalled = 0;
}

int
vigetch()
{
    int c;

    if (do_dot) {
	if (dotb[doti] != '\0') {
	    return (dotb[doti++]);
	} else {
	    do_dot = 0;
	    doti = 0;
	    return (nmgetch());
	}
    }
    update(1);
    c = nmgetch();
    return (c);
}

/* saves the current line for possible use by an undo cmd */
static void
u_save(int c)
{
    static unsigned	undolen = 0;

    if (strlen(line)+1 > undolen) {
    	undolen = strlen(line)+40;

	undo_line = scxrealloc(undo_line, undolen);
    }
    (void) strcpy(undo_line, line);

    undo_lim = linelim;

    /* reset dot command if not processing it. */

    if (!do_dot) {
        doti = 0;
	savedot(c);
    }
}

/* Restores the current line saved by u_save() */
static void
restore_it()
{
    static char		*tempc = NULL;
    static unsigned	templen = 0;
    int			tempi;

    if ((undo_line == NULL) || (*undo_line == '\0')) 
	return;

    if (strlen(line)+1 > templen) {
    	templen = strlen(line) + 40;
	tempc = scxrealloc(tempc, templen);
    }

    strcpy(tempc, line);
    tempi = linelim;
    (void) strcpy(line, undo_line);
    linelim = undo_lim;
    strcpy(undo_line, tempc);
    undo_lim = tempi;
}

/* This command stops the editing process. */
static void
stop_edit()
{
    if (search_ind != ' ') {
	search_ind = ' ';
	(void) strcpy(line, history[0].histline);
	write_line('G');
    } else {
	showrange = 0;
	numeric_field = 0;
	linelim = -1;
	(void) move(1, 0);
	(void) clrtoeol();
    }
}

/*
 * Motion commands.  Forward motion commands take an argument
 * which, when set, cause the forward motion to continue onto
 * the null at the end of the line instead of stopping at the
 * the last character of the line.
 */
static int
for_line(int arg, int stop_null)
{
    int cpos = linelim;

    if (linelim < 0)
	return (linelim);
    else if (linelim + arg <= strlen(line))
	cpos += arg;
    else
	cpos = strlen(line);

    if (cpos == strlen(line) && cpos > 0 && !stop_null)
	return (cpos - 1);
    else
	return (cpos);
}

/* If end_word is non-zero, go to next end-of-word.  Otherwise, go to next
 * beginning-of-word.
 */

static int
for_word(int arg, int end_word, int big_word, int stop_null)
{
    register int c;
    register int cpos;

    cpos = linelim;

    while (cpos < strlen(line) && arg--) {
	if (end_word)
	    cpos++;

	if (line[cpos] == ' ') {
	    while (line[cpos] == ' ')
		cpos++;
	    if (cpos > 0 && line[cpos] == '\0')
		--cpos;
	    if (!end_word)
		continue;
	}

	if (big_word)
	    while ((c = line[cpos]) && c != ' ')
		cpos++;
	else if (istext(line[cpos])) {
	    while ((c = line[cpos]) && istext(c)) 
		cpos++;
	} else {
	    while ((c = line[cpos]) && !istext(c) && c != ' ')
		cpos++;
	}

	if (end_word)
	    cpos--;
	else while (line[cpos] == ' ')
	    cpos++;

	if (cpos > 0 && line[cpos] == '\0' && !stop_null) 
	    --cpos;
    }

    return (cpos);
}

static int
back_line(int arg)
{
    if (linelim > arg)
        return (linelim - arg);
    else
	return (0);
}

static int
back_word(int arg, int big_word)
{
    register int c;
    register int cpos;

    cpos = linelim;

    while (cpos > 0 && arg--) {
	if (line[cpos] == ' ') {
	    /* Skip white space */
	    while (cpos > 0 && line[cpos] == ' ')
		--cpos;
	} else if (cpos > 0 && (line[cpos-1] == ' ' 
			|| ( istext(line[cpos]) && !istext(line[cpos-1]))
			|| (!istext(line[cpos]) &&  istext(line[cpos-1])))) {
	    /* Started on the first char of a word - back up to prev. word */
	    --cpos;
	    while (cpos > 0 && line[cpos] == ' ')
		--cpos;
	}

	/* Skip across the word - goes 1 too far */
	if (big_word)
	    while (cpos > 0 && (c = line[cpos]) && c != ' ')
		--cpos;
	else if (istext(line[cpos])) {
	    while (cpos > 0 && (c = line[cpos]) && istext(c)) 
		--cpos;
	} else {
	    while (cpos > 0 && (c = line[cpos]) && !istext(c) && c != ' ')
		--cpos;
	}

	/* We are done - fix up the one too far */
	if (cpos > 0 && line[cpos] && line[cpos+1]) 
	    cpos++;
    }

    return(cpos);
}

/* Text manipulation commands */

/* If back_null is set, back up if the deletion leaves you at the null
 * line terminator.  Otherwise, don't.
 */
static void
del_in_line(int arg, int back_null)
{
    register int len, i;

    if (linelim >= 0) {
	len = strlen(line);
	if (arg > len - linelim)
	    arg = len - linelim;
	if (linelim == len && linelim > 0)
	    linelim--;
	strncpy(putbuf, line + linelim, arg);
	putbuf[arg] = '\0';
	for (i = linelim; i < len; i++)
	    line[i] = line[i+arg];
    }
    if (back_null && linelim > 0 && line[linelim] == '\0')
	--linelim;
}

void
ins_in_line(int c)
{
    int i, len;
    static int inabbr;

    if (c < 256) {
	if (linelim < 0 && c > 0) {
	    *line = '\0';
	    linelim = 0;
	}
	if (!inabbr && linelim > 0 && !(isalpha(c) || isdigit(c) || c == '_')) {
	    inabbr++;
	    doabbrev();
	    inabbr--;
	}
	if (c > 0) {
	    len = strlen(line);
	    for (i = len; i >= linelim; --i)
		line[i+1] = line[i];
	    line[linelim++] = c;
	    line[len+1] = '\0';
	}
    }
}

void
ins_string(char *s)
{
    while (*s)
	ins_in_line(*s++);
}

void
doabbrev()
{
    int len, pos;
    struct abbrev *a;
    struct abbrev *prev;
    
    if (!(isalpha(line[linelim-1]) || isdigit(line[linelim-1]) ||
	    line[linelim-1] == '_') || !(mode == INSERT_MODE ||
	    mode == SEARCH_MODE) || istart >= linelim)
	return;

    pos = linelim - 2;
    if (isalpha(line[pos]) || isdigit(line[pos]) || line[pos] == '_') {
	for (; pos >= istart; pos--)
	    if (!(isalpha(line[pos]) || isdigit(line[pos]) || line[pos] == '_'))
		break;
    } else if (line[pos] != ' ')
	for (; pos >= istart; pos--)
	    if (isalpha(line[pos]) || isdigit(line[pos]) || line[pos] == '_' ||
		    line[pos] == ' ')
		break;
    pos++;

    if (istart && pos == istart) {
	if (isalpha(line[pos]) || isdigit(line[pos]) || line[pos] == '_') {
	    if (isalpha(line[--pos]) || isdigit(line[pos]) || line[pos] == '_')
		return;
	} else {
	    if (!(isalpha(line[--pos]) || isdigit(line[pos]) ||
		    line[pos] == '_' || line[pos] == ' '))
		return;
	}
	pos++;
    }

    len = linelim - pos;
    if (len && (a = find_abbr(line + pos, len, &prev))) {
	if (len > 1 || pos == 0 || line[pos-1] == ' ') {
	    linelim = pos;
	    del_in_line(len, 0);
	    ins_string(a->exp);
	}
    }
}

static void
append_line()
{
    register int i;

    i = linelim;
    if (i >= 0 && line[i])
	linelim++;
    insert_mode();
}

static void
change_case(arg)
{
    if (linelim < 0) {
    	linelim = 0;
	*line = '\0';
    }
    while (arg--) {
	if (islower(line[linelim]))
	    line[linelim] = toupper(line[linelim]);
	else if (isupper(line[linelim]))
	    line[linelim] = tolower(line[linelim]);
	linelim = for_line(1, 0);
    }
}

static void
rep_char()
{
    int c;

    if (linelim < 0) {
    	linelim = 0;
	*line = '\0';
    }
    c = vigetch();
    savedot(c);
    if (c < 256 && c!= ESC && c != ctl('g')) {
	if (line[linelim] == '\0')
	    line[linelim+1] = '\0';
	line[linelim] = c;
    }
}

static void
replace_in_line(int c)
{
    register int len;

    if (c < 256) {
	if (linelim < 0) {
	    linelim = 0;
	    *line = '\0';
	}
	len = strlen(line);
	line[linelim++] = c;
	if (linelim > len)
	    line[linelim] = '\0';
    }
}

static void
back_space()
{
    if (linelim == 0)
	return;

    if (line[linelim] == '\0') {
	linelim = back_line(1);
	del_in_line(1, 1);
	linelim = strlen(line);
    } else {
	linelim = back_line(1);
	del_in_line(1, 1);
    }
    if (linelim < istart)
	istart = linelim;
}

/* Setting change to 1 makes `w' act like `e' so that `cw' will act like
 * `ce', just as in vi.  Setting change to 0 causes `w' to act as usual.
 */

int
get_motion(int change)
{
    int c;
    int arg2 = 0;

    c = vigetch();
    if (c == '0') {
	savedot(c);
	return (0);
    }
    while (c >= '0' && c <= '9') {
	arg2 = 10 * arg2 + c - '0';
	c = vigetch();
    }
    if (!arg2)
	arg2++;
    arg *= arg2;
    if (!nosavedot) {
	savedot(c);
	dotarg = arg;
    }
    switch (c) {
	case '$':	return (strlen(line));
	case 'b':	return (back_word(arg, 0));
	case 'B':	return (back_word(arg, 1));
	case 'c':	return (change ? -1 : linelim);
	case 'd':	return (!change ? -1 : linelim);
	case 'e':	return (for_word(arg, 1, 0, 1) + 1);
	case 'E':	return (for_word(arg, 1, 1, 1) + 1);
	case 'f':	return ((c = find_char(arg, 1)) == linelim ? c : c + 1);
	case 'F':	return (find_char(arg, -1));
	case 'h':	return (back_line(arg));
	case 'l':	return (for_line(arg, 1));
	case 't':	return ((c = to_char(arg, 1)) == linelim ? c : c + 1);
	case 'T':	return (to_char(arg, -1));
	case 'w':	return (for_word(arg, change, 0, 1) + change);
	case 'W':	return (for_word(arg, change, 1, 1) + change);
	default:	return (linelim);
    }
}

static void
yank_cmd(int delete, int change)
{
    int cpos;

    if ((cpos = get_motion(change)) == -1) {
	cpos++;
	linelim = strlen(line);
    }
    yank_chars(cpos, linelim, delete);
}

static void
yank_chars(register int first, register int last, int delete)
{
    int temp;

    if (first == last)
	return;

    if (last < first) {
	temp = last; last = first; first = temp;
    }

    linelim = first;
    strncpy(putbuf, line + first, last - first);
    putbuf[last - first] = '\0';
    if (delete)
	memmove(line + first, line + last, strlen(line + last) + 1);
}

static void
del_to_end()
{
    if (linelim < 0)
	return;
    strcpy(putbuf, line + linelim);
    line[linelim] = '\0';
    linelim = back_line(1);
}

static void
cr_line()
{
    struct frange *fr;

    ins_in_line(0);
    insert_mode();
    numeric_field = 0;
    if (linelim == -1) {	/* '\n' alone will put you into insert mode */
    	*line = '\0';		/* unless numeric and craction are both	set */
	linelim = 0;
	if (numeric && craction)
	    cellassign = 1;
	else
	    return;
    }
    save_hist();
    nosavedot = 1;
    linelim = 0;
    (void) yyparse();
    showrange = 0;
    linelim = -1;
    if (cellassign) {
	cellassign = 0;
	switch (craction) {
	    case CRROWS:
		if ((rowlimit >= 0) && (currow >= rowlimit)) {
		    forwcol(1);
		    currow = 0;
		} else {
		    if ((fr = find_frange(currow, curcol))) {
			forwrow(1);
			if (currow > fr->ir_right->row) {
			    backrow(1);
			    if (autowrap) {
				forwcol(1);
				currow = fr->ir_left->row;
				if (row_hidden[currow])
				    forwrow(1);
				if (curcol > fr->ir_right->col) {
				    backcol(1);
				    if (autoinsert)
					insertcol(1, 1);
				    else {
					currow = fr->ir_right->row;
					if (row_hidden[currow])
					    backrow(1);
				    }
				}
			    } else if (autoinsert)
				insertrow(1, 1);
			}
		    } else
			forwrow(1);
		}
		break;
	    case CRCOLS:
		if ((collimit >= 0) && (curcol >= collimit)) {
		    forwrow(1);
		    curcol = 0;
		} else {
		    if ((fr = find_frange(currow, curcol))) {
			forwcol(1);
			if (curcol > fr->ir_right->col) {
			    backcol(1);
			    if (autowrap) {
				forwrow(1);
				curcol = fr->ir_left->col;
				if (col_hidden[curcol])
				    forwcol(1);
				if (currow > fr->ir_right->row) {
				    backrow(1);
				    if (autoinsert)
					insertrow(1, 1);
				    else {
					curcol = fr->ir_right->col;
					if (col_hidden[curcol])
					    backcol(1);
				    }
				}
			    } else if (autoinsert)
				insertcol(1, 1);
			}
		    } else
			forwcol(1);
		}
		break;
	    default:
		break;
	}
    }
}

void
doshell()
{
    /*
    *  "! command"  executes command
    *  "!"	forks a shell
    *  "!!" repeats last command
    */
#if VMS || MSDOS
    error("Not implemented on VMS or MS-DOS");
#else /* VMS */
    char *shl;
    int pid, temp;
    char cmd[MAXCMD];
    static char lastcmd[MAXCMD];

    if (!(shl = getenv("SHELL")))
	shl = "/bin/sh";

    deraw(1);
    (void) fputs("! ", stdout);
    (void) fflush(stdout);
    (void) fgets(cmd, MAXCMD, stdin);
    cmd[strlen(cmd) - 1] = '\0';	/* clobber \n */
    if (strcmp(cmd,"!") == 0)		/* repeat? */
	(void) strcpy(cmd, lastcmd);
    else
	(void) strcpy(lastcmd, cmd);

    if (modflg) {
	(void) puts("[No write since last change]");
	(void) fflush(stdout);
    }

    if (!(pid = fork())) {
	(void) signal(SIGINT, SIG_DFL);  /* reset */
	if (strlen(cmd))
	    (void) execl(shl, shl, "-c", cmd, NULL);
	else
	    (void) execl(shl, shl, NULL);
	exit (-127);
    }

    while (pid != wait(&temp));

    (void) printf("Press any key to continue ");
    fflush(stdout);
    cbreak();
    (void)getch();
    goraw();
    clear();
#endif /* VMS */
}

/* History functions */

static void
save_hist()
{
    if (!lasthist || strcmp(history[lasthist].histline, line)) {
	if (lasthist < 0)
	    lasthist = 1;
	else
	    lasthist = lasthist % HISTLEN + 1;

	if (lasthist > endhist)
	    endhist = lasthist;

	if (history[lasthist].len < strlen(line) + 1) {
	    history[lasthist].len = strlen(line) + 40;
	    history[lasthist].histline = scxrealloc(history[lasthist].histline,
		    history[lasthist].len);
	}
	(void) strcpy(history[lasthist].histline, line);
	histsessionnew++;
    }
    if (history[0].histline) {
	scxfree(history[0].histline);
	history[0].histline = NULL;
	history[0].len = 0;
    }
    histp = 0;
}

static void
for_hist()
{
    if (histp == 0) {
	last_col();
    	return;
    }

    if (histp == lasthist)
	histp = 0;
    else
	histp = histp % endhist + 1;

    if (lasthist >= 0) {
	(void) strcpy(line, history[histp].histline);
	last_col();
    }
    if (histp)
	error("History line %d", endhist - lasthist + histp);
    else
	error("");
}

static void
back_hist()
{
    if (histp == 0) {
	if (history[0].len < strlen(line) + 1) {
	    history[0].len = strlen(line) + 40;
	    history[0].histline = scxrealloc(history[0].histline,
		    history[0].len);
	}
	(void) strcpy(history[0].histline, line);

	if (lasthist >= 0)
	    histp = lasthist;
    } else if (histp == 1) {
    	if (endhist != lasthist)
	    histp = endhist;
    } else if (histp != ((lasthist + 1) % (endhist + 1)))
	histp--;

    if (lasthist >= 0) {
    	(void) strcpy(line, history[histp].histline);
	last_col();
    }
    if (histp)
	error("History line %d", endhist - lasthist + histp);
    else
	error("");
}

static void
search_hist()
{
#ifdef RECOMP
    char	*tmp;
#endif
#if !defined(REGCOMP) && !defined(RE_COMP) && !defined(REGCMP)
    static	unsigned lastsrchlen = 0;
#endif

    if (linelim < 1) {
	linelim = 0;
	edit_mode();
	return;
    }

#ifdef REGCOMP
    if (last_search)
	regfree(last_search);
    else
	last_search = &preg;
    if ((errcode = regcomp(last_search, line, REG_EXTENDED))) {
	char *tmp = scxmalloc((size_t)160);
	regerror(errcode, last_search, tmp, sizeof(tmp));
	error(tmp);
	scxfree(tmp);
	return;
    }
#else
#ifdef RE_COMP
    if ((tmp = re_comp(line)) != NULL) {
	error(tmp);
	return;
    }
#else
#ifdef REGCMP
    free(last_search);
    if ((last_search = regcmp(line, NULL)) == NULL) {
	error("Invalid search string");
	return;
    }
#else
    if (strlen(line) + 1 > lastsrchlen) {
    	lastsrchlen = strlen(line) + 40;
	last_search = scxrealloc(last_search, lastsrchlen);
    }
    (void) strcpy(last_search, line);
#endif
#endif
#endif
    (void) strcpy(line, history[0].histline);
    search_again(false);
    if (mode != EDIT_MODE) edit_mode();
    search_ind = ' ';
}

static void
search_again(bool reverse)
{
    int prev_match;
    int found_it;
#if !defined(REGCOMP) && !defined(RE_COMP) && !defined(REGCMP)
    char *look_here;
    int do_next;
#endif

#ifdef REGCOMP
    if ((last_search == NULL))
	return;
#else
#ifndef RE_COMP
    if ((last_search == NULL) || (*last_search == '\0'))
	return;
#endif
#endif
    prev_match = histp > 0 ? histp : 0;
    error("");

    do {
	if (lasthist > 0) {
	    if (!(search_dir ^ reverse) && histp != lasthist)
		if (histp <= 0) {
		    histp = ((lasthist + 1) % endhist);
		    (void) strcpy(line, history[histp].histline);
		} else
		    for_hist();
	    else if ((search_dir ^ reverse) && histp != ((lasthist + 1) % endhist))
		back_hist();
	    else {
		histp = 0;
		(void) strcpy(line, history[0].histline);
		last_col();
	    }
	} else
	    break;
	if (histp == prev_match) {
	    if (histp <= 0) {
		error("No matches found");
		break;
	    }
	}
	if (histp <= 0) {
	    if (search_dir ^ reverse)
		back_hist();
	    else
		histp = ((lasthist + 1) % endhist);
		(void) strcpy(line, history[histp].histline);
	}
	found_it = 0;
#ifdef REGCOMP
	if (regexec(last_search, line, 0, NULL, 0) == 0)
	    found_it++;
#else
#ifdef RE_COMP
	if (re_exec(line) != 0)
	    found_it++;
#else
#ifdef REGCMP
	if (regex(last_search, line) != NULL)
	    found_it++;
#else
	look_here = line;
	do_next = 0;
	while ((look_here = (char *)strchr(look_here, *last_search)) != NULL &&
		!found_it && !do_next) {

	    if (strncmp(look_here, last_search, strlen(last_search)) == 0)
		found_it++;
	    else if (look_here < line + strlen(line) - 1)
	        look_here++;
	    else
		do_next++;
	}
#endif
#endif
#endif
	if (histp == prev_match)
	    break;
    } while (!found_it);
    if (found_it)
	error("History line %d", endhist - lasthist + histp);
    else
	error("No matches found");
    edit_mode();
    linelim = strlen(line) - 1;
}

#if !defined(MSDOS) && defined HISTORY_FILE
void
write_hist()
{
    int i;
    FILE *fp, *tmpfp = NULL;
    char histfile[PATHLEN];

    if (histsessionnew < HISTLEN) {
	/* write the new history for this session to a tmp file */
	tmpfp = tmpfile();
	for (i = 1; i <= histsessionnew; i++) {
	    histsessionstart = histsessionstart % endhist + 1;
	    if (history[histsessionstart].len > 40)
		fprintf(tmpfp, "%s\n", history[histsessionstart].histline);
	}
	fseek(tmpfp, 0, SEEK_SET);

	/* re-read the main history, then read back in the saved session hist*/
	histp = 0;
	lasthist = 0;
	endhist = -1;
	read_hist();
	readhistfile(tmpfp);
    }

    /* now write to whole lot out to the proper save file */
    strcpy(histfile, HISTORY_FILE);
    if (findhome(histfile) && (fp = fopen(histfile, "w")) != NULL) {
	for (i = 1; i <= endhist; i++) {
	    lasthist = lasthist % endhist + 1;
	    if (history[lasthist].len > 40)
		fprintf(fp, "%s\n", history[lasthist].histline);
	}
	fclose(fp);
    }
}

static void
readhistfile(FILE *fp)
{
    while (fgets(line, FBUFLEN, fp)) {
	line[strlen(line)-1] = '\0'; /* chop the \n */
	save_hist();
    }
    fclose(fp);
}

void
read_hist()
{
    FILE *fp;
    char histfile[PATHLEN];

    strcpy(histfile, HISTORY_FILE);
    if (findhome(histfile) && (fp = fopen(histfile, "r")) != NULL)
	readhistfile(fp);
    histsessionstart = lasthist;
    histsessionnew = 0;
}
#endif

static void
col_0()
{
    linelim = 0;
}

static void
last_col()
{
    linelim = strlen(line);
    if (linelim > 0 && mode_ind == 'e')
	--linelim;
}

static int
find_char(int arg, int n)
{
    register int i;

    if (findchar)
	finddir = n;
    findchar = vigetch();
    switch (dotb[doti - 1]) {
	case 'f': case 'F': case 't': case 'T':
	    savedot(findchar);
	default:
	    break;
    }
    i = linelim;
    while (arg--) {
	i += n;
	while (i >= 0 && line[i] && line[i] != findchar)
	    i += n;
	if (i < 0 || !line[i]) {
	    i = linelim;
	    break;
	}
    }
    findfunc = 'f';
    return (i);
}

static int
to_char(int arg, int n)
{
    register int i;
    int tmp = linelim;

    if (linelim + n >= 0 && linelim + n < strlen(line))
	linelim += n;
    i = find_char(arg, n);
    if (i != linelim)
	i -= n;
    linelim = tmp;
    findfunc = 't';

    return (i);
}

static void
match_paren()
{
    register int i;
    int nest = 1;
    int tmp = linelim;

    if (line[linelim] == '(') {
	while (nest && ++linelim >= 0 && line[linelim]) {
	    if (line[linelim] == '(')
		nest++;
	    else if (line[linelim] == ')')
		nest--;
	}
	if (line[linelim] != ')')
	    linelim = tmp;
    }
    else if (line[linelim] == ')') {
	while (nest && --linelim >= 0 && line[linelim]) {
	    if (line[linelim] == ')')
		nest++;
	    else if (line[linelim] == '(')
		nest--;
	}
	if (line[linelim] != '(')
	    linelim = tmp;
    }
}

/* If save is 0, remember the current position.  Otherwise, if the current
 * cell has changed since the last remember(0), save the remembered location
 * for the `, ', and c comands.
 */
void
remember(int save)
{
    static int remrow, remcol, remstrow, remstcol;

    if (save && (currow != remrow || curcol != remcol ||
	    strow != remstrow || stcol != remstcol)) {
	savedrow[0] = remrow;
	savedcol[0] = remcol;
	savedstrow[0] = remstrow;
	savedstcol[0] = remstcol;
    } else {
	remrow = currow;
	remcol = curcol;
	remstrow = strow;
	remstcol = stcol;
    }
}

void
gohome()
{
    struct frange *fr;

    remember(0);
    if ((fr = find_frange(currow, curcol))) {
	if (currow >= fr->ir_left->row &&
		currow <= fr->ir_right->row &&
		curcol >= fr->ir_left->col &&
		curcol <= fr->ir_right->col &&
		(currow > fr->ir_left->row ||
		curcol > fr->ir_left->col)) {
	    currow = fr->ir_left->row;
	    curcol = fr->ir_left->col;
	} else if (currow > fr->or_left->row ||
		curcol > fr->or_left->col) {
	    currow = fr->or_left->row;
	    curcol = fr->or_left->col;
	} else {
	    currow = 0;
	    curcol = 0;
	}
    } else {
	currow = 0;
	curcol = 0;
    }
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
    remember(1);
    FullUpdate++;
}

void
leftlimit()
{
    struct frange *fr;

    remember(0);
    if ((fr = find_frange(currow, curcol))) {
	if (currow >= fr->ir_left->row &&
		currow <= fr->ir_right->row &&
		curcol > fr->ir_left->col &&
		curcol <= fr->ir_right->col)
	    curcol = fr->ir_left->col;
	else if (curcol > fr->or_left->col &&
		curcol <= fr->or_right->col)
	    curcol = fr->or_left->col;
	else
	    curcol = 0;
    } else
	curcol = 0;
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
    remember(1);
}

void
rightlimit()
{
    register struct ent *p;
    struct frange *fr;

    remember(0);
    if ((fr = find_frange(currow, curcol))) {
	if (currow >= fr->ir_left->row &&
		currow <= fr->ir_right->row &&
		curcol >= fr->ir_left->col &&
		curcol < fr->ir_right->col)
	    curcol = fr->ir_right->col;
	else if (curcol >= fr->or_left->col &&
		curcol < fr->or_right->col)
	    curcol = fr->or_right->col;
	else {
	    curcol = maxcols - 1;
	    while (!VALID_CELL(p, currow, curcol) &&
		    curcol > fr->or_right->col)
		curcol--;
	    if ((fr = find_frange(currow, curcol)))
		curcol = fr->or_right->col;
	}
    } else {
	curcol = maxcols - 1;
	while (!VALID_CELL(p, currow, curcol) && curcol > 0)
	    curcol--;
	if ((fr = find_frange(currow, curcol)))
	    curcol = fr->or_right->col;
    }
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
    remember(1);
}

void
gototop()
{
    struct frange *fr;

    remember(0);
    if ((fr = find_frange(currow, curcol))) {
	if (curcol >= fr->ir_left->col &&
		curcol <= fr->ir_right->col &&
		currow > fr->ir_left->row &&
		currow <= fr->ir_right->row)
	    currow = fr->ir_left->row;
	else if (currow > fr->or_left->row &&
		currow <= fr->or_right->row)
	    currow = fr->or_left->row;
	else
	    currow = 0;
    } else
	currow = 0;
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
    remember(1);
}

void
gotobottom()
{
    register struct ent *p;
    struct frange *fr;

    remember(0);
    if ((fr = find_frange(currow, curcol))) {
	if (curcol >= fr->ir_left->col &&
		curcol <= fr->ir_right->col &&
		currow >= fr->ir_left->row &&
		currow < fr->ir_right->row)
	    currow = fr->ir_right->row;
	else if (currow >= fr->or_left->row &&
		currow < fr->or_right->row)
	    currow = fr->or_right->row;
	else {
	    currow = maxrows - 1;
	    while (!VALID_CELL(p, currow, curcol) &&
		    currow > fr->or_right->row)
		currow--;
	    if ((fr = find_frange(currow, curcol)))
		currow = fr->or_right->row;
	}
    } else {
	currow = maxrows - 1;
	while (!VALID_CELL(p, currow, curcol) && currow > 0)
	    currow--;
	if ((fr = find_frange(currow, curcol)))
	    currow = fr->or_right->row;
    }
    rowsinrange = 1;
    colsinrange = fwidth[curcol];
    remember(1);
}

static void
dogoto()
{
    static char		*tempc = NULL;
    static unsigned	templen = 0;
    int			tempi;

    if (strlen(line) + 1 > templen) {
    	templen = strlen(line) + 40;
	tempc = scxrealloc(tempc, templen);
    }

    strcpy(tempc, line);
    tempi = linelim;

    /* Can't switch back to navigate mode if insert_mode() is used here
     * instead of toggle_navigate_mode(), which is what we want when doing
     * a goto from within navigate mode.
     */
    insert_mode();
    /* Tempted as I was, I had to resist making this "Where would you like
     * to go today?" - CRM :)
     */
    query("goto where?", NULL);
    if (linelim >= 0) {
	memmove(line + 5, line, strlen(line) + 1);
	strncpy(line, "goto ", 5);
	linelim = 0;
	yyparse();
    }

    strcpy(line, tempc);
    linelim = tempi;
    /* Now we need to change back to navigate mode ourselves so that
     * toggle_navigate_mode() will work properly again.
     */
    mode_ind = 'v';
    mode = NAVIGATE_MODE;
    if (!showrange)
	toggle_navigate_mode();
}

void
query(char *s, char *data)
{
    int c;

    insert_mode();
    if (data != NULL) {
	strcpy(line, data);
	linelim = strlen(line);
    } else {
    	*line = '\0';
	linelim = 0;
    }
    if (s != NULL) error(s);

    while (linelim >= 0) {
	update(0);
	switch (c = nmgetch()) {
	    case ctl('m'):
		error("");
		return;
	    case ctl('g'):
		line[0] = '\0';
		linelim = -1;
		error("");
		update(0);
		return;
	    case ctl('l'):
		FullUpdate++;
		clearok(stdscr,1);
		update(1);
		break;
	    default:
		write_line(c);
	}
    }
}
