/*	Copyright (C) 1990 Free Software Foundation, Inc.

This file is part of Oleo, the GNU Spreadsheet.

Oleo 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.

Oleo 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 Oleo; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>

#ifdef I_IOCTL
#include <sys/ioctl.h>
#endif

#include "funcdef.h"

#define obstack_chunk_alloc ck_malloc
#define obstack_chunk_free free
#include "obstack.h"

#ifdef __TURBOC__
#define RCFILE "oleo.rc"
#else
#define RCFILE ".oleorc"
#endif

#include "sysdef.h"

#include "global.h"
#include "cell.h"
#include "kbd.h"

#define CTRL(X) ((X)&037)

#define LINE_MIN 28

#ifdef __TURBOC__
#define TIMER_MULT    10000
#else
#if defined(NO_UALARM) && !defined(SETITIMER)
#define TIMER_MULT     1/10
#else
#define TIMER_MULT   100000
#endif
#endif

/* Structures for this file */

/* for func_flags */
#define NONTOP	0x1
#define TOPLN	0x2

#define ALL	0x3


#define WTH_CHR 0x4
#define BRK	0x8
#define NC	0x10

struct line {
	int alloc;
	char *buf;
};

struct macro {
	struct macro *mac_prev;
	unsigned char *mac_exe;
	int mac_flags;
	CELLREF mac_row, mac_col;
	struct rng mac_rng;
};

/* External spreadsheet functions */

/* utils.c */
extern char *char_to_string EXT1(char);
extern int string_to_char EXT1(char **);
extern FILE *xopen_with_backup EXT2(const char *,const char *);
extern int xclose EXT1(FILE *);
extern char *err_msg EXT0();
extern char *mk_sprintf EXT1N(char *);

extern void init_mem EXT0();
extern void init_eval EXT0();
extern void init_refs EXT0();
extern void init_cells EXT0();

extern void panic_read_file EXT2(FILE *,int);
extern void panic_write_file EXT2(FILE *,struct rng *);
extern  int panic_set_options EXT2(int, char *);
extern void panic_show_options EXT0();

#ifdef USE_DLD
	/* If we're using dynamic linking, we get the names of the
	   functions to call by prepending the basename of save_name onto
		   _read_file
		   _write_file
		   _set_options
		   _show_options
	   so, if the file is sylk.o , the functions are named
		   sylk_read_file
		   sylk_write_file
		   sylk_set_options
		   sylk_show_options
*/
char *io_name;

#else

extern void sylk_read_file EXT2(FILE *,int);
extern void sylk_write_file EXT2(FILE *,struct rng *);
extern  int sylk_set_options EXT2(int, char *);
extern void sylk_show_options EXT0();

extern void sc_read_file EXT2(FILE *,int);
extern void sc_write_file EXT2(FILE *,struct rng *);
extern  int sc_set_options EXT2(int, char *);
extern void sc_show_options EXT0();

extern void list_read_file EXT2(FILE *,int);
extern void list_write_file EXT2(FILE *,struct rng *);
extern  int list_set_options EXT2(int, char *);
extern void list_show_options EXT0();

/* extern char sl_sep; */

#endif

/* regions.c */
extern void set_rng EXT5(struct rng *, CELLREF, CELLREF, CELLREF, CELLREF);
extern void lock_region EXT2(struct rng *, int);
extern void format_region EXT3(struct rng *, int, int);
extern unsigned short print_width;

/* ref.c */
extern int eval_next_cell EXT0();
extern struct var *find_var EXT2(char *, int);
extern timer_active;

/* io_utils.c */
extern void get_usr_stats EXT2(int, char **);
extern void set_usr_stats EXT2(int, char **);

/* io_disp.c */
extern void redisp EXT0();
extern void open_display EXT1(int);
extern void clear_top_before EXT0();
extern void clear_top_after EXT0();
extern void close_display();
extern void disp_scrn EXT0();
extern int get_chr_prompt EXT1(char *);
extern void recenter_cur_win EXT0();
extern void recenter_all_win EXT0();
extern void cur_status EXT0();
extern int move_cell_cursor EXT2(CELLREF, CELLREF);
extern int get_inp_line EXT2(char *, struct line  *);

/* utils.c */
extern char *myname;
extern int __make_backups;
extern int __backup_by_copying;


	/* Routines for manipulating 'struct line's */
void set_line EXT2(struct line *, char *);

	/* Terminal I/O functions */
int get_chr EXT0();

static unsigned char real_get_chr EXT0();

	/* Routines for manipulating key bindings */
static void do_bind_key EXT4(struct keymap *, int, unsigned char, unsigned char);
static void desc_map EXT1(struct keymap *);

	/* File I/O functions */
static FILE *open_file EXT3(char *, struct line *, char *);
static void close_file EXT2(struct line *, FILE *);

	/* Routines for putting info into 'struct rng's */
static int get_two_ranges EXT4(char *, struct rng *, struct rng *, struct line *);
static FILE * get_range_and_file EXT3(char *, struct rng *, struct line *);
static int get_a_range EXT3(char *,struct rng *,struct line *);
static int get_abs_rng EXT2(char **, struct rng *);

static void sprint_line EXT2N(struct line *, char *);

	/* Routines for converting text<-->cell_format */
static int str_to_fmt EXT1(char *);

	/* Routines for converting text<-->cell_jst */
static int chr_to_jst EXT1(int);

	/* Routines for implementing user commands */
int global_cmd EXT1(int);

void set_options EXT1(char *);

	/* Backends for other functions */
static int do_set_option EXT1(char *);


		/* Spreadsheet (global) functions declared in this file */


int main EXT2(int, char **);

void map_chr EXT1(int);

extern void clear_spreadsheet EXT0();

void pr_cell EXT3(CELLREF, CELLREF, CELL *);

char *cell_name EXT2(CELLREF, CELLREF);
char *range_name EXT1(struct rng *);

char *fmt_to_str EXT1(int);
char *jst_to_str EXT1(int);

#ifdef A0_REFS
char *col_to_str EXT1(CELLREF);
#endif

const int colmagic[] = {
	 0, 0, 1,-1, 1,-1, 1,-1, 0};
const int rowmagic[] = {
	-1, 1, 0, 0,-1,-1, 1, 1, 0};

char * ename[] = {
	"#WHAT?",
	"#ERROR",	"#BAD_INPUT",	"#NON_NUMBER",	"#NON_STRING",
	"#NON_BOOL",	"#NON_RANGE",	"#OUT_OF_RANGE","#NO_VALUES",
	"#DIV_BY_ZERO",	"#BAD_NAME",	"#NOT_AVAIL",	"#PARSE_ERROR",
	"#NEED_OPEN",   "#NEED_CLOSE",  "#NEED_QUOTE",  "#UNK_CHAR",
	"#UNK_FUNC",
	0
};

const char tname[] = "#TRUE";
const char fname[] = "#FALSE";


int cur_chr;
struct cmd_func *cur_cmd;
unsigned char cur_vector;

/* User settable options */
int bkgrnd_recalc = 1;
int auto_recalc = 1;
/* Original value is 10 seconds/tick */
unsigned signal_ticks = 10 * TIMER_MULT;

void (*read_file) EXT2(FILE *,int)           = panic_read_file;
void (*write_file) EXT2(FILE *,struct rng *) = panic_write_file;
int (*set_file_opts) EXT2(int, char *)	     = panic_set_options;
void (*show_file_opts) EXT0()		     = panic_show_options;

int n_bound_macros;
struct rng *bound_macros;
int bound_macro_vec;

char *macro_func_arg = 0;

/* This variable is non-zero if the spreadsheet has been changed in any way */
int modified = 0;

CELLREF setrow,
	setcol;

CELLREF curow = MIN_ROW,
	cucol = MIN_COL;

CELLREF mkrow = NON_ROW,
	mkcol = NON_COL;

struct rng mkrng;

/* CELLREF srl,srh,scl,sch; */

unsigned int how_many = 1;

static struct macro *rmac = 0;
static struct obstack macro_stack;

static unsigned char *making_macro;
static unsigned char *making_macro_start;
static unsigned int making_macro_size;

/* If 2, clear the top line before reading a character */
/* if 1, clear it after reading a char */
int topclear = 0;

struct line val_line;
struct line fmt_line;
struct line wid_line;

/* Lines 'a' through 'z' */
struct line in_line[26];

unsigned short default_width=8;
int default_jst = JST_LFT;
int default_fmt = FMT_GEN;
int default_lock = LCK_UNL;

#ifndef __TURBOC__
char buf[BUFSIZ];
int term_flag;

void got_sigio EXT1(int);
#endif
static void got_sigint EXT1(int);

extern char print_buf[];


struct keymap **the_maps;
char **map_names;
int num_maps;

struct cmd_func **the_funcs;
int num_funcs;

/* Keymap stuff:  This is the 'main' keymap */

#define GO_UP_CMD	1		/* We want these-all to be low */
#define GO_DN_CMD	2		/* numbers so that rowmagic and */
#define GO_RT_CMD	3		/* colmagic can be small arrays */
#define GO_LF_CMD	4		/* Also note that GO, SCR, and  */
#define GO_UPRT_CMD	5		/* MAGIC mumble must all be in  */
#define GO_UPLF_CMD	6		/* the same order, and they     */
#define GO_DNRT_CMD	7		/* must all agree with rowmagic */
#define GO_DNLF_CMD	8		/* and colmagic */

#define SCR_UP_CMD	9
#define SCR_DN_CMD	10
#define SCR_RT_CMD	11
#define SCR_LF_CMD	12
#define SCR_UPRT_CMD	13
#define SCR_UPLF_CMD	14
#define SCR_DNRT_CMD	15
#define SCR_DNLF_CMD	16

#define SCAN_UP_CMD	17
#define SCAN_DN_CMD	18
#define SCAN_RT_CMD	19
#define SCAN_LF_CMD	20

#define BREAK_CMD	21
#define RECENTER_CMD	22
#define SET_OPT_CMD	23
#define SET_DEF_CMD	24
#define QUIT_CMD	25
#define REDRAW_CMD	26
#define USR_FMT_CMD	27

#define SET_VAR_CMD	28
#define SHO_VAR_CMD	29
#define SHO_ALL_VAR_CMD	30

#define RECALC_CMD	31
#define BIND_KEY_CMD	32
#define DESC_KEY_CMD	33
#define READ_KEY_CMD	34
#define SAVE_KEY_CMD	35

#define WRITE_ALL_CMD	36
#define READ_ALL_CMD	37
#define READ_MERGE_CMD	38
#define KILL_ALL_CMD	39

#define COPY_REG	40
#define COPY_VAL_REG	41
#define FORMAT_REG	42
#define MOVE_REG	43
#define PRINT_REG	44
#define SORT_REG	45
#define WRITE_REG	46
#define KILL_REG	47

#define START_MACRO	48
#define EXECUTE_MACRO	49
#define INTERACT_MACRO	50

#define GOTO_CMD	51
#define SET_MARK_CMD	52
#define EDIT_CELL	53
#define EDIT_VAL_CELL	54
#define FORMAT_CELL	55
#define KILL_CELL	56
#define NEW_DEF_CELL	57

#define DIGIT_0		58
#define DIGIT_1		59
#define DIGIT_2		60
#define DIGIT_3		61
#define DIGIT_4		62
#define DIGIT_5		63
#define DIGIT_6		64
#define DIGIT_7		65
#define DIGIT_8		66
#define DIGIT_9		67

#define NEW_CELL	68

#define SHOW_OPT_CMD	69

#define OPEN_WIN_CMD	70
#define CLOSE_WIN_CMD	71
#define GOTO_WIN_CMD	72

#define END_MACRO	73
#define MK_KEYMAP	74

#ifdef TEST
#define DEBUG_CMD	75

#endif

