/*
 *	modes.c
 *
 * Maintain and list the editor modes and value sets.
 *
 * Original code probably by Dan Lawrence or Dave Conroy for MicroEMACS.
 * Major extensions for vile by Paul Fox, 1991
 * Majormode extensions for vile by T.E.Dickey, 1997
 *
 * $Header: /usr/build/vile/vile/RCS/modes.c,v 1.119 1998/07/17 10:25:48 tom Exp $
 *
 */

#include	"estruct.h"
#include	"edef.h"
#include	"chgdfunc.h"

#define	NonNull(s)	((s == 0) ? "" : s)
#define	ONE_COL	(80/3)

#define isLocalVal(valptr)          ((valptr)->vp == &((valptr)->v))
#define makeLocalVal(valptr)        ((valptr)->vp = &((valptr)->v))

/* FIXME */
#define OPT_COLOR_CHOICES	OPT_COLOR
#define OPT_BOOL_CHOICES	1
#define OPT_POPUP_CHOICES	OPT_POPUPCHOICE
#define OPT_BACKUP_CHOICES	OPT_FILEBACK
#define OPT_HILITE_CHOICES	OPT_HILITEMATCH

#include "nefsms.h"

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

#if OPT_UPBUFF
static	void	relist_settings (void);
#else
#define relist_settings() /* nothing */
#endif

#if OPT_ENUM_MODES
static	const char * choice_to_name (const FSM_CHOICES *choices, int code);
static	const FSM_CHOICES * name_to_choices (const struct VALNAMES *names);
#endif

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

#if OPT_EVAL || OPT_MAJORMODE
static	const char **my_varmodes;	/* list for modename-completion */
#endif

#if OPT_MAJORMODE
typedef struct {
	char *name;		/* copy of MAJORMODE.name */
	MAJORMODE *data;	/* pointer to actual data */
	int init;		/* predefined during initialization */
	int flag;		/* true when majormode is active/usable */
	struct VALNAMES qual[MAX_M_VALUES+1]; /* qualifier names */
	struct VALNAMES used[MAX_B_VALUES+1]; /* submode names */
} MAJORMODE_LIST;

static MAJORMODE_LIST *my_majormodes;
static struct VALNAMES *major_valnames;
static struct VAL *major_g_vals;	/* on/off values of major modes */
static struct VAL *major_l_vals;	/* dummy, for convenience */

static const char **my_mode_list;	/* copy of 'all_modes[]' */
#define MODE_CLASSES 5
#define is_bool_type(type) ((type) == VALTYPE_BOOL || (type) == VALTYPE_MAJOR)

static MAJORMODE * lookup_mm_data(const char *name);
static MAJORMODE_LIST * lookup_mm_list(const char *name);
static char * majorname(char *dst, const char *majr, int flag);
static const char *ModeName(const char *name);
static int attach_mmode(BUFFER *bp, const char *name);
static int detach_mmode(BUFFER *bp, const char *name);
static int enable_mmode(const char *name, int flag);
static void init_my_mode_list(void);

#if OPT_UPBUFF
static void relist_majormodes(void);
#endif

#else

#define MODE_CLASSES 3
#define ModeName(s) s
#define init_my_mode_list() /* nothing */
#define is_bool_type(type) ((type) == VALTYPE_BOOL)
#define my_mode_list all_modes
#define relist_majormodes() /* nothing */

#endif /* OPT_MAJORMODE */

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

static void
set_winflags(int glob_vals, USHORT flags)
{
	if (glob_vals) {
		register WINDOW *wp;
		for_each_visible_window(wp) {
			if ((wp->w_bufp == NULL)
			 || !b_is_scratch(wp->w_bufp)
			 || !(flags & WFMODE))
				wp->w_flag |= flags;
		}
	} else {
		curwp->w_flag |= flags;
	}
}

static int
same_val(const struct VALNAMES *names, struct VAL *tst, struct VAL *ref)
{
	if (ref == 0)	/* can't test, not really true */
		return -TRUE;

	switch (names->type) {
#if OPT_MAJORMODE
	case VALTYPE_MAJOR:
		/*FALLTHRU*/
#endif
	case VALTYPE_BOOL:
	case VALTYPE_ENUM:
	case VALTYPE_INT:
		return	(tst->vp->i == ref->vp->i);
	case VALTYPE_STRING:
		return	(tst->vp->p != 0)
		  &&	(ref->vp->p != 0)
		  &&	!strcmp(tst->vp->p, ref->vp->p);
	case VALTYPE_REGEX:
		return	(tst->vp->r->pat != 0)
		  &&	(ref->vp->r->pat != 0)
		  &&	!strcmp(tst->vp->r->pat, ref->vp->r->pat);
	default:
		mlforce("BUG: bad type %s %d", ModeName(names->name), names->type);
	}

	return FALSE;
}

/*
 * Returns the value if it is a string, null otherwise.
 */
static const char *
string_val(const struct VALNAMES *names, struct VAL *values)
{
	const char *s = 0;

	switch (names->type) {
#if	OPT_ENUM_MODES		/* will show the enum name too */
	case VALTYPE_ENUM:
		s = choice_to_name(name_to_choices(names), values->vp->i);
		break;
#endif
	case VALTYPE_STRING:
		s = values->vp->p;
		break;
	case VALTYPE_REGEX:
		if (values->vp->r)
			s = values->vp->r->pat;
		break;
	}

	return (s != 0 && *s == EOS) ? 0 : s;
}

/*
 * Returns the formatted length of a string value.
 */
static int
size_val(const struct VALNAMES *names, struct VAL *values)
{
	return strlen(ModeName(names->name))
		+ 3
		+ strlen(NonNull(string_val(names, values)));
}

/*
 * Returns a mode-value formatted as a string
 */
char *
string_mode_val(VALARGS *args)
{
	register const struct VALNAMES *names = args->names;
	register struct VAL     *values = args->local;
	switch(names->type) {
#if OPT_MAJORMODE
	case VALTYPE_MAJOR:
#endif
	case VALTYPE_BOOL:
		return values->vp->i ? truem : falsem;
	case VALTYPE_ENUM:
#if OPT_ENUM_MODES
		{
		static	char	temp[20];	/* FIXME: const workaround */
		(void)strcpy(temp,
			choice_to_name(name_to_choices(names), values->vp->i));
		return temp;
		}
#endif				/* else, fall-thru to use int-code */
	case VALTYPE_INT:
		return l_itoa(values->vp->i);
	case VALTYPE_STRING:
		return NonNull(values->vp->p);
	case VALTYPE_REGEX:
		if (values->vp->r == 0)
			break;
		return NonNull(values->vp->r->pat);
	}
	return errorm;
}

/* listvalueset:  print each value in the array according to type, along with
 * its name, until a NULL name is encountered.  If not local, only print if the value in the
 * two arrays differs, or the second array is nil.  If local, print only the
 * values in the first array that are local.
 */
static int
listvalueset(
const char *which,
int nflag,
int local,
const struct VALNAMES *names,
struct VAL *values,
struct VAL *globvalues)
{
	int	show[MAX_G_VALUES+MAX_B_VALUES+MAX_W_VALUES];
	int	any	= 0,
		passes	= 1,
		ncols	= term.t_ncol / ONE_COL,
		padded,
		perline,
		percol,
		total;
	register int j, pass;

	if (ncols > MAXCOLS)
		ncols = MAXCOLS;

	/*
	 * First, make a list of values we want to show.
	 * Store:
	 *	0 - don't show
	 *	1 - show in first pass
	 *	2 - show in second pass (too long)
	 */
	for (j = 0; names[j].name != 0; j++) {
		int ok = FALSE;
		show[j] = 0;
		if (local) {
			ok = is_local_val(values,j);
		} else {
			ok = (same_val(names+j, values+j, globvalues ? globvalues+j : 0) != TRUE);
		}
		if (ok) {
			switch (names[j].type) {
			case VALTYPE_STRING:
			case VALTYPE_REGEX:
				if (string_val(names+j, values+j) == 0) {
					ok = FALSE;
					break;
				}
				if (size_val(names+j, values+j) > ONE_COL) {
					show[j] += 1;
					passes = 2;
				}
				/* fall-thru */
			default:
				show[j] += 1;
			}
		}
		if (ok && !any++) {
			if (nflag)
				bputc('\n');
			bprintf("%s:\n", which);
		}
	}
	total = j;

	if (any) {
		if (!passes)
			passes = 1;
	} else
		return nflag;

