/*
 * vile -- "vi like emacs"
 *
 * this used to be MicroEMACS, a public domain program written by
 * dave g. conroy, further improved and modified by daniel m. lawrence.
 *
 * the original author of vile is paul fox.  tom dickey and kevin
 * buettner made huge contributions along the way, as did rick
 * sladkey and other people (see all of the CHANGES files for
 * details).  vile is now principally maintained by tom dickey.
 *
 * previous versions of vile were limited to non-commercial use due to
 * their inclusion of code from versions of MicroEMACS which were
 * restricted in that way.  in the current version of vile, every
 * attempt has been made to ensure that this later code has been
 * removed or rewritten, returning vile's original basis to freely
 * distributable status.  This version of vile is distributed under the
 * terms of the GNU Public License (see COPYING).
 *
 * Copyright (c) 1992-2003 by Paul Fox and Thomas Dickey
 *
 */

/*
 * $Header: /usr/build/vile/vile/RCS/main.c,v 1.510 2003/07/27 15:18:54 tom Exp $
 */

#define realdef			/* Make global definitions not external */

#include	"estruct.h"	/* global structures and defines */
#include	"edef.h"	/* global declarations */
#include	"nevars.h"
#include	"nefunc.h"
#include	"nefsms.h"

#include	<assert.h>
#include	<ctype.h>

#if OPT_LOCALE
#include	<locale.h>

#ifdef HAVE_WCTYPE
#include	<wctype.h>
#define sys_iscntrl(n)  iswcntrl(n)
#define sys_isdigit(n)  iswdigit(n)
#define sys_islower(n)  iswlower(n)
#define sys_isprint(n)  iswprint(n)
#define sys_ispunct(n)  iswpunct(n)
#define sys_isspace(n)  iswspace(n)
#define sys_isupper(n)  iswupper(n)
#define sys_isxdigit(n) iswxdigit(n)
#else
#define sys_iscntrl(n)  iscntrl(n)
#define sys_isdigit(n)  isdigit(n)
#define sys_islower(n)  islower(n)
#define sys_isprint(n)  isprint(n)
#define sys_ispunct(n)  ispunct(n)
#define sys_isspace(n)  isspace(n)
#define sys_isupper(n)  isupper(n)
#define sys_isxdigit(n) isxdigit(n)
#endif

#endif /* OPT_LOCALE */

#if CC_NEWDOSCC
#include <io.h>
#if CC_DJGPP
#include <dpmi.h>
#include <go32.h>
#endif
#endif

#if SYS_VMS
#include <processes.h>
#endif

extern char *exec_pathname;
extern const char *const pathname[];	/* startup file path/name array */

/* if on a crippled system, try to survive: bigger stack */
#if SYS_MSDOS && CC_TURBO
unsigned _stklen = 24000U;
#endif

static int cmd_mouse_motion(const CMDFUNC * cfp);
static void get_executable_dir(void);
static void global_val_init(void);
static void main_loop(void);
static void make_startup_file(char *name);
static void siginit(int enabled);
static void start_debug_log(int ac, char **av);

extern const int nametblsize;

/*--------------------------------------------------------------------------*/

/*
 * Get the argument parameter.  The 'param' points to the option flag.  Set it
 * to a 1-character string to force the next entry from argv[] to be fetched.
 */
static char *
get_argvalue(char *param, char *argv[], int *argcp)
{
    if (!*(++param)) {
	*argcp += 1;
	param = argv[*argcp];
    }
    if (param == 0)
	print_usage(BADEXIT);
    return param;
}

#define	GetArgVal(param)	get_argvalue(param, argv, &carg)

/*--------------------------------------------------------------------------*/