static struct key main_keys_1[] = {
	{ 0,	SET_MARK_CMD },		/* ^@ */
	{ 0,	UNBOUND },
	{ 0,	GO_LF_CMD },
	{ 0,	UNBOUND },
	{ 0,	UNBOUND },
	{ 0,	UNBOUND },		/* ^E */
	{ 0,	GO_RT_CMD },
	{ 0,	BREAK_CMD },
	{ 0,	UNBOUND },
	{ 0,	UNBOUND },
	{ 0,	UNBOUND },		/* ^J */
	{ 0,	UNBOUND },
	{ 0,	RECENTER_CMD },
 	{ 0,	UNBOUND },
	{ 0,	GO_DN_CMD },
	{ 0,	UNBOUND },		/* ^O */
	{ 0,	GO_UP_CMD },
	{ 0,	UNBOUND },
	{ 0,	REDRAW_CMD },
	{ 0,	UNBOUND },
	{ 0,	UNBOUND },		/* ^T */
	{ 0,	UNBOUND },
	{ 0,	SHO_ALL_VAR_CMD },
	{ 0,	UNBOUND },
	{ 0,	KILL_ALL_CMD },
	{ 0,	UNBOUND },		/* ^Y */
	{ 0,	UNBOUND },
	{ 255,	META_MAP },		/* ^[ */
	{ 0,	UNBOUND },		/* ^\ */
	{ 0,	UNBOUND },		/* ^] */
	{ 0,	UNBOUND },		/* ^^ */
	{ 0,	UNBOUND },		/* ^_ */
	{ 0,	NEW_CELL },		/* space */
	{ 0,	RECALC_CMD },		/* ! */
	{ 0,	NEW_CELL },		/* " */
	{ 0,	NEW_CELL },		/* # */
	{ 0,	NEW_CELL },		/* $ */
	{ 0,	UNBOUND },		/* % */
	{ 0,	UNBOUND },		/* & */
	{ 0,	UNBOUND },		/* ' */
	{ 0,	NEW_CELL },		/* ( */
	{ 0,	UNBOUND },		/* ) */
	{ 0,	UNBOUND },		/* * */
	{ 0,	NEW_CELL },		/* + */
	{ 0,	UNBOUND },		/* , */
	{ 0,	NEW_CELL },		/* - */
	{ 0,	NEW_CELL },		/* . */
	{ 0,	UNBOUND },		/* / */
	{ 0,	NEW_CELL },		/* 0 */
	{ 0,	NEW_CELL },		/* 1 */
	{ 0,	NEW_CELL },		/* 2 */
	{ 0,	NEW_CELL },		/* 3 */
	{ 0,	NEW_CELL },		/* 4 */
	{ 0,	NEW_CELL },		/* 5 */
	{ 0,	NEW_CELL },		/* 6 */
	{ 0,	NEW_CELL },		/* 7 */
	{ 0,	NEW_CELL },		/* 8 */
	{ 0,	NEW_CELL },		/* 9 */
	{ 0,	BIND_KEY_CMD },		/* : */
	{ 0,	DESC_KEY_CMD },		/* ; */
	{ 0,	UNBOUND },		/* < */
	{ 0,	NEW_DEF_CELL },		/* = */
	{ 0,	UNBOUND },		/* > */
	{ 0,	UNBOUND },		/* ? */
	{ 0,	NEW_CELL },		/* @ */
	{ 0,	UNBOUND },		/* A */
	{ 0,	SCR_DNLF_CMD },
	{ 0,	COPY_VAL_REG },
	{ 0,	UNBOUND },
	{ 0,	EDIT_VAL_CELL },
	{ 0,	FORMAT_REG },		/* F */
	{ 0,	UNBOUND },
	{ 0,	SCR_LF_CMD },
	{ 0,	UNBOUND },
	{ 0,	SCR_DN_CMD },
	{ 0,	SCR_UP_CMD },		/* K */
	{ 0,	SCR_RT_CMD },
	{ 0,	UNBOUND },
	{ 0,	SCR_DNRT_CMD },
	{ 0,	SHOW_OPT_CMD },
	{ 0,	UNBOUND },		/* P */
	{ 0,	QUIT_CMD },
	{ 0,	READ_MERGE_CMD },
	{ 0,	UNBOUND },
	{ 0,	UNBOUND },
	{ 0,	SCR_UPRT_CMD },		/* U */
	{ 0,	SHO_VAR_CMD },
	{ 0,	WRITE_REG },
	{ 0,	KILL_REG },
	{ 0,	SCR_UPLF_CMD },
	{ 0,	UNBOUND },		/* Z */
	{ 0,	UNBOUND },		/* [ */
	{ 0,	UNBOUND },		/* \ */
	{ 0,	UNBOUND },		/* ] */
	{ 0,	UNBOUND },		/* ^ */
	{ 0,	UNBOUND },		/* _ */
	{ 0,	UNBOUND },		/* ` */
	{ 0,	UNBOUND },		/* a */
	{ 0,	GO_DNLF_CMD },
	{ 0,	COPY_REG },
	{ 0,	SET_DEF_CMD },
	{ 0,	EDIT_CELL },
	{ 0,	FORMAT_CELL },		/* f */
	{ 0,	GOTO_CMD },
	{ 0,	GO_LF_CMD },
	{ 0,	UNBOUND },
	{ 0,	GO_DN_CMD },
	{ 0,	GO_UP_CMD },		/* k */
	{ 0,	GO_RT_CMD },
	{ 0,	MOVE_REG },
	{ 0,	GO_DNRT_CMD },
	{ 0,	SET_OPT_CMD },
	{ 0,	PRINT_REG },		/* p */
	{ 0,	UNBOUND },
	{ 0,	READ_ALL_CMD },
	{ 0,	SORT_REG },
	{ 0,	UNBOUND },
	{ 0,	GO_UPRT_CMD },		/* u */
	{ 0,	SET_VAR_CMD },
	{ 0,	WRITE_ALL_CMD },
	{ 0,	KILL_CELL },
	{ 0,	GO_UPLF_CMD }		/* y */
};
static struct key main_keys_2[] = {
  { 255, ANSI_MAP }   /* CSI , 0x9b hopefully */
};

struct keymap main_map[] = {
	{&main_map[1], 0, 0, 1,
	'\0','y',
	&main_keys_1[0]},

	{0, 1, 0, 1,
	'\233','\233',
	&main_keys_2[0]}
};

/* The meta-map, for key sequences that start with escape */
/* We declare these in reverse order so we won't have to forward-reference
   all the pointers. . . */
static struct key meta_key_4[] = {
	{ 0,	USR_FMT_CMD },
	{ 0,	UNBOUND },
	{ 0,	OPEN_WIN_CMD },
	{ 0,	EXECUTE_MACRO },
	{ 0,	UNBOUND },
	{ 0,	UNBOUND },
	{ 255,	ANSI_MAP }
};

static struct key meta_key_3[] = {
	{ 0,	CLOSE_WIN_CMD },
	{ 0,	UNBOUND },
	{ 0,	UNBOUND },
	{ 0,	UNBOUND },
	{ 0,	GOTO_WIN_CMD },
	{ 0,	SCAN_LF_CMD },
	{ 0,	UNBOUND },
	{ 0,	SCAN_DN_CMD },
	{ 0,	SCAN_UP_CMD },
	{ 0,	SCAN_RT_CMD },
};

static struct key meta_key_2[] = {
	{ 0,	DIGIT_0 },
	{ 0,	DIGIT_1 },
	{ 0,	DIGIT_2 },
	{ 0,	DIGIT_3 },
	{ 0,	DIGIT_4 },
	{ 0,	DIGIT_5 },
	{ 0,	DIGIT_6 },
	{ 0,	DIGIT_7 },
	{ 0,	DIGIT_8 },
	{ 0,	DIGIT_9 },
	{ 0,	UNBOUND },
	{ 0,	UNBOUND },
	{ 0,	READ_KEY_CMD },
	{ 0,	UNBOUND },
	{ 0,	SAVE_KEY_CMD }
};

static struct key meta_key_1[] = {
	{ 0,	START_MACRO },
	{ 0,	END_MACRO }
};

#ifdef TEST
#define DBG_ADD 1
static struct key meta_key_0 = 	{ 0,	DEBUG_CMD };
#else
#define DBG_ADD 0
#endif

struct keymap meta_map[] = {
#ifdef TEST
	{
		&meta_map[DBG_ADD],	0,	0,	1,
		CTRL('d'),CTRL('d'),
		&meta_key_0
	},
#endif
	{
		&meta_map[1+DBG_ADD],	0,	0,	1,
		'(',')',
		&meta_key_1[0]
	},{
		&meta_map[2+DBG_ADD],	0,	0,	0,
		'0','>',
		&meta_key_2[0]
	},{
		&meta_map[3+DBG_ADD],	0,	0,	0,
		'C', 'L',
		&meta_key_3[0]
	},{
		&meta_map[4+DBG_ADD],	0,	0,	0,
		'U','[',
		&meta_key_4[0]
	},{
		&meta_map[5+DBG_ADD],	0,	0,	0,
		'c', 'l',
		&meta_key_3[0],
	},{
		0,			1,	0,	0,
		'u', 'x',
		&meta_key_4[0]
	}
};

/* For keyboards that return ^[ [ {A B C D} for the arrow keys */
static struct key ansi_key[] = {
	{ 0,	GO_UP_CMD },
	{ 0,	GO_DN_CMD },
	{ 0,	GO_RT_CMD },
	{ 0,	GO_LF_CMD }
};

struct keymap ansi_map = {
	0,		1,	0,	1,
	'A', 'D',
	&ansi_key[0]
};
/* FOr dealing with meta-digit commands */
static struct keymap digit_map = {
	&main_map[0],	1,	0,	0,
	'0','9',
	&meta_key_2[0]
};

/* The user commands that are defined in this file. . . */
extern void scroll_cell_cursor EXT1(int);
extern void copy_region();
extern void copy_values_region();
extern void set_format();
extern void move_region();
extern void delete_region();
extern void format_area();
extern void open_window();
extern void close_window();
extern void goto_window();
extern void print_region EXT2(struct rng *, char *);

static void do_debug EXT1(char *);
static void end_macro EXT0();
/* static void do_ansi_cmd EXT0(); */
static void do_break_cmd EXT0();
static void set_default EXT0();
static void quit_cmd EXT0();
static void set_usr_fmt EXT1(char *);
static void set_var EXT1(char *);
static void show_var EXT1(char *);
static void show_all_var EXT0();
static void recalc_cmd EXT0();
static void bind_key_cmd EXT1(char *);
static void desc_key_cmd EXT0();
static void read_cmds_cmd EXT1(FILE *);
static void write_keys_cmd EXT1(FILE *);
static void write_cmd EXT1(FILE *);
static void read_cmd EXT1(FILE *);
static void read_merge_cmd EXT1(FILE *);
static void kill_all_cmd EXT0();
static void sort_region_cmd EXT1(char *);
static void write_reg_cmd EXT2(FILE *, struct rng *);
static void start_macro EXT0();
static void execute_cmd EXT1(char *);
static void interact_macro_cmd EXT0();
static void goto_region EXT1(struct rng *);
static void mark_cell_cmd EXT0();
static void do_input_cmd EXT2(int, int);
static void format_cell_cmd EXT0();
static void kill_cell_cmd EXT0();
static void digit_cmd EXT1(int);
static void show_options EXT0();
static void bound_macro EXT1(int);
static void make_keymap EXT1(char *);
static void scan_cell_cursor EXT1(int);
static void shift_cell_cursor EXT1(int);

/* For lines that take text input, the second letter (eg 'ta' or 'fdr')
   specifies which struct line to use to contain the text generated for that
   command.  That way, the command will have the appropriate defaults, etc.
   Currently 'a' through 'n' are used (?).
	a	set options
	b	set/show variables
	c	read/write keymap
	d	read/write file
	e	read-merge	write-region
	f	copy region/copy values region
	g	format-region
	h	move-region
	i	print-region
	j	sort-region
	k	delete-region
	l	execute-command
	m	goto-cell
	n	open-window
	o	close-window
	p	debug
	q	bind-key
	r	set-user-format
	s	create-keymap
 */

static struct cmd_func cmd_funcs[] = {
{	"unbound",		0,	ALL,	0 },

{	"go-up",		"n0",	ALL,	shift_cell_cursor },
{	"go-down",		"n1",	ALL,	shift_cell_cursor },
{	"go-right",		"n2",	ALL,	shift_cell_cursor },
{	"go-left",		"n3",	ALL,	shift_cell_cursor },
{	"go-upright",		"n4",	ALL,	shift_cell_cursor },
{	"go-upleft",		"n5",	ALL,	shift_cell_cursor },
{	"go-downright",		"n6",	ALL,	shift_cell_cursor },
{	"go-downleft",		"n7",	ALL,	shift_cell_cursor },

{	"scroll-up",		"n0",	ALL,	scroll_cell_cursor },
{	"scroll-down",		"n1",	ALL,	scroll_cell_cursor },
{	"scroll-right",		"n2",	ALL,	scroll_cell_cursor },
{	"scroll-left",		"n3",	ALL,	scroll_cell_cursor },
{	"scroll-upright",	"n4",	ALL,	scroll_cell_cursor },
{	"scroll-upleft",	"n5",	ALL,	scroll_cell_cursor },
{	"scroll-downright",	"n6",	ALL,	scroll_cell_cursor },
{	"scroll-downleft",	"n7",	ALL,	scroll_cell_cursor },

{	"scan-up",		"n0",	ALL,	scan_cell_cursor },
{	"scan-down",		"n1",	ALL,	scan_cell_cursor },
{	"scan-right",		"n2",	ALL,	scan_cell_cursor },
{	"scan-left",		"n3",	ALL,	scan_cell_cursor },

{	"break",		0,	ALL|BRK,do_break_cmd },
{	"recenter-window",	0,	ALL,	recenter_cur_win },
{	"set-option",		"ta",	ALL,	set_options },
{	"set-defaults",		"T",	ALL,	set_default },
{	"quit",			"C",	ALL,	quit_cmd },
{	"redraw-screen",	0,	ALL,	disp_scrn },
{	"set-user-format",	"trT",	ALL,	set_usr_fmt },

/* If you change the line used by {set,show}-variable, you must change
   {set,show}_var to use the correct line, else bad things will happen */
{	"set-variable",		"tb",	ALL,	set_var },
{	"show-variable",	"tb",	ALL,	show_var },
{	"show-all-variables",	0,	ALL,	show_all_var },

{	"recalculate",		0,	ALL,	recalc_cmd },
{	"bind-key",		"tqT",	ALL,	bind_key_cmd },
{	"describe-key",		"T",	ALL,	desc_key_cmd },
{	"read-commands",	"fcr",	ALL,	read_cmds_cmd },
{	"write-keys",		"fcw",	ALL,	write_keys_cmd },

/* If you change the line used by {read,write}-file, you must change
   main() to use the correct line, else chaos will erupt */
{	"write-file",		"fdw",	ALL,	write_cmd },
{	"read-file",		"Cfdr",	ALL,	read_cmd },
{	"read-merge",		"fer",	ALL,	read_merge_cmd },
{	"clear-spreadsheet",	"C",	ALL,	kill_all_cmd },

{	"copy-region",		"Rf",	ALL,	copy_region },
{	"copy-values-in_region","Rf",	ALL,	copy_values_region },
{	"format-region",	"rg",	ALL,	format_area },
{	"move-region",		"Rh",	ALL,	move_region },
{	"print-region",		"Fi",	ALL,	print_region },
{	"sort-region",		"tj",	ALL,	sort_region_cmd },
{	"write-region-to-file",	"Fe",	ALL,	write_reg_cmd },
{	"delete-region",	"rk",	ALL,	delete_region },

{	"start-entering-macro",	0,	ALL,	start_macro },
{	"execute-command",	"tl",	ALL,	execute_cmd },
{	"macro-interactive-here",0,	ALL,	interact_macro_cmd },

/* If you change which line goto-cell uses, you *must* modify goto_region() */
{	"goto-cell",		"rm",	ALL,	goto_region },
{	"mark-cell",		0,	ALL,	mark_cell_cmd },
{	"edit-cell",		"T0",	ALL|WTH_CHR,do_input_cmd },
{	"edit-value-cell",	"T1",	ALL|WTH_CHR,do_input_cmd },
{	"format-cell",		"T",	ALL,	format_cell_cmd },
{	"delete-cell",		0,	ALL,	kill_cell_cmd },
{	"edit-cell-with-default","T3",	ALL|WTH_CHR,do_input_cmd },

{	"digit-0",		"N",	ALL|NC,	digit_cmd },
{	"digit-1",		"N",	ALL|NC,	digit_cmd },
{	"digit-2",		"N",	ALL|NC,	digit_cmd },
{	"digit-3",		"N",	ALL|NC,	digit_cmd },
{	"digit-4",		"N",	ALL|NC,	digit_cmd },
{	"digit-5",		"N",	ALL|NC,	digit_cmd },
{	"digit-6",		"N",	ALL|NC,	digit_cmd },
{	"digit-7",		"N",	ALL|NC,	digit_cmd },
{	"digit-8",		"N",	ALL|NC,	digit_cmd },
{	"digit-9",		"N",	ALL|NC,	digit_cmd },

{	"enter-text-in-cell",	"T2",	WTH_CHR|ALL,do_input_cmd },

{	"show-options",		0,	ALL,	show_options },

{	"open-window",		"tn",	ALL,	open_window },
{	"close-window",		"to",	ALL,	close_window },
{	"goto-window",		"to",	ALL,	goto_window },

{	"stop-entering-macro",	0,	ALL,	end_macro },

{	"create-keymap",	"ts",	ALL,	make_keymap },

#ifdef TEST
{	"debug",		"tp",	ALL,	do_debug },
#endif

{	"bound-menu",		0,	WTH_CHR|ALL,0 },
{	0,			0,	0,	0 }

};