	/*
	 * Now, go back and display the values
	 */
	for (pass = 1; pass <= passes; pass++) {
		register int	line, col, k;
		int	offsets[MAXCOLS+1];

		offsets[0] = 0;
		if (pass == 1) {
			for (j = percol = 0; j < total; j++) {
				if (show[j] == pass)
					percol++;
			}
			for (j = 1; j < ncols; j++) {
				offsets[j]
					= (percol + ncols - j) / ncols
					+ offsets[j-1];
			}
			perline = ncols;
		} else {	/* these are too wide for ONE_COL */
			offsets[1] = total;
			perline = 1;
		}
		offsets[ncols] = total;

		line = 0;
		col  = 0;
		for_ever {
			k = line + offsets[col];
			for (j = 0; j < total; j++) {
				if (show[j] == pass) {
					if (k-- <= 0)
						break;
				}
			}
			if (k >= 0)	/* no more cells to display */
				break;

			if (col == 0)
				bputc(' ');
			padded = (col+1) < perline ? ONE_COL : 1;
			if (is_bool_type(names[j].type)) {
				bprintf("%s%s%*P",
					values[j].vp->i ? "  " : "no",
					ModeName(names[j].name),
					padded, ' ');
			} else {
				VALARGS args;	/* patch */
				args.names  = names+j;
				args.local  = values+j;
				args.global = 0;
				bprintf("  %s=%s%*P",
					ModeName(names[j].name),
					string_mode_val(&args),
					padded, ' ');
			}
			if (++col >= perline) {
				col = 0;
				bputc('\n');
				if (++line >= offsets[1])
					break;
			} else if (line+offsets[col] >= offsets[col+1])
				break;
		}
		if ((col != 0) || (pass != passes)) {
			if (pass != passes)
				bputc('\n');
			bputc('\n');
		}
	}
	return TRUE;
}

#ifdef lint
static	/*ARGSUSED*/ WINDOW *ptr2WINDOW(void *p) { return 0; }
#else
#define	ptr2WINDOW(p)	(WINDOW *)p
#endif

/* list the current modes into the current buffer */
/* ARGSUSED */
static
void
makemodelist(int local, void *ptr)
{
	static
	const	char	gg[] = "Universal",
			bb[] = "Buffer",
			ww[] = "Window";
	int	nflag, nflg2;

	register WINDOW *localwp = ptr2WINDOW(ptr);  /* alignment okay */
	register BUFFER *localbp = localwp->w_bufp;
	struct VAL	*local_b_vals = localbp->b_values.bv;
	struct VAL	*local_w_vals = localwp->w_values.wv;
	struct VAL	*globl_b_vals = global_b_values.bv;

#if OPT_UPBUFF
	if (relisting_b_vals != 0)
		local_b_vals = relisting_b_vals;
	if (relisting_w_vals != 0)
		local_w_vals = relisting_w_vals;
#endif

#if OPT_MAJORMODE
	if (local && (localbp->majr != 0)) {
		bprintf("--- \"%s\" settings, if different than \"%s\" majormode %*P\n",
			localbp->b_bname,
			localbp->majr->name,
			term.t_ncol-1, '-');
		globl_b_vals = localbp->majr->mb.bv;
	} else
#endif
	bprintf("--- \"%s\" settings, if different than globals %*P\n",
		localbp->b_bname, term.t_ncol-1, '-');

	nflag = listvalueset(bb, FALSE, FALSE, b_valnames, local_b_vals, globl_b_vals);
	nflg2 = listvalueset(ww, nflag, FALSE, w_valnames, local_w_vals, global_w_values.wv);
	if (!(nflag || nflg2))
	 	bputc('\n');
	bputc('\n');

	bprintf("--- %s settings %*P\n",
		local ? "Local" : "Global", term.t_ncol-1, '-');

#if OPT_MAJORMODE
	if (!local) {
		int n;
		for (n = 0; major_valnames[n].name != 0; n++) {
			make_local_val(major_g_vals, n);
			major_g_vals[n].vp->i = my_majormodes[n].flag;
		}
		nflag = listvalueset("Majormodes", nflag, local, major_valnames, major_g_vals, (struct VAL *)0);
	}
#endif
	if (local) {
		nflag = listvalueset(bb, nflag, local, b_valnames, local_b_vals, (struct VAL *)0);
		(void)  listvalueset(ww, nflag, local, w_valnames, local_w_vals, (struct VAL *)0);
	} else {
		nflag = listvalueset(gg, nflag, local, g_valnames, global_g_values.gv, (struct VAL *)0);
		nflag = listvalueset(bb, nflag, local, b_valnames, globl_b_vals, (struct VAL *)0);
		(void)  listvalueset(ww, nflag, local, w_valnames, global_w_values.wv, (struct VAL *)0);
	}
}

/*
 * Set tab size
 */
int
settab(int f, int n)
{
	register WINDOW *wp;
#if OPT_MAJORMODE
	int val = VAL_TAB;
	const char *whichtabs = "T";
#else
	int val;
	const char *whichtabs;
	if (is_c_mode(curbp)) {
		val = VAL_C_TAB;
		whichtabs = "C-t";
	} else {
		val = VAL_TAB;
		whichtabs = "T";
	}
#endif
	if (f && n >= 1) {
		make_local_b_val(curbp,val);
		set_b_val(curbp,val,n);
		curtabval = n;
		for_each_visible_window(wp)
			if (wp->w_bufp == curbp) wp->w_flag |= WFHARD;
	} else if (f) {
		mlwarn("[Illegal tabstop value]");
		return FALSE;
	}
	if (!global_b_val(MDTERSE) || !f)
		mlwrite("[%sabs are %d columns apart, using %s value.]", whichtabs,
			curtabval,
			is_local_b_val(curbp,val) ? "local" : "global" );
	return TRUE;
}

/*
 * Set fill column to n.
 */
int
setfillcol(int f, int n)
{
	if (f && n >= 1) {
		make_local_b_val(curbp,VAL_FILL);
		set_b_val(curbp,VAL_FILL,n);
	} else if (f) {
		mlwarn("[Illegal fill-column value]");
		return FALSE;
	}
	if (!global_b_val(MDTERSE) || !f)
		mlwrite("[Fill column is %d, and is %s]",
			b_val(curbp,VAL_FILL),
			is_local_b_val(curbp,VAL_FILL) ? "local" : "global" );
	return(TRUE);
}

/*
 * Release storage of a REGEXVAL struct
 */
static REGEXVAL *
free_regexval(register REGEXVAL *rp)
{
	if (rp != 0) {
		FreeAndNull(rp->pat);
		FreeAndNull(rp->reg);
		free((char *)rp);
	}
	return 0;
}

/*
 * Allocate/set a new REGEXVAL struct
 */
REGEXVAL *
new_regexval(const char *pattern, int magic)
{
	register REGEXVAL *rp;

	if ((rp = typealloc(REGEXVAL)) != 0) {
		rp->pat = strmalloc(pattern);
		if ((rp->reg = regcomp(rp->pat, magic)) == 0)
			rp = free_regexval(rp);
	}
	return rp;
}

/*
 * Release storage of a VAL struct
 */
static void
free_val(const struct VALNAMES *names, struct VAL *values)
{
	switch (names->type) {
	case VALTYPE_STRING:
		FreeAndNull(values->v.p);
		break;
	case VALTYPE_REGEX:
		values->v.r = free_regexval(values->v.r);
		break;
	default:	/* nothing to free */
		break;
	}
}

/*
 * Copy a VAL-struct, preserving the sense of local/global.
 */
static int
copy_val(struct VAL *dst, struct VAL *src)
{
	register int local = isLocalVal(src);

	*dst = *src;
	if (local)
		makeLocalVal(dst);
	return local;
}

void
copy_mvals(
int maximum,
struct VAL *dst,
struct VAL *src)
{
	register int	n;
	for (n = 0; n < maximum; n++)
		(void)copy_val(&dst[n], &src[n]);
}

/*
 * This is a special routine designed to save the values of local modes and to
 * restore them.  The 'recompute_buffer()' procedure assumes that global modes
 * do not change during the recomputation process (so there is no point in
 * trying to convert any of those values to local ones).
 */
#if OPT_UPBUFF
void
save_vals(
int maximum,
struct VAL *gbl,
struct VAL *dst,
struct VAL *src)
{
	register int	n;
	for (n = 0; n < maximum; n++)
		if (copy_val(&dst[n], &src[n]))
			make_global_val(src, gbl, n);
}
#endif

/*
 * free storage used by local mode-values, called only when we are freeing
 * all other storage associated with a buffer or window.
 */
void
free_local_vals(
const struct VALNAMES *names,
struct VAL *gbl,
struct VAL *val)
{
	register int	j;

	for (j = 0; names[j].name != 0; j++) {
		if (is_local_val(val,j)) {
			make_global_val(val, gbl, j);
			free_val(names+j, val+j);
		}
	}
}

/*
 * Convert a string to boolean, checking for errors
 */
static int
string_to_bool(const char *base, int *np)
{
	if (is_truem(base))
		*np = TRUE;
	else if (is_falsem(base))
		*np = FALSE;
	else {
		mlforce("[Not a boolean: '%s']", base);
		return FALSE;
	}
	return TRUE;
}

/*
 * Convert a string to number, checking for errors
 */
int
string_to_number(const char *from, int *np)
{
	long n;
	char *p;

	/* accept decimal, octal, or hex */
	n = strtol(from, &p, 0);
	if (p == from || *p != EOS) {
		mlforce("[Not a number: '%s']", from);
		return FALSE;
	}
	*np = (int)n;
	return TRUE;
}

/*
 * Validate a 'glob' mode-value.  It is either a boolean, or it must be a
 * pipe-expression with exactly one "%s" embedded (no other % characters,
 * unless escaped).  That way, we can use the string to format the pipe
 * command.
 */