int
MainProgram(int argc, char *argv[])
{
    int tt_opened;
    BUFFER *bp;
    int carg;			/* current arg to scan */
    char *vileinit = NULL;	/* the startup file or VILEINIT var */
    int startstat = TRUE;	/* result of running startup */
    BUFFER *havebp = NULL;	/* initial buffer to read */
    char *havename = NULL;	/* name of first buffer in cmd line */
    int gotoflag = FALSE;	/* do we need to goto line at start? */
    int gline = FALSE;		/* if so, what line? */
    int helpflag = FALSE;	/* do we need help at start? */
    REGEXVAL *search_exp = 0;	/* initial search-pattern */
    const char *msg;
#if SYS_VMS
    char *init_descrip = NULL;
#endif
#ifdef VILE_OLE
    int ole_register = FALSE;
#endif
#if OPT_TAGS
    int didtag = FALSE;		/* look up a tag to start? */
    char *tname = NULL;
#endif
#if DISP_NTCONS
    int new_console = FALSE;
#endif
#if OPT_ENCRYPT
    char startkey[NKEYLEN];	/* initial encryption key */
    *startkey = EOS;
#endif

#if OPT_LOCALE
    {
	char *env = "";

	/*
	 * Force 8-bit locale for display drivers where we only support 8-bits
	 */
#if DISP_TERMCAP
	if (((env = getenv("LC_ALL")) != 0 && *env != 0) ||
	    ((env = getenv("LC_CTYPE")) != 0 && *env != 0) ||
	    ((env = getenv("LANG")) != 0 && *env != 0)) {
	    char *utf = strstr(env, ".UTF-8");

	    if (utf != 0) {
		char *tmp = strmalloc(env);
		tmp[utf - env] = EOS;
		env = tmp;
		utf8_locale = TRUE;
	    } else {
		env = "";
	    }
	} else {
	    env = "";
	}
#endif
	setlocale(LC_ALL, env);	/* set locale according to environment vars */
    }
#endif /* OPT_LOCALE */

#if OPT_NAMEBST
    build_namebst(nametbl, 0, nametblsize - 1);
#endif
    global_val_init();		/* global buffer values */
    charinit();			/* character types -- we need these early  */
    winit(FALSE);		/* command-line */
#if !SYS_UNIX
    expand_wild_args(&argc, &argv);
#endif
    prog_arg = argv[0];		/* this contains our only clue to exec-path */
#if SYS_MSDOS || SYS_OS2 || SYS_OS2_EMX || SYS_WINNT
    if (strchr(pathleaf(prog_arg), '.') == 0) {
	char *t = malloc(strlen(prog_arg) + 5);
	lsprintf(t, "%s.exe", prog_arg);
	prog_arg = t;
    }
#endif

    /*
     * Attempt to appease perl if we're running in a process which was setuid'd
     * or setgid'd.
     */
#if defined(HAVE_SETUID) && defined(HAVE_SETGID) && defined(HAVE_GETEGID) && defined(HAVE_GETEUID)
    setgid(getegid());
    setuid(geteuid());
#endif

    start_debug_log(argc, argv);

    get_executable_dir();

    if (strcmp(pathleaf(prog_arg), "view") == 0)
	set_global_b_val(MDREADONLY, TRUE);

#if DISP_X11
    if (argc != 2 || strcmp(argv[1], "-V") != 0) {
	if (x_preparse_args(&argc, &argv) != TRUE)
	    startstat = FALSE;
    }
#endif

    /*
     * Allow for I/O to the command-line before we initialize the screen
     * driver.
     *
     * FIXME: we only know how to do this for displays that open the
     * terminal in the same way for command-line and screen.
     */
    siginit(TRUE);
#if OPT_DUMBTERM
    if (isatty(fileno(stdin))
	&& isatty(fileno(stdout))) {
	tt_opened = open_terminal(&dumb_term);
    } else
#endif
	tt_opened = open_terminal(&null_term);

    /* Parse the passed in parameters */
    for (carg = 1; carg < argc; ++carg) {
	char *param = argv[carg];

	/* evaluate switches */
	if (*param == '-') {
	    ++param;
#if DISP_IBMPC || DISP_BORLAND || SYS_VMS
	    /* if it's a digit, it's probably a screen
	       resolution */
	    if (isDigit(*param)) {
#if DISP_IBMPC || DISP_BORLAND
		current_res_name = param;
#else
		if (strcmp(param, "132") == 0)
		    init_descrip = "WIDE";
		else if (strcmp(param, "80") == 0)
		    init_descrip = "NORMAL";
		else
		    print_usage(BADEXIT);
#endif
		continue;
	    } else
#endif /* DISP_IBMPC */
		switch (*param) {
#if DISP_NTCONS
		case 'c':
		    if (strcmp(param, "console") == 0) {
			/*
			 * start editor in a new console env if
			 * stdin is redirected.  if this option
			 * is not set when stdin is redirected,
			 * console vile's mouse features are
			 * unavailable (bug).
			 */
			new_console = TRUE;
		    } else
			print_usage(BADEXIT);
		    break;
#endif /* DISP_NTCONS */
#if OPT_EVAL || OPT_DEBUGMACROS
		case 'D':
		    tracemacros = TRUE;
		    break;
#endif
		case 'e':	/* -e for Edit file */
		case 'E':
		    set_global_b_val(MDVIEW, FALSE);
		    break;
		case 'g':	/* -g for initial goto */
		case 'G':
		    gotoflag = TRUE;
		    param = GetArgVal(param);
		    gline = atoi(param);
		    break;
		case 'h':	/* -h for initial help */
		case 'H':
		    helpflag = TRUE;
		    break;

		case 'i':
		case 'I':
		    vileinit = "vileinit.rc";
		    /*
		     * If the user has no startup file, make a simple
		     * one that points to this one.
		     */
		    if (cfg_locate(vileinit, LOCATE_SOURCE) != 0
			&& cfg_locate(startup_file, LOCATE_SOURCE) == 0)
			make_startup_file(vileinit);
		    break;

#if OPT_ENCRYPT
		case 'k':	/* -k<key> for code key */
		case 'K':
		    param = GetArgVal(param);
		    vl_make_encrypt_key(startkey, param);
		    break;
#endif
#ifdef VILE_OLE
		case 'O':
		    if (param[1] == 'r')
			ole_register = TRUE;
		    else
			print_usage(BADEXIT);
		    break;
#endif
		case 's':	/* -s <pattern> */
		case 'S':
		  dosearch:
		    param = GetArgVal(param);
		    search_exp = new_regexval(param,
					      global_b_val(MDMAGIC));
		    break;
#if OPT_TAGS
		case 't':	/* -t for initial tag lookup */
		case 'T':
		    param = GetArgVal(param);
		    tname = param;
		    break;
#endif
		case 'v':	/* -v is view mode */
		    set_global_b_val(MDVIEW, TRUE);
		    break;

		case 'R':	/* -R is readonly mode */
		    set_global_b_val(MDREADONLY, TRUE);
		    break;

		case 'V':
#if DISP_NTWIN
		    gui_version(prog_arg);
#else
		    (void) printf("%s\n", getversion());
#endif
		    tidy_exit(GOODEXIT);
		    /* NOTREACHED */

		case '?':
		    /* FALLTHRU */
		default:	/* unknown switch */
		    print_usage(GOODEXIT);
		}

	} else if (*param == '+') {	/* alternate form of -g */
	    if (*(++param) == '/') {
		int len = strlen(param);
		if (len > 0 && param[len - 1] == '/')
		    param[--len] = EOS;
		if (len == 0)
		    print_usage(BADEXIT);
		goto dosearch;
	    }
	    gotoflag = TRUE;
	    gline = atoi(param);
	} else if (*param == '@') {
	    vileinit = ++param;
	} else if (*param != EOS) {

	    /* must be a filename */
#if OPT_ENCRYPT
	    cryptkey = (*startkey != EOS) ? startkey : 0;
#endif
	    /* set up a buffer for this file */
	    bp = getfile2bp(param, FALSE, TRUE);
	    if (bp) {
		bp->b_flag |= BFARGS;	/* treat this as an argument */
		make_current(bp);	/* pull it to the front */
		if (!havebp) {
		    havebp = bp;
		    havename = param;
		}
	    }
#if OPT_ENCRYPT
	    cryptkey = 0;
#endif
	}
    }
#ifdef VILE_OLE
    if (ole_register) {
	/*
	 * Now that all command line options have been successfully
	 * parsed, register the OLE automation server and exit.
	 */

	ntwinio_oleauto_reg();
	/* NOT REACHED */
    }
#endif

    /* if stdin isn't a terminal, assume the user is trying to pipe a
     * file into a buffer.
     */
#if SYS_UNIX || SYS_VMS || SYS_MSDOS || SYS_OS2 || SYS_WINNT
#if DISP_NTWIN
    if (stdin_data_available())
#else
    if (!isatty(fileno(stdin)))
#endif
    {

#if !SYS_WINNT
#if !DISP_X11
#if SYS_UNIX
	char *tty = "/dev/tty";
#else
	FILE *in;
	int fd;
#endif /* SYS_UNIX */
#endif /* DISP_X11 */
#endif /* !SYS_WINNT */
	BUFFER *lastbp = havebp;
	int nline = 0;

	bp = bfind(STDIN_BufName, BFARGS);
	make_current(bp);	/* pull it to the front */
	if (!havebp)
	    havebp = bp;
	ffp = fdopen(dup(fileno(stdin)), "r");
#if !DISP_X11
# if SYS_UNIX
# if defined(HAVE_TTYNAME) && !defined(HAVE_DEV_TTY)
	tty = ttyname(fileno(stderr));
# endif
	/*
	 * Note: On Linux, the low-level close/dup operation
	 * doesn't work, since something hangs, apparently
	 * because substituting the file descriptor doesn't communicate
	 * properly up to the stdio routines.
	 */
	if ((freopen(tty, "r", stdin)) == 0
	    || !isatty(fileno(stdin))) {
	    fprintf(stderr, "cannot open a terminal (%s)\n", tty);
	    tidy_exit(BADEXIT);
	}
#else
# if SYS_WINNT
#  if DISP_NTCONS
	if (new_console) {
	    if (FreeConsole()) {
		if (!AllocConsole()) {
		    fputs("unable to allocate new console\n", stderr);
		    tidy_exit(BADEXIT);
		}
	    } else {
		fputs("unable to release existing console\n", stderr);
		tidy_exit(BADEXIT);
	    }
	}

	/*
	 * The editor must reopen the console, not fd 0.  If the console
	 * is not reopened, the nt console I/O routines die immediately
	 * when attempting to fetch a STDIN handle.
	 */
	freopen("con", "r", stdin);
#  endif
# else
#  if SYS_VMS
	fd = open("tt:", O_RDONLY, S_IREAD);	/* or sys$command */
#  else				/* e.g., DOS-based systems */
	fd = fileno(stderr);	/* this normally cannot be redirected */
#  endif
	if ((fd >= 0)
	    && (close(0) >= 0)
	    && (fd = dup(fd)) == 0
	    && (in = fdopen(fd, "r")) != 0)
	    *stdin = *in;
#  endif			/* SYS_WINNT */
# endif				/* SYS_UNIX */
#endif /* DISP_X11 */

#if OPT_ENCRYPT
	if (*startkey != EOS) {
	    strcpy(bp->b_cryptkey, startkey);
	    make_local_b_val(bp, MDCRYPT);
	    set_b_val(bp, MDCRYPT, TRUE);
	}
#endif
	(void) slowreadf(bp, &nline);
	set_rdonly(bp, bp->b_fname, MDREADONLY);
	(void) ffclose();

	if (is_empty_buf(bp)) {
	    (void) zotbuf(bp);
	    curbp = havebp = lastbp;
	}
#if OPT_FINDERR
	else {
	    set_febuff(bp->b_bname);
	}
#endif
    }
#endif

    if (!tt_opened)
	siginit(TRUE);
    (void) open_terminal((TERM *) 0);
    term.kopen();		/* open the keyboard */
    term.rev(FALSE);

    /*
     * The terminal driver sets default colors and $palette during the
     * open_terminal function.  Save that information as the 'default'
     * color scheme.  We have to do this before reading .vilerc
     */
#if OPT_COLOR_SCHEMES
    init_scheme();
#endif

    if (vtinit() != TRUE)	/* allocate display memory */
	tidy_exit(BADEXIT);

    winit(TRUE);		/* windows */

    /* this comes out to 70 on an 80 (or greater) column display */
    {
	register int fill;
	fill = (7 * term.cols) / 8;	/* must be done after vtinit() */
	if (fill > 70)
	    fill = 70;
	set_global_b_val(VAL_FILL, fill);
    }

    /* Create an unnamed buffer, so that the initialization-file will have
     * something to work on.  We don't pull in any of the command-line
     * filenames yet, because some of the initialization stuff has to be
     * triggered by switching buffers after reading the .vilerc file.
     *
     * If nothing modifies it, this buffer will be automatically removed
     * when we switch to the first file (i.e., havebp), because it is
     * empty (and presumably isn't named the same as an actual file).
     */
    bp = bfind(UNNAMED_BufName, 0);
    bp->b_active = TRUE;
#if OPT_DOSFILES
    /* an empty non-existent buffer defaults to line-style
       favored by the OS */
    make_local_b_val(bp, MDDOS);
    set_b_val(bp, MDDOS, CRLF_LINES);
#endif
    fix_cmode(bp, FALSE);
    swbuffer(bp);

    /* run the specified, or the system startup file here.
       if vileinit is set, it's the name of the user's
       command-line startup file, i.e. 'vile @mycmds'
     */
    if (vileinit && *vileinit) {
	if (do_source(vileinit, 1, FALSE) != TRUE) {
	    startstat = FALSE;
	    goto begin;
	}
	free(startup_file);
	startup_file = strmalloc(vileinit);
    } else {

	/* else vileinit is the contents of their VILEINIT variable */
	vileinit = getenv("VILEINIT");
	if (vileinit != NULL) {	/* set... */
	    BUFFER *vbp, *obp;
	    UINT oflags = 0;
	    if (*vileinit) {	/* ...and not null */
		/* mark as modified, to prevent
		 * undispbuff() from clobbering */
		obp = curbp;
		if (obp) {
		    oflags = obp->b_flag;
		    b_set_changed(obp);
		}

		if ((vbp = bfind(VILEINIT_BufName, BFEXEC)) == 0)
		    tidy_exit(BADEXIT);

		/* don't want swbuffer to try to read it */
		vbp->b_active = TRUE;
		swbuffer(vbp);
		b_set_scratch(vbp);
		bprintf("%s", vileinit);
		/* if we leave it scratch, swbuffer(obp)
		   may zot it, and we may zot it again */
		b_clr_scratch(vbp);
		set_rdonly(vbp, vbp->b_fname, MDVIEW);

		/* go execute it! */
		if (dobuf(vbp, 1) != TRUE) {
		    startstat = FALSE;
		    goto begin;
		}
		if (obp) {
		    swbuffer(obp);
		    obp->b_flag = oflags;
		}
		/* remove the now unneeded buffer */
		b_set_scratch(vbp);	/* make sure it will go */
		(void) zotbuf(vbp);
	    }
	} else {		/* find and run .vilerc */
	    if (do_source(startup_file, 1, TRUE) != TRUE) {
		startstat = FALSE;
		goto begin;
	    }
	}
    }

    /*
     * before reading, double-check, since a startup-script may
     * have removed the first buffer.
     */
    if (havebp && find_bp(havebp)) {
	if (find_bp(bp) && is_empty_buf(bp) && !b_is_changed(bp))
	    b_set_scratch(bp);	/* remove the unnamed-buffer */
	if (swbuffer(havebp) == TRUE) {
#if OPT_MAJORMODE && OPT_HOOKS
	    /*
	     * Partial fix in case we edit .vilerc (vile.rc) or
	     * filters.rc, or related files that initialize syntax
	     * highlighting.
	     */
	    VALARGS args;
	    infer_majormode(curbp);
	    if (find_mode(curbp, "vilemode", FALSE, &args) == TRUE)
		run_readhook();
#else
	    ;
#endif
	} else {
	    startstat = FALSE;
	}
	if (havename)
	    set_last_file_edited(havename);
	if (bp2any_wp(bp) && bp2any_wp(havebp))
	    zotwp(bp);
    }
#if OPT_TAGS
    else if (tname) {
	cmdlinetag(tname);
	didtag = TRUE;
    }
#endif
    msg = s_NULL;
    if (helpflag) {
	if (vl_help(TRUE, 1) != TRUE) {
	    msg =
		"[Problem with help information. Type \":quit\" to exit if you wish]";
	}
    } else {
	msg = "[Use ^A-h, ^X-h, or :help to get help]";
    }

    /* honor command-line actions */
    if (gotoflag + (search_exp != 0)
#if OPT_TAGS
	+ (tname ? 1 : 0)
#endif
	> 1) {
#if OPT_TAGS
	msg = "[Search, goto and tag are used one at a time]";
#else
	msg = "[Cannot search and goto at the same time]";
#endif
    } else if (gotoflag) {
	if (gotoline(gline != 0, gline) == FALSE) {
	    msg = "[Not that many lines in buffer]";
	    (void) gotoeob(FALSE, 1);
	}
    } else if (search_exp) {
	FreeIfNeeded(gregexp);
	searchpat = tb_string(search_exp->pat);
	gregexp = search_exp->reg;
	(void) forwhunt(FALSE, 0);
#if OPT_TAGS
    } else if (tname && !didtag) {
	cmdlinetag(tname);
#endif
    }
#if OPT_POPUP_MSGS
    purge_msgs();
#endif
    if (startstat == TRUE)	/* else there's probably an error message */
	mlforce(msg);

  begin:
#if SYS_VMS
    if (init_descrip) {
	/*
	 * All terminal inits are complete.  Switch to new screen
	 * resolution specified from command line.
	 */
	(void) term.setdescrip(init_descrip);
    }
#endif
    (void) update(FALSE);

#if DISP_NTWIN
    /* Draw winvile's main window, for the very first time. */
    winvile_start();
#endif

#if OPT_POPUP_MSGS
    if (global_g_val(GMDPOPUP_MSGS) && (startstat != TRUE)) {
	bp = bfind(MESSAGES_BufName, BFSCRTCH);
	bsizes(bp);
	TRACE(("Checking size of popup messages: %d\n", bp->b_linecount));
	if (bp->b_linecount > 1) {
	    int save = global_g_val(GMDPOPUP_MSGS);
	    set_global_g_val(GMDPOPUP_MSGS, TRUE);
	    popup_msgs();
	    tb_init(&mlsave, EOS);
	    set_global_g_val(GMDPOPUP_MSGS, save);
	}
    }
    if (global_g_val(GMDPOPUP_MSGS) == -TRUE)
	set_global_g_val(GMDPOPUP_MSGS, FALSE);
#endif

#ifdef GMDCD_ON_OPEN
    /* reset a one-shot, e.g., for winvile's drag/drop code */
    if (global_g_val(GMDCD_ON_OPEN) < 0) {
	set_directory_from_file(curbp);
	set_global_g_val(GMDCD_ON_OPEN, FALSE);
    }
#endif

    /* We won't always be able to show messages before the screen is
     * initialized.  Give it one last chance.
     */
    if ((startstat != TRUE) && tb_length(mlsave))
	mlforce("%*S", tb_length(mlsave), tb_values(mlsave));

    /* process commands */
    main_loop();

    /* NOTREACHED */
    return BADEXIT;
}