#ifdef __TURBOC__
extern void deal_alarm EXT0();

static unsigned char
real_get_chr FUN0()
{
	unsigned char ch;

	if(auto_recalc) {
		current_cycle++;
		if(!bkgrnd_recalc) {
			while(eval_next_cell())
				;
		} else {
			if(timer_active) {

				static int ttick;
				int ev = 1;

				for(;kbhit()==0;) {
					if(ev) {
						ev=eval_next_cell();
						if(!ev)
							redisp();
					}
 					if(ttick++>=signal_ticks) {
						deal_alarm();
						ttick=0;
						ev=1;
					}
				}
			}

			while(kbhit()==0 && eval_next_cell())
				;
		}
	}
	redisp();
	ch=getch();
	if(making_macro) {
		if(ch==0)
			*making_macro++=0x80|'a';
		else if(ch=='{')
			*making_macro++=0x80|'b';
		else
			*making_macro++=ch;
		if(making_macro>=making_macro_start+making_macro_size) {

			making_macro_start=ck_realloc(making_macro_start,5+making_macro_size*2);
			making_macro=making_macro_start+making_macro_size;
			making_macro_size*=2;
		}
	}
	return ch;
}

#else
#if !defined(SIGIO) || defined(AMIGA)
#undef CTRL(X)
#undef NC
#include <curses.h>

int intr1 = 0;
int intr2 = 0;
static unsigned char
real_get_chr FUN0()
{
	static int saved;
	unsigned char ret;

	if(saved) {
		ret=saved-1;
		saved=0;
		goto fini;
	}
	if(auto_recalc) {
		current_cycle++;
		if(!bkgrnd_recalc) {
			while(eval_next_cell())
				;
		} else {
			int ev;

			do {
#if !defined(AMIGA)
				nodelay(stdscr, TRUE);
#endif
				while(ev=eval_next_cell())
					if((ret=getch())!=ERR)
						goto fini;
#ifdef TEST
					else
						intr1++;
#endif
#if !defined(AMIGA)
				nodelay(stdscr,FALSE);
#endif
				redisp();
				ret=getch();
#ifdef TEST
				if(ret==ERR)
					intr2++;
#endif
			} while(ret==ERR);
			goto fini;
		}
	}
	redisp();
	ret= getch();
 fini:
	if(making_macro) {
		if(ret==0)
			*making_macro++=0x80|'a';
		else if(ret=='{')
			*making_macro++=0x80|'b';
		else
			*making_macro++=ret;
		if(making_macro>=making_macro_start+making_macro_size) {

			making_macro_start=ck_realloc(making_macro_start,5+making_macro_size*2);
			making_macro=making_macro_start+making_macro_size;
			making_macro_size*=2;
		}
	}
	if(ret&0x80) {
	  if(ret==0x9b)
	    {
/* this is a test pass travis */
	      return 0x9b;
	    }

	  saved=1+(ret&0x7f);
	  return CTRL('[');
	}
	return ret;
}
#else

#ifdef __STDC__
volatile
#endif
int have_kbd_input = 0;

static char ibuf[100];
static int i_in,i_cnt;

static unsigned char
real_get_chr FUN0()
{
	unsigned char ch;
	static int save;

	if(save) {
		ch=save-1;
		save=0;
		goto fini;
	}
	if(i_cnt) {
		ch=ibuf[i_cnt++];
		if(i_cnt==i_in)
			i_cnt=i_in=0;
		goto fini;
	}
	for(;;) {
		if(auto_recalc) {
			current_cycle++;
			if(bkgrnd_recalc) {
				while(!have_kbd_input && eval_next_cell())
					;
			} else {
				while(eval_next_cell())
					;
			}
		}
		redisp();
		if(have_kbd_input) {
			int ret;

			ret=read(0,ibuf,sizeof(ibuf));
			if(ret==1) {
				have_kbd_input=0;
				ch=ibuf[0];
				goto fini;
			}
			if(ret>1) {
				if(have_kbd_input<=ret)
					have_kbd_input=0;
				else
					have_kbd_input-=ret;
				i_cnt=1;
				i_in=ret;
				ch=ibuf[0];
				goto fini;
			}
			if(ret==0 || (errno!=EWOULDBLOCK && errno!=EINTR))
				return EOF;
		}
		pause();
	}
 fini:
	if(making_macro) {
		if(ch==0)
			*making_macro++=0x80|'a';
		else if(ch=='{')
			*making_macro++=0x80|'b';
		else
			*making_macro++=ch;
		if(making_macro>=making_macro_start+making_macro_size) {

			making_macro_start=ck_realloc(making_macro_start,5+making_macro_size*2);
			making_macro=making_macro_start+making_macro_size;
			making_macro_size*=2;
		}
	}
	if(ch&0x80) {
		save=1+(ch&0x7f);
		ch=CTRL('[');
	}
	return ch;
}

void
got_sigio FUN1(int,sig)
{
	have_kbd_input++;
}

#endif
#endif

#ifdef TEST
extern int yydebug;

int debug;
int dbg_do_stderr;

extern void dbg_print_ref_fm();

static void
dbg_show_var FUN2(char *,name, struct var *,v)
{
	char *ptr;

	/* if(*cmd && strncmp(cmd,v->var_name,strlen(cmd)))
		continue; */
	if(v->var_flags==VAR_UNDEF)
		ptr="Undefined";
	else if(v->var_flags==VAR_CELL)
		ptr="Cell";
	else if(v->var_flags==VAR_RANGE)
		ptr="Range";
	else
		ptr="*UNKNOWN*";

	text_line("%s %s %s  ",v->var_name,ptr,range_name(&(v->v_rng)));
	if(!v->var_ref_fm)
		text_line("no refs");
	else
		dbg_print_ref_fm(v->var_ref_fm);
}
/* Debugging is very important.  That's why there is an entire function to
   handle it. . .  :-)
 */
static void
do_debug FUN1(char *,cmd)
{
	CELL *cp;
	char *ptr;
	FILE *output;
	extern struct ref_fm *timer_cells;
	extern struct rng all_rng;

	extern void dbg_print_ref_to();
	extern void dbg_print_formula();
	extern void dbg_print_cell EXT1(CELL *);
	extern void dbg_print_cols EXT1(CELLREF);
	extern void dbg_print_rows EXT0();

	extern void dbg_print_array EXT0();
	extern void ref_stats EXT0();

	extern int fputs();
	extern void text_start();
	extern void text_finish();

	output=stdout;
	ptr=cmd;
	switch(*ptr++) {
	case 'd':
		debug=astol(&ptr);
		info_msg("Debug now %d",debug);
		return;

	case 'f':
		while(*ptr==' ')
			ptr++;
		freopen(ptr,"w",stderr);
		dbg_do_stderr=0;
		return;

	case 'y':
		yydebug= !yydebug;
		return;

	case 'E':
		output=stderr;
		break;

	default:
		break;
	}

	--ptr;
	text_start();
	switch(*ptr++) {
		CELLREF rr,cc;
		int n;
		int todo;
		struct rng rn;
	default:
		print_buf[0]='\0';
		break;

	case 'R':
		dbg_print_rows();
		break;

	case 'C':
		n=astol(&ptr);
		dbg_print_cols(n);
		break;

	case 'p':
		dbg_print_array();
		break;

	case 'S':
		ref_stats();
		break;

	case 'r':
		todo= *ptr++;
		if(!*ptr)
			find_cells_in_range(&all_rng);
		else {
			get_abs_rng(&ptr,&rn);
			find_cells_in_range(&rn);
		}
		ptr=print_buf;
		while(cp=next_row_col_in_range(&rr,&cc)) {
			text_line("(r%uc%u)",rr,cc);
			if(todo=='f' && cp->cell_refs_from) {
				dbg_print_ref_fm(cp->cell_refs_from);
			} else if(todo=='t' && cp->cell_refs_to) {
				dbg_print_ref_to(cp->cell_refs_to,cp->cell_formula);
			} else if(todo=='c') {
				dbg_print_cell(cp);
			} else if(todo=='e' && cp->cell_formula) {
				dbg_print_formula(cp);
			} else if(todo=='F') {
				text_line("flags %#x  cycle %d",cp->cell_flags,cp->cell_cycle);
			}
		}
		break;

	case 't':
#ifdef hp800
	{
		sigset_t sigs;

		sigprocmask(0,(void *)0,&sigs);
		text_line("Timer is %sactive, ticks=%lu (%lu), sigs %x.%x.%x.%x.%x.%x.%x.%x",timer_active ? "" : "not ",signal_ticks,signal_ticks/TIMER_MULT,sigs.sigset[0],sigs.sigset[1],sigs.sigset[2],sigs.sigset[3],sigs.sigset[4],sigs.sigset[5],sigs.sigset[6],sigs.sigset[7]);
		dbg_print_ref_fm(timer_cells);
	}
#else
		text_line("Timer is %sactive, ticks=%lu (%lu)",timer_active ? "" : "not ",signal_ticks,signal_ticks/TIMER_MULT);
		dbg_print_ref_fm(timer_cells);
#endif
		break;

	case 'v':
		while(*ptr==' ')
			ptr++;
		cmd=ptr;
		for_all_vars(dbg_show_var);
		break;

	case 'm':
		if(*ptr)
			desc_map(the_maps[atoi(ptr)]);
		break;

	case 'w':
		dbg_show_wins();
		break;
#if defined(SIGIO) && !defined(AMIGA)
	case 'k':
		text_line("i_in %d  i_cnt %d  have_input %d",i_in,i_cnt,have_kbd_input);
		break;
#endif
	}
	text_finish();
}

#endif

void
init_maps FUN0()
{
	static char main_name[] = "main";
	static char meta_name[] = "meta";
	static char edit_name[] = "edit";
	static char em_name[] = "edit-meta";
	static char ansi_name[] = "ansi";
	static char digit_name[] = "digit";

	extern struct keymap edit_map[];
	extern struct keymap edit_meta_map[];

	extern struct cmd_func edit_funcs[];

	the_maps=ck_malloc(sizeof(struct keymap *)*6);
	map_names=ck_malloc(sizeof(char *)*6);
	num_maps=6;
	the_maps[0]= &main_map[0];
	the_maps[1]= &meta_map[0];
	the_maps[2]= &edit_map[0];
	the_maps[3]= &edit_meta_map[0];
	the_maps[4]= &ansi_map;
	the_maps[5]= &digit_map;
	map_names[0]=main_name;
	map_names[1]=meta_name;
	map_names[2]=edit_name;
	map_names[3]=em_name;
	map_names[4]=ansi_name;
	map_names[5]=digit_name;
	the_funcs=ck_malloc(sizeof(struct cmd_func *)*2);
	num_funcs=2;
	the_funcs[0]= &cmd_funcs[0];
	the_funcs[1]= &edit_funcs[0];
}
static int add_usr_cmds FUN1(struct cmd_func *, new_cmds)
{
	num_funcs++;

	the_funcs=ck_realloc(the_funcs,num_funcs*sizeof(struct cmd_func *));
	the_funcs[num_funcs-1]=new_cmds;
	return num_funcs-1;
}
#ifdef USE_DLD
static int add_usr_maps FUN1(struct keymap **, new_maps)
{
	int n;

	for(n=1;new_maps[n];n++)
		;

	the_maps=ck_realloc(the_maps,(n+num_maps)*sizeof(struct keymap *));
	bcopy(new_maps, &the_maps[num_maps],n*sizeof(struct keymap *));
	num_maps+=n;
	return num_maps-n;
}
#endif