#if defined(GMD_GLOB) || defined(GVAL_GLOB)
static int
legal_glob_mode(const char *base)
{
#ifdef GVAL_GLOB	/* string */
	if (isShellOrPipe(base)) {
		register const char *s = base;
		int	count = 0;
		while (*s != EOS) {
			if (*s == '%') {
				if (*++s != '%') {
					if (*s == 's')
						count++;
					else
						count = 2;
				}
			}
			if (*s != EOS)
				s++;
		}
		if (count == 1)
			return TRUE;
	}
#endif
	if (!strcmp(base, "off")
	 || !strcmp(base, "on"))
	 	return TRUE;

	mlforce("[Illegal value for glob: '%s']", base);
	return FALSE;
}
#endif

/*
 * FSM stands for fixed string mode, so called because the strings which the
 * user is permitted to enter are non-arbitrary (fixed).
 *
 * It is meant to handle the following sorts of things:
 *
 * 	:set popup-choices off
 * 	:set popup-choices immediate
 * 	:set popup-choices delayed
 *
 * 	:set error quiet
 * 	:set error beep
 * 	:set error flash
 */
#if OPT_ENUM_MODES

#if NEVER
FSM_CHOICES fsm_error[] = {
	{ "beep",      1},
	{ "flash",     2},
	{ "quiet",     0},
	END_CHOICES
};
#endif

static
struct FSM fsm_tbl[] = {
	{ "*bool",           fsm_bool_choices  },
#if OPT_COLOR_CHOICES
	{ "fcolor",          fsm_color_choices },
	{ "bcolor",          fsm_color_choices },
#endif
#if OPT_POPUP_CHOICES
	{ "popup-choices",   fsm_popup_choices },
#endif
#if NEVER
	{ "error",           fsm_error },
#endif
#if OPT_BACKUP_CHOICES
	{ "backup-style",    fsm_backup_choices },
#endif
#if OPT_HILITE_CHOICES
	{ "visual-matches",  fsm_hilite_choices },
#endif
	{ "mini-hilite",     fsm_hilite_choices },
};

static int fsm_idx;

static int
choice_to_code (const FSM_CHOICES *choices, const char *name)
{
	int code = ENUM_ILLEGAL;
	register int i;
	char	temp[64];
	(void)mklower(strncpy0(temp, name, sizeof(temp)));

	for (i = 0; choices[i].choice_name != 0; i++) {
		if (strcmp(temp, choices[i].choice_name) == 0) {
			code = choices[i].choice_code;
			break;
		}
	}
	return code;
}

static const char *
choice_to_name (const FSM_CHOICES *choices, int code)
{
	const char *name = 0;
	register int i;

	for (i = 0; choices[i].choice_name != 0; i++) {
		if (choices[i].choice_code == code) {
			name = choices[i].choice_name;
			break;
		}
	}
	return name;
}

static const FSM_CHOICES *
name_to_choices (const struct VALNAMES *names)
{
	register SIZE_T i;

	for (i = 1; i < TABLESIZE(fsm_tbl); i++)
		if (strcmp(fsm_tbl[i].mode_name, names->name) == 0)
			return fsm_tbl[i].choices;

	return 0;
}

static int
is_fsm(const struct VALNAMES *names)
{
	register SIZE_T i;

	if (names->type == VALTYPE_ENUM
	 || names->type == VALTYPE_STRING) {
		for (i = 1; i < TABLESIZE(fsm_tbl); i++) {
			if (strcmp(fsm_tbl[i].mode_name, names->name) == 0) {
				fsm_idx = (int)i;
				return TRUE;
			}
		}
	} else if (is_bool_type(names->type)) {
		fsm_idx = 0;
		return TRUE;
	}
	fsm_idx = -1;
	return FALSE;
}

/*
 * Test if we're processing an enum-valued mode.  If so, lookup the mode value.
 * We'll allow a numeric index also (e.g., for colors).  Note that we're
 * returning the table-value in that case, so we'll have to ensure that we
 * don't corrupt the table.
 */
static const char *
legal_fsm(const char *val)
{
	if (fsm_idx >= 0) {
		int i;
		int idx = fsm_idx;
		const FSM_CHOICES *p = fsm_tbl[idx].choices;
		const char *s;

		if (isDigit(*val)) {
			if (!string_to_number(val, &i))
				return 0;
			if ((s = choice_to_name(p, i)) != 0)
				return s;
		} else {
			if (choice_to_code(p, val) != ENUM_ILLEGAL)
				return val;
		}
		mlforce("[Illegal value for %s: '%s']",
			fsm_tbl[idx].mode_name,
			val);
		return 0;
	}
	return val;
}

static int
fsm_complete(int c, char *buf, unsigned *pos)
{
    if (isDigit(*buf)) {		/* allow numbers for colors */
	if (c != NAMEC)  		/* put it back (cf: kbd_complete) */
	    unkeystroke(c);
	return isSpace(c);
    }
    return kbd_complete(FALSE, c, buf, pos,
                        (const char *)(fsm_tbl[fsm_idx].choices),
			sizeof (FSM_CHOICES) );
}
#endif	/* OPT_ENUM_MODES */

/*
 * Lookup the mode named with 'cp[]' and adjust its value.
 */
int
adjvalueset(
const char *cp,			/* name of the mode we are changing */
int setting,			/* true if setting, false if unsetting */
int global,
VALARGS *args)			/* symbol-table entry for the mode */
{
	const struct VALNAMES *names = args->names;
	struct VAL     *values = args->local;
	struct VAL     *globls = args->global;

	char prompt[NLINE];
	char respbuf[NFILEN];
	int no = !strncmp(cp, "no", 2);
	const char *rp = NULL;
	int status = TRUE;
	int unsetting = !setting && !global;

	if (no && !is_bool_type(names->type))
		return FALSE;		/* this shouldn't happen */

	/*
	 * Check if we're allowed to change this mode in the current context.
	 */
	if ((names->side_effect != 0)
	 && !(*(names->side_effect))(args, (values==globls), TRUE)) {
		return FALSE;
	 }

	/* get a value if we need one */
	if ((end_string() == '=')
	 || (!is_bool_type(names->type) && !unsetting)) {
		int	regex = (names->type == VALTYPE_REGEX);
		int	opts = regex ? 0 : KBD_NORMAL;
		int	eolchar = (names->type == VALTYPE_REGEX
				|| names->type == VALTYPE_STRING) ? '\n' : ' ';
		int	(*complete) (DONE_ARGS) = no_completion;

		respbuf[0] = EOS;
		(void)lsprintf(prompt, "New %s %s: ",
			cp,
			regex ? "pattern" : "value");

#if OPT_ENUM_MODES
		if (is_fsm(names))
			complete = fsm_complete;
#endif

		status = kbd_string(prompt, respbuf, sizeof(respbuf), eolchar,
		               opts, complete);
		if (status != TRUE)
			return status;
		if (!strlen(rp = respbuf))
			return FALSE;
	}
#if OPT_HISTORY
	else
		hst_glue(' ');
#endif
	status = set_mode_value(cp, setting, global, args, rp);
	TRACE(("...adjvalueset(%s)=%d\n", cp, status))

	return status;
}

int
set_mode_value(const char *cp, int setting, int global, VALARGS *args, const char *rp)
{
	const struct VALNAMES *names = args->names;
	struct VAL     *values = args->local;
	struct VAL     *globls = args->global;
	REGEXVAL *r;

	struct VAL oldvalue;
	int no = !strncmp(cp, "no", 2);
	int nval, status = TRUE;
	int unsetting = !setting && !global;
	int changed = FALSE;

	if (rp == NULL) {
		rp = no ? cp+2 : cp;
	}
	else {
		if (no && !is_bool_type(names->type))
			return FALSE;		/* this shouldn't happen */

		/*
		 * Check if we're allowed to change this mode in the current context.
		 */
		if ((names->side_effect != 0)
		 && !(*(names->side_effect))(args, (values==globls), TRUE)) {
			return FALSE;
		}

#if defined(GMD_GLOB) || defined(GVAL_GLOB)
		if (!strcmp(names->name, "glob")
		 && !legal_glob_mode(rp))
			return FALSE;
#endif
#if OPT_ENUM_MODES
		(void) is_fsm(names);		/* evaluated for its side effects */
		if ((rp = legal_fsm(rp)) == 0)
			return FALSE;
#endif
		/* Test after fsm, to allow translation */
		if (is_bool_type(names->type)) {
			if (!string_to_bool(rp, &setting))
				return FALSE;
		}
	}

	/* save, to simplify no-change testing */
	(void)copy_val(&oldvalue, values);

	if (unsetting) {
		make_global_val(values, globls, 0);
#if OPT_MAJORMODE
		switch(names->type) {
		case VALTYPE_MAJOR:
			if (values == globls)
				changed = enable_mmode(names->shortname, FALSE);
			else
				changed = detach_mmode(curbp, names->shortname);
			break;
		}
#endif
	} else {
		makeLocalVal(values);	/* make sure we point to result! */

		/* we matched a name -- set the value */
		switch(names->type) {
#if OPT_MAJORMODE
		case VALTYPE_MAJOR:
			values->vp->i = no ? !setting : setting;
			if (values == globls) {
				changed = enable_mmode(names->shortname, values->vp->i);
			} else {
				changed = no
					? detach_mmode(curbp, names->shortname)
					: attach_mmode(curbp, names->shortname);
			}
			break;
#endif
		case VALTYPE_BOOL:
			values->vp->i = no ? !setting : setting;
			break;

		case VALTYPE_ENUM:
#if OPT_ENUM_MODES
			{
				const FSM_CHOICES *fp = name_to_choices(names);

				if (isDigit(*rp)) {
					if (!string_to_number(rp, &nval))
						return FALSE;
					if (choice_to_name(fp, nval) == 0)
						nval = ENUM_ILLEGAL;
				} else {
					nval = choice_to_code(fp, rp);
				}
				if (nval == ENUM_ILLEGAL) {
					mlforce("[Not a legal enum-index: %s]",
						rp);
					return FALSE;
				}
			}
			values->vp->i = nval;
			break;
#endif /* OPT_ENUM_MODES */

		case VALTYPE_INT:
			if (!string_to_number(rp, &nval))
				return FALSE;
			values->vp->i = nval;
			break;

		case VALTYPE_STRING:
			values->vp->p = strmalloc(rp);
			break;

		case VALTYPE_REGEX:
			if ((r = new_regexval(rp, TRUE)) == 0) {
				values->vp->r = new_regexval("", TRUE);
				return FALSE;
			}
			values->vp->r = r;
			break;

		default:
			mlforce("BUG: bad type %s %d", names->name, names->type);
			return FALSE;
		}
	}

	/*
	 * Set window flags (to force the redisplay as needed), and apply
	 * side-effects.
	 */
	status = TRUE;
	if (!same_val(names, values, &oldvalue))
		changed = TRUE;

	if (changed
	 && (names->side_effect != 0)
	 && !(*(names->side_effect))(args, (values==globls), FALSE))
		status = FALSE;

	if (isLocalVal(&oldvalue)
	 && (values != globls))
		free_val(names, &oldvalue);

	return status;
}