/* this is nothing but the main command loop */
static void
main_loop(void)
{
    const CMDFUNC *cfp = NULL;
    const CMDFUNC *last_cfp = NULL;
    const CMDFUNC *last_failed_motion_cfp = NULL;
    int s, c, f, n;

    for_ever {

#if OPT_WORKING && DOALLOC
	assert(allow_working_msg());
#endif
	hst_reset();

	/* vi doesn't let the cursor rest on the newline itself.  This
	   takes care of that. */
	/* if we're inserting, or will be inserting again, then
	   suppress.  this happens if we're using arrow keys
	   during insert */
	if (is_at_end_of_line(DOT) && (DOT.o > w_left_margin(curwp)) &&
	    !insertmode && !cmd_mouse_motion(cfp))
	    backchar(TRUE, 1);

	/* same goes for end-of-file -- I'm actually not sure if
	   this can ever happen, but I _am_ sure that it's
	   a lot safer not to let it... */
	if (is_header_line(DOT, curbp) && !is_empty_buf(curbp))
	    (void) backline(TRUE, 1);

	/* start recording for '.' command */
	dotcmdbegin();

	/* bring the screen up to date */
	s = update(FALSE);

	/* get a user command */
	kbd_mac_check();
	c = kbd_seq();

	/* reset the contents of the command/status line */
	if (kbd_length() > 0) {
	    mlerase();
	    if (s != SORTOFTRUE)	/* did nothing due to typeahead */
		(void) update(FALSE);
	}

	f = FALSE;
	n = 1;

#if VILE_SOMEDAY
/* insertion is too complicated to pop in
	and out of so glibly...   -pgf */
#ifdef insertmode
	/* FIXME: Paul and Tom should check this over. */
	if (insertmode != FALSE) {
	    if (!kbd_replaying(FALSE))
		mayneedundo();
	    unkeystroke(c);
	    insert(f, n);
	    dotcmdfinish();
	    continue;
	}
#endif /* insertmode */
#endif /* SOMEDAY */

	do_repeats(&c, &f, &n);

	kregflag = 0;

	/* flag the first time through for some commands -- e.g. subst
	   must know to not prompt for strings again, and pregion
	   must only restart the p-lines buffer once for each
	   command. */
	calledbefore = FALSE;

	/* convert key code to a function pointer */
	cfp = DefaultKeyBinding(c);

	if (cfp == &f_dotcmdplay &&
	    (last_cfp == &f_undo ||
	     last_cfp == &f_forwredo ||
	     last_cfp == &f_backundo ||
	     last_cfp == &f_inf_undo))
	    cfp = &f_inf_undo;

	s = execute(cfp, f, n);

	last_cfp = cfp;

	/* stop recording for '.' command */
	dotcmdfinish();

	/* if we had an error in playback, stop right away, since the remaining
	 * characters may be from the middle of a change-command which failed
	 * due to invalid motion.
	 */
	if ((s == FALSE) && (dotcmdactive == PLAY)) {
	    dotcmdactive = 0;
	}

	/* If this was a motion that failed, sound the alarm (like vi),
	 * but limit it to once, in case the user is holding down the
	 * autorepeat-key.
	 */
	if ((cfp != NULL)
	    && ((cfp->c_flags & MOTION) != 0)
	    && (s == FALSE)) {
	    if (cfp != last_failed_motion_cfp ||
		global_g_val(GMDMULTIBEEP)) {
		last_failed_motion_cfp = cfp;
		kbd_alarm();
	    }
	} else {
	    last_failed_motion_cfp = NULL;	/* avoid noise! */
	}

	attrib_matches();
	regionshape = EXACT;

	/*
	 * Some perl scripts may not reset this flag.
	 */
	vile_is_busy = FALSE;

    }
}

/* attempt to locate the executable that contains our code.
* leave its directory name in exec_pathname and shorten prog_arg
* to the simple filename (no path).
*/
static void
get_executable_dir(void)
{
#if SYS_UNIX || SYS_VMS
    char temp[NFILEN];
    char *s, *t;

    if (last_slash(prog_arg) == NULL) {
	/* If there are no slashes, we can guess where we came from,
	 */
	if ((s = cfg_locate(prog_arg, FL_PATH | FL_EXECABLE)) != 0)
	    s = strmalloc(s);
    } else {
	/* if there _are_ slashes, then argv[0] was either
	 * absolute or relative. lengthen_path figures it out.
	 */
	s = strmalloc(lengthen_path(strcpy(temp, prog_arg)));
    }
    if (s == 0)
	return;

    t = pathleaf(s);
    if (t != s) {
	/*
	 * On a unix host, 't' points past slash.  On a VMS host,
	 * 't' points to first char after the last ':' or ']' in
	 * the exe's path.
	 */
	prog_arg = strmalloc(t);
	*t = EOS;
	exec_pathname = strmalloc(lengthen_path(strcpy(temp, s)));
    }
    free(s);
#endif
}

void
tidy_exit(int code)
{
#if OPT_PERL
    perl_exit();
#endif
#if DISP_NTWIN
    winvile_cleanup();
#endif
#if DISP_X11
    term.close();		/* need this if $xshell left subprocesses */
#endif
    ttclean(TRUE);
#if SYS_UNIX
    setup_handler(SIGHUP, SIG_IGN);
#endif
    ExitProgram(code);
}

#ifndef strmalloc
char *
strmalloc(const char *s)
{
    char *ns;
    beginDisplay();
    ns = castalloc(char, strlen(s) + 1);
    if (ns != 0)
	(void) strcpy(ns, s);
    endofDisplay();
    return ns;
}
#endif

int
no_memory(const char *s)
{
    mlforce("[%s] %s", out_of_mem, s);
    return FALSE;
}

#define setIntValue(vp, value) vp->v.i = value
#define setPatValue(vp, value) vp->v.r = new_regexval(value, TRUE)
#define setTxtValue(vp, value) vp->v.p = strmalloc(value)

#define setINT(name,value)  case name: setIntValue(d,value); break
#define setPAT(name,value)  case name: setPatValue(d,value); break
#define setTXT(name,value)  case name: setTxtValue(d,value); break

#define DFT_FENCE_BEGIN "/\\*"
#define DFT_FENCE_END   "\\*/"

#define DFT_FENCE_IF    "^\\s*#\\s*if"
#define DFT_FENCE_ELIF  "^\\s*#\\s*elif\\>"
#define DFT_FENCE_ELSE  "^\\s*#\\s*else\\>"
#define DFT_FENCE_FI    "^\\s*#\\s*endif\\>"

			/* where do paragraphs start? */
#define DFT_PARAGRAPHS  "^\\.[ILPQ]P\\>\\|^\\.P\\>\\|\
^\\.LI\\>\\|^\\.[plinb]p\\>\\|^\\.\\?\\s*$"

			/* where do comments start and end */
#define DFT_COMMENTS    "^\\s*/\\?\\(\\s*[#*>/]\\)\\+/\\?\\s*$"

#define DFT_CMT_PREFIX  "^\\s*\\(\\(\\s*[#*>]\\)\\|\\(///*\\)\\)\\+"

			/* where do sections start? */
#define DFT_SECTIONS    "^[{\014]\\|^\\.[NS]H\\>\\|^\\.HU\\?\\>\\|\
^\\.[us]h\\>\\|^+c\\>"

			/* where do sentences start? */
#define DFT_SENTENCES   "[.!?][])\"']* \\?$\\|[.!?][])\"']*  \\|\
^\\.[ILPQ]P\\>\\|^\\.P\\>\\|^\\.LI\\>\\|^\\.[plinb]p\\>\\|^\\.\\?\\s*$"

#if SYS_MSDOS			/* actually, 16-bit ints */
#define DFT_TIMEOUTUSERVAL 30000
#else
#define DFT_TIMEOUTUSERVAL 60000
#endif

#if OPT_MSDOS_PATH
#define DFT_BACKUPSTYLE ".bak"
#else
#define DFT_BACKUPSTYLE "off"
#endif

#if SYS_VMS
#define DFT_CSUFFIX "\\.\\(\\([CHIS]\\)\\|CC\\|CXX\\|HXX\\)\\(;[0-9]*\\)\\?$"
#endif
#if SYS_MSDOS
#define DFT_CSUFFIX "\\.\\(\\([chis]\\)\\|cc\\|cpp\\|cxx\\|hxx\\)$"
#endif
#ifndef DFT_CSUFFIX		/* UNIX or OS2/HPFS (mixed-case names) */
#define DFT_CSUFFIX "\\.\\(\\([CchisS]\\)\\|CC\\|cc\\|cpp\\|cxx\\|hxx\\|scm\\)$"
#endif

#define DFT_FENCE_CHARS "{}()[]"
#define DFT_CINDENT_CHARS ":#" DFT_FENCE_CHARS

/*
 * For the given class/mode, initialize the VAL struct to the default value
 * for that mode.
 */
