ftp.nice.ch/pub/next/unix/editor/vile-7.0.N.bs.tar.gz#/vile-7.0.N.bs/modes.c

This is modes.c in view mode; [Download] [Up]

/*
 *
 *	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
 *
 * $Header: /home/tom/src/vile/RCS/modes.c,v 1.81 1997/02/09 19:33:57 tom Exp $
 *
 */

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

#define	NonNull(s)	((s == 0) ? "" : s)
#define	ONE_COL	26
#define	NCOLS	3

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

/*
 * The symbol tables stored in FSM_CHOICES lists are sorted by the 'choice_name'
 * field, with a final null entry so that name-completion works on the lists.
 */
#if OPT_ENUM_MODES
#define ENUM_ILLEGAL   (-2)
#define ENUM_UNKNOWN   (-1)
#define END_CHOICES    { (char *)0, ENUM_ILLEGAL }

typedef struct {
	const char * choice_name;
	int    choice_code;
} FSM_CHOICES;

struct FSM {
	const char * mode_name;
	const FSM_CHOICES * choices;
};
#endif

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

#if OPT_UPBUFF
static	void	relist_settings (void);
#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

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

static void
set_winflags(int glob_vals, int flags)
{
	if (glob_vals) {
		register WINDOW *wp;
		for_each_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) {
	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", names->name, names->type);
	}

	return FALSE;
}

/*
 * Returns the formatted length of a string value.
 */
static int
size_val(const struct VALNAMES *names, struct VAL *values)
{
	register int	n = strlen(names->name) + 3;
	register 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:
		s = values->vp->r->pat;
		break;
	}
	return	n + strlen(NonNull(s));
}

/*
 * Returns a mode-value formatted as a string
 */