int
main FUN2(int,argc, char **,argv)
{
	int c;
	int num;
	void got_sig();

	myname=argv[0];

	__make_backups=1;
#if !defined(__TURBOC__) && !defined(SYSV)
	setbuffer(stdout,buf,sizeof(buf));
#endif
	if(argc<2 || strcmp(argv[1],"-q")) {
		if(!getenv("OLEO")) {
			fprintf(stdout,"Oleo version 0.03.2, Copyright (C) 1990 Free Software Foundation, Inc.\n");
			fprintf(stdout,"There is ABSOLUTELY NO WARRANTY for Oleo; see the file COPYING\n");
			fprintf(stdout,"for details.  Oleo is free software and you are welcome to distribute\n");
			fprintf(stdout,"copies of it under certain conditions; see the file COPYING to see the\n");
			fprintf(stdout,"conditions.\n\n");
			fflush(stdout);
			sleep(5);
		}
 	} else {
		--argc;
		++argv;
	}
	init_mem();
	init_eval();
	init_refs();
	init_cells();
	init_maps();
	obstack_init(&macro_stack);

	c=0;
	if(argc>2 && !strcmp(argv[1],"-f")) {
 		--argc;
		++argv;
		c++;
	}
	open_display(c);

	if(argc>2) {
		fprintf(stderr,"Usage: %s [filename]\n",argv[0]);
		exit(1);
	}
#ifdef USE_DLD
	if(!index(myname,'/')) {
		char *name;

		name=dld_find_executable(myname);
		num=dld_init(name);
		free(name);
	} else
		num=dld_init(myname);
	if(num)
		error_msg("dld_init() failed: %s",(dld_errno < 0 || dld_errno > dld_nerr) ? "Unknown error" : dld_errlst[dld_errno]);
	dld_search_path=":/usr/local/lib/oleo:/lib:/usr/lib:/usr/local/lib";
#endif


	{
		FILE *fp;
		extern int fclose();
		char *ptr,*home;
		/* would be nice to use ck_fopen, but these files might not
		   exist. . . */

		home=getenv("HOME");
		if(home) {
			ptr=mk_sprintf("%s/%s",home,RCFILE);
			if(fp=fopen(ptr,"r")) {
				read_cmds_cmd(fp);
				fclose(fp);
			}
			free(ptr);
		}

		if(fp=fopen(RCFILE,"r")) {
			read_cmds_cmd(fp);
			fclose(fp);
		}
		if(argc==2) {
			set_line(&in_line[3],argv[1]);
			if(fp=fopen(argv[1],"r")) {
				(*read_file)(fp,0);
				fclose(fp);
			} else
				error_msg("Can't open %s: %s",argv[1],err_msg());
		}
	}

	signal(SIGINT,got_sigint);
#ifdef SIGQUIT
	signal(SIGQUIT,got_sig);
	signal(SIGILL,got_sig);
	signal(SIGEMT,got_sig);
	signal(SIGBUS,got_sig);
	signal(SIGSEGV,got_sig);
	signal(SIGPIPE,got_sig);
#endif
#if  defined(SIGIO) && !defined(AMIGA)
	signal(SIGIO,got_sigio);
	/* close(0);
	if(open("/dev/tty",0)!=0) {
		fprintf(stderr,"Didn't get 0 when trying to re-open stdin\n");
		exit(1);
	} */
#ifdef FASYNC
	term_flag=fcntl(0,F_GETFL,FASYNC /* |FNDELAY */);
	c=fcntl(0,F_SETFL,FASYNC /* |FNDELAY */);
	if(c==-1) {
		fprintf(stderr,"Can't turn on FASYNC|FNDELAY on stdin: %s\n",err_msg());
		exit(1);
	}
#else
#ifdef FIOSSAIOSTAT
	{
		int i;

		ioctl(0,FIOGSAIOSTAT,&i);
		term_flag=i;
		ioctl(0,FIOGNBIO, &i);
		if(i)
			term_flag+=2;
		i=1;
		ioctl(0,FIOSSAIOSTAT,&i);
		/* ioctl(0,FIOSNBIO,&i); */
		i=getpid();
		ioctl(0,FIOSSAIOOWN,&i);
	}
#endif
#endif
#endif
	recenter_cur_win();

	for(;;) {
		if(how_many!=1) {
			how_many=1;
			cur_status();
		}
		clear_top_before();
		map_chr(MAIN_MAP);

		if(topclear && !rmac)
			clear_top_after();

	swtch:
		if(!cur_cmd) {
			error_msg("That key is unbound");
			continue;
		}
		if((cur_cmd->func_flags&NONTOP)==0) {
			error_msg("Command '%s' is not appropriate now.",cur_cmd->func_name);
			continue;
		}
		num=global_cmd(MAIN_MAP);
		if(num==-2)
			error_msg("Command '%s' is *not* appropriate now.",cur_cmd->func_name);
		else if(num>=0) {
			c=num;
			goto swtch;
		}
	}
}

static FILE *
open_file FUN3(char *,prompt, struct line *,line, char *,mode)
{
	FILE *fp;

	if(get_inp_line(prompt,line))
		return 0;
	if((fp=xopen_with_backup(line->buf,mode))==0) {
		error_msg("Can't open file '%s':%s",line->buf,err_msg());
		return 0;
	}
	return fp;
}

static void
close_file FUN2(struct line *,line, FILE *,fp)
{
	int num;

	if(num=xclose(fp))
		error_msg("Can't close '%s': Error code %d: %s",line->buf,num,err_msg());
}

/* Deal with point-n-shoot */
static int
get_a_range FUN3(char *,whatfor, struct rng *,r, struct line *,line)
{
	char *ptr;

	if(mkrow!=NON_ROW) {
		/* The range has already been selected */
	got_rng:
		set_rng(r,mkrow,mkcol,curow,cucol);
		mkrow=NON_ROW;
		return 0;
	}
	switch(get_inp_line(whatfor,line)) {
	case 0:
		if(mkrow!=NON_ROW)
			goto got_rng;
		ptr=line->buf;
		if(get_abs_rng(&ptr,r) || *ptr!='\0') {
			error_msg("Can't parse '%s'",line->buf);
			return 1;
		}
		return 0;
	case 1:
		if(mkrow!=NON_ROW)
			goto got_rng;
		return 1;
	case 2:
		return 1;
#ifdef TEST
	default:
		panic("Get_inp_line() what?");
#endif
	}
	return 1;
}

static FILE *
get_range_and_file FUN3(char *,whatfor, struct rng *,r, struct line *,line)
{
	char *ptr;
	char *prompt;
	int got_rng = 0;
	FILE *ret;
	extern char *strdup();

	if(mkrow!=NON_ROW) {
		/* We've got the range, just get the file */
		set_rng(r,mkrow,mkcol,curow,cucol);
		got_rng++;
		mkrow=NON_ROW;
		prompt=mk_sprintf("%s %s to file",whatfor,range_name(r));
	} else
		prompt=strdup(whatfor);

	if(get_inp_line(prompt,line)) {
		free(prompt);
		return 0;
	}
	free(prompt);

	if(mkrow!=NON_ROW) {
		if(got_rng) {
			error_msg("Two ranges?");
			return 0;
		}
		set_rng(r,mkrow,mkcol,curow,cucol);
		mkrow=NON_ROW;
		got_rng++;
	}
	ptr=line->buf;
	if(!got_rng) {
		if(get_abs_rng(&ptr,r)) {
			error_msg("Can't parse '%s'",line->buf);
			return 0;
		}
		if(*ptr==',')
			ptr++;
	}
	while(*ptr==' ')
		ptr++;
	if(!*ptr)
		return 0;
	ret=xopen_with_backup(ptr,"w");
	if(!ret)
		error_msg("Can't open file '%s':%s",ptr,err_msg());
	return ret;
}

static int
get_two_ranges FUN4(char *,whatfor, struct rng *,one, struct rng *,two, struct line *,line)
{
	char *ptr;
	char *prompt;
	int num_got = 0;
	int n;

	prompt=0;
	if(mkrow!=NON_ROW) {
		/* We've got the first one already */
		set_rng(one,mkrow,mkcol,curow,cucol);
		mkrow=NON_ROW;
		num_got++;
	}

	while(num_got!=2) {
		prompt = (num_got==1) ? mk_sprintf("%s %s to",whatfor,range_name(one)) : whatfor;
		n=get_inp_line(prompt,line);
		if(prompt!=whatfor)
			free(prompt);

		if(n>1 || (n==1 && mkrow==NON_ROW))
			return 1;

		if(mkrow!=NON_ROW) {
			if(line->buf[0]) {
				error_msg(num_got ? "Extra characters '%s'" : "'%s' is ambiguous", line->buf);
				return 1;
			}
			set_rng(num_got ? two : one, mkrow,mkcol,curow,cucol);
			mkrow=NON_ROW;

			num_got++;
		} else {
			ptr=line->buf;
			if(!num_got) {
				if(get_abs_rng(&ptr,one)) {
					error_msg("Can't parse first range in '%s'",line->buf);
					return 1;
				}
				if(*ptr==',')
					ptr++;
			}

			if(get_abs_rng(&ptr,two)) {
				if(num_got) {
					error_msg("Can't find second range in '%s'",line->buf);
					return 1;
				}
				num_got++;
			} else if(!num_got)
				num_got+=2;
			else
				num_got++;
		}
	}
	if(*ptr) {
		error_msg("Extra characters in '%s'",line->buf);
		return 1;
	}

	sprint_line(line,"%s %s",range_name(one),range_name(two));
	return 0;
}

/* Read a character.  If we're in a macro, read from the macro. . . */
int
get_chr FUN0()
{
	unsigned int ch;

#ifdef TEST
	extern CELL *my_cell;

	if(my_cell)
		error_msg("Something didn't clear my_cell!");
#endif
	if(rmac) {
		ch= *(rmac->mac_exe++);
		switch(ch) {
		case '{':	/* What else can we do? */
		case '\0':
			--(rmac->mac_exe);
			break;

		case (0x80|'a'):
			ch=0;
			break;

		case (0x80|'b'):
			ch='{';
			break;
		default:
			break;
		}
	} else
		ch=real_get_chr();
	return ch;
}

void
  map_chr FUN1(int, map)
{
  unsigned int ch;
  struct keymap *keymap;
  struct key *key;
#ifdef TEST
  extern CELL *my_cell;
  
  if(my_cell)
    error_msg("Something didn't clear my_cell!");
#endif
  keymap= the_maps[map];
  for(;;) {
    /* ch=get_chr(); */
    if(rmac) {
      int len;
      unsigned char *ptr;
      
    tryagain:
      ch= *(rmac->mac_exe++);
      switch(ch) {
      case '\0':
	cur_vector=0;
	cur_cmd= &cmd_funcs[END_MACRO];
	cur_chr=0;
	return;
	
      case 0x80|'a':
	ch='\0';
	break;
	
      case 0x80|'b':
	ch='{';
	break;
	
      case '{':
	for(ptr=rmac->mac_exe;*ptr && *ptr!=' ' && *ptr!='}';ptr++)
	  ;
	len=ptr-rmac->mac_exe;
	for(cur_vector=0;cur_vector<num_funcs;cur_vector++)
	  for(cur_cmd= &the_funcs[cur_vector][0];cur_cmd->func_name;cur_cmd++)
	    if(!strincmp((char *)(rmac->mac_exe),cur_cmd->func_name,len) && cur_cmd->func_name[len]=='\0') {
	      cur_chr='\0';
	      goto out;
	    }
	error_msg("Ignoring unknown function '%.*s' in macro",len,rmac->mac_exe);
	while(*ptr!='\0' && *ptr!='}')
	  ptr++;
	if(*ptr=='}')
	  ptr++;
	rmac->mac_exe=ptr;
	goto tryagain;
	
      out:
	if(*ptr==' ') {
	  /* ... add argument support here ... */
	  if(!cur_cmd->func_args) {
	    error_msg("Ignoring extra operand to %s",cur_cmd->func_name);
	    while(*ptr && *ptr!='}')
	      ptr++;
	    if(*ptr=='}')
	      ptr++;
	  } else if(cur_cmd->func_args[0]=='n') {
	    how_many=astol((char **)(&ptr));
	    if(*ptr=='}')
	      ptr++;
	  } else {
	    macro_func_arg=(char *)ptr;
	    while(*ptr && *ptr!='}')
	      ptr++;
	    if(*ptr=='}')
	      *ptr++='\0';
	  }
	  rmac->mac_exe=ptr;
	} else
	  rmac->mac_exe+=len+1;
	return;
      }
    } else
      ch=real_get_chr();
    for(;;) {
      if(ch>=keymap->lochr && ch<=keymap->hichr) {
	key= keymap->map_dense ? keymap->keys : &(keymap->keys[ch-keymap->lochr]);
	if(key->vector==255) {
	  keymap=the_maps[key->code];
	  break;
	}
	if(key->vector!=0 || key->code!=0) {
	  cur_vector=key->vector;
	  cur_cmd= &(the_funcs[key->vector][key->code]);
	  cur_chr=ch;
	  return;
	}
      }
      if (!(keymap=keymap->map_next)) {
	/* Searched through all the keymaps, and didn't find a match */
	cur_vector=0;
	cur_cmd=0;
	cur_chr=ch;
	return;
      }
    }
  }
}

/* Return values:
	-3	break key!
	-2	c is inappropriate
	-1	C is OK
	0+	New char value is ret-1;
 */