void
init_mode_value(struct VAL *d, MODECLASS v_class, int v_which)
{
    static const char expand_chars[] =
    {EXPC_THIS, EXPC_THAT, EXPC_SHELL, EXPC_TOKEN, EXPC_RPAT, 0};

    switch (v_class) {
    case UNI_MODE:
	switch (v_which) {
	    setINT(GMDABUFF, TRUE);	/* auto-buffer */
	    setINT(GMDALTTABPOS, FALSE);	/* emacs-style tab positioning */
	    setINT(GMDERRORBELLS, TRUE);	/* alarms are noticeable */
	    setINT(GMDEXPAND_PATH, FALSE);
	    setINT(GMDIMPLYBUFF, FALSE);	/* imply-buffer */
	    setINT(GMDMULTIBEEP, TRUE);		/* multiple beeps for multiple motion failures */
	    setINT(GMDREMAP, TRUE);	/* allow remapping by default */
	    setINT(GMDRONLYRONLY, FALSE);	/* Set readonly-on-readonly */
	    setINT(GMDRONLYVIEW, FALSE);	/* Set view-on-readonly */
	    setINT(GMDSMOOTH_SCROLL, FALSE);
	    setINT(GMDSPACESENT, TRUE);		/* add two spaces after each sentence */
	    setINT(GMDWARNRENAME, TRUE);	/* warn before renaming a buffer */
	    setINT(GMDWARNREREAD, TRUE);	/* warn before rereading a buffer */
	    setINT(GMDWARNUNREAD, TRUE);	/* warn if quitting without looking at all buffers */
	    setINT(GVAL_MAPLENGTH, 1200);
	    setINT(GVAL_MINI_HILITE, VAREV);	/* reverse hilite */
	    setINT(GVAL_PRINT_HIGH, 0);
	    setINT(GVAL_PRINT_LOW, 0);
	    setINT(GVAL_REPORT, 5);	/* report changes */
	    setINT(GVAL_TIMEOUTUSERVAL, 30000);		/* how long to wait for user seq */
	    setINT(GVAL_TIMEOUTVAL, 500);	/* how long to wait for ESC seq */
	    setTXT(GVAL_EXPAND_CHARS, expand_chars);
#ifdef GMDALL_VERSIONS
	    setINT(GMDALL_VERSIONS, FALSE);
#endif
#ifdef GMDCD_ON_OPEN
	    setINT(GMDCD_ON_OPEN, cd_on_open);
#endif
#ifdef GMDDIRC
	    setINT(GMDDIRC, FALSE);	/* directory-completion */
#endif
#ifdef GMDFLASH
	    setINT(GMDFLASH, FALSE);	/* beeps beep by default */
#endif
#ifdef FORCE_CONSOLE_RESURRECTED
# ifdef GMDFORCE_CONSOLE	/* deprecated (unused) */
	    setINT(GMDFORCE_CONSOLE, is_win95());
# endif
#endif
#ifdef GMDGLOB
	    setINT(GMDGLOB, TRUE);
#endif
#ifdef GMDHEAPSIZE
	    setINT(GMDHEAPSIZE, TRUE);	/* show heap usage */
#endif
#ifdef GMDHISTORY
	    setINT(GMDHISTORY, TRUE);
#endif
#ifdef GMDPOPUP_CHOICES
	    setINT(GMDPOPUP_CHOICES, TRUE);
#endif
#ifdef GMDPOPUPMENU
	    setINT(GMDPOPUPMENU, TRUE);		/* enable popup menu */
#endif
#ifdef GMDPOPUP_MSGS
	    setINT(GMDPOPUP_MSGS, -TRUE);	/* popup-msgs */
#endif
#ifdef GMDRESOLVE_LINKS
	    setINT(GMDRESOLVE_LINKS, FALSE);	/* set noresolve-links by default in case we've got NFS problems */
#endif
#ifdef GMDSWAPTITLE
	    setINT(GMDSWAPTITLE, FALSE);
#endif
#ifdef GMDUSEFILELOCK
	    setINT(GMDUSEFILELOCK, FALSE);	/* Use filelocks */
#endif
#ifdef GMDW32PIPES
	    setINT(GMDW32PIPES, is_winnt());	/* use native pipes?  */
#endif
#ifdef GMDWARNBLANKS
	    setINT(GMDWARNBLANKS, FALSE);	/* if filenames have blanks */
#endif
#ifdef GMDWORKING
	    setINT(GMDWORKING, TRUE);	/* we put up "working..." */
#endif
#ifdef GMDXTERM_MOUSE
	    setINT(GMDXTERM_MOUSE, FALSE);	/* mouse-clicking */
#endif
#ifdef GMDXTERM_TITLE
	    setINT(GMDXTERM_TITLE, FALSE);	/* xterm window-title */
#endif
#ifdef GVAL_BACKUPSTYLE
	    setTXT(GVAL_BACKUPSTYLE, DFT_BACKUPSTYLE);
#endif
#ifdef GVAL_BCOLOR
	    setINT(GVAL_BCOLOR, C_BLACK);	/* background color */
#endif
#ifdef GVAL_CCOLOR
	    setINT(GVAL_CCOLOR, ENUM_UNKNOWN);	/* cursor color */
#endif
#ifdef GVAL_CSUFFIXES
	    setPAT(GVAL_CSUFFIXES, DFT_CSUFFIX);
#endif
#ifdef GVAL_FCOLOR
	    setINT(GVAL_FCOLOR, C_WHITE);	/* foreground color */
#endif
#ifdef GVAL_FINDCFG
	    setTXT(GVAL_FINDCFG, "");
#endif
#ifdef GVAL_GLOB
	    setTXT(GVAL_GLOB, "!echo %s");
#endif
#ifdef GVAL_ICURSOR
	    setTXT(GVAL_ICURSOR, "0");	/* no insertion cursor */
#endif
#ifdef GVAL_MCOLOR
	    setINT(GVAL_MCOLOR, VAREV);		/* show in reverse */
#endif
#ifdef GVAL_POPUP_CHOICES
	    setINT(GVAL_POPUP_CHOICES, POPUP_CHOICES_DELAYED);
#endif
#ifdef GVAL_REDIRECT_KEYS
	    setTXT(GVAL_REDIRECT_KEYS,
		   "F5::S,F10::S,F11::S,F7::F,F5:C:,F9::Y");
#endif
#ifdef GVAL_SCROLLPAUSE
	    setINT(GVAL_SCROLLPAUSE, 0);
#endif
#ifdef GVAL_VTFLASH
	    setINT(GVAL_VTFLASH, VTFLASH_OFF);	/* hardwired flash off */
#endif
	default:
	    setIntValue(d, 0);
	    break;
	}
	break;
    case BUF_MODE:
	switch (v_which) {
	    setINT(MDAIND, FALSE);	/* auto-indent */
	    setINT(MDASAVE, FALSE);	/* auto-save */
	    setINT(MDBACKLIMIT, TRUE);	/* limit backspacing to insert point */
	    setINT(MDCINDENT, FALSE);	/* C-style indent */
	    setINT(MDDOS, CRLF_LINES);	/* on by default on DOS, off others */
	    setINT(MDIGNCASE, FALSE);	/* exact matches */
	    setINT(MDMAGIC, TRUE);	/* magic searches */
	    setINT(MDMETAINSBIND, TRUE);	/* honor meta-bindings when in insert mode */
	    setINT(MDNEWLINE, TRUE);	/* trailing-newline */
	    setINT(MDREADONLY, FALSE);	/* readonly */
	    setINT(MDSHOWMAT, FALSE);	/* show-match */
	    setINT(MDSHOWMODE, TRUE);	/* show-mode */
	    setINT(MDSWRAP, TRUE);	/* scan wrap */
	    setINT(MDTABINSERT, TRUE);	/* allow tab insertion */
	    setINT(MDTAGSRELTIV, FALSE);	/* path relative tag lookups */
	    setINT(MDTERSE, FALSE);	/* terse messaging */
	    setINT(MDUNDOABLE, TRUE);	/* undo stack active */
	    setINT(MDUNDO_DOS_TRIM, FALSE);	/* undo dos trimming */
	    setINT(MDVIEW, FALSE);	/* view-only */
	    setINT(MDWRAP, FALSE);	/* wrap */
	    setINT(MDYANKMOTION, TRUE);		/* yank-motion */
	    setINT(VAL_ASAVECNT, 256);	/* autosave count */
	    setINT(VAL_PERCENT_CRLF, 50);	/* threshold for crlf conversion */
	    setINT(VAL_RECORD_SEP, RS_DEFAULT);
	    setINT(VAL_SHOW_FORMAT, SF_FOREIGN);
	    setINT(VAL_SWIDTH, 8);	/* shiftwidth */
	    setINT(VAL_TAB, 8);	/* tab stop */
	    setINT(VAL_TAGLEN, 0);	/* significant tag length */
	    setINT(VAL_UNDOLIM, 10);	/* undo limit */
	    setPAT(VAL_CMT_PREFIX, DFT_CMT_PREFIX);
	    setPAT(VAL_COMMENTS, DFT_COMMENTS);
	    setPAT(VAL_FENCE_BEGIN, DFT_FENCE_BEGIN);
	    setPAT(VAL_FENCE_ELIF, DFT_FENCE_ELIF);
	    setPAT(VAL_FENCE_ELSE, DFT_FENCE_ELSE);
	    setPAT(VAL_FENCE_END, DFT_FENCE_END);
	    setPAT(VAL_FENCE_FI, DFT_FENCE_FI);
	    setPAT(VAL_FENCE_IF, DFT_FENCE_IF);
	    setPAT(VAL_PARAGRAPHS, DFT_PARAGRAPHS);
	    setPAT(VAL_SECTIONS, DFT_SECTIONS);
	    setPAT(VAL_SENTENCES, DFT_SENTENCES);
	    setTXT(VAL_CINDENT_CHARS, DFT_CINDENT_CHARS);	/* C-style indent flags */
	    setTXT(VAL_FENCES, "{}()[]");	/* fences */
	    setTXT(VAL_TAGS, "tags");	/* tags filename */
#ifdef MDCHK_MODTIME
	    setINT(MDCHK_MODTIME, FALSE);	/* modtime-check */
#endif
#ifdef MDCMOD
	    setINT(MDCMOD, FALSE);	/* C mode */
#endif
#ifdef MDCRYPT
	    setINT(MDCRYPT, FALSE);	/* crypt */
#endif
#ifdef MDHILITE
	    setINT(MDHILITE, TRUE);	/* syntax-highlighting */
#endif
#ifdef MDHILITEOVERLAP
	    setINT(MDHILITEOVERLAP, TRUE);	/* overlap visual-matches */
#endif
#ifdef MDLOCKED
	    setINT(MDLOCKED, FALSE);	/* LOCKED */
#endif
#ifdef MDUPBUFF
	    setINT(MDUPBUFF, TRUE);	/* animated */
#endif
#ifdef VAL_AUTOCOLOR
	    setINT(VAL_AUTOCOLOR, 0);	/* auto syntax coloring timeout */
#endif
#ifdef VAL_HILITEMATCH
	    setINT(VAL_HILITEMATCH, 0);		/* no hilite */
#endif
#ifdef VAL_FENCE_LIMIT
	    setINT(VAL_FENCE_LIMIT, 10);	/* fences iteration timeout */
#endif
#ifdef VAL_LOCKER
	    setTXT(VAL_LOCKER, "");	/* Name locker */
#endif
#ifdef VAL_RECORD_FORMAT
	    setINT(VAL_RECORD_FORMAT, FAB$C_UDF);
#endif
#ifdef VAL_RECORD_LENGTH
	    setINT(VAL_RECORD_LENGTH, 0);
#endif
#ifdef VAL_C_SWIDTH
	    setINT(VAL_C_SWIDTH, 8);	/* C file shiftwidth */
#endif
#ifdef VAL_C_TAB
	    setINT(VAL_C_TAB, 8);	/* C file tab stop */
#endif
	default:
	    setIntValue(d, 0);
	    break;
	}
	break;
    case WIN_MODE:
	switch (v_which) {
	    setINT(WMDHORSCROLL, TRUE);		/* horizontal scrolling */
	    setINT(WMDLIST, FALSE);	/* list-mode */
	    setINT(WMDNUMBER, FALSE);	/* number */
	    setINT(WVAL_SIDEWAYS, 0);	/* list-mode */
#ifdef WMDLINEWRAP
	    setINT(WMDLINEWRAP, FALSE);		/* line-wrap */
#endif
#ifdef WMDTERSELECT
	    setINT(WMDTERSELECT, TRUE);		/* terse selections */
#endif
	default:
	    setIntValue(d, 0);
	    break;
	}
	break;
    default:
	setIntValue(d, 0);
	break;
    }
}

	/*
	 * The $filename-expr expression is used with a trailing '*' to repeat
	 * the last field.  Otherwise it is complete.
	 *
	 * Limitations - the expressions are used in the error finder, which
	 * has to choose the first pattern which matches a filename embedded
	 * in other text.  UNIX-style pathnames could have embedded blanks,
	 * but it's not common.  It is common on win32, but is not often a
	 * problem with grep, etc., since the full pathname of the file is not
	 * usually given.
	 */