/* ARGSUSED */
int
listmodes(int f, int n GCC_UNUSED)
{
	register WINDOW *wp = curwp;
	register int s;

	s = liststuff(SETTINGS_BufName, FALSE, makemodelist,f,(void *)wp);
	/* back to the buffer whose modes we just listed */
	if (swbuffer(wp->w_bufp))
		curwp = wp;
	return s;
}

/*
 * The 'mode_complete()' and 'mode_eol()' functions are invoked from
 * 'kbd_reply()' to setup the mode-name completion and query displays.
 */
static int
mode_complete(DONE_ARGS)
{
	init_my_mode_list();

	return kbd_complete(FALSE, c, buf, pos,
		(const char *)&my_mode_list[0], sizeof(my_mode_list[0]));
}

int
/*ARGSUSED*/
mode_eol(const char * buffer GCC_UNUSED, unsigned cpos GCC_UNUSED, int c, int eolchar)
{
	return (c == ' ' || c == eolchar);
}

static int
lookup_valnames(const char *rp, const struct VALNAMES *table)
{
	register int j;

	for (j = 0; table[j].name != 0; j++) {
		if (!strcmp(rp, table[j].name)
		 || !strcmp(rp, table[j].shortname)) {
			return j;
		}
	}
	return -1;
}

int
find_mode(const char *mode, int global, VALARGS *args)
{
	register const char *rp = !strncmp(mode, "no", 2) ? mode+2 : mode;
	register int	mode_class;
	register int	j;

	TRACE(("find_mode(%s) %s\n", mode, global ? "global" : "local"))

	for (mode_class = 0; mode_class < MODE_CLASSES; mode_class++) {
		memset(args, 0, sizeof(*args));
		switch (mode_class) {
		default: /* universal modes */
			args->names  = g_valnames;
			args->global = global_g_values.gv;
			args->local  = (global != FALSE)
				? args->global
				: (struct VAL *)0;
			break;
		case 1:	/* buffer modes */
			args->names  = b_valnames;
			args->global = global_b_values.bv;
			args->local  = (global == TRUE)
				? args->global
				: ((curbp != 0)
					? curbp->b_values.bv
					: (struct VAL *)0);
			break;
		case 2:	/* window modes */
			args->names  = w_valnames;
			args->global = global_w_values.wv;
			args->local  = (global == TRUE)
				? args->global
				: ((curwp != 0)
					? curwp->w_values.wv
					: (struct VAL *)0);
			break;
#if OPT_MAJORMODE
		case 3: /* major modes */
			args->names  = major_valnames;
			args->global = major_g_vals;
			args->local  = (global == TRUE)
				? args->global
				: ((curbp != 0)
					? major_l_vals
					: (struct VAL *)0);
			break;
		case 4: /* major submodes (qualifiers) */
			if (my_majormodes != 0) {
				size_t n = strlen(rp);

				for (j = 0; my_majormodes[j].name; j++) {
					MAJORMODE_LIST *p = my_majormodes+j;
					size_t len = strlen(p->name);

					if (n >= len
					 && !strncmp(rp, p->name, len)
					 && (lookup_valnames(rp, p->qual)) >= 0) {
						args->names  = p->qual;
						args->global = p->data->mm.mv;
						args->local  = (global != FALSE)
							? args->global
							: (struct VAL *)0;
						break;
					}
				}
			}
			break;
#endif
		}
		if (args->names != 0
		 && args->local != 0) {
			if ((j = lookup_valnames(rp, args->names)) >= 0) {
				args->names  += j;
				args->local  += j;
				args->global += j;
				TRACE(("...found class %d %s\n", mode_class, rp))
#if OPT_MAJORMODE
				if (mode_class == 3) {
					char *it = (curbp->majr != 0)
						? curbp->majr->name
						: "?";
					make_global_val(args->local,args->global,0);
					if (global) {
						MAJORMODE_LIST *ptr =
							lookup_mm_list(it);
						args->local[0].v.i =
							(ptr != 0 && ptr->flag);
						;
					} else {
						char temp[NSTRING];
						majorname(temp, it, TRUE);
						make_local_val(args->local,0);
						args->local[0].v.i = !strcmp(temp, rp);
					}
				}
#endif
				return TRUE;
			}
		}
	}
#if OPT_MAJORMODE
	/* major submodes (buffers) */
	if (my_majormodes != 0) {
		int k = 0;
		size_t n = strlen(rp);

		for (j = 0; my_majormodes[j].name; j++) {
			MAJORMODE_LIST *p = my_majormodes+j;
			size_t len = strlen(p->name);

			if (n >= len
			 && !strncmp(rp, p->name, len)
			 && (k = lookup_valnames(rp+len+(rp[len]=='-'), b_valnames)) >= 0
			 && is_local_val(p->data->mb.bv,k)) {
				TRACE(("...found submode %s\n", b_valnames[k].name))
				if (global == FALSE) {
					if (curbp != 0
					 && (curbp->majr == 0
					  || strcmp(curbp->majr->name, p->name))) {
						TRACE(("...not applicable\n"))
						return FALSE;
					}
					args->names  = b_valnames + k;
					args->global = p->data->mb.bv + k;
					args->local  = ((curbp != 0)
							? curbp->b_values.bv + k
							: (struct VAL *)0);
				} else {
					args->names  = b_valnames + k;
					args->global = p->data->mb.bv + k;
					args->local  = args->global;
				}
				return TRUE;
			}
		}
	}
#endif
	TRACE(("...not found\n"))
	return FALSE;
}

/*
 * Process a single mode-setting
 */
static int
do_a_mode(int kind, int global)
{
	VALARGS	args;
	register int	s;
	static TBUFF *cbuf; 	/* buffer to receive mode name into */

	/* prompt the user and get an answer */
	tb_scopy(&cbuf, "");
	if ((s = kbd_reply(
		global	? "Global value: "
			: "Local value: ",
		&cbuf,
		mode_eol, '=', KBD_NORMAL, mode_complete)) != TRUE)
		return ((s == FALSE) ? SORTOFTRUE : s);

	if (!strcmp(tb_values(cbuf), "all")) {
		hst_glue(' ');
		return listmodes(FALSE,1);
	}

	if ((s = find_mode(tb_values(cbuf), global, &args)) != TRUE) {
#if OPT_EVAL
		if (!global && (s = find_mode(tb_values(cbuf), TRUE, &args)) == TRUE) {
			mlforce("[Not a local mode: \"%s\"]", tb_values(cbuf));
			return FALSE;
		}
		return set_variable(tb_values(cbuf));
#else
		mlforce("[Not a legal set option: \"%s\"]", tb_values(cbuf));
#endif
	} else if ((s = adjvalueset(tb_values(cbuf), kind, global, &args)) != 0) {
		if (s == TRUE)
			mlerase();	/* erase the junk */
		return s;
	}

	return FALSE;
}

/*
 * Process the list of mode-settings
 */