const char *
string_mode_val(VALARGS *args)
{
	register const struct VALNAMES *names = args->names;
	register struct VAL     *values = args->local;
	switch(names->type) {
	case VALTYPE_BOOL:
		return values->vp->i ? truem : falsem;
	case VALTYPE_ENUM:
#if OPT_ENUM_MODES
		{
		static	char	temp[20];
		(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:
		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.  Only print
	if the value in the two arrays differs, or the second array is nil */
static int
listvalueset(
char *which,
int nflag,
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,
		padded,
		perline,
		percol,
		total;
	register int j, pass;

	/*
	 * 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++) {
		show[j] = 0;
		if (same_val(names+j, values+j, globvalues ? globvalues+j : 0) != TRUE) {
			if (!any++) {
				if (nflag)
					bputc('\n');
				bprintf("%s:\n", which);
			}
			switch (names[j].type) {
			case VALTYPE_STRING:
			case VALTYPE_REGEX:
				if (size_val(names+j, values+j) > ONE_COL) {
					show[j] += 1;
					passes = 2;
				}
				/* fall-thru */
			default:
				show[j] += 1;
			}
		}
	}
	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[NCOLS+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 (names[j].type == VALTYPE_BOOL) {
				bprintf("%s%s%*P",
					values[j].vp->i ? "  " : "no",
					names[j].name,
					padded, ' ');
			} else {
				VALARGS args;	/* patch */
				args.names  = names+j;
				args.local  = values+j;
				args.global = 0;
				bprintf("  %s=%s%*P",
					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))
			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 dum1, void *ptr)
{
	static	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;
#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

	bprintf("--- \"%s\" settings, if different than globals %*P\n",
			localbp->b_bname, term.t_ncol-1, '-');
	nflag = listvalueset(bb, FALSE, b_valuenames, local_b_vals, global_b_values.bv);
	nflg2 = listvalueset(ww, nflag, w_valuenames, local_w_vals, global_w_values.wv);
	if (!(nflag || nflg2))
	 	bputc('\n');
	bputc('\n');

	bprintf("--- Global settings %*P\n", term.t_ncol-1, '-');
	nflag = listvalueset(gg, nflag, g_valuenames, global_g_values.gv, (struct VAL *)0);
	nflag = listvalueset(bb, nflag, b_valuenames, global_b_values.bv, (struct VAL *)0);
	(void)  listvalueset(ww, nflag, w_valuenames, global_w_values.wv, (struct VAL *)0);
}

/*
 * Set tab size
 */
int
settab(int f, int n)
{
	register WINDOW *wp;
	int val;
	const char *whichtabs;
	if (b_val(curbp, MDCMOD)) {
		val = VAL_C_TAB;
		whichtabs = "C-t";
	} else {
		val = VAL_TAB;
		whichtabs = "T";
	}
	if (f && n >= 1) {
		make_local_b_val(curbp,val);
		set_b_val(curbp,val,n);
		curtabval = n;
		for_each_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);
}

/*
 * 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);
		rp->reg = regcomp(rp->pat, magic);
	}
	return rp;
}

/*
 * Release storage of a REGEXVAL struct
 */
static void
free_regexval(register REGEXVAL *rp)
{
	if (rp != 0) {
		FreeAndNull(rp->pat);
		FreeAndNull(rp->reg);
		free((char *)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:
		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

	/*
	 * These names and codes match the ANSI color codes.
	 */
static const
FSM_CHOICES fsm_color_choices[] = {	/* names of colors */
	{ "black",     0 },
	{ "blue",      4 },
	{ "cyan",      6 },
#if DISP_TERMCAP || DISP_IBMPC	/* FIXME: implement this for all drivers */
	{ "default",   ENUM_UNKNOWN },
#endif
	{ "green",     2 },
	{ "magenta",   5 },
	{ "red",       1 },
	{ "white",     7 },
	{ "yellow",    3 },
	END_CHOICES
	};

static const
FSM_CHOICES fsm_bool_choices[] = {
	{ "false",     FALSE },
	{ "true",      TRUE  },
	END_CHOICES
};

#if OPT_POPUPCHOICE
static const
FSM_CHOICES fsm_popup_choices[] = {
	{ "delayed",   POPUP_CHOICES_DELAYED},
	{ "immediate", POPUP_CHOICES_IMMED},
	{ "off",       POPUP_CHOICES_OFF},
	END_CHOICES
};
#endif

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

#if OPT_FILEBACK
static const
FSM_CHOICES fsm_backupstyle[] = {
	{ "off",       0},
	{ ".bak",      1},
#if SYS_UNIX
	{ "tilde",     2},
	/* "tilde_N_existing", */
	/* "tilde_N", */
#endif
	END_CHOICES
};
#endif

#if OPT_HILITEMATCH
static const
FSM_CHOICES fsm_mono_attributes[] = {
	{ "bold",       VABOLD  },
	{ "color",      VACOLOR },
	{ "italic",     VAITAL  },
	{ "none",       0       },
	{ "reverse",    VAREV   },
	{ "underline",  VAUL    },
	END_CHOICES
};
#endif

static const
struct FSM fsm_tbl[] = {
	{ "*bool",           fsm_bool_choices  },
#if OPT_COLOR
	{ "fcolor",          fsm_color_choices },
	{ "bcolor",          fsm_color_choices },
#endif
#if OPT_POPUPCHOICE
	{ "popup-choices",   fsm_popup_choices },
#endif
#if NEVER
	{ "error",           fsm_error },
#endif
#if OPT_FILEBACK
	{ "backup-style",    fsm_backupstyle },
#endif
#if OPT_HILITEMATCH
	{ "visual-matches",  fsm_mono_attributes },
#endif
};

static int fsm_idx;

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

	for (i = 0; choices[i].choice_name != 0; i++) {
		if (strcmp(name, 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 (names->type == VALTYPE_BOOL) {
		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, int *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,
                        (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;

	struct VAL oldvalue;
	char prompt[NLINE];
	char respbuf[NFILEN];
	int no = !strncmp(cp, "no", 2);
	const char *rp = no ? cp+2 : cp;
	int nval, status = TRUE;
	int unsetting = !setting && !global;

	if (no && (names->type != VALTYPE_BOOL))
		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() == '=')
	 || (names->type != VALTYPE_BOOL && !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 defined(GMD_GLOB) || defined(GVAL_GLOB)
		if (!strcmp(names->name, "glob")
		 && !legal_glob_mode(rp))
		 	return FALSE;
#endif
#if OPT_ENUM_MODES
		 if ((rp = legal_fsm(rp)) == 0)
		    return FALSE;
#endif  
		/* Test after fsm, to allow translation */
		if (names->type == VALTYPE_BOOL) {
			if (!string_to_bool(rp, &setting))
				return FALSE;
		}
	}
#if OPT_HISTORY
	else
		hst_glue(' ');
#endif

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

	if (unsetting) {
		make_global_val(values, globls, 0);
	} else {
		makeLocalVal(values);	/* make sure we point to result! */

		/* we matched a name -- set the value */
		switch(names->type) {
		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:
			values->vp->r = new_regexval(rp, TRUE);
			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)
	 && (names->side_effect != 0)
	 && !(*(names->side_effect))(args, (values==globls), FALSE))
		status = FALSE;

	if (isLocalVal(&oldvalue))
		free_val(names, &oldvalue);
	return status;
}

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

	s = liststuff(SETTINGS_BufName, FALSE, makemodelist,0,(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(int c, char *buf, int *pos)
{
	return kbd_complete(FALSE, c, buf, pos,
		(char *)&all_modes[0], sizeof(all_modes[0]));
}

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

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

	for (class = 0; class < 3; class++) {
		switch (class) {
		default: /* universal modes */
			args->names  = g_valuenames;
			args->global = global_g_values.gv;
			args->local  = (global != FALSE)
				? args->global
				: (struct VAL *)0;
			break;
		case 1:	/* buffer modes */
			args->names  = b_valuenames;
			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_valuenames;
			args->global = global_w_values.wv;
			args->local  = (global == TRUE)
				? args->global
				: ((curwp != 0)
					? curwp->w_values.wv
					: (struct VAL *)0);
			break;
		}
		if (args->local != 0) {
			for (j = 0; args->names[j].name != NULL; j++) {
				if (!strcmp(rp, args->names[j].name)
				 || !strcmp(rp, args->names[j].shortname)) {
					args->names  += j;
					args->local  += j;
					args->global += j;
					return TRUE;
				}
			}
		}
	}
	return FALSE;
}

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

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

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

	if ((s = find_mode(cbuf, global, &args)) != TRUE) {
#if OPT_EVAL
		if (!global && (s = find_mode(cbuf, TRUE, &args)) == TRUE) {
			mlforce("[Not a local mode: \"%s\"]", cbuf);
			return FALSE;
		}
		return set_variable(cbuf);
#else
		mlforce("[Not a legal set option: \"%s\"]", cbuf);
#endif
	} else if ((s = adjvalueset(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;

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

#if OPT_UPBUFF
	/* if the settings are up, redisplay them */
	relist_settings();
#endif /* OPT_UPBUFF */

	if (curbp) {
		curtabval = tabstop_val(curbp);
		curswval = shiftwid_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, int n)	/* prompt and set an editor mode */
{
	return adjustmode(TRUE, FALSE);
}

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

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

/* ARGSUSED */
int
delglobmode(int f, int n)	/* 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(CHGD_ARGS)
{
	if (glob_vals)
		sortlistbuffers();
	return TRUE;
}

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

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

#if OPT_COLOR
int
chgd_color(CHGD_ARGS)
{
	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;
}
#endif	/* OPT_COLOR */

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

	/* Change "fences" mode */
/*ARGSUSED*/
int
chgd_fences(CHGD_ARGS)
{
	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(CHGD_ARGS)
{
	/* 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(CHGD_ARGS)
{
	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(CHGD_ARGS)
{
	if (!testing) {
		set_winflags(glob_vals, WFSTAT);
	}
	return TRUE;
}

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

	/* Change the working mode */
#if OPT_WORKING
/*ARGSUSED*/
int
chgd_working(CHGD_ARGS)
{
	if (glob_vals)
		imworking(0);
	return TRUE;
}
#endif

	/* Change the xterm-mouse mode */
/*ARGSUSED*/
int
chgd_xterm(CHGD_ARGS)
{
#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(CHGD_ARGS)
{
	if (!testing)
		attrib_matches();
	return TRUE;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.