#if OPT_MSDOS_PATH
#define DFT_FILENAME_EXPR "\\([a-zA-Z]:\\)\\?[^ \t\":*?<>|]"
#else
#if OPT_VMS_PATH
#define DFT_FILENAME_EXPR "[-/\\w.;\\[\\]<>$:]"
#else /* UNIX-style */
#define DFT_FILENAME_EXPR "[-/\\w.~]"
#endif
#endif

#define DFT_HELP_FILE "vile.hlp"

#define DFT_MENU_FILE "vilemenu.rc"

#ifdef VILE_LIBDIR_PATH
#define DFT_LIBDIR_PATH VILE_LIBDIR_PATH
#else
#define DFT_LIBDIR_PATH ""
#endif

#if SYS_MSDOS || SYS_OS2 || SYS_WINNT || SYS_VMS
#define DFT_STARTUP_FILE "vile.rc"
#else /* SYS_UNIX */
#define DFT_STARTUP_FILE ".vilerc"
#endif

#define DFT_MLFORMAT \
"%-%i%- %b %m:: :%f:is : :%=%F: : :%l:(:,:%c::) :%p::% :%S%-%-%|"

#define DFT_POSFORMAT \
"Line %{$curline}d of %{$blines}d,\
 Col %{$curcol}d of %{$lcols}d,\
 Char %{$curchar}d of %{$bchars}d\
 (%P%%) char is 0x%{$char}x or 0%{$char}o"

static char *
default_help_file(void)
{
    char *result;
    if ((result = getenv("VILE_HELP_FILE")) == 0)
	result = DFT_HELP_FILE;
    return result;
}

static char *
default_libdir_path(void)
{
    char *result;
    if ((result = getenv("VILE_LIBDIR_PATH")) == 0)
	result = DFT_LIBDIR_PATH;
    return result;
}

#if OPT_MENUS
static char *
default_menu_file(void)
{
    static char default_menu[] = DFT_MENU_FILE;
    char temp[NSTRING];
    char *menurc = getenv("VILE_MENU");

    if (menurc == NULL || *menurc == EOS) {
	sprintf(temp, "%.*s_MENU", (int) (sizeof(temp) - 6), prognam);
	mkupper(temp);
	menurc = getenv(temp);
	if (menurc == NULL || *menurc == EOS) {
	    menurc = default_menu;
	}
    }

    return menurc;
}
#endif

static char *
default_startup_file(void)
{
    char *result;
    if ((result = getenv("VILE_STARTUP_FILE")) == 0)
	result = DFT_STARTUP_FILE;
    return result;
}

static char *
default_startup_path(void)
{
    char *result = 0;
    char *s;

    if ((s = getenv("VILE_STARTUP_PATH")) != 0) {
	append_to_path_list(&result, s);
    } else {
#if SYS_MSDOS || SYS_OS2 || SYS_WINNT
	append_to_path_list(&result, "/sys/public");
	append_to_path_list(&result, "/usr/bin");
	append_to_path_list(&result, "/bin");
	append_to_path_list(&result, "/");
#else
#if SYS_VMS
	append_to_path_list(&result, "sys$login");
	append_to_path_list(&result, "sys$sysdevice:[vmstools]");
	append_to_path_list(&result, "sys$library");
#else /* SYS_UNIX */
#if defined(VILE_STARTUP_PATH)
	append_to_path_list(&result, VILE_STARTUP_PATH);
#else
	append_to_path_list(&result, "/usr/local/lib");
	append_to_path_list(&result, "/usr/local");
	append_to_path_list(&result, "/usr/lib");
#endif /* VILE_STARTUP_PATH */
#endif /* SYS_VMS... */
#endif /* SYS_MSDOS... */
    }
#ifdef HELP_LOC
    append_to_path_list(&result, HELP_LOC);
#endif
    return result;
}

#if OPT_EVAL
/*
 * Returns a string representing the default/initial value of the given
 * state-variable.
 */
char *
init_state_value(int which)
{
    char *result = 0;
    int allocated = FALSE;

    switch (which) {
#if OPT_FINDERR
    case VAR_FILENAME_EXPR:
	result = DFT_FILENAME_EXPR;
	break;
#endif
    case VAR_HELPFILE:
	result = default_help_file();
	break;
    case VAR_LIBDIR_PATH:
	result = default_libdir_path();
	break;
#if OPT_MENUS
    case VAR_MENU_FILE:
	result = default_menu_file();
	break;
#endif
#if OPT_MLFORMAT
    case VAR_MLFORMAT:
	result = DFT_MLFORMAT;
	break;
#endif
#if OPT_POSFORMAT
    case VAR_POSFORMAT:
	result = DFT_POSFORMAT;
	break;
#endif
    case VAR_PROMPT:
	result = ": ";
	break;
    case VAR_REPLACE:
	result = "";
	break;
    case VAR_STARTUP_FILE:
	result = default_startup_file();
	break;
    case VAR_STARTUP_PATH:
	result = default_startup_path();
	allocated = TRUE;
	break;
    }
    return (result != 0 && !allocated) ? strmalloc(result) : result;
}
#endif /* OPT_EVAL */

#if OPT_REBIND
/*
 * Cache the length of kbindtbl[].
 */
static size_t
len_kbindtbl(void)
{
    static size_t result = 0;
    if (result == 0)
	while (kbindtbl[result].k_cmd != 0)
	    result++;
    return result + 1;
}

/*
 * At startup, construct copies of kbindtbl[] for the alternate bindings, so
 * we can have separate bindings of special keys from the default bindings.
 */
static void
copy_kbindtbl(BINDINGS * dst)
{
    KBIND *result;
    size_t len = len_kbindtbl();

    if (dst->kb_special == kbindtbl) {
	if ((result = typecallocn(KBIND, len)) == 0) {
	    (void) no_memory("copy_kbindtbl");
	    return;
	}
	dst->kb_special = dst->kb_extra = result;
    } else {
	result = dst->kb_special;
    }
    while (len-- != 0) {
	result[len] = kbindtbl[len];
    }
}
#endif

#if OPT_FILTER && defined(WIN32)
extern void flt_array(void);
#endif

static void
global_val_init(void)
{
    int i;

    TRACE((T_CALLED "global_val_init()\n"));
#if OPT_FILTER && defined(WIN32)
    flt_array();
#endif
    /* set up so the global value pointers point at the global
       values.  we never actually use the global pointers
       directly, but when buffers get a copy of the
       global_b_values structure, the pointers will still point
       back at the global values, which is what we want */
    for (i = 0; i <= NUM_G_VALUES; i++) {
	make_local_val(global_g_values.gv, i);
	init_mode_value(&global_g_values.gv[i], UNI_MODE, i);
    }

    for (i = 0; i <= NUM_B_VALUES; i++) {
	make_local_val(global_b_values.bv, i);
	init_mode_value(&global_b_values.bv[i], BUF_MODE, i);
    }

    for (i = 0; i <= NUM_W_VALUES; i++) {
	make_local_val(global_w_values.wv, i);
	init_mode_value(&global_w_values.wv[i], WIN_MODE, i);
    }

#if OPT_MAJORMODE
    /*
     * Built-in majormodes (see 'modetbl')
     */
    alloc_mode("c", TRUE);
    alloc_mode("vile", TRUE);

    set_submode_val("c", VAL_SWIDTH, 8);	/* C file shiftwidth */
    set_submode_val("c", VAL_TAB, 8);	/* C file tab stop */
    set_submode_val("c", MDCINDENT, TRUE);	/* C-style indent */
    set_submode_txt("c", VAL_CINDENT_CHARS, DFT_CINDENT_CHARS);		/* C-style indent flags */

    set_majormode_rexp("c", MVAL_SUFFIXES, DFT_CSUFFIX);
#endif

#if OPT_EVAL
    /*
     * Note that not all state variables can be initialized explicitly, many
     * are either read-only, or have too many side-effects to set blindly.
     */
    for (i = 0; i < Num_StateVars; i++) {
	char *s;
	if ((s = init_state_value(i)) != 0) {
	    set_state_variable(statevars[i], s);
	    free(s);
	}
    }
#else
    helpfile = default_help_file();
    startup_file = strmalloc(default_startup_file());
    startup_path = default_startup_path();
    libdir_path = default_libdir_path();
#if OPT_MENUS
    menu_file = default_menu_file();
#endif
#if OPT_MLFORMAT
    modeline_format = strmalloc(DFT_MLFORMAT);
#endif
#if OPT_POSFORMAT
    position_format = strmalloc(DFT_POSFORMAT);
#endif
#if OPT_FINDERR
    filename_expr = strmalloc(DFT_FILENAME_EXPR);
#endif
#endif

    /*
     * Initialize the "normal" bindings for insert and command mode to
     * the motion-commands that are safe if insert-exec mode is enabled.
     */
#if OPT_REBIND
    copy_kbindtbl(&sel_bindings);
    copy_kbindtbl(&ins_bindings);
    copy_kbindtbl(&cmd_bindings);
#endif
    for (i = 0; i < N_chars; i++) {
	const CMDFUNC *cfp;

	sel_bindings.kb_normal[i] = dft_bindings.kb_normal[i];
	ins_bindings.kb_normal[i] = 0;
	cmd_bindings.kb_normal[i] = dft_bindings.kb_normal[i];
	if (i < 32
	    && (cfp = dft_bindings.kb_normal[i]) != 0
	    && (cfp->c_flags & (GOAL | MOTION)) != 0) {
	    ins_bindings.kb_normal[i] =
		cmd_bindings.kb_normal[i] = cfp;
	}
    }
    returnVoid();
}