static int
adjustmode(	/* change the editor mode status */
int kind,	/* true = set,		false = delete */
int global)	/* true = global flag,	false = current buffer flag */
{
	int s;
	int anything = 0;

	if (kind && global && isreturn(end_string()))
		return listmodes(TRUE,1);

	while (((s = do_a_mode(kind, global)) == TRUE) && (end_string() == ' '))
		anything++;
	if ((s == SORTOFTRUE) && anything) /* fix for trailing whitespace */
		return TRUE;

	/* if the settings are up, redisplay them */
	relist_settings();
	relist_majormodes();

	if (curbp) {
		curtabval = tabstop_val(curbp);
	}

	return s;
}

/*
 * Buffer-animation for [Settings]
 */
#if OPT_UPBUFF
static int
show_Settings(BUFFER *bp)
{
	b_clr_obsolete(bp);
	return listmodes(FALSE, 1);
}

static void
relist_settings(void)
{
	update_scratch(SETTINGS_BufName, show_Settings);
}
#endif	/* OPT_UPBUFF */

/* ARGSUSED */
int
setlocmode(int f GCC_UNUSED, int n GCC_UNUSED)	/* prompt and set an editor mode */
{
	return adjustmode(TRUE, FALSE);
}

/* ARGSUSED */
int
dellocmode(int f GCC_UNUSED, int n GCC_UNUSED)	/* prompt and delete an editor mode */
{
	return adjustmode(FALSE, FALSE);
}

/* ARGSUSED */
int
setglobmode(int f GCC_UNUSED, int n GCC_UNUSED)	/* prompt and set a global editor mode */
{
	return adjustmode(TRUE, TRUE);
}

/* ARGSUSED */
int
delglobmode(int f GCC_UNUSED, int n GCC_UNUSED)	/* prompt and delete a global editor mode */
{
	return adjustmode(FALSE, TRUE);
}

/*
 * The following functions are invoked to carry out side effects of changing
 * modes.
 */
/*ARGSUSED*/
int
chgd_autobuf(VALARGS *args GCC_UNUSED, int glob_vals, int testing GCC_UNUSED)
{
	if (glob_vals)
		sortlistbuffers();
	return TRUE;
}

/*ARGSUSED*/
int
chgd_buffer(VALARGS *args GCC_UNUSED, int glob_vals, int testing GCC_UNUSED)
{
	if (!glob_vals) {	/* i.e., ":setl" */
		if (curbp == 0)
			return FALSE;
		b_clr_counted(curbp);
		(void)bsizes(curbp);
	}
	return TRUE;
}

int
chgd_charset(VALARGS *args, int glob_vals, int testing)
{
	if (!testing) {
		charinit();
	}
	return chgd_window(args, glob_vals, testing);
}

#if OPT_COLOR
int
chgd_color(VALARGS *args, int glob_vals, int testing)
{
	if (!testing) {
		if (&args->local->vp->i == &gfcolor)
			TTforg(gfcolor);
		else if (&args->local->vp->i == &gbcolor)
			TTbacg(gbcolor);
		set_winflags(glob_vals, WFHARD|WFCOLR);
		vile_refresh(FALSE,0);
	}
	return TRUE;
}

#if OPT_EVAL
static void
set_fsm_choice(const char *name, const FSM_CHOICES *choices)
{
	size_t n;
	for (n = 0; n < TABLESIZE(fsm_tbl); n++) {
		if (!strcmp(name, fsm_tbl[n].mode_name)) {
			fsm_tbl[n].choices = choices;
			break;
		}
	}
}
#endif	/* OPT_EVAL */

static int
reset_color(int n)
{
	if (global_g_val(n) > ncolors) {
		set_global_g_val(n, global_g_val(n) % ncolors);
		return TRUE;
	}
	return FALSE;
}

#if OPT_ENUM_MODES
static FSM_CHOICES *my_colors;
static FSM_CHOICES *my_hilite;
#endif

/*
 * Set the number of colors to a subset of that which is configured.  The main
 * use for this is to switch between 16-colors and 8-colors, though it should
 * work for setting any power of 2 up to the NCOLORS value.
 */
int set_ncolors(int n)
{
	static int initialized;
#if OPT_ENUM_MODES
	const FSM_CHOICES *the_colors, *the_hilite;
	size_t s, d;
#endif

	if (n > NCOLORS || n < 2)
		return FALSE;
	if (!initialized)
		initialized = n;
	if (n > initialized)
		return FALSE;
	ncolors = n;
	if (reset_color(GVAL_FCOLOR)
	 || reset_color(GVAL_BCOLOR)) {
		vile_refresh(FALSE,0);
	}

#if OPT_ENUM_MODES
	if (ncolors == NCOLORS) {
		the_colors = fsm_color_choices;
		the_hilite = fsm_hilite_choices;
	} else {
		my_colors = typecallocn(FSM_CHOICES,TABLESIZE(fsm_color_choices));
		my_hilite = typecallocn(FSM_CHOICES,TABLESIZE(fsm_hilite_choices));
		the_colors = my_colors;
		the_hilite = my_hilite;
		for (s = d = 0; s < TABLESIZE(fsm_color_choices)-1; s++) {
			my_colors[d] = fsm_color_choices[s];
			if (my_colors[d].choice_code > 0) {
				if (!(my_colors[d].choice_code %= ncolors))
					continue;
			}
			if (strncmp(my_colors[d].choice_name, "bright", 6))
				d++;
		}
		my_colors[d].choice_name = 0;

		for (s = d = 0; s < TABLESIZE(fsm_hilite_choices)-1; s++) {
			my_hilite[d] = fsm_hilite_choices[s];
			if (my_hilite[d].choice_code & VASPCOL) {
				unsigned code = my_hilite[d].choice_code % NCOLORS;
				if (code != 0) {
					if ((code %= ncolors) == 0)
						continue;
					my_hilite[d].choice_code = VASPCOL | code;
				}
				if (strncmp(my_hilite[d].choice_name, "bright", 6))
					d++;
			} else {
				d++;
			}
		}
		my_hilite[d].choice_name = 0;
	}
	set_fsm_choice("fcolor", the_colors);
	set_fsm_choice("bcolor", the_colors);
	set_fsm_choice("visual-matches", the_hilite);
	set_fsm_choice("mini-hilite", the_hilite);
#endif /* OPT_ENUM_MODES */
	return TRUE;
}
#endif	/* OPT_COLOR */

	/* Report mode that cannot be changed */
/*ARGSUSED*/
int
chgd_disabled(VALARGS *args, int glob_vals GCC_UNUSED, int testing GCC_UNUSED)
{
	mlforce("[Cannot change \"%s\" ]", args->names->name);
	return FALSE;
}

	/* Change "fences" mode */
/*ARGSUSED*/
int
chgd_fences(VALARGS *args, int glob_vals GCC_UNUSED, int testing)
{
	if (!testing) {
		/* was even number of fence pairs specified? */
		char *value = args->local->v.p;
		size_t len = strlen(value);

		if (len & 1) {
			value[len-1] = EOS;
			mlwrite(
			"[Fence-pairs not in pairs:  truncating to \"%s\"",
				value);
			return FALSE;
		}
	}
	return TRUE;
}

	/* Change a "major" mode */
int
chgd_major(VALARGS *args, int glob_vals, int testing)
{
	/* prevent major-mode changes for scratch-buffers */
	if (testing) {
		if (!glob_vals) {
			if (b_is_scratch(curbp))
				return chgd_disabled(args, glob_vals, testing);
		}
	} else {
		set_winflags(glob_vals, WFMODE);
	}
	return TRUE;
}

	/* Change a major mode that affects the windows on the buffer */
int
chgd_major_w(VALARGS *args, int glob_vals, int testing)
{
	if (testing) {
		if (!chgd_major(args, glob_vals, testing))
			return FALSE;
		return chgd_window(args, glob_vals, testing);
	}

	set_winflags(glob_vals, WFHARD|WFMODE);
	return TRUE;
}

	/* Change something on the mode/status line */
/*ARGSUSED*/
int
chgd_status(VALARGS *args GCC_UNUSED, int glob_vals, int testing)
{
	if (!testing) {
		set_winflags(glob_vals, WFSTAT);
	}
	return TRUE;
}

	/* Change a mode that affects the windows on the buffer */
/*ARGSUSED*/
int
chgd_window(VALARGS *args GCC_UNUSED, int glob_vals, int testing)
{
	if (!testing) {
		set_winflags(glob_vals, WFHARD);
	}
	return TRUE;
}

	/* Change the working mode */
#if OPT_WORKING
/*ARGSUSED*/
int
chgd_working(VALARGS *args GCC_UNUSED, int glob_vals, int testing GCC_UNUSED)
{
	if (glob_vals)
		imworking(0);
	return TRUE;
}
#endif

	/* Change the xterm-mouse mode */
/*ARGSUSED*/
int
chgd_xterm(VALARGS *args GCC_UNUSED, int glob_vals, int testing GCC_UNUSED)
{
#if	OPT_XTERM
	if (glob_vals) {
		int	new_state = global_g_val(GMDXTERM_MOUSE);
		set_global_g_val(GMDXTERM_MOUSE,TRUE);
		if (!new_state)	TTkclose();
		else		TTkopen();
		set_global_g_val(GMDXTERM_MOUSE,new_state);
	}
#endif
	return TRUE;
}

	/* Change a mode that affects the search-string highlighting */