int
global_cmd FUN1(int,magic)
{
	char *ptr;
	struct rng fm,to;
	FILE *fp;
	static struct line confirm;
	struct cmd_func *cmd;

	if(!cur_cmd)
		return -2;
	cmd=cur_cmd;
	if(cmd->func_args) {
		ptr=cmd->func_args;

	next_arg:
		switch(*ptr++) {
		case 'R':
			if(get_two_ranges(cmd->func_name,&fm,&to,&in_line[*ptr++-'a']))
				break;
			(*cmd->func_func)(&fm,&to);
			break;

		case 'r':
			if(get_a_range(cmd->func_name,&to,&in_line[*ptr++-'a']))
				break;
			(*cmd->func_func)(&to);
			break;

		case 't':
			if(get_inp_line(cmd->func_name,&in_line[*ptr++-'a']))
				break;
			(*cmd->func_func)(in_line[cmd->func_args[1]-'a'].buf);
			break;

		case 'C':
			if(modified==0)
				goto next_arg;
			set_line(&confirm,0);
			if(   get_inp_line("Are you SURE",&confirm)
			   || (confirm.buf[0]!='Y' && confirm.buf[0]!='y'))
				return -1;
			goto next_arg;

		case 'f':
			fp=open_file(cmd->func_name,&in_line[*ptr-'a'],ptr+1);
			if(!fp)
				break;
			(*cmd->func_func)(fp);
			close_file(&in_line[*ptr++-'a'],fp);
			break;

		case 'F':
			fp=get_range_and_file(cmd->func_name,&to,&in_line[*ptr-'a']);
			if(!fp)
				break;
			(*cmd->func_func)(fp,&to);
			close_file(&in_line[*ptr++-'a'],fp);
			break;

		case 'n':
			if(!*ptr)
				(*cmd->func_func)();
			else if(*ptr>='0' && *ptr<='9')
				(*cmd->func_func)(*ptr-'0');
			else if(*ptr>='a' && *ptr<='z')
				(*cmd->func_func)((*ptr-'a')*26+ptr[1]-'a');
			else
				(*cmd->func_func)();
			break;

		case 'T':
			if(*ptr>='0' && *ptr<='9')
				(*cmd->func_func)(*ptr-'0',cur_chr);
			else
				(*cmd->func_func)();
			break;

		case 'N':
			(*cmd->func_func)(magic);
			return cur_chr;

		case '\0':
			(*cmd->func_func)();
			break;

#ifdef TEST
		default:
			panic("Unknown arg type %s in global_cmd",ptr);
#endif
		}
		if(cmd->func_flags&BRK)
			return -3;
		return -1;
	} else if(cmd->func_func) {
		if(cmd->func_flags&WTH_CHR)
			(*cmd->func_func)(cur_chr);
		else
			(*cmd->func_func)();
		if(cmd->func_flags&BRK)
			return -3;
		return -1;
	}
	return -2;
}

static void
write_keys_cmd FUN1(FILE *,fp)
{
	struct keymap *map;
	int n;
	int key;
	int vec;
	int code;

	for(n=0;n<num_maps;n++) {
		char *def;

		for(map=the_maps[n];map && !map->map_end;map=map->map_next)
			;
		def=0;
		if(map && map->map_next) {
			for(key=0;key<num_maps;key++)
				if(the_maps[key]==map->map_next) {
					def=map_names[key];
					break;
				}
		}
		if(def)
			fprintf(fp,"create-keymap %s %s\n",map_names[n],def);
		else
			fprintf(fp,"create-keymap %s\n",map_names[n]);
	}
	for(n=0;n<num_maps;n++) {
		for(map=the_maps[n];map;map=map->map_next) {
			for(key=map->lochr;key<=map->hichr;key++) {
				if(map->map_dense) {
					vec=map->keys[0].vector;
					code=map->keys[0].code;
				} else {
					vec=map->keys[key-map->lochr].vector;
					code=map->keys[key-map->lochr].code;
				}
				if(vec==255)
					fprintf(fp,"bind-key %s %s %s\n",
						map_names[n],
						map_names[code],
						char_to_string(key));
				else if(vec || code)
					fprintf(fp,"bind-key %s %s %s\n",
						map_names[n],
						the_funcs[vec][code].func_name,
						char_to_string(key));
			}
			if(map->map_end)
				map=0;
		}
	}
}

static void
clear_keymap FUN1(struct keymap *,m)
{
	int n;

	while(m) {
		if(m->map_malloc) {
			if(m->map_dense) {
				m->keys[0].vector=0;
				m->keys[0].code=0;
				m->hichr=m->lochr;
			} else {
				for(n=0;n<=m->hichr-m->lochr;n++) {
					m->keys[n].vector=0;
					m->keys[n].code=0;
				}
			}
		} else {
			m->keys=ck_malloc(sizeof(struct key)*(1+m->hichr-m->lochr));
			for(n=0;n<=m->hichr-m->lochr;n++) {
				m->keys[n].vector=0;
				m->keys[n].code=0;
			}
			m->map_malloc=2;
		}
		if(m->map_end)
			break;
		m=m->map_next;
	}
}

static void
make_keymap FUN1(char *,mapname)
{
	char *ptr;
	int num;
	struct keymap *linkmap;
	extern char *strdup();

	for(ptr=mapname;*ptr && *ptr!=' ';ptr++)
		;
	linkmap=0;
	if(*ptr) {
		*ptr++='\0';
		for(num=0;num<num_maps;num++) {
			if(!strcmp(map_names[num],ptr)) {
				linkmap=the_maps[num];
				break;
			}
		}
	}
	for(num=0;num<num_maps;num++) {
		if(!strcmp(map_names[num],mapname)) {
			clear_keymap(the_maps[num]);
			return;
		}
	}

	the_maps=ck_realloc(the_maps,(num_maps+1)*sizeof(struct keymap *));
	the_maps[num_maps]=ck_malloc(sizeof(struct keymap));
	the_maps[num_maps]->map_next=linkmap;
	the_maps[num_maps]->map_end=1;
	the_maps[num_maps]->map_dense=1;
	the_maps[num_maps]->lochr='\0';
	the_maps[num_maps]->hichr='\0';
	the_maps[num_maps]->keys=ck_malloc(sizeof(struct key));
	the_maps[num_maps]->keys[0].vector=0;
	the_maps[num_maps]->keys[0].code=0;


	map_names=ck_realloc(map_names,(num_maps+1)*sizeof(char *));
	map_names[num_maps]=strdup(mapname);
	num_maps++;
}


static void
read_cmds_cmd FUN1(FILE *,fp)
{
	char *ptr;

	while(fgets(print_buf,300,fp)) {
		if(ptr=rindex(print_buf,'\n'))
			*ptr='\0';
		execute_cmd(print_buf);
	}
}

static void
write_cmd FUN1(FILE *,fp)
{
	(*write_file)(fp,0);
	modified=0;
}

static void
read_cmd FUN1(FILE *,fp)
{
	(*read_file)(fp,0);
}

static void
read_merge_cmd FUN1(FILE *,fp)
{
	(*read_file)(fp,1);
}

static void
write_reg_cmd FUN2(FILE *,fp,struct rng *,rng)
{
	(*write_file)(fp,rng);
}

static void
sort_region_cmd FUN1(char *,ptr)
{
	struct cmp {
		char mult;
		CELLREF row;
		CELLREF col;
	};

	struct rng tmp_rng;

	extern struct rng sort_rng;
	extern struct rng sort_ele;
	extern struct cmp *sort_keys;
	extern int sort_keys_num;
	extern int sort_keys_alloc;
	extern void sort_region EXT0();

	if(get_abs_rng(&ptr,&sort_rng)) {
		error_msg("Can't find a range to sort in %s", ptr);
		return;
	}

	cur_row=sort_rng.lr;
	cur_col=sort_rng.lc;

	while(*ptr==' ')
		ptr++;
	if(!*ptr) {
		sort_ele.lr=0;
		sort_ele.lc=0;
		sort_ele.hr=0;
		sort_ele.hc=0;
	} else if(!parse_cell_or_range(&ptr,&sort_ele)) {
		error_msg("Can't parse elements in %s",ptr);
		return;
	} else {
		sort_ele.lr-=sort_rng.lr;
		sort_ele.lc-=sort_rng.lc;
		sort_ele.hr-=sort_rng.lr;
		sort_ele.hc-=sort_rng.lc;
	}

	sort_keys_num=0;
	while(*ptr==' ')
		ptr++;
	for(;*ptr;) {
		if(sort_keys_num==sort_keys_alloc) {
			sort_keys_alloc++;
			if(sort_keys_alloc>1)
			  {

				sort_keys=ck_realloc(sort_keys,sort_keys_alloc*sizeof(struct cmp));
}
			else
				sort_keys=ck_malloc(sizeof(struct cmp));
		}
		sort_keys[sort_keys_num].mult=1;
		if(*ptr=='+')
			ptr++;
		else if(*ptr=='-') {
			sort_keys[sort_keys_num].mult= -1;
			ptr++;
		}
		if(!*ptr) {
			sort_keys[sort_keys_num].row=0;
			sort_keys[sort_keys_num].col=0;
			sort_keys_num++;
			break;
		}
		if(!parse_cell_or_range(&ptr,&tmp_rng) || tmp_rng.lr!=tmp_rng.hr || tmp_rng.lc!=tmp_rng.hc) {
			error_msg("Can't parse key #%d in %s",sort_keys_num+1,ptr);
			sort_keys_num= -1;
			return;
		}
		sort_keys[sort_keys_num].row=tmp_rng.lr - sort_rng.lr;
		sort_keys[sort_keys_num].col=tmp_rng.lc - sort_rng.lc;
		sort_keys_num++;

		while(*ptr==' ')
			ptr++;
	}
	if(sort_keys_num==0) {
		if(sort_keys_alloc==0) {
			sort_keys_alloc++;
			sort_keys=ck_malloc(sizeof(struct cmp));
		}
		sort_keys[0].mult=1;
		sort_keys[0].row=0;
		sort_keys[0].col=0;
		sort_keys_num++;
	}
	sort_region();
	disp_scrn();
}

static void
set_var FUN1(char *,ptr)
{
	int num;
	char *ret;
	extern char *new_var_value EXT3(char *, int, char *);

	while(*ptr==' ')
		ptr++;
	for(num=0;ptr[num] && ptr[num]!=' ';num++)
			;
	modified=1;
	if(!ptr[num])
		ret=new_var_value(ptr,num,(char *)0);
	else
		ret=new_var_value(ptr,num,&ptr[num+1]);
	if(ret)
		error_msg("Can't set-variable %s: %s\n",ptr,ret);
}

static void
show_var FUN1(char *,ptr)
{
	struct var *v;
	int num;

	while(*ptr==' ')
		ptr++;
	for(num=0;ptr[num] && ptr[num]!=' ';num++)
			;

	v=find_var(ptr,num);
	if(!v || v->var_flags==VAR_UNDEF) {
		error_msg("There is no '%s'",ptr);
		return;
	}
#ifdef A0_REFS
	if(v->v_rng.lr!=v->v_rng.hr || v->v_rng.lc!=v->v_rng.hc)
		/* FOO */sprintf(print_buf,"%s $%s$%u:$%s$%u",v->var_name,col_to_str(v->v_rng.lc),v->v_rng.lr,col_to_str(v->v_rng.hc),v->v_rng.hr);
	else
		/* FOO */sprintf(print_buf,"%s $%s$%u",v->var_name,col_to_str(v->v_rng.lc),v->v_rng.lr);
#else
	sprintf(print_buf,"%s %s",v->var_name,range_name(&(v->v_rng)));
#endif
	info_msg(print_buf);
	set_line(&in_line[1],print_buf);
}

static void
show_a_var FUN2(char *,name, struct var *,v)
{
	if(v->var_flags==VAR_UNDEF)
		return;
#ifdef A0_REFS
	if(v->v_rng.lr!=v->v_rng.hr || v->v_rng.lc!=v->v_rng.hc)
		/* FOO */text_line("%-20s $%s$%u:$%s$%u",v->var_name,col_to_str(v->v_rng.lc),v->v_rng.lr,col_to_str(v->v_rng.hc),v->v_rng.hr);
	else
		/* FOO */text_line("%-20s  $%s$%u",v->var_name,col_to_str(v->v_rng.lc),v->v_rng.lr);
#else
	text_line("%-20s  %s",v->var_name,range_name(&(v->v_rng)));
#endif
}

static void
show_all_var FUN0()
{
	extern void text_start EXT0();
	extern void text_finish EXT0();

	text_start();
	for_all_vars(show_a_var);
	text_finish();
}

static void
shift_cell_cursor FUN1(int, dirn)
{
	CELLREF c;
	int w = 0;
	int over,down;

	over=colmagic[dirn]*how_many;
	down=rowmagic[dirn]*how_many;
	if(over>0) {
		c=cucol;
		while(c<MAX_COL && over-->0) {
			c++;
			while((w=get_width(c))==0 && c<MAX_COL)
				c++;
		}
		if(over>0 || c==cucol || w==0) {
			error_msg("Can't go right");
			return;
		}
	} else if(over<0) {
		c=cucol;
		while(c>MIN_COL && over++<0) {
			--c;
			while((w=get_width(c))==0 && c>MIN_COL)
				--c;
		}
		if(over<0 || c==cucol || w==0) {
			error_msg("Can't go left");
			return;
		}
	} else
		c = cucol;
	if(down>0) {
		if(curow>MAX_ROW-down) {
			error_msg("Can't go down");
			return;
		}
	} else if(down<0) {
		if(curow<MIN_ROW-down) {
			error_msg("Can't go up");
			return;
		}
	}
	move_cell_cursor(curow+down,c);
}

static void
scan_cell_cursor FUN1(int,magic)
{
	CELLREF pos,pos_end,pos_inc;
	CELL *cp;
	int over,down;

	extern CELLREF max_row(),max_col();

	over=colmagic[magic]*how_many;
	down=rowmagic[magic]*how_many;
	if(over) {
		if(over>0) {
			pos=max_col(curow);
			pos_end=MIN_COL;
			pos_inc= -1;
		} else {
			pos=MIN_COL;
			pos_end=max_col(curow);
			pos_inc=1;
		}
		for(;;) {
			for(;((!(cp=find_cell(curow,pos)) || !GET_TYP(cp)));pos+=pos_inc) {
				if(pos==pos_end) {
					how_many=1;
					pos=MIN_COL;
					break;
				}
			}
			if(--how_many==0)
				break;
			pos+=pos_inc;
		}
		move_cell_cursor(curow,pos);
	} else {
		if(down>0) {
			pos=max_row(cucol);
			pos_end=MIN_ROW;
			pos_inc= -1;
		} else {
			pos=MIN_ROW;
			pos_end=max_row(cucol);
			pos_inc=1;
		}
		for(;;) {
			for(;(!(cp=find_cell(pos,cucol)) || !GET_TYP(cp));pos+=pos_inc) {
				if(pos==pos_end) {
					how_many=1;
					pos=MIN_ROW;
					break;
				}
			}
			if(--how_many==0)
				break;
			pos+=pos_inc;
		}

		move_cell_cursor(pos,cucol);
	}
}