#if SYS_UNIX || SYS_MSDOS || SYS_OS2 || SYS_WINNT || SYS_VMS

/* ARGSUSED */
SIGT
catchintr(int ACTUAL_SIG_ARGS)
{
    am_interrupted = TRUE;
#if SYS_MSDOS || SYS_OS2 || SYS_WINNT
    sgarbf = TRUE;		/* there's probably a ^C on the screen. */
#endif
    setup_handler(SIGINT, catchintr);
    if (im_waiting(-1))
	longjmp(read_jmp_buf, signo);
    SIGRET;
}
#endif

#ifndef interrupted		/* i.e. unless it's a macro */
int
interrupted(void)
{
#if SYS_MSDOS && CC_DJGPP

    if (_go32_was_ctrl_break_hit() != 0) {
	while (keystroke_avail())
	    (void) keystroke();
	return TRUE;
    }
    if (was_ctrl_c_hit() != 0) {
	while (keystroke_avail())
	    (void) keystroke();
	return TRUE;
    }

    if (am_interrupted)
	return TRUE;
    return FALSE;
#endif
}
#endif

void
not_interrupted(void)
{
    am_interrupted = FALSE;
#if SYS_MSDOS
# if CC_DJGPP
    (void) _go32_was_ctrl_break_hit();	/* flush any pending kbd ctrl-breaks */
    (void) was_ctrl_c_hit();	/* flush any pending kbd ctrl-breaks */
# endif
#endif
}

#if SYS_MSDOS
# if CC_WATCOM
int
wat_crit_handler(unsigned deverror, unsigned errcode, unsigned *devhdr)
{
    _hardresume((int) _HARDERR_FAIL);
    return (int) _HARDERR_FAIL;
}

#else
void
dos_crit_handler(void)
{
#  if ! CC_DJGPP
    _hardresume(_HARDERR_FAIL);
#  endif
}

#  endif
#endif

/*
 * Setup signal handlers, if 'enabled'.  Otherwise reset to default all of the
 * ones that pointed to imdying().  The latter is used only in ExitProgram() to
 * ensure that abort() can really generate a core dump.
 */
static void
siginit(int enabled)
{
    /* *INDENT-OFF* */
    static const struct {
	int signo;
	void (*handler) (int ACTUAL_SIG_ARGS);
    } table[] = {
	{ 0, 0 },		/* just for VMS */
#if SYS_UNIX || SYS_MSDOS || SYS_OS2 || SYS_WINNT
#if defined(SIGINT)
	{ SIGINT, catchintr },
#endif
#endif
#if SYS_UNIX
#if defined(SIGHUP)
	{ SIGHUP, imdying },
#endif
#if defined(SIGBUS)
	{ SIGBUS, imdying },
#endif
#if defined(SIGILL)
	{ SIGILL, imdying },
#endif
#if defined(SIGFPE)
	{ SIGFPE, imdying },
#endif
#if defined(SIGSYS)
	{ SIGSYS, imdying },
#endif
	{ SIGSEGV, imdying },
	{ SIGTERM, imdying },
#ifdef VILE_DEBUG
	{ SIGQUIT, imdying },
#else
	{ SIGQUIT, SIG_IGN },
#endif
	{ SIGPIPE, SIG_IGN },
#if defined(SIGWINCH) && ! DISP_X11
	{ SIGWINCH, sizesignal },
#endif
#endif /* SYS_UNIX */
    };
    /* *INDENT-ON* */

    unsigned n;

    for (n = 1; n < TABLESIZE(table); ++n) {
	if (enabled)
	    setup_handler(table[n].signo, table[n].handler);
	else if (table[n].handler == imdying)
	    setup_handler(table[n].signo, SIG_DFL);
    }

#if SYS_MSDOS
# if CC_DJGPP
    _go32_want_ctrl_break(TRUE);
    setcbrk(FALSE);
    want_ctrl_c(TRUE);
    hard_error_catch_setup();
# else
#  if CC_WATCOM
    {
	/* clean up Warning from Watcom C */
	void *ptrfunc = wat_crit_handler;
	_harderr(ptrfunc);
    }
#  else				/* CC_TURBO */
    _harderr(dos_crit_handler);
#  endif
# endif
#endif
}

static void
siguninit(void)
{
#if SYS_MSDOS
# if CC_DJGPP
    _go32_want_ctrl_break(FALSE);
    want_ctrl_c(FALSE);
    hard_error_teardown();
    setcbrk(TRUE);
# endif
#endif
}

/* do number processing if needed */
static void
do_num_proc(int *cp, int *fp, int *np)
{
    register int c, f, n;
    register int mflag;
    register int oldn;

    c = *cp;

    if (isCntrl(c) || isSpecial(c))
	return;

    f = *fp;
    n = *np;
    if (f)
	oldn = n;
    else
	oldn = 1;
    n = 1;

    if (isDigit(c) && c != '0') {
	n = 0;			/* start with a zero default */
	f = TRUE;		/* there is a # arg */
	mflag = 1;		/* current minus flag */
	while ((isDigit(c) && !isSpecial(c)) || (c == '-')) {
	    if (c == '-') {
		/* already hit a minus or digit? */
		if ((mflag == -1) || (n != 0))
		    break;
		mflag = -1;
	    } else {
		n = n * 10 + (c - '0');
	    }
	    if ((n == 0) && (mflag == -1))	/* lonely - */
		mlwrite("arg:");
	    else
		mlwrite("arg: %d", n * mflag);

	    c = kbd_seq();	/* get the next key */
	}
	n = n * mflag;		/* figure in the sign */
    }
    *cp = c;
    *fp = f;
    *np = n * oldn;
}

/* do emacs ^U-style repeat argument processing -- vile binds this to 'K' */
static void
do_rept_arg_proc(int *cp, int *fp, int *np)
{
    register int c, f, n;
    register int mflag;
    register int oldn;
    c = *cp;

    if (c != reptc)
	return;

    f = *fp;
    n = *np;

    if (f)
	oldn = n;
    else
	oldn = 1;

    n = 4;			/* start with a 4 */
    f = TRUE;			/* there is a # arg */
    mflag = 0;			/* that can be discarded. */
    mlwrite("arg: %d", n);
    while ((isDigit(c = kbd_seq()) && !isSpecial(c))
	   || c == reptc || c == '-') {
	if (c == reptc)
	    n = n * 4;

	/*
	 * If dash, and start of argument string, set arg.
	 * to -1.  Otherwise, insert it.
	 */
	else if (c == '-') {
	    if (mflag)
		break;
	    n = 0;
	    mflag = -1;
	}
	/*
	 * If first digit entered, replace previous argument
	 * with digit and set sign.  Otherwise, append to arg.
	 */
	else {
	    if (!mflag) {
		n = 0;
		mflag = 1;
	    }
	    n = 10 * n + c - '0';
	}
	mlwrite("arg: %d", (mflag >= 0) ? n : (n ? -n : -1));
    }
    /*
     * Make arguments preceded by a minus sign negative and change
     * the special argument "^U -" to an effective "^U -1".
     */
    if (mflag == -1) {
	if (n == 0)
	    n++;
	n = -n;
    }

    *cp = c;
    *fp = f;
    *np = n * oldn;
}

/* handle all repeat counts */
void
do_repeats(int *cp, int *fp, int *np)
{
    do_num_proc(cp, fp, np);
    do_rept_arg_proc(cp, fp, np);
    if (dotcmdactive == PLAY) {
	if (dotcmdarg)		/* then repeats are done by dotcmdcnt */
	    *np = 1;
    } else {
	/* then we want to cancel any dotcmdcnt repeats */
	if (*fp)
	    dotcmdarg = FALSE;
    }
}

/* the vi ZZ command -- write all, then quit */
int
zzquit(int f, int n)
{
    int thiscmd;
    int cnt;
    BUFFER *bp;

    thiscmd = lastcmd;
    cnt = any_changed_buf(&bp);
    if (cnt) {
	if (cnt > 1) {
	    mlprompt("Will write %d buffers.  %s ", cnt,
		     clexec ? s_NULL : "Repeat command to continue.");
	} else {
	    mlprompt("Will write buffer \"%s\".  %s ",
		     bp->b_bname,
		     clexec ? s_NULL : "Repeat command to continue.");
	}
	if (!clexec && !isnamedcmd) {
	    if (thiscmd != kbd_seq())
		return FALSE;
	}

	if (writeall(f, n, FALSE, TRUE, FALSE, FALSE) != TRUE) {
	    return FALSE;
	}

    } else if (!clexec && !isnamedcmd) {
	/* consume the next char. anyway */
	if (thiscmd != kbd_seq())
	    return FALSE;
    }
    return quit(f, n);
}

/*
 * attempt to write all changed buffers, and quit
 */
int
quickexit(int f, int n)
{
    register int status;
    if ((status = writeall(f, n, FALSE, TRUE, FALSE, FALSE)) == TRUE)
	status = quithard(f, n);	/* conditionally quit */
    return status;
}

/* Force quit by giving argument */
/* ARGSUSED */
int
quithard(int f GCC_UNUSED, int n GCC_UNUSED)
{
    return quit(TRUE, 1);
}

/*
 * Quit command. If an argument, always quit. Otherwise confirm if a buffer
 * has been changed and not written out.
 */
/* ARGSUSED */
int
quit(int f, int n GCC_UNUSED)
{
    int cnt;
    BUFFER *bp;
    const char *sadj, *sverb;

    run_a_hook(&exithook);

    if (f == FALSE) {
	cnt = any_changed_buf(&bp);
	sadj = "modified";
	sverb = "Write";
	if (cnt == 0 && global_g_val(GMDWARNUNREAD)) {
	    cnt = any_unread_buf(&bp);
	    sadj = "unread";
	    sverb = "Look at";
	}
	if (cnt != 0) {
	    if (cnt == 1)
		mlforce(
			   "Buffer \"%s\" is %s.  %s it, or use :q!",
			   bp->b_bname, sadj, sverb);
	    else
		mlforce(
			   "There are %d %s buffers.  %s them, or use :q!",
			   cnt, sadj, sverb);
	    return FALSE;
	}
    }
#if OPT_LCKFILES
    /* Release all placed locks */
    if (global_g_val(GMDUSEFILELOCK)) {
	for_each_buffer(bp) {
	    if (bp->b_active) {
		if (!b_val(curbp, MDLOCKED) &&
		    !b_val(curbp, MDVIEW))
		    release_lock(bp->b_fname);
	    }
	}
    }
#endif
    siguninit();
#if OPT_WORKING
    setup_handler(SIGALRM, SIG_IGN);
#endif
#if NO_LEAKS
    {
	beginDisplay();		/* ...this may take a while... */

	/* free all of the global data structures */
	onel_leaks();
	path_leaks();
	kbs_leaks();
	bind_leaks();
	map_leaks();
	tags_leaks();
	wp_leaks();
	bp_leaks();
	vt_leaks();
#if !SMALLER
	ev_leaks();
#endif
	mode_leaks();
	vars_leaks();
	fileio_leaks();
#if DISP_X11
	x11_leaks();
#endif

	free_local_vals(g_valnames, global_g_values.gv, global_g_values.gv);
	free_local_vals(b_valnames, global_b_values.bv, global_b_values.bv);
	free_local_vals(w_valnames, global_w_values.wv, global_w_values.wv);

	FreeAndNull(libdir_path);
	FreeAndNull(startup_path);
	FreeAndNull(gregexp);
	tb_free(&tb_matched_pat);
#if OPT_MLFORMAT
	FreeAndNull(modeline_format);
#endif
#if OPT_POSFORMAT
	FreeAndNull(position_format);
#endif
	FreeAndNull(helpfile);
	FreeAndNull(startup_file);

#if SYS_UNIX
	if (strcmp(exec_pathname, "."))
	    FreeAndNull(exec_pathname);
#endif
#if OPT_FILTER
	flt_leaks();
	filters_leaks();
#endif
	/* do these last, e.g., for tb_matched_pat */
	itb_leaks();
	tb_leaks();

	term.close();
	/* whatever is left over must be a leak */
	show_alloc();
	trace_leaks();
    }
#endif
    tidy_exit(GOODEXIT);
    /* NOTREACHED */
    return FALSE;
}