/*ARGSUSED*/
int
chgd_hilite(VALARGS *args GCC_UNUSED, int glob_vals GCC_UNUSED, int testing)
{
	if (!testing)
		attrib_matches();
	return TRUE;
}

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

#if OPT_EVAL || OPT_MAJORMODE
/*
 * Test for mode-names that we'll not show in the variable name-completion.
 */
int
is_varmode (const char *name)
{
	return (strncmp(name, "no", 2)
	   &&   strcmp(name, "all"));
}

static int
is_identifier (const char *name)
{
	int first = TRUE;;

	while (*name != EOS) {
		if (first) {
			if (!isAlpha(*name))
				return FALSE;
			first = FALSE;
		} else if (!isident(*name))
			return FALSE;
		name++;
	}
	return TRUE;
}

/*
 * Returns the current number of items in the list of modes
 */
static size_t
count_modes (void)
{
	size_t n;

	init_my_mode_list();

	for (n = 0; my_mode_list[n] != 0; n++)
		;
	return n;
}

/*
 * Return a list of only the modes that can be set with ":setv", ignoring
 * artifacts such as "all".
 */
const char *const *
list_of_modes (void)
{
	if (my_varmodes == 0) {
		const char *const *s;
		const char **d;
		size_t n = count_modes();
		my_varmodes = typeallocn(const char *, n + 1);
		for (s = my_mode_list, d = my_varmodes; (*d = *s) != 0; s++) {
			if (is_varmode(*d)) {
				d++;
			}
		}
	}
	return my_varmodes;
}
#endif /* OPT_EVAL || OPT_MAJORMODE */

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

#if OPT_MAJORMODE
static int
ok_submode(const char *name)
{
	/* like is_varmode, but allow "no" prefix */
	return strcmp(name, "all") != 0 ? TRUE : FALSE;
}

/* format the name of a majormode's qualifier */
static char *
per_major(char *dst, const char *majr, int code, int brief)
{
	if (brief) {
		(void) lsprintf(dst, "%s%s", majr, m_valnames[code].shortname);
	} else {
		(void) lsprintf(dst, "%s-%s", majr, m_valnames[code].name);
	}
	return dst;
}

/* format the name of a majormode's submode */
static char *
per_submode(char *dst, const char *majr, int code, int brief)
{
	if (brief) {
		if (!strcmp(b_valnames[code].shortname, "X")) {
			*dst = EOS;
			return 0;
		}
		(void) lsprintf(dst, "%s%s", majr, b_valnames[code].shortname);
	} else {
		(void) lsprintf(dst, "%s-%s", majr, b_valnames[code].name);
	}
	return dst;
}

static char *TheMajor;

static const char *
ModeName(const char *name)
{
	if (TheMajor != 0) {
		static char *dst;
		if (dst != 0)
			free(dst);
		dst = typeallocn(char, strlen(TheMajor) + strlen(name) + 3);
		(void) lsprintf(dst, "%s-%s", TheMajor, name);
		return dst;
	}
	return name;
}

/* format the name of a majormode */
static char *
majorname(char *dst, const char *majr, int flag)
{
	(void) lsprintf(dst, "%s%smode", flag ? "" : "no", majr);
	return dst;
}

/*
 * Returns the current number of items in the list of modes
 */
static size_t
count_majormodes (void)
{
	size_t n = 0;

	if (my_majormodes != 0) {
		for (n = 0; my_majormodes[n].name != 0; n++)
			;
	}
	return n;
}

static int
found_per_submode(const char *majr, int code)
{
	size_t n;
	char temp[NSTRING];

	init_my_mode_list();

	(void) per_submode(temp, majr, code, TRUE);
	for (n = 0; my_mode_list[n] != 0; n++) {
		if (!strcmp(my_mode_list[n], temp))
			return TRUE;
	}
	return FALSE;
}

/*
 * Insert 'name' into 'my_mode_list[]', which has 'count' entries.
 */
static size_t
insert_per_major(size_t count, const char *name)
{
	if (name != 0) {
		size_t j, k;

		TRACE(("insert_per_major %ld %s\n", (long) count, name))

		for (j = 0; j < count; j++) {
			if (strcmp(my_mode_list[j], name) > 0)
				break;
		}
		for (k = ++count; k != j; k--)
			my_mode_list[k] = my_mode_list[k-1];
		my_mode_list[j] = strmalloc(name);
	}
	return count;
}

/*
 * Remove 'name' from 'my_mode_list[]', which has 'count' entries.
 */
static size_t
remove_per_major(size_t count, const char *name)
{
	if (name != 0) {
		size_t j, k;

		for (j = 0; j < count; j++) {
			if (strcmp(my_mode_list[j], name) == 0) {
				free(TYPECAST(char,my_mode_list[j]));
				count--;
				for (k = j; k <= count; k++)
					my_mode_list[k] = my_mode_list[k+1];
				break;
			}
		}
	}
	return count;
}

/*
 * Lookup a majormode's data area, given its short name, e.g., "c" vs "cmode".
 * We store the majormodes in an array to simplify name completion, though this
 * complicates definition and removal.
 */
static MAJORMODE *
lookup_mm_data(const char *name)
{
	size_t n;
	if (my_majormodes != 0) {
		for (n = 0; my_majormodes[n].name != 0; n++) {
			if (!strcmp(name, my_majormodes[n].name))
				return my_majormodes[n].data;
		}
	}
	return 0;
}

/*
 * Lookup a majormode's data area, given its short name, e.g., "c" vs "cmode".
 * We store the majormodes in an array to simplify name completion, though this
 * complicates definition and removal.
 */
static MAJORMODE_LIST *
lookup_mm_list(const char *name)
{
	size_t n;
	if (my_majormodes != 0) {
		for (n = 0; my_majormodes[n].name != 0; n++) {
			if (!strcmp(name, my_majormodes[n].name))
				return my_majormodes+n;
		}
	}
	return 0;
}

/* Check if a majormode is predefined.  There are some things we don't want to
 * do to them (such as remove them).
 */
static int predef_majormode(const char *name)
{
	size_t n;
	int status = FALSE;

	if (my_majormodes != 0) {
		for (n = 0; my_majormodes[n].name != 0; n++) {
			if (!strcmp(name, my_majormodes[n].name)) {
				status = my_majormodes[n].init;
				break;
			}
		}
	}
	return status;
}

static int
major_complete(int c, char *buf, unsigned *pos)
{
	return kbd_complete(FALSE, c, buf, pos, (const char *)&my_majormodes[0],
		sizeof(my_majormodes[0]));
}

static int
prompt_majormode(char **result, int defining)
{
	static TBUFF *cbuf; 	/* buffer to receive mode name into */
	int status;

	/* prompt the user and get an answer */
	tb_scopy(&cbuf, "");
	if ((status = kbd_reply("majormode: ",
		&cbuf,
		eol_history, ' ',
		KBD_NORMAL,	/* FIXME: KBD_MAYBEC if !defining */
		(defining || clexec)
			? no_completion
			: major_complete)) == TRUE) {
		/* check for legal name (alphanumeric) */
		if ((status = is_identifier(tb_values(cbuf))) != TRUE) {
			mlwarn("[Not an identifier: %s]", tb_values(cbuf));
			return status;
		}
		if ((status = is_varmode(tb_values(cbuf))) == TRUE) {
			*result = tb_values(cbuf);
			if (defining && lookup_mm_data(*result) != 0) {
				TRACE(("Mode already exists\n"))
				return SORTOFTRUE;
			} else if (!defining && lookup_mm_data(*result) == 0) {
				TRACE(("Mode does not exist\n"))
				return SORTOFTRUE;
			}
			return TRUE;
		}
	}
	if (status != FALSE)
		mlwarn("[Illegal name %s]", tb_values(cbuf));
	return status;
}

static int
submode_complete(int c, char *buf, unsigned *pos)
{
	return kbd_complete(FALSE, c, buf, pos, (const char *)&all_submodes[0],
		sizeof(all_submodes[0]));
}

static int
prompt_submode(char **result, int defining)
{
	static TBUFF *cbuf; 	/* buffer to receive mode name into */
	register const char *rp;
	int status;

	/* prompt the user and get an answer */
	tb_scopy(&cbuf, "");
	if ((status = kbd_reply("submode: ",
		&cbuf,
		eol_history, '=',
		KBD_NORMAL,
		submode_complete)) == TRUE) {
		if ((status = ok_submode(tb_values(cbuf))) == TRUE) {
			*result = tb_values(cbuf);
			rp = !strncmp(*result, "no", 2) ? *result+2 : *result;
			if (defining && lookup_mm_data(rp) != 0) {
				TRACE(("Mode already exists\n"))
				return SORTOFTRUE;
			} else if (!defining && lookup_mm_data(rp) == 0) {
				TRACE(("Mode does not exist\n"))
				return SORTOFTRUE;
			}
			return TRUE;
		}
	}
	mlwarn("[Illegal name %s]", tb_values(cbuf));
	return status;
}

/*
 * Attach a buffer to the given majormode.  Adjust all of the non-local buffer
 * modes to point to the majormode's values where those in turn are local.
 */