char *
fmt_to_str FUN1(int, fmt)
{
	char *ptr;
	static char buf[30];
	char nbuf[10];

	if(fmt==FMT_DEF)
		return "default";
	if(fmt==FMT_HID)
		return "hidden";
	if(fmt==FMT_GPH)
		return "graph";
	if((fmt&PRC_FLT)==PRC_FLT)
		strcpy(nbuf,"float");
	else
		sprintf(nbuf,"%d",(fmt&PRC_FLT));
	switch(fmt|PRC_FLT) {
	case FMT_USR:
		ptr="user-";
		sprintf(nbuf,"%d",(fmt&PRC_FLT)+1);
		break;
	case FMT_GEN:
		ptr="general.";
		break;
	case FMT_DOL:
		ptr="dollar.";
		break;
	case FMT_CMA:
		ptr="comma.";
		break;
	case FMT_PCT:
		ptr="percent.";
		break;
	case FMT_FXT:
		if((fmt&PRC_FLT)==0)
			return "integer";
		if(fmt==FMT_FXT)
			return "decimal";
		ptr="fixed.";
		break;
	case FMT_EXP:
		ptr="exponent.";
		break;
	default:
		error_msg("Unknown format %d (%x)",fmt,fmt);
		ptr="UNKNOWN";
		break;
	}
	sprintf(buf,"%s%s",ptr,nbuf);
	return buf;
}

struct fmt {
	int fmt;
	char **strs;
};

static char *def_names[] = {"default","def","D",0};
static char *hid_names[] = {"hidden","hid","H",0};
static char *gph_names[] = {"graph","gph","*",0};
static char *int_names[] = {"integer","int","I",0};
static char *dec_names[] = {"decimal","dec",0};

static struct fmt simple[] = {
	{ FMT_DEF,	   def_names },
	{ FMT_HID,	   hid_names },
	{ FMT_GPH,	   gph_names },
	{ FMT_FXT-PRC_FLT, int_names },
	{ FMT_FXT,	   dec_names },
	{ 0,		   0}
};

char *gen_names[] = {"general.","gen.","G",0};
char *dol_names[] = {"dollar.","dol.","$",0};
char *cma_names[] = {"comma.","com.",",",0};
char *pct_names[] = {"percent.","pct.","%",0};
char *fxt_names[] = {"fixed.","fxt.","F",0};
char *exp_names[] = {"exponent.","exp.","E",0};

static struct fmt withprec[] = {
	{ FMT_GEN-PRC_FLT, gen_names },
	{ FMT_DOL-PRC_FLT, dol_names },
	{ FMT_CMA-PRC_FLT, cma_names },
	{ FMT_PCT-PRC_FLT, pct_names },
	{ FMT_FXT-PRC_FLT, fxt_names },
	{ FMT_EXP-PRC_FLT, exp_names },
	{ 0,		   0 }
};

static int
str_to_fmt FUN1(char *,ptr)
{
	struct fmt *f;
	char **strs;
	int n;
	int ret;
	char *p1,*p2;

	for(f=simple;f->strs;f++) {
		for(strs=f->strs;*strs;strs++) {
			if(*ptr!=**strs)
				continue;
			for(p1=ptr,p2= *strs;*p1==*p2 && *p1;p1++,p2++)
				;
			if(*p1=='\0' && *p2=='\0')
				return f->fmt;
		}
	}
	if(!strncmp(ptr,"user-",5)) {
		ptr+=5;
		n=astol(&ptr);
		if(*ptr || n<1 || n>16)
			return -1;
		return n-1-PRC_FLT+FMT_USR;
	}
	for(f=withprec,ret=0;!ret && f->strs;f++) {
		for(strs=f->strs;*strs;strs++) {
			if(*ptr!=**strs)
				continue;
			for(p1=ptr,p2= *strs;*p2 && *p1==*p2;p1++,p2++)
				;
			if(!*p2) {
				ret=f->fmt;
				ptr=p1;
				break;
			}
		}
	}

	if(!ret || !*ptr)
		return -1;
	if(!strcmp(ptr,"float") || !strcmp(ptr,"f")) {
		n=PRC_FLT;
	} else {
		n=astol(&ptr);
		if(*ptr || n<0 || n>14)
			return -1;
	}
	return ret+n;
}

char *
jst_to_str FUN1(int, jst)
{
	if(jst==JST_DEF)
		return "default";
	if(jst==JST_LFT)
		return "left";
	if(jst==JST_RGT)
		return "right";
	if(jst==JST_CNT)
		return "center";
	return "unknown";
}

static int
chr_to_jst FUN1(int, chr)
{
	if(chr=='d' || chr=='D')
		return JST_DEF;
	if(chr=='l' || chr=='L')
		return JST_LFT;
	if(chr=='r' || chr=='R')
		return JST_RGT;
	if(chr=='c' || chr=='C')
		return JST_CNT;
	return -1;
}

/* parse a range, then turn it into an absolute rng */
static int
get_abs_rng FUN2(char **,pptr, struct rng *,retp)
{
	unsigned char n;

	while(**pptr==' ')
		(*pptr)++;
	if(!**pptr)
		return 1;
	cur_row=curow;
	cur_col=cucol;
	n=parse_cell_or_range(pptr,retp);
	if(!n) {
		struct var *v;
		char *ptr;

		ptr= *pptr;
		while(ptr[n] && ptr[n]!=' ')
			n++;
		v=find_var(ptr,n);
		if(!v)
			return 1;
		(*pptr)+=n;
		*retp= v->v_rng;
	}
	return 0;
}

static void desc_map FUN1(struct keymap *,map)
{
	int n;
	int maxn;

	
	text_start();
	while(map) {
		maxn=1+map->hichr-map->lochr;
		if(map->map_dense)
			text_line("%s - %s: %d %d %s",
				  char_to_string(map->lochr),
 				  char_to_string(map->hichr),
				  map->keys[0].vector,
 				  map->keys[0].code,
 				  map->keys[0].vector==255 ? "vector" : the_funcs[map->keys[0].vector][map->keys[0].code].func_name);
		else
			for(n=0;n<maxn;n++)
				text_line("%s: %d %d %s",
					  char_to_string(map->lochr+n),
					  map->keys[n].vector,
					  map->keys[n].code,
					  map->keys[n].vector==255 ? "vector" : the_funcs[map->keys[n].vector][map->keys[n].code].func_name);
		map=map->map_end ? 0 : map->map_next;
	}
	text_finish();
}

#if 0
static char *
key_name FUN1(int,key)
{
/*	int mac;

	if(main_map[key]==BOUND_MACRO) {
		for(mac=0;mac<n_bound_macros;mac++)
			if(bound_macros[mac].c==key)
				return range_name(&(bound_macros[mac].r));
#ifdef TEST
		error_msg("Unknown bound macro on key %d",key);
#endif
	}
	return cmd_funcs[main_map[key]].func_name; */
}
#endif

static void
set_usr_fmt FUN1(char *,fmtstr)
{
	int u_num;
	char *ptr;
	static struct line usr_line;
	struct line tmp_line;
	char *tmp_ptr;
	char *data_buf[9];
	int i;
	static char *names[9]= {
		"Positive header",
		"Negative header",
		"Positive trailer",
		"Negative trailer",
		"Zero",
		"Comma",
		"Decimal point",
		"Precision",
		"Scale-factor"
	};

	tmp_line.buf=ck_malloc(10);
	tmp_line.alloc=10;
	tmp_ptr=tmp_line.buf;

	ptr=fmtstr;
	u_num=astol(&ptr);
	if(u_num<1 || u_num>16 || *ptr!='\0') {
		error_msg("Unknown number %s",fmtstr);
		return;
	}
	--u_num;

	get_usr_stats(u_num,data_buf);
	for(i=0;i<9;i++) {
		int slen;
		int prevlen;

		set_line(&usr_line,data_buf[i]);
		if(get_inp_line(names[i],&usr_line)>1)
			return;

		slen=strlen(usr_line.buf);
		if(tmp_line.alloc<=(tmp_ptr-tmp_line.buf)+slen) {
			prevlen=tmp_ptr-tmp_line.buf;
			tmp_line.alloc+=slen+1;

			tmp_line.buf=ck_realloc(tmp_line.buf,tmp_line.alloc);
			tmp_ptr=tmp_line.buf+prevlen;
		}
		strcpy(tmp_ptr,usr_line.buf);
		data_buf[i]=tmp_ptr;
		while(*tmp_ptr!='\0')
			tmp_ptr++;
		tmp_ptr++;
	}

	set_usr_stats(u_num, data_buf);
	free(tmp_line.buf);
	disp_scrn();
}


/* Modify this to write out *all* the options */
void
write_mp_options FUN1(FILE *,fp)
{
	fprintf(fp,"O;%sauto;%sbackground;ticks %d\n",
		auto_recalc ? "" : "no",
		bkgrnd_recalc ? "" : "no",
		signal_ticks/TIMER_MULT);
}

void
read_mp_options FUN1(char *,str)
{
	char *np;

	while(np=index(str,';')) {
		*np='\0';
		(void)do_set_option(str);
		*np++=';';
		str=np;
	}
	if(np=rindex(str,'\n'))
		*np='\0';
	(void)do_set_option(str);
}

void
set_options FUN1(char *,ptr)
{
	if(do_set_option(ptr))
		recenter_cur_win();
}

static void
show_options FUN0()
{
	int n;
	int fmts;
	char *data_buf[9];

	extern char *strdup();
	extern void text_start EXT0();
	extern void text_finish EXT0();
	extern void show_window_options();

	n=auto_recalc;
	text_start();

	text_line("auto-recalculation: %s        Recalculate in background: %s",
 		n ? " on" : "off",bkgrnd_recalc ? "on" : "off");
	text_line("make backup files:  %s        Copy files into backups:   %s",
		__make_backups ? " on" : "off",__backup_by_copying ? "on" : "off");

	text_line("Asynchronous updates every %u ???",
		signal_ticks/TIMER_MULT);

	text_line("Print width:      %5u",print_width);

	text_line("");

	(*show_file_opts)();

	text_line("");
	show_window_options();
	text_line("");

	fmts=usr_set_fmts();
	if(fmts) {
		text_line("User-defined formats:");
		text_line("Fmt    +Hdr    -Hdr   +Trlr   -Trlr    Zero   Comma Decimal  Prec         Scale");
		for(n=0;n<16;n++) {
			if(fmts&(1<<n)) {
				get_usr_stats(n,data_buf);
				text_line("%3d %7s %7s %7s %7s %7s %7s %7s %5s %13s",
				  n+1,
				  data_buf[0],
				  data_buf[1],
				  data_buf[2],
				  data_buf[3],
				  data_buf[4],
				  data_buf[5],
				  data_buf[6],
				  data_buf[7],
				  data_buf[8]);
			}
		}
	} else
		text_line("No user-defined formats have been defined");

	text_finish();
}