/* ARGSUSED */
int
writequit(int f GCC_UNUSED, int n)
{
    int s;
    s = filesave(FALSE, n);
    if (s != TRUE)
	return s;
    return quit(FALSE, n);
}

/*
 * Abort.
 * Beep the beeper. Kill off any keyboard macro, etc., that is in progress.
 * Sometimes called as a routine, to do general aborting of stuff.
 */
/* ARGSUSED */
int
esc_func(int f GCC_UNUSED, int n GCC_UNUSED)
{
    dotcmdactive = 0;
    regionshape = EXACT;
    doingopcmd = FALSE;
    do_sweep(FALSE);
    sweephack = FALSE;
    opcmd = 0;
    mlwarn("[Aborted]");
    return ABORT;
}

/* tell the user that this command is illegal while we are in
   VIEW (read-only) mode				*/

int
rdonly(void)
{
    mlwarn("[No changes are allowed while in \"view\" mode]");
    return FALSE;
}

/* ARGSUSED */
int
unimpl(int f GCC_UNUSED, int n GCC_UNUSED)
{
    mlwarn("[Sorry, that vi command is unimplemented in vile ]");
    return FALSE;
}

int
opercopy(int f, int n)
{
    return unimpl(f, n);
}

int
opermove(int f, int n)
{
    return unimpl(f, n);
}

int
opertransf(int f, int n)
{
    return unimpl(f, n);
}

int
operglobals(int f, int n)
{
    return unimpl(f, n);
}

int
opervglobals(int f, int n)
{
    return unimpl(f, n);
}

int
vl_source(int f, int n)
{
    return unimpl(f, n);
}

int
visual(int f, int n)
{
    return unimpl(f, n);
}

int
ex(int f, int n)
{
    return unimpl(f, n);
}

/* ARGSUSED */
/* user function that only returns true */
int
nullproc(int f GCC_UNUSED, int n GCC_UNUSED)
{
    return TRUE;
}

/* dummy functions for binding to key sequence prefixes */
/* ARGSUSED */
int
cntl_a_func(int f GCC_UNUSED, int n GCC_UNUSED)
{
    return TRUE;
}

/* ARGSUSED */
int
cntl_x_func(int f GCC_UNUSED, int n GCC_UNUSED)
{
    return TRUE;
}

/* ARGSUSED */
int
poundc_func(int f GCC_UNUSED, int n GCC_UNUSED)
{
    return TRUE;
}

/* ARGSUSED */
int
reptc_func(int f GCC_UNUSED, int n GCC_UNUSED)
{
    return TRUE;
}

/*----------------------------------------------------------------------------*/

/* initialize our version of the "chartypes" stuff normally in ctypes.h */
/* also called later, if charset-affecting modes change, for instance */
void
charinit(void)
{
    register int c;

    TRACE((T_CALLED "charinit() lo=%d, hi=%d\n",
	   global_g_val(GVAL_PRINT_LOW),
	   global_g_val(GVAL_PRINT_HIGH)));

    /* If we're using the locale functions, set our flags based on its
     * tables.  Note that just because you have 'setlocale()' doesn't mean
     * that the tables are present or correct.  But this is a start.
     */
#if OPT_LOCALE
    for (c = 0; c < N_chars; c++) {
	vlCTYPE(c) = 0;
	if (sys_iscntrl(c))
	    vlCTYPE(c) |= vl_cntrl;
	if (sys_isdigit(c))
	    vlCTYPE(c) |= vl_digit;
	if (sys_islower(c))
	    vlCTYPE(c) |= vl_lower;
	if (sys_isprint(c))
	    vlCTYPE(c) |= vl_print;
	if (sys_ispunct(c))
	    vlCTYPE(c) |= vl_punct;
	if (sys_isspace(c))
	    vlCTYPE(c) |= vl_space;
	if (sys_isupper(c))
	    vlCTYPE(c) |= vl_upper;
#ifdef vl_xdigit
	if (sys_isxdigit(c))
	    vlCTYPE(c) |= vl_xdigit;
#endif
	vl_uppercase[c] = toupper(c);
	vl_lowercase[c] = tolower(c);
    }
#else /* ! OPT_LOCALE */
    (void) memset((char *) vl_chartypes_, 0, sizeof(vl_chartypes_));

    /* control characters */
    for (c = 0; c < ' '; c++)
	vlCTYPE(c) |= vl_cntrl;
    vlCTYPE(127) |= vl_cntrl;

    /* lowercase */
    for (c = 'a'; c <= 'z'; c++)
	vlCTYPE(c) |= vl_lower;
#if OPT_ISO_8859
    for (c = 0xc0; c <= 0xd6; c++)
	vlCTYPE(c) |= vl_lower;
    for (c = 0xd8; c <= 0xde; c++)
	vlCTYPE(c) |= vl_lower;
#endif
    /* uppercase */
    for (c = 'A'; c <= 'Z'; c++)
	vlCTYPE(c) |= vl_upper;
#if OPT_ISO_8859
    for (c = 0xdf; c <= 0xf6; c++)
	vlCTYPE(c) |= vl_upper;
    for (c = 0xf8; c <= 0xff; c++)
	vlCTYPE(c) |= vl_upper;
#endif

    /*
     * If you want to do this properly, compile-in locale support.
     */
    for (c = 0; c < N_chars; c++) {
	vl_uppercase[c] = (char) c;
	vl_lowercase[c] = (char) c;
	if (isAlpha(c)) {
	    if (isUpper(c)) {
		vl_lowercase[c] = (char) (c ^ DIFCASE);
	    } else {
		vl_uppercase[c] = (char) (c ^ DIFCASE);
	    }
	}
    }

    /* digits */
    for (c = '0'; c <= '9'; c++)
	vlCTYPE(c) |= vl_digit;
#ifdef vl_xdigit
    /* hex digits */
    for (c = '0'; c <= '9'; c++)
	vlCTYPE(c) |= vl_xdigit;
    for (c = 'a'; c <= 'f'; c++)
	vlCTYPE(c) |= vl_xdigit;
    for (c = 'A'; c <= 'F'; c++)
	vlCTYPE(c) |= vl_xdigit;
#endif

    /* punctuation */
    for (c = '!'; c <= '/'; c++)
	vlCTYPE(c) |= vl_punct;
    for (c = ':'; c <= '@'; c++)
	vlCTYPE(c) |= vl_punct;
    for (c = '['; c <= '`'; c++)
	vlCTYPE(c) |= vl_punct;
    for (c = L_CURLY; c <= '~'; c++)
	vlCTYPE(c) |= vl_punct;
#if OPT_ISO_8859
    for (c = 0xa1; c <= 0xbf; c++)
	vlCTYPE(c) |= vl_punct;
#endif

    /* printable */
    for (c = ' '; c <= '~'; c++)
	vlCTYPE(c) |= vl_print;

    /* whitespace */
    vlCTYPE(' ') |= vl_space;
#if OPT_ISO_8859
    vlCTYPE(0xa0) |= vl_space;
#endif
    vlCTYPE('\t') |= vl_space;
    vlCTYPE('\r') |= vl_space;
    vlCTYPE('\n') |= vl_space;
    vlCTYPE('\f') |= vl_space;

#endif /* OPT_LOCALE */

    /* legal in pathnames */
    vlCTYPE('.') |= vl_pathn;
    vlCTYPE('_') |= vl_pathn;
    vlCTYPE('~') |= vl_pathn;
    vlCTYPE('-') |= vl_pathn;
    vlCTYPE('*') |= vl_pathn;
    vlCTYPE('/') |= vl_pathn;

    /* legal in "identifiers" */
    vlCTYPE('_') |= vl_ident | vl_qident;
    vlCTYPE(':') |= vl_qident;
#if SYS_VMS
    vlCTYPE('$') |= vl_ident | vl_qident;
#endif

    c = global_g_val(GVAL_PRINT_LOW);

    /*
     * Guard against setting printing-high before printing-low while we have a
     * buffer which may be repainted and possibly trashing the display.
     */
    if (c == 0
	&& global_g_val(GVAL_PRINT_HIGH) >= 254)
	c = 160;

    if (c < HIGHBIT)
	c = HIGHBIT;
    while (c <= global_g_val(GVAL_PRINT_HIGH) && c < N_chars)
	vlCTYPE(c++) |= vl_print;

#if DISP_X11
    for (c = 0; c < N_chars; c++) {
	if (isPrint(c) && !gui_isprint(c)) {
	    vlCTYPE(c) &= ~vl_print;
	}
    }
#endif
    /* backspacers: ^H, rubout */
    vlCTYPE('\b') |= vl_bspace;
    vlCTYPE(127) |= vl_bspace;

    /* wildcard chars for most shells */
    vlCTYPE('*') |= vl_wild;
    vlCTYPE('?') |= vl_wild;
#if !OPT_VMS_PATH
#if SYS_UNIX
    vlCTYPE('~') |= vl_wild;
#endif
    vlCTYPE(L_BLOCK) |= vl_wild;
    vlCTYPE(R_BLOCK) |= vl_wild;
    vlCTYPE(L_CURLY) |= vl_wild;
    vlCTYPE(R_CURLY) |= vl_wild;
    vlCTYPE('$') |= vl_wild;
    vlCTYPE('`') |= vl_wild;
#endif

    /* ex mode line specifiers */
    vlCTYPE(',') |= vl_linespec;
    vlCTYPE('%') |= vl_linespec;
    vlCTYPE('-') |= vl_linespec;
    vlCTYPE('+') |= vl_linespec;
    vlCTYPE(';') |= vl_linespec;
    vlCTYPE('.') |= vl_linespec;
    vlCTYPE('$') |= vl_linespec;
    vlCTYPE('\'') |= vl_linespec;

    /* fences */
    vlCTYPE(L_CURLY) |= vl_fence;
    vlCTYPE(R_CURLY) |= vl_fence;
    vlCTYPE(L_PAREN) |= vl_fence;
    vlCTYPE(R_PAREN) |= vl_fence;
    vlCTYPE(L_BLOCK) |= vl_fence;
    vlCTYPE(R_BLOCK) |= vl_fence;

#if OPT_VMS_PATH
    vlCTYPE(L_BLOCK) |= vl_pathn;	/* actually, "<", ">" too */
    vlCTYPE(R_BLOCK) |= vl_pathn;
    vlCTYPE('$') |= vl_pathn;
    vlCTYPE(':') |= vl_pathn;
    vlCTYPE(';') |= vl_pathn;
#endif

#if OPT_MSDOS_PATH
    vlCTYPE(BACKSLASH) |= vl_pathn;
    vlCTYPE(':') |= vl_pathn;
#endif

#if OPT_WIDE_CTYPES
    /* scratch-buffer-names (usually superset of vl_pathn) */
    vlCTYPE(SCRTCH_LEFT[0]) |= vl_scrtch;
    vlCTYPE(SCRTCH_RIGHT[0]) |= vl_scrtch;
    vlCTYPE(' ') |= vl_scrtch;	/* ...to handle "[Buffer List]" */
#endif

    for (c = 0; c < N_chars; c++) {
	if (!(isSpace(c)))
	    vlCTYPE(c) |= vl_nonspace;
	if (isDigit(c))
	    vlCTYPE(c) |= vl_linespec;
	if (isAlpha(c) || isDigit(c))
	    vlCTYPE(c) |= vl_ident | vl_pathn | vl_qident;
#if OPT_WIDE_CTYPES
	if (isSpace(c) || isPrint(c))
	    vlCTYPE(c) |= vl_shpipe;
	if (ispath(c))
	    vlCTYPE(c) |= vl_scrtch;
#endif
    }
    returnVoid();
}