static int
attach_mmode(BUFFER *bp, const char *name)
{
	int n;

	if (bp != 0) {
		if (bp->majr != 0
		 && strcmp(bp->majr->name, name) != 0)
			(void) detach_mmode(bp, bp->majr->name);

		TRACE(("attach_mmode '%s' to '%s'\n", name, bp->b_bname))
		if ((bp->majr = lookup_mm_data(name)) != 0) {
			struct VAL *mm = bp->majr->mb.bv;

			/* adjust buffer modes */
			for (n = 0; n < MAX_B_VALUES; n++) {
				if (!is_local_b_val(bp,n)
				 && is_local_val(mm,n)) {
					make_global_val(bp->b_values.bv, mm, n);
				}
			}
			return TRUE;
		}
		return (bp->majr != 0);
	}

	return FALSE;
}

/*
 * Detach a buffer from the given majormode.  Modify the buffer's minor modes
 * to point to global modes where they've been pointed to the majormode's data.
 */
static int
detach_mmode(BUFFER *bp, const char *name)
{
	size_t n;
	MAJORMODE *mp = 0;

	if (bp != 0
	 && (mp = bp->majr) != 0
	 && !strcmp(mp->name, name)) {
		TRACE(("detach_mmode '%s', given '%s'\n", name, mp->name))
		/* readjust the buffer's modes */
		for (n = 0; n < MAX_B_VALUES; n++) {
			if (!is_local_b_val(bp,n)
			 && is_local_val(mp->mb.bv,n)) {
				make_global_b_val(bp,n);
			}
		}
		relist_settings();
		bp->majr = 0;
		return TRUE;
	}

	return FALSE;
}

static int
enable_mmode(const char *name, int flag)
{
	MAJORMODE_LIST *ptr = lookup_mm_list(name);
	if (ptr != 0
	 && ptr->flag != flag) {
		ptr->flag = flag;
		return TRUE;
	}
	return FALSE;
}

static int
free_majormode(const char *name)
{
	MAJORMODE *ptr = lookup_mm_data(name);
	size_t j, k;
	int n;
	char temp[NSTRING];
	BUFFER *bp;

	if (ptr != 0) {
		int init = TRUE;
		for (j = 0; my_majormodes[j].name != 0; j++) {
			if (my_majormodes[j].data == ptr) {
				init = my_majormodes[j].init;
				for_each_buffer(bp) {
					if (detach_mmode(bp, my_majormodes[j].name)) {
						set_winflags(TRUE, WFHARD|WFMODE);
					}
				}
				free_local_vals(m_valnames, major_g_vals, ptr->mm.mv);
				free_local_vals(b_valnames, global_b_values.bv, ptr->mb.bv);
				for (k = 0; k < MAX_M_VALUES; k++) {
					free(TYPECAST(char,my_majormodes[j].qual[k].name));
					free(TYPECAST(char,my_majormodes[j].qual[k].shortname));
				}
				free(ptr->name);
				free(ptr);
				do {
					my_majormodes[j] = my_majormodes[j+1];
				} while (my_majormodes[j++].name != 0);
				break;
			}
		}
		if (my_mode_list != all_modes && !init) {
			j = count_modes();
			j = remove_per_major(j, majorname(temp, name, FALSE));
			j = remove_per_major(j, majorname(temp, name, TRUE));
			for (n = 0; n < MAX_M_VALUES; n++) {
				j = remove_per_major(j,
					per_major(temp, name, n, TRUE));
				j = remove_per_major(j,
					per_major(temp, name, n, FALSE));
			}
		}
		if (major_valnames != 0) {
			for (n = 0; major_valnames[n].name != 0; n++) {
				if (!strcmp(name, major_valnames[n].shortname)) {
					free(TYPECAST(char,major_valnames[n].name));
					free(TYPECAST(char,major_valnames[n].shortname));
					while (major_valnames[n].name != 0) {
						major_valnames[n] =
						major_valnames[n+1];
						n++;
					}
					break;
				}
			}
		}
		return TRUE;
	}
	return FALSE;
}

static void
init_my_mode_list(void)
{
	if (my_mode_list == 0)
		my_mode_list = TYPECAST(const char *,all_modes);
}

static int
extend_mode_list(int increment)
{
	int j = count_modes();
	int k = increment + j + 1;

	TRACE(("extend_mode_list from %d by %d\n", j, increment))

	if (my_mode_list == all_modes) {
		my_mode_list = typeallocn(const char *, k);
		memcpy((char *)my_mode_list, all_modes, (j+1) * sizeof(*my_mode_list));
	} else {
		my_mode_list = typereallocn(const char *, my_mode_list, k);
	}
	return j;
}

static struct VAL *
extend_VAL_array(struct VAL *ptr, size_t item, size_t len)
{
	size_t j, k;

	TRACE(("extend_VAL_array %p item %ld of %ld\n", ptr, (long)item, (long)len))

	if (ptr == 0) {
		ptr = typeallocn(struct VAL, len + 1);
	} else {
		ptr = typereallocn(struct VAL, ptr, len + 1);
		for (j = k = 0; j < len; j++) {
			k = (j >= item) ? j+1 : j;
			ptr[k] = ptr[j];
			make_local_val(ptr, k);
		}
	}
	make_local_val(ptr, item);
	make_local_val(ptr, len);
	ptr[item].v.i = FALSE;
	return ptr;
}

static void
set_qualifier(const struct VALNAMES *names, struct VAL *values, const char *s)
{
	switch (names->type) {
	case VALTYPE_STRING:
		if (values->v.p)
			free(values->v.p);
		values->v.p = strmalloc(s);
		break;
	case VALTYPE_REGEX:
		free_regexval(values->v.r);
		values->v.r = new_regexval(s, TRUE);
		break;
	}
	make_local_val(values, 0);
}

static void
reset_qualifier(const struct VALNAMES *names, struct VAL *values)
{
	set_qualifier(names, values, "");
}

/*
 * Buffer-animation for [Major Modes]
 */
#if OPT_UPBUFF
static int
show_majormodes(BUFFER *bp)
{
	b_clr_obsolete(bp);
	return list_majormodes(FALSE, 1);
}

static void
relist_majormodes(void)
{
	update_scratch(MAJORMODES_BufName, show_majormodes);
}
#endif	/* OPT_UPBUFF */

/* list the current modes into the current buffer */
/* ARGSUSED */
static void
makemajorlist(int local, void *ptr GCC_UNUSED)
{
	int j;
	int nflag;
	MAJORMODE *data;

	if (my_majormodes != 0) {
		for (j = 0; my_majormodes[j].name != 0; j++) {
			if (local)
				TheMajor = my_majormodes[j].name;
			nflag = 0;
			data = my_majormodes[j].data;
			bprintf("--- \"%s\" majormode settings %*P\n",
				my_majormodes[j].name,
				term.t_ncol-1, '-');
			nflag = listvalueset("Qualifier", FALSE, TRUE,
				m_valnames,
				data->mm.mv,
				data->mm.mv);
			nflag = listvalueset("Buffer",    nflag, TRUE,
				b_valnames,
				data->mb.bv,
				global_b_values.bv);
			if (my_majormodes[j+1].data)
				bputc('\n');
		}
	}
	TheMajor = 0;
}

/* ARGSUSED */
int
list_majormodes(int f, int n GCC_UNUSED)
{
	register WINDOW *wp = curwp;
	register int s;

	s = liststuff(MAJORMODES_BufName, FALSE, makemajorlist,f,(void *)wp);
	/* back to the buffer whose modes we just listed */
	if (swbuffer(wp->w_bufp))
		curwp = wp;

	return s;
}

int
alloc_mode(const char *name, int predef)
{
	size_t j, k;
	int n;
	char temp[NSTRING];

	if (major_valnames == 0) {
		major_valnames = typecallocn(struct VALNAMES, 2);
		j = 0;
		k = 1;
	} else {
		k = count_majormodes();
		major_valnames = typereallocn(struct VALNAMES, major_valnames, k+2);
		for (j = k++; j != 0; j--) {
			major_valnames[j] = major_valnames[j-1];
			if (strcmp(major_valnames[j-1].shortname, name) < 0) {
				break;
			}
		}
	}

	(void) majorname(temp, name, TRUE);
	major_valnames[j].name        = strmalloc(temp);
	major_valnames[j].shortname   = strmalloc(name);
	major_valnames[j].type        = VALTYPE_MAJOR;
	major_valnames[j].side_effect = chgd_major_w;

	memset(major_valnames+k, 0, sizeof(*major_valnames));

	/* build arrays needed for 'find_mode()' bookkeeping */
	major_g_vals = extend_VAL_array(major_g_vals, j, k);
	major_l_vals = extend_VAL_array(major_l_vals, j, k);

	if (my_majormodes == 0) {
		my_majormodes = typecallocn(MAJORMODE_LIST, 2);
		j = 0;
		k = 1;
	} else {
		k = count_majormodes();
		my_majormodes = typereallocn(MAJORMODE_LIST, my_majormodes, k+2);
		for (j = k++; j != 0; j--) {
			my_majormodes[j] = my_majormodes[j-1];
			if (strcmp(my_majormodes[j-1].name, name) < 0) {
				break;
			}
		}
	}

	my_majormodes[j].data = typecalloc(MAJORMODE);
	my_majormodes[j].name = my_majormodes[j].data->name = strmalloc(name);
	my_majormodes[j].init = predef;
	my_majormodes[j].flag = TRUE;
	memset(my_majormodes+k, 0, sizeof(*my_majormodes));

	for (k = 0; k < MAX_B_VALUES; k++) {
		make_global_val(my_majormodes[j].data->mb.bv, global_b_values.bv, k);
	}

	/* copy array to get types, then overwrite the name-pointers */
	memcpy(my_majormodes[j].qual, m_valnames, sizeof(m_valnames));
	for (k = 0; k < MAX_M_VALUES; k++) {
		reset_qualifier(m_valnames+k, my_majormodes[j].data->mm.mv+k);
		my_majormodes[j].qual[k].name =
				strmalloc(per_major(temp, name, k, TRUE));
		my_majormodes[j].qual[k].shortname =
				strmalloc(per_major(temp, name, k, FALSE));
	}

	/*
	 * Create the majormode-specific names.  If this is predefined, we
	 * already had mktbls do this.
	 */
	if (!predef) {
		j = extend_mode_list((MAX_M_VALUES + 2) * 2);
		j = insert_per_major(j, majorname(temp, name, FALSE));
		j = insert_per_major(j, majorname(temp, name, TRUE));
		for (n = 0; n < MAX_M_VALUES; n++) {
			j = insert_per_major(j, per_major(temp, name, n, TRUE));
			j = insert_per_major(j, per_major(temp, name, n, FALSE));
		}
	}

	return TRUE;
}