static int
do_set_option FUN1(char *,ptr)
{
	int set_opt = 1;
	extern int set_window_option();

	while(*ptr==' ')
		ptr++;
	if(!strincmp("no",ptr,2)) {
		ptr+=2;
		set_opt=0;
		while(*ptr==' ')
			ptr++;
	}
	if(!stricmp("auto",ptr)) {
		auto_recalc = set_opt;
		return 0;
	}
	if(!stricmp("bkgrnd",ptr) || !stricmp("background",ptr)) {
		bkgrnd_recalc = set_opt;
		return 0;
	}
	if(!stricmp("backup",ptr)) {
		__make_backups = set_opt;
		return 0;
	}
	if(!stricmp("bkup_copy",ptr)) {
		__backup_by_copying = set_opt;
		return 0;
	}
	if(set_opt && !strincmp("ticks ",ptr,6)) {
		extern unsigned ualarm EXT2(unsigned int, unsigned int);

		ptr+=6;
		signal_ticks= astol(&ptr) * TIMER_MULT;
#ifndef __TURBOC__
		if(timer_active)
			ualarm(signal_ticks,signal_ticks);
#endif
		return 0;
	}
	if(set_opt && !strincmp("print ",ptr,6)) {
		ptr+=6;
		print_width=astol(&ptr);
		return 0;
	}
	if(set_opt && !strincmp("file ",ptr,5)) {
#ifdef USE_DLD
		char *tmpstr;
		extern char *strdup();

		ptr+=5;
		tmpstr=ck_malloc(strlen(ptr)+20);
		if(io_name) {
			sprintf(tmpstr, "%s.o", ptr);
			if(dld_unlink_by_file(tmpstr,0)) {
				error_msg("Couldn't unlink old file format %s: %s",io_name,(dld_errno < 0 || dld_errno > dld_nerr) ? "Unknown error" : dld_errlst[dld_errno]);
				goto bad_file;
			}
			free(io_name);
		}
		if(!stricmp(ptr,"panic")) {
			io_name=0;
			read_file      = panic_read_file;
			write_file     = panic_write_file;
			set_file_opts  = panic_set_options;
			show_file_opts = panic_show_options;
			free(tmpstr);
			return 0;
		}
		io_name=strdup(ptr);
		sprintf(tmpstr, "%s.o", ptr);
		if(dld_link(tmpstr)) {
			error_msg("Couldn't link new file format %s: %s",io_name,(dld_errno < 0 || dld_errno > dld_nerr) ? "Unknown error" : dld_errlst[dld_errno]);
			goto bad_file;
		}
		if(dld_link("libc.a"))
			error_msg("Couldn't link libc.a");
		if(dld_link("libm.a"))
			error_msg("Couldn't link libm.a");

		sprintf(tmpstr, "%s_read_file",ptr);
		read_file=dld_function_executable_p(tmpstr) ? dld_get_func(tmpstr) : 0;
		sprintf(tmpstr, "%s_write_file",ptr);
		write_file=dld_function_executable_p(tmpstr) ? dld_get_func(tmpstr) : 0;

		sprintf(tmpstr, "%s_set_options",ptr);
		set_file_opts=(int(*)())(dld_function_executable_p(tmpstr) ? dld_get_func(tmpstr) : 0);
		sprintf(tmpstr, "%s_show_options",ptr);
		show_file_opts=dld_function_executable_p(tmpstr) ? dld_get_func(tmpstr) : 0;

		if(   !read_file
		   || !write_file
		   || !set_file_opts
		   || !show_file_opts) {
		   	char **missing;
			int n;

			missing=dld_list_undefined_sym();
		   	text_start();
			text_line("Undefined symbols in file format %s:",ptr);
			text_line("");
			for(n=0;n<dld_undefined_sym_count;n++)
				text_line("%s",missing[n]);
			text_line("");
			text_finish();
			free(missing);
			error_msg("File format %s has undefined symbols: not loaded",ptr);
	bad_file:
			sprintf(tmpstr, "%s.o",io_name);
			dld_unlink_by_file(io_name,0);
			if(io_name)
				free(io_name);
			io_name=0;
			read_file=panic_read_file;
			write_file=panic_write_file;
			set_file_opts=panic_set_options;
			show_file_opts=panic_show_options;
		}
		free(tmpstr);
#else
		ptr+=5;
		if(!stricmp("sylk",ptr)) {
			read_file     = sylk_read_file;
			write_file    = sylk_write_file;
			set_file_opts = sylk_set_options;
			show_file_opts= sylk_show_options;
		} else if(!stricmp("sc",ptr)) {
			read_file     = sc_read_file;
			write_file    = sc_write_file;
			set_file_opts = sc_set_options;
			show_file_opts= sc_show_options;
		} else if(!stricmp("panic",ptr)) {
			read_file     = panic_read_file;
			write_file    = panic_write_file;
			set_file_opts = panic_set_options;
			show_file_opts= panic_show_options;
		} else if(!stricmp("list",ptr)) {
			read_file     = list_read_file;
			write_file    = list_write_file;
			set_file_opts = list_set_options;
			show_file_opts= list_show_options;
			/*if(ptr[4]) {
				ptr+=4;
				sl_sep=string_to_char(&ptr);
			} */
		} else
			error_msg("Unknown file format %s",ptr);
#endif
		return 0;
	}
#ifdef USE_DLD
	else if(!strincmp(ptr,"load ",5)) {
		char *tmpstr;
		struct function *new_funs;
		struct cmd_func *new_cmds;
		struct keymap **new_maps;
		void (*init_cmd) EXT0();

		extern unsigned long dld_get_symbol EXT1(char *);
		extern void add_usr_funs EXT1(struct function *);

		ptr+=5;
		tmpstr=ck_malloc(strlen(ptr)+20);
		sprintf(tmpstr,"%s.o",ptr);
		if(dld_link(tmpstr)) {
			error_msg("Couldn't link %s: %s",tmpstr,(dld_errno < 0 || dld_errno > dld_nerr) ? "Unknown error" : dld_errlst[dld_errno]);
			free(tmpstr);
			return 0;
		}
		if(dld_link("libc.a"))
			error_msg("Couldn't link libc.a");
		if(dld_link("libm.a"))
			error_msg("Couldn't link libm.a");

		if(dld_undefined_sym_count) {
		   	char **missing;
			int n;

			missing=dld_list_undefined_sym();
		   	text_start();
			text_line("Undefined symbols in file format %s:",ptr);
			text_line("");
			for(n=0;n<dld_undefined_sym_count;n++)
				text_line("%s",missing[n]);
			text_line("");
			text_finish();
			free(missing);
			error_msg("%d undefined symbols in %s",dld_undefined_sym_count,ptr);
			dld_unlink_by_file(tmpstr,0);
			free(tmpstr);
			return 0;
		}
		sprintf(tmpstr,"%s_funs",ptr);
		new_funs=(struct function *)dld_get_symbol(tmpstr);
		if(new_funs)
			add_usr_funs(new_funs);
		sprintf(tmpstr,"%s_cmds", ptr);
		new_cmds=(struct cmd_func *)dld_get_symbol(tmpstr);
		if(new_cmds)
			add_usr_cmds(new_cmds);
		sprintf(tmpstr,"%s_maps", ptr);
		new_maps=(struct keymap **)dld_get_symbol(tmpstr);
		if(new_maps)
			add_usr_maps(new_maps);
		if(!new_funs && !new_cmds && !new_maps) {
			error_msg("Couldn't find anything to load in %s",ptr);
			sprintf(tmpstr,"%s.o",ptr);
			dld_unlink_by_file(tmpstr,0);
		}
		sprintf(tmpstr,"%s_init",ptr);
		init_cmd=dld_function_executable_p(tmpstr) ? dld_get_func(tmpstr) : 0;
		if(init_cmd)
			(*init_cmd)();
		free(tmpstr);
		return 0;
	}
#endif
	if(set_window_option(set_opt,ptr)==0) {
		if((*set_file_opts)(set_opt,ptr))
			error_msg("Unknown option '%s'",ptr);
		return 0;
	}
	return 1;
}

void
set_line FUN2(struct line *,line, char *,string)
{
	int len;

	if(!string) {
		if(line->alloc)
			line->buf[0]='\0';
		return;
	}
	len=strlen(string);
	if(line->alloc<=len) {
		if(len<LINE_MIN)
			len=LINE_MIN;
		else
			len++;
		line->alloc=len+1;
		if(line->buf)
		  {

			line->buf=ck_realloc(line->buf,line->alloc);
}
		else
			line->buf=ck_malloc(line->alloc);
	}
	strcpy(line->buf,string);
}

static void
sprint_line FUN2N(struct line *,line, char *,fmt)
{
	va_list iggy;
	int len;

	len=strlen(fmt)+200;
	if(!line->alloc) {
		line->buf=ck_malloc(len);
		line->alloc=len;
	} else if(line->alloc<len) {

		line->buf=ck_realloc(line->buf,len);
		line->alloc=len;
	}
	var_start(iggy,fmt);
	vsprintf(line->buf,fmt,iggy);
	va_end(iggy);
}

static void
execute_cmd FUN1(char *,str)
{
	CELL *cp;
	struct macro *old;
	struct rng r;
	char *ptr;

	for(ptr=str;*ptr && *ptr!=' ';ptr++)
		;
	if(*ptr)
		*ptr++='\0';
	else
		ptr=0;

	for(cur_vector=0;cur_vector<num_funcs;cur_vector++)
		for(cur_cmd= &the_funcs[cur_vector][0];cur_cmd->func_name;cur_cmd++)
			if(!stricmp(str,cur_cmd->func_name)) {
				/* these lines stolen from map_char */
				if(ptr) {
					if(!cur_cmd->func_args)
						error_msg("Ignoring extra operand to %s",cur_cmd->func_name);
					else if(cur_cmd->func_args[0]=='n')
						how_many=astol((char **)(&ptr));
					else
						macro_func_arg=ptr;
				}

				cur_chr='\0';
				global_cmd(MAIN_MAP);
				return;
			}

	if(get_abs_rng(&str,&r)) {
		error_msg("Unknown command %s",str);
		return;
	}
	if(ptr) {
		error_msg("Macros can't take arguments");
		return;
	}

	if(  !(cp=find_cell(r.lr,r.lc))
 	   || GET_TYP(cp)!=TYP_STR
	   || cp->cell_str[0]=='\0')
		return;

	old=rmac;
	rmac=(struct macro *)obstack_alloc(&macro_stack,sizeof(struct macro));
	rmac->mac_prev=old;
	rmac->mac_rng= r;
	rmac->mac_row=r.lr;
	rmac->mac_col=r.lc;
	(void)obstack_grow(&macro_stack,cp->cell_str,1+strlen(cp->cell_str));
	rmac->mac_exe=(unsigned char *)obstack_finish(&macro_stack);
	rmac->mac_flags=0;
}

static void
goto_region FUN1(struct rng *,r)
{
	struct rng tmp;

	if(mkrow!=NON_ROW) {
		CELLREF cx,cy;

		set_rng(&tmp,curow,cucol,mkrow,mkcol);
		set_line(&in_line[12],range_name(&tmp));
		cx=mkrow;
		mkrow=curow;
		cy=mkcol;
		mkcol=cucol;
		(void)move_cell_cursor(cx,cy);
	} else {
		set_line(&in_line[12],cell_name(curow,cucol));
		(void)move_cell_cursor(r->lr,r->lc);
	}
	if(r->hr!=r->lr || r->hc!=r->lc) {
		mkrow=r->hr;
		mkcol=r->hc;
		cur_status();
	} else if(mkrow!=NON_ROW) {
		mkrow=NON_ROW;
		cur_status();
	}
}

static void
set_default FUN0()
{
	int fun;
	char *ptr;
	int num;
	char buf[20];

	sprintf(buf,"%d",default_width);
	set_line(&wid_line,buf);
	set_line(&fmt_line,fmt_to_str(default_fmt));
	info_msg("Alignment %s   Format %s   %srotected  Width %d",
		jst_to_str(default_jst),
		fmt_line.buf,
		default_lock==LCK_LCK ? "P" : "Unp",
		default_width);

	fun=get_chr_prompt("[A]lignment, [F]ormat, [P]rotection, or [W]idth  ");
	switch(fun) {
	case 'w':
	case 'W':
		if(get_inp_line("set-default-width",&wid_line))
			break;
		ptr=wid_line.buf;
		num=astol(&ptr);
		if(num<1)
			error_msg("Can't set default width to '%s'",wid_line.buf);
		else {
			default_width=num;
			recenter_all_win();
			return;
		}
		break;

	case 'p':
	case 'P':
		fun=get_chr_prompt("[P]rotected, or [U]nprotected");
		if(fun=='p' || fun=='P')
			default_lock= LCK_LCK;
		else if(fun=='u' || fun=='U')
			default_lock=LCK_UNL;
		else /* if(main_map<[fun]!=BREAK_CMD) */
			error_msg("Unknown char '%s'",char_to_string(fun));
		break;

	case 'f':
	case 'F':
		if(get_inp_line("set-default-format",&fmt_line))
			break;
		num=str_to_fmt(fmt_line.buf);
		if(num==-1 || num==FMT_DEF) {
			error_msg("Unknown format '%s'",fmt_line.buf);
			break;
		}
		default_fmt=num;
		disp_scrn();
		return;

	case 'a':
	case 'A':
		fun=get_chr_prompt("[L]eft, [R]ight, or [C]enter");
		num=chr_to_jst(fun);
		if(num!=-1 && num!=JST_DEF) {
			default_jst=num;
			disp_scrn();
			return;
		} else /* if(main_map[fun]!=BREAK_CMD) */
			error_msg("Unknown Alignment '%s'",char_to_string(fun));
		break;

	default:
		/* if(main_map[fun]!=BREAK_CMD) */
			error_msg("Unknown command '%s'",char_to_string(fun));
		/* else
			error_msg(""); */
		break;
	}
	cur_status();
	/* topclear=2; */
}

static void
format_area FUN1(struct rng *,f)
{
	int c;
	int fmt,jst,wid;
	CELLREF cc;
	char *locked;
	CELL *cp;
	int fun;
	char *ptr;
	extern unsigned short get_nodef_width EXT1(CELLREF);

	cp=find_cell(f->lr,f->lc);
	if(!cp) {
		fmt=FMT_DEF;
		jst=JST_DEF;
	} else {
		fmt=GET_FMT(cp);
		jst=GET_JST(cp);
	}

	wid=get_nodef_width(f->lc);
	if(wid==0)
		set_line(&wid_line,"default");
	else
		sprint_line(&wid_line,"%d",wid-1);

	set_line(&fmt_line,fmt_to_str(fmt));
	if(cp) {
		if(GET_LCK(cp)==LCK_DEF)
			locked = (default_lock==LCK_UNL ? "default unprotected" : "default protected");
		else if(GET_LCK(cp)==LCK_UNL)
			locked = "unprotected";
		else if(GET_LCK(cp)==LCK_LCK)
			locked = "protected";
		else
			locked = "Huh What?";
	} else
		locked = (default_lock==LCK_UNL ? "default unprotected" : "default protected");

	info_msg("Alignment %s   Format %s   Width %s   %s",
		jst_to_str(jst),
		fmt_line.buf,
		wid_line.buf,
		locked);

	fun=get_chr_prompt("[A]lignment, [F]ormat, [P]rotection, or [W]idth  ");
	switch(fun) {
	case 'f':
	case 'F':
		if(get_inp_line("set-format",&fmt_line))
			break;
		fmt=str_to_fmt(fmt_line.buf);
		if(fmt!=-1)
			format_region(f,fmt,-1);
		else
			error_msg("Unknown format '%s'",fmt_line.buf);
		break;

	case 'a':
	case 'A':
		c=get_chr_prompt("Align [L]eft, [R]ight, [C]enter, or [D]efault");
		fun=chr_to_jst(c);
		if(fun!=-1)
			format_region(f,-1,fun);
 		else /* if(main_map[c]!=BREAK_CMD) */
			error_msg("Unknown Justify '%s'",char_to_string(c));
		break;

	case 'p':
	case 'P':
		c=get_chr_prompt("[D]efault, [P]rotect, or [U]nprotect");
		if(c=='d' || c=='D')
			lock_region(f,LCK_DEF);
		else if(c=='p' || c=='P')
			lock_region(f,LCK_LCK);
		else if(c=='u' || c=='U')
			lock_region(f,LCK_UNL);
		else /* if(main_map[c]!=BREAK_CMD) */
			error_msg("Unknown lock %s",char_to_string(c));
		break;

	case 'w':
	case 'W':
		if(get_inp_line("set-width",&wid_line))
			break;
		ptr=wid_line.buf;
		if(*ptr=='d' || *ptr=='D')
			fun=0;
		else if(isdigit(*ptr))
			fun=astol(&ptr)+1;
		else {
			error_msg("Unknown width '%s'",wid_line.buf);
			break;
		}
		for(cc=f->lc;;cc++) {
			set_width(cc,fun);
			if(cc==f->hc)
				break;
		}
		recenter_all_win();
		return;

	default:
		/* if(main_map[fun]!=BREAK_CMD) */
			error_msg("Unknown command '%s'",char_to_string(fun));
		break;
	}
	cur_status();
	/* topclear=2; */
}