#if OPT_HEAPSIZE

/* track overall heap usage */

#undef	realloc
#undef	malloc
#undef	free

long currentheap;		/* current heap usage */

typedef struct {
    size_t length;
    unsigned magic;
} HEAPSIZE;

#define MAGIC_HEAP 0x12345678

/* display the amount of HEAP currently malloc'ed */
static void
display_heap_usage(void)
{
    beginDisplay();
    if (global_g_val(GMDHEAPSIZE)) {
	char membuf[20];
	int saverow = ttrow;
	int savecol = ttcol;

	if (saverow >= 0 && saverow < term.rows
	    && savecol >= 0 && savecol < term.cols) {
	    (void) lsprintf(membuf, "[%ld]", currentheap);
	    kbd_overlay(membuf);
	    kbd_flush();
	    movecursor(saverow, savecol);
	}
    }
    endofDisplay();
}

static char *
old_ramsize(char *mp)
{
    HEAPSIZE *p = (HEAPSIZE *) (mp - sizeof(HEAPSIZE));

    if (p->magic == MAGIC_HEAP) {
	mp = (char *) p;
	currentheap -= p->length;
    }
    return mp;
}

static char *
new_ramsize(char *mp, unsigned nbytes)
{
    HEAPSIZE *p = (HEAPSIZE *) mp;
    if (p != 0) {
	p->length = nbytes;
	p->magic = MAGIC_HEAP;
	currentheap += nbytes;
	mp = (char *) ((long) p + sizeof(HEAPSIZE));
    }
    return mp;
}

/* track reallocs */
char *
track_realloc(char *p, unsigned size)
{
    if (!p)
	return track_malloc(size);

    size += sizeof(HEAPSIZE);
    p = new_ramsize(realloc(old_ramsize(p), size), size);
    display_heap_usage();
    return p;
}

/* track mallocs */
char *
track_malloc(
		unsigned size)
{
    char *p;

    size += sizeof(HEAPSIZE);
    if ((p = malloc(size)) != 0) {
	(void) memset(p, 0, size);	/* so we can use for calloc */
	p = new_ramsize(p, size);
	display_heap_usage();
    }

    return p;
}

/* track free'd memory */
void
track_free(char *p)
{
    if (!p)
	return;
    free(old_ramsize(p));
    display_heap_usage();
}
#endif /* OPT_HEAPSIZE */

#ifdef MALLOCDEBUG
mallocdbg(int f, int n)
{
    int lvl;
    lvl = malloc_debug(n);
    mlwrite("malloc debug level was %d", lvl);
    if (!f) {
	malloc_debug(lvl);
    } else if (n > 2) {
	malloc_verify();
    }
    return TRUE;
}
#endif

/*
 *	the log file is left open, unbuffered.  thus any code can do
 *
 *	extern FILE *FF;
 *	fprintf(FF, "...", ...);
 *
 *	to log events without disturbing the screen
 */

#ifdef DEBUGLOG
/* suppress the declaration so that the link will fail if someone uses it */
FILE *FF;
#endif

/*ARGSUSED*/
static void
start_debug_log(int ac GCC_UNUSED, char **av GCC_UNUSED)
{
#ifdef DEBUGLOG
    int i;
    FF = fopen("vilelog", "w");
    setbuf(FF, NULL);
    for (i = 0; i < ac; i++)
	(void) fprintf(FF, "arg %d: %s\n", i, av[i]);
#endif
}

#if SYS_MSDOS

#if CC_TURBO
int
showmemory(int f, int n)
{
    extern long coreleft(void);
    mlforce("Memory left: %ld bytes", coreleft());
    return TRUE;
}
#endif

#if CC_WATCOM
int
showmemory(int f, int n)
{
    mlforce("Watcom C doesn't provide a very useful 'memory-left' call.");
    return TRUE;
}
#endif

#if CC_DJGPP
int
showmemory(int f, int n)
{
    mlforce("Memory left: %ld Kb virtual, %ld Kb physical",
	    _go32_dpmi_remaining_virtual_memory() / 1024,
	    _go32_dpmi_remaining_physical_memory() / 1024);
    return TRUE;
}
#endif
#endif /* SYS_MSDOS */

char *
strncpy0(char *t, const char *f, size_t l)
{
    (void) strncpy(t, f, l);
    if (l)
	t[l - 1] = EOS;
    return t;
}

/*
 * This is probably more efficient for copying short strings into a large
 * fixed-size buffer, because strncpy always zero-pads the destination to
 * the given length.
 */
char *
vl_strncpy(char *dest, const char *src, size_t destlen)
{
    size_t srclen = strlen(src) + 1;
    if (srclen > destlen)
	srclen = destlen;
    return strncpy0(dest, src, srclen);
}

#if defined(SA_RESTART)
/* several systems (SCO, SunOS) have sigaction without SA_RESTART */
/*
 * Redefine signal in terms of sigaction for systems which have the
 * SA_RESTART flag defined through <signal.h>
 *
 * This definition of signal will cause system calls to get restarted for a
 * more BSD-ish behavior.  This will allow us to use the OPT_WORKING feature
 * for such systems.
 */

void
setup_handler(int sig, void (*disp) (int ACTUAL_SIG_ARGS))
{
    struct sigaction act, oact;

    act.sa_handler = disp;
    sigemptyset(&act.sa_mask);
#ifdef SA_NODEFER		/* don't rely on it.  if it's not there, signals
				   probably aren't deferred anyway. */
    act.sa_flags = SA_RESTART | SA_NODEFER;
#else
    act.sa_flags = SA_RESTART;
#endif

    (void) sigaction(sig, &act, &oact);

}

#else
void
setup_handler(int sig, void (*disp) (int ACTUAL_SIG_ARGS))
{
    (void) signal(sig, disp);
}
#endif

/* put us in a new process group, on command.  we don't do this all the
* time since it interferes with suspending xvile on some systems with some
* shells.  but we _want_ it other times, to better isolate us from signals,
* and isolate those around us (like buggy window/display managers) from
* _our_ signals.  so we punt, and leave it up to the user.
*/
/* ARGSUSED */
int
newprocessgroup(int f GCC_UNUSED, int n GCC_UNUSED)
{
#if DISP_X11

    int pid;

    if (f) {
#if SYS_VMS
	pid = vfork();		/* VAX C and DEC C have a 'vfork()' */
#else
	pid = fork();
#endif

	if (pid > 0)
	    ExitProgram(GOODEXIT);
	else if (pid < 0) {
	    fputs("cannot fork\n", stderr);
	    tidy_exit(BADEXIT);
	}
    }
# if !SYS_VMS
#  ifdef HAVE_SETSID
    (void) setsid();
#  else
#   ifdef HAVE_BSD_SETPGRP
    (void) setpgrp(0, 0);
#   else
    (void) setpgrp();
#   endif			/* HAVE_BSD_SETPGRP */
#  endif			/* HAVE_SETSID */
# endif				/* SYS_VMS */
#endif /* DISP_X11 */
    return TRUE;
}

static int
cmd_mouse_motion(const CMDFUNC * cfp)
{
    int result = FALSE;
#if (OPT_XTERM || DISP_X11)
    if (cfp != 0
	&& CMD_U_FUNC(cfp) == mouse_motion)
	result = TRUE;
#endif
    return result;
}

static void
make_startup_file(char *name)
{
    FILE *fp;
    char temp[NFILEN];

    if (strcmp(pathcat(temp, home_dir(), startup_file), startup_file)
	&& ((fp = fopen(temp, "w")) != 0)) {
	fprintf(fp, "; generated by %s -I\n", prog_arg);
	fprintf(fp, "source %s\n", name);
	fclose(fp);
    }
}

#ifndef SIGTERM
#define SIGTERM 9
#endif

#ifndef valid_buffer
int
valid_buffer(BUFFER *test)
{
    beginDisplay();
    if (test != NULL && test != bminip) {
	BUFFER *bp;
	int valid = FALSE;

	for_each_buffer(bp) {
	    if (bp == test) {
		valid = TRUE;
		break;
	    }
	}
	if (!valid)
	    imdying(SIGTERM);
    }
    endofDisplay();

    return (test != NULL);
}
#endif

#ifndef valid_window
int
valid_window(WINDOW *test)
{
    beginDisplay();
    if (test != NULL && test != wminip) {
	WINDOW *wp;
	int valid = FALSE;

	for_each_window(wp) {
	    if (wp == test) {
		valid = TRUE;
		break;
	    }
	}
	if (!valid)
	    imdying(SIGTERM);
    }
    endofDisplay();

    return (test != NULL);
}
#endif

#ifdef VILE_ERROR_ABORT
#undef ExitProgram		/* in case it is oleauto_exit() */

void
ExitProgram(int code)
{
    char *env;
    if (code != GOODEXIT
	&& (env = getenv("VILE_ERROR_ABORT")) != 0
	&& *env != '\0') {
	siginit(FALSE);
	abort();
    }
    exit(code);
}
#endif