/* ARGSUSED */
int
define_mode(int f GCC_UNUSED, int n GCC_UNUSED)
{
	char *name;
	int status;

	if ((status = prompt_majormode(&name, TRUE)) == TRUE) {
		TRACE(("define majormode:%s\n", name))
		status = alloc_mode(name, FALSE);
		relist_settings();
		relist_majormodes();
	} else if (status == SORTOFTRUE) {
		status = TRUE;	/* don't complain if it's already true */
	}
	return status;
}

static int
do_a_submode(int defining)
{
	char *name;
	char *subname;
	int status;
	MAJORMODE *ptr;
	VALARGS args;
	int j, k;
	int qualifier = FALSE;
	char temp[NSTRING];
	char *rp;

	if ((status = prompt_majormode(&name, FALSE)) != TRUE)
		return status;

	if ((status = prompt_submode(&subname, TRUE)) != TRUE)
		return status;

	ptr = lookup_mm_data(name);
	rp = !strncmp(subname, "no", 2) ? subname+2 : subname;
	if ((j = lookup_valnames(rp, m_valnames)) >= 0) {
		qualifier   = TRUE;
		args.names  = m_valnames;
		args.global = ptr->mm.mv;
		args.local  = ptr->mm.mv;
	} else if ((j = lookup_valnames(rp, b_valnames)) >= 0) {
		args.names  = b_valnames;
		args.global = defining ? ptr->mb.bv :global_b_values.bv;
		args.local  = ptr->mb.bv;
	} else {
		mlwarn("[BUG: no such submode %s]", rp);
		return FALSE;
	}

	args.names  += j;
	args.global += j;
	args.local  += j;

	/*
	 * We store submodes in the majormode as local values.
	 */
	status = adjvalueset(subname, defining, FALSE, &args);

	/*
	 * Check if we deleted one of the qualifiers, since there's no global
	 * value to inherit back to, we'll have to ensure there's valid data.
	 */
	if (status == TRUE
	 && qualifier
	 && !defining) {
		reset_qualifier(args.names, args.global);
	}

	if (status == TRUE
	 && !qualifier) {
		if (defining && found_per_submode(name, j)) {
			TRACE(("submode names for %d present\n", j))
		} else if (defining) {
			TRACE(("construct submode names for %d\n", j))
			k = extend_mode_list(2);
			k = insert_per_major(k,
				per_submode(temp, name, j, TRUE));
			k = insert_per_major(k,
				per_submode(temp, name, j, FALSE));
		} else {
			TRACE(("destroy submode names for %d\n", j))
			k = count_modes();
			k = remove_per_major(k,
				per_submode(temp, name, j, TRUE));
			k = remove_per_major(k,
				per_submode(temp, name, j, FALSE));
		}
	}

	/* FIXME: remember to adjust all buffers that used this mode, in
	 * case we make a minor mode part-of or removed from the major mode.
	 */
	relist_settings();
	relist_majormodes();
	return status;
}

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

/* ARGSUSED */
int
remove_submode(int f GCC_UNUSED, int n GCC_UNUSED)
{
	return do_a_submode(FALSE);
}

/* ARGSUSED */
int
remove_mode(int f GCC_UNUSED, int n GCC_UNUSED)
{
	char *name;
	int status;

	if ((status = prompt_majormode(&name, FALSE)) == TRUE) {
		if ((status = !predef_majormode(name)) == TRUE) {
			TRACE(("remove majormode:%s\n", name))
			free_majormode(name);
			relist_settings();
			relist_majormodes();
		} else {
			mlwarn("[This is a predefined mode: %s]", name);
		}
	} else if (status == SORTOFTRUE) {
		status = TRUE;
	}
	return status;
}

/*
 * Returns the regular expression for the given indices, checking that the
 * pattern is non-null.
 */
static regexp *
get_mm_rexp(int n, int m)
{
	struct VAL *mv = my_majormodes[n].data->mm.mv;

	if (mv[m].vp->r != 0
	 && mv[m].vp->r->pat != 0
	 && mv[m].vp->r->pat[0] != 0
	 && mv[m].vp->r->reg != 0) {
		TRACE(("get_mm_rexp(%s) %s\n",
			my_majormodes[n].name,
			mv[m].vp->r->pat))
		return mv[m].vp->r->reg;
	}
	return 0;
}

/*
 * Use a regular expression (normally a suffix, such as ".c") to match the
 * buffer's filename.  If found, set the first matching majormode.
 */
void
setm_by_suffix(register BUFFER *bp)
{
	if (my_majormodes != 0) {
		size_t n = 0;
		int savecase = ignorecase;
#if OPT_CASELESS
		ignorecase = TRUE;
#else
		ignorecase = FALSE;
#endif

		for (n = 0; my_majormodes[n].name != 0; n++) {
			if (my_majormodes[n].flag) {
				regexp *exp = get_mm_rexp(n, MVAL_SUFFIXES);
				if (exp != 0
				 && regexec(exp, bp->b_fname, (char *)0, 0, -1)) {
					attach_mmode(bp, my_majormodes[n].name);
					break;
				}
			}
		}
		ignorecase = savecase;
	}
}

static LINE *
get_preamble(register BUFFER *bp)
{
	if (!is_empty_buf(bp)) {
		LINE *lp = lforw(buf_head(bp));
		if (lisreal(lp))
			return lp;
	}
	return 0;
}

/*
 * Match the first line of the buffer against a regular expression, setting
 * the first matching majormode, if any.
 */
void
setm_by_preamble(register BUFFER *bp)
{
	LINE *lp = get_preamble(bp);

	if (lp != 0
	 && my_majormodes != 0) {
		size_t n = 0;
		int savecase = ignorecase;
#if OPT_CASELESS
		ignorecase = TRUE;
#else
		ignorecase = FALSE;
#endif

		for (n = 0; my_majormodes[n].name != 0; n++) {
			if (my_majormodes[n].flag) {
				regexp *exp = get_mm_rexp(n, MVAL_PREAMBLE);
				if (exp != 0
				 && lregexec(exp, lp, 0, llength(lp))) {
					attach_mmode(bp, my_majormodes[n].name);
					break;
				 }
			}
		}
		ignorecase = savecase;
	}
}

void
set_submode_val(const char *name, int n, int value)
{
	MAJORMODE *p;
	TRACE(("set_majormode_val(%s, %d, %d)\n", name, n, value))
	if ((p = lookup_mm_data(name)) != 0) {
		p->mb.bv[n].v.i = value;
		make_local_val(p->mb.bv, n);
	}
}

void
set_majormode_rexp(const char *name, int n, const char *r)
{
	MAJORMODE *p;
	TRACE(("set_majormode_rexp(%s, %d, %s)\n", name, n, r))
	if ((p = lookup_mm_data(name)) != 0)
		set_qualifier(m_valnames+n, p->mm.mv + n, r);
}
#endif /* OPT_MAJORMODE */

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

#if NO_LEAKS
void
mode_leaks(void)
{
#if OPT_ENUM_MODES && OPT_COLOR
	FreeAndNull(my_colors);
	FreeAndNull(my_hilite);
#endif

#if OPT_EVAL || OPT_MAJORMODE
	FreeAndNull(my_varmodes);
#endif
#if OPT_MAJORMODE
	while (my_majormodes != 0 && my_majormodes->name != 0) {
		char temp[NSTRING];
		free_majormode(strcpy(temp, my_majormodes->name));
	}
	FreeAndNull(my_majormodes);

	FreeAndNull(major_g_vals);
	FreeAndNull(major_l_vals);
	FreeAndNull(my_mode_list);
	FreeAndNull(major_valnames);
#endif
}
#endif /* NO_LEAKS */