void
got_sig FUN0()
{
	FILE *fp;
	extern int write();

	/* Using xopen_with_backup is almost certainly a bad idea, however
	   using open() (preferred) or fopen() causes trouble in the restore,
	   so we suffer. . . */

	fp=xopen_with_backup("oleo.panic","w");
	if(fp) {
		write(1,"Performing panic save to 'oleo.panic'\r\n",39);
		panic_write_file(fp,0);
		xclose(fp);
	} else
		write(1,"Can't open 'oleo.panic'!  You lose!\r\n",37);
	close_display();
#ifdef FASYNC
	fcntl(0,F_SETFL,term_flag);
#endif
#ifdef FIOSSAIOSTAT
	if((term_flag&1)==0) {
		int i=0;

		ioctl(0,FIOSSAIOSTAT,&i);
	}
	if((term_flag&2)==0) {
		int i=0;

		ioctl(0,FIOSNBIO,&i);
	}
#endif

	exit(11);
}

static void
got_sigint FUN1(int, ign)
{
	int ch;

	ch=get_chr_prompt("Panic save and exit?");
	if(ch=='y' || ch=='Y')
		got_sig();
}


static void
start_macro FUN0()
{
	if(making_macro) {
		error_msg("Can't define two macros at once");
		return;
	}
	making_macro_size=20;
	making_macro=making_macro_start=ck_malloc(5+making_macro_size);
	/* *making_macro++='"'; */
}

static void
end_macro FUN0()
{
	union vals z;
	struct rng to;
	CELL *cp;
	struct macro *old;
	static struct line macro_line;

	if(!rmac && !making_macro) {
		error_msg("Not executing or defining a macro!");
		return;
	}
	if(rmac) {
		if(rmac->mac_row==rmac->mac_rng.hr && rmac->mac_col==rmac->mac_rng.hc) {
			old=rmac->mac_prev;
			(void)obstack_free(&macro_stack,rmac);
			rmac=old;
			goto deal_making;
		}

		if(rmac->mac_row==rmac->mac_rng.hr) {
			rmac->mac_row=rmac->mac_rng.lr;
			rmac->mac_col++;
		} else
			rmac->mac_row++;
		if(!(cp=find_cell(rmac->mac_row,rmac->mac_col)) || GET_TYP(cp)!=TYP_STR || cp->cell_str[0]=='\0') {
			old=rmac->mac_prev;
			(void)obstack_free(&macro_stack,rmac);
			rmac=old;
			goto deal_making;
		}
		(void)obstack_grow(&macro_stack,cp->cell_str,1+strlen(cp->cell_str));
		rmac->mac_exe=(unsigned char *)obstack_finish(&macro_stack);
		rmac->mac_flags=0;
	}
 deal_making:
	if(!making_macro)
		return;


	making_macro[0]='\0';
	making_macro=0;
	if(get_a_range("Put macro where",&to,&macro_line))
		error_msg("Forgetting new macro");
	else {
		extern void set_new_value();
			z.c_s=(char *)making_macro_start;
		set_new_value(to.lr,to.lc,TYP_STR,&z);
	}
	free(making_macro_start);
}

static void
quit_cmd FUN0()
{
	int c;

	close_display();
#ifndef AMIGA
#ifdef FASYNC
	c=fcntl(0,F_SETFL,term_flag);
#endif
#ifdef FIOSSAIOSTAT
	if((term_flag&1)==0) {
		int i=0;

		ioctl(0,FIOSSAIOSTAT,&i);
	}
	if((term_flag&2)==0) {
		int i=0;

		ioctl(0,FIOSNBIO,&i);
	}
#endif
#endif
	exit(0);
}

static void
bound_macro FUN1(int, num)
{
	struct macro *old;
	CELL *cp;
	/* CELLREF rr,cc; */

	cp=find_cell(bound_macros[num].lr,bound_macros[num].lc);
	if(!cp || GET_TYP(cp)!=TYP_STR || cp->cell_str[0]=='\0')
		return;
	old=rmac;
	rmac=(struct macro *)obstack_alloc(&macro_stack,sizeof(struct macro));
	rmac->mac_prev=old;
	rmac->mac_rng=bound_macros[num];
	rmac->mac_row=bound_macros[num].lr;
	rmac->mac_col=bound_macros[num].lc;
	(void)obstack_grow(&macro_stack,cp->cell_str,1+strlen(cp->cell_str));
	rmac->mac_exe=(unsigned char *)obstack_finish(&macro_stack);
	rmac->mac_flags=0;
}

static void
mark_cell_cmd FUN0()
{
	mkrow=curow;
	mkcol=cucol;
	cur_status();
}

static void
recalc_cmd FUN0()
{
	current_cycle++;
	while(eval_next_cell())
		;
}

static void
desc_key_cmd FUN0()
{
	error_msg("Key:  ");
	map_chr(MAIN_MAP);
	if(!cur_cmd)
		info_msg("%s is unbound", char_to_string(cur_chr));
	else
		info_msg("%s --> %s",char_to_string(cur_chr), cur_cmd->func_name);
}

static void
bind_key_cmd FUN1(char *,text)
{
	int map;
	int c;
	/* int n; */
	int vec;
	int code;
	char *ptr;
	char *cmdstr;
	struct rng rng;
	struct cmd_func *tmpfunc;
	static struct line tmpline;

	for(ptr=text;*ptr && *ptr!=' ';ptr++)
		;
	if(!*ptr) {
		if(get_inp_line("Command",&tmpline))
			return;
		ptr=tmpline.buf;
	} else
		*ptr++='\0';
	while(*ptr==' ')
		ptr++;
	cmdstr=ptr;
	while(*ptr && *ptr!=' ')
		ptr++;
	if(!*ptr)
		c=get_chr_prompt("Key:  ");
	else {
		*ptr++='\0';
		c=string_to_char(&ptr);
	}

	for(map=0;map<num_maps;map++)
		if(!strcmp(text,map_names[map]))
			break;
	if(map==num_maps) {
		error_msg("Can't find keymap '%s'",text);
		goto fail;
	}
	for(vec=0;vec<num_funcs;vec++) {
		for(code=0;the_funcs[vec][code].func_name;code++)
			if(!stricmp(cmdstr,the_funcs[vec][code].func_name))
				goto fini;
	}
	for(code=0;code<num_maps;code++) {
		if(!strcmp(cmdstr,map_names[code])) {
			vec=255;
			goto fini;
		}
	}
	if(get_abs_rng(&cmdstr,&rng)) {
		error_msg("Unknown command '%s'",cmdstr);
		goto fail;
	}

	vec=bound_macro_vec;
	for(code=0;code<n_bound_macros;code++) {
		if(!bcmp(&bound_macros[code],&rng,sizeof(struct rng)))
			goto fini;
	}

	ptr=range_name(&rng);
	if(!bound_macro_vec) {
		bound_macros=ck_malloc(sizeof(struct rng));
		n_bound_macros=1;
		tmpfunc=ck_malloc(sizeof(struct cmd_func));
		bound_macro_vec=add_usr_cmds(tmpfunc);
	} else {
		n_bound_macros++;

		bound_macros=ck_realloc(bound_macros,n_bound_macros*sizeof(struct rng));

		the_funcs[bound_macro_vec]=ck_realloc(the_funcs[bound_macro_vec],n_bound_macros*sizeof(struct cmd_func));
		tmpfunc= &the_funcs[bound_macro_vec][n_bound_macros-1];
	}
	bound_macros[n_bound_macros-1]=rng;
	tmpfunc->func_args=ck_malloc(strlen(ptr)+5);
	tmpfunc->func_args[0]='n';
	tmpfunc->func_args[1]='a'+(n_bound_macros-1)/26;
	tmpfunc->func_args[2]='a'+(n_bound_macros-1)%26;
	tmpfunc->func_args[3]='\0';
	tmpfunc->func_name=tmpfunc->func_args+4;
	strcpy(tmpfunc->func_name,ptr);
	tmpfunc->func_flags=ALL;
	tmpfunc->func_func=bound_macro;
 fini:
	do_bind_key(the_maps[map],c,vec,code);
 fail:
	sprint_line(&in_line[16],"%s %s %s",text,cmdstr,char_to_string(c));
}

static void
do_bind_key FUN4(struct keymap *,map, int,key, unsigned char,vector, unsigned char,code)
{
	struct keymap *m;
	struct key *k;
	int n;


	for(m=map;m;m=m->map_next) {
		if(key+1>=m->lochr && key-1<=m->hichr)
			break;
		if(m->map_end)
			m=0;
	}
	if(m->map_dense) {
		if(vector==m->keys[0].vector && code==m->keys[0].code) {
			if(key<m->lochr)
				m->lochr=key;
			if(key>m->hichr)
				m->hichr=key;
			return;
		} else if(m->lochr!=m->hichr)
			m=0;
		else
			m->map_dense=0;
	}
	if(m==0) {
		m=ck_malloc(sizeof(struct keymap));
		*m= *map;
		map->map_next=m;
		m=map;
		m->map_end=0;
		m->map_dense=1;
		m->map_malloc=2;
		m->lochr=key;
		m->hichr=key;
		m->keys=ck_malloc(sizeof(struct key));
	}
	if(key<m->lochr) {
		n=sizeof(struct key)*(1+m->hichr-m->lochr);
		k=ck_malloc(n+sizeof(struct key));
		bcopy(&m->keys[0],&k[1],n);
		if(m->map_malloc==2)
			free(m->keys);
		m->keys=k;
		m->lochr=key;
		m->map_malloc=2;
	}
	if(key>m->hichr) {
		if(m->map_malloc==2)
		  {

			m->keys=ck_realloc(m->keys,sizeof(struct key)*(1+m->hichr-m->lochr));
}
		else {
			n=sizeof(struct key)*(1+m->hichr-m->lochr);
			k=ck_malloc(n+sizeof(struct key));
			bcopy(&m->keys[0],k,n);
			m->keys=k;
			m->map_malloc=2;
		}
		m->hichr=key;
	}
	if(!m->map_malloc) {
		n=sizeof(struct key)*(1+m->hichr-m->lochr);
		k=ck_malloc(n);
		bcopy(&m->keys[0],k,n);
		m->keys=k;
		m->map_malloc=2;
	}
	m->keys[key-m->lochr].vector=vector;
	m->keys[key-m->lochr].code=code;
}

static void
interact_macro_cmd FUN0()
{
	error_msg("Command not implemented");
	return;
}

static void
kill_cell_cmd FUN0()
{
	CELL *cp;

	cp=find_cell(curow,cucol);
	if(!cp)
		return;
	if((GET_LCK(cp)==LCK_DEF && default_lock==LCK_LCK) || GET_LCK(cp)==LCK_LCK) {
		error_msg("Cell %s is locked",cell_name(curow,cucol));
		return;
	}
	new_value(curow,cucol,"");
	cp->cell_flags=0;
	modified=1;
}

static void
format_cell_cmd FUN0()
{
	struct rng to;

	to.lr=to.hr=curow;
	to.lc=to.hc=cucol;
	format_area(&to);
}

static void
kill_all_cmd FUN0()
{
	clear_spreadsheet();
	disp_scrn();
}

#if 0
static void
do_ansi_cmd FUN0()
{
	int c;

	c=get_chr();
	if(c>='A' && c<='D')
		shift_cell_cursor(c-'A');
	else
		/* ? ? ? */;
}
#endif

static void
do_input_cmd FUN2(int, n, int,c)
{
	CELL *cp;
	char *fail;

	extern char *cell_value_string EXT2(CELLREF, CELLREF);

	switch(n) {
		/* Edit cell */
	case 0:
		if(cp=find_cell(curow,cucol)) {
			set_line(&val_line,decomp(curow,cucol,cp));
			decomp_free();
		} else
			set_line(&val_line,0);
		break;

		/* Edit Cell Value */
	case 1:
		set_line(&val_line,cell_value_string(curow,cucol));
		break;

	case 2:	/* Set-cell */
		set_line(&val_line,"x");
		val_line.buf[0]=c;
		break;

		/* New Def Cell */
	case 3:
		break;
#ifdef TEST
	default:
		panic("Unknown value in do_input_cell (%d)",n);
#endif
	}
	cp=find_cell(curow,cucol);
	if(((!cp || GET_LCK(cp)==LCK_DEF) && default_lock==LCK_LCK) || (cp && GET_LCK(cp)==LCK_LCK)) {
		error_msg("Cell %s is locked",cell_name(curow,cucol));
		return;
	}
	setrow=curow;
	setcol=cucol;
	if(get_inp_line("set-cell",&val_line)>1)
		return;
	fail=new_value(setrow,setcol,val_line.buf);
	if(fail)
		error_msg(fail);
	else
		modified=1;
}

static void
do_break_cmd FUN0()
{
	if(mkrow!=NON_ROW) {
		mkrow=NON_ROW;
		how_many=1;
		cur_status();
	}
}

static void
digit_cmd FUN1(int, magic)
{
	struct keymap *map;

	for(map=the_maps[DIGIT_MAP];map->map_end==0;map=map->map_next)
		;
	map->map_next= the_maps[magic];
	how_many=0;
	do {
		how_many=how_many * 10 + cur_cmd - &cmd_funcs[DIGIT_0];
		cur_status();
		map_chr(DIGIT_MAP);
	} while(cur_cmd>= &cmd_funcs[DIGIT_0] && cur_cmd<= &cmd_funcs[DIGIT_9]);
}


