Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: f0b27d1210
Fetching contributors…

Cannot retrieve contributors at this time

1927 lines (1737 sloc) 46.991 kB
/* options.c */
/* Copyright 1995 by Steve Kirkendall */
/* This file contains functions which manipulate options.
*
* If compiled with -DTRY, it will include a main() function which can be
* used to test these functions.
*/
#include "elvis.h"
#ifdef FEATURE_RCSID
char id_options[] = "$Id: options.c,v 2.79 2004/03/21 23:24:41 steve Exp $";
#endif
#ifndef OPT_MAXCOLS
# define OPT_MAXCOLS 7
#endif
/* This data type is used to record a collection of options that were added
* via a call to optinsert().
*/
typedef struct domain_s
{
struct domain_s *next; /* next domain in a linked list */
char *name; /* domain name */
int nopts; /* number of options in this domain */
OPTDESC *desc; /* descriptions */
OPTVAL *val; /* option values */
} OPTDOMAIN;
/* This data type is used to collect the names & values of options which are
* supposed to be output.
*/
typedef struct optout_s
{
struct optout_s *next; /* another option to be output */
OPTDOMAIN *dom; /* domain of option to be output */
int idx; /* index of option to be output */
int width; /* width of name+value */
} OPTOUT;
/* This is used for storing the names and values of :local options, so they can
* be restored when the script/alias is done.
*/
typedef struct optstk_s
{
struct optstk_s *next; /* another stack entry, or NULL */
OPTDESC *desc; /* descriptions of options */
OPTVAL *val; /* values of options */
int i; /* index of the value being stored */
ELVBOOL wasset; /* was the OPT_SET flag set originally? */
CHAR *value; /* original value of the option, as a string */
BUFFER buffer; /* original bufdefault -- this might not be */
/* valid anymore when values get restored */
} OPTSTK;
/* This stores the args of a ":set gui.options..." command line, where "gui"
* is anything other than the current user interface.
*/
typedef struct optforeign_s
{
struct optforeign_s *next; /* some other options */
CHAR *args; /* the arguments of the :set command */
} OPTFOREIGN;
#if USE_PROTOTYPES
static ELVBOOL optshow(char *name);
static void optoutput(ELVBOOL domain, ELVBOOL all, ELVBOOL set, CHAR *outbuf, size_t outsize);
# ifdef FEATURE_MISC
static void savelocal(OPTDESC *desc, OPTVAL *val, int i);
static OPTSTK *restorelocal(OPTSTK *item);
# endif
#endif
/* head of the list of current option domains */
static OPTDOMAIN *head;
#ifdef FEATURE_MISC
/* stack of local options */
static OPTSTK *stack = (OPTSTK *)1;
# ifdef FEATURE_MKEXRC
/* list of foreign GUI settings. Note that we also store a tail pointer,
* so we can easily add items to the end of the list. This is important
* because sometimes it matters which option gets set first; e.g., xll.font
* must be set before x11.italicfont.
*/
static OPTFOREIGN *foreignhead, *foreigntail;
# endif
#endif
/* Check a number's validity. If valid & different, then set val and return
* 1; if valid & same, then return 0; else give error message and return -1.
*/
int optisnumber(desc, val, newval)
OPTDESC *desc; /* description of the option */
OPTVAL *val; /* value of the option */
CHAR *newval;/* value the option should have (as a string) */
{
long min, max, value;
/* convert value to binary */
if (!calcnumber(newval))
{
msg(MSG_ERROR, "[s]$1 requires a numeric value", desc->longname);
return -1;
}
value = CHAR2long(newval);
/* compare against range string */
if (desc->limit)
{
sscanf(desc->limit, "%ld:%ld", &min, &max);
if (value < min || value > max)
{
msg(MSG_ERROR, "[sdd]$1 must be between $2 and $3", desc->longname, min, max);
return -1;
}
}
/* same value as before? */
if (val->value.number == value)
{
return 0;
}
/* store the value */
val->value.number = value;
return 1;
}
/* Check a strings validity (all are valid) and set the option. Return 1
* if different, or 0 if same.
*/
int optisstring(desc, val, newval)
OPTDESC *desc; /* description of the option */
OPTVAL *val; /* value of the option */
CHAR *newval;/* value the option should have (as a string) */
{
/* if value is the same, do nothing */
if (val->value.string && !CHARcmp(val->value.string, newval))
{
return 0;
}
/* free the old string, if necessary */
if (val->value.string && (val->flags & OPT_FREE))
{
safefree(val->value.string);
}
/* store a copy of the new string */
val->value.string = CHARkdup(newval);
val->flags |= OPT_FREE;
return 1;
}
/* Check a strings validity (all are valid) and set the option. Return 1
* if different, or 0 if same.
*/
int optispacked(desc, val, newval)
OPTDESC *desc; /* description of the option */
OPTVAL *val; /* value of the option */
CHAR *newval;/* value the option should have (as a string) */
{
CHAR *scan, *next;
ELVBOOL takesvalue;
ELVBOOL hasvalue;
CHAR name[50];
char *errormsg;
/* first make sure there are no duplicates */
for (scan = newval; *scan; scan = next)
{
next = calcelement(newval, scan);
if (next < scan)
{
errormsg = "[SS]$1.$2 appears more than once";
goto Error;
}
while (*next && *next++ != ',')
{
}
}
/* make sure each field is legal, and has a value if it should,
* or doesn't have a value if it shouldn't
*/
for (scan = newval; *scan; scan = next)
{
/* locate this field in limit */
next = calcelement(toCHAR(desc->limit), scan);
if (!next)
{
errormsg = "[SS]$2 is unrecognized in $1";
goto Error;
}
/* determine whether this field takes a value */
takesvalue = (ELVBOOL)(*next == ':');
/* advance to next field in newval */
hasvalue = ElvFalse;
next = scan;
while (*next && *next++ != ',')
{
if (*next == ':')
hasvalue = ElvTrue;
}
/* complain if it has a value and shouldn't, or
* vice versa.
*/
if (takesvalue != hasvalue)
{
/* output the error message */
if (takesvalue)
errormsg = "[SS]$1.$2 requires a value";
else
errormsg = "[SS]$1.$2 does not take a value";
goto Error;
}
}
/* the value is valid. store it like a string */
return optisstring(desc, val, newval);
Error:
/* an error was detected. "scan" points to field name, and "errormsg"
* is an appropriate error message, with [SS] arguments.
*/
/* extract the field name */
next = name;
while (next < &name[sizeof name - 1]
&& *scan != ',' && *scan != ':' && *scan)
{
*next++ = *scan++;
}
*next = '\0';
/* output the error message */
msg(MSG_ERROR, errormsg, desc->longname, name);
return -1;
}
/* Check a string's validity against a space-delimited list of legal values.
* If valid & different, then set val to the string's first character, and
* return 1; if valid & same, then return 0; else give error message and
* return -1. Note that each acceptable valid string must begin with a
* unique character for this to work.
*/
int optisoneof(desc, val, newval)
OPTDESC *desc; /* description of the option */
OPTVAL *val; /* value of the option */
CHAR *newval;/* value the option should have (as a string) */
{
int len;
char *scan;
assert(desc->limit != NULL);
/* compute the length of newval */
len = CHARlen(newval);
if (len <= 0)
{
goto NoMatch;
}
/* compare against each legal value */
for (scan = desc->limit; scan - 1 != NULL; scan = strchr(scan, ' ') + 1)
{
/* does it match this value? */
if (!CHARncmp(newval, toCHAR(scan), (size_t)len))
{
/* yes! Either save it & return 1, or just return 0 */
if ((char)*newval != val->value.character)
{
val->value.character = (char)*newval;
return 1;
}
return 0;
}
}
/* no match. Give an error message and exit */
NoMatch:
msg(MSG_ERROR, "[ss]$1 must be one of {$2}", desc->longname, desc->limit);
return -1;
}
/* Check if a string is an acceptable list of tabstop positions, and update an
* option if they are. Return 1 for success, -1 if error.
*/
int optistab(desc, val, newval)
OPTDESC *desc; /* description of the option */
OPTVAL *val; /* value of the option */
CHAR *newval;/* value the option should have (as a string) */
{
int i, j = 0;
long width, total;
short *tab;
/* if no new value, then use the limit value. If there is no limit,
* then discard the settings.
*/
if (!newval || !*newval)
{
newval = toCHAR(desc->limit);
if (!newval)
{
if (!val->value.tab)
return 0;
if (val->flags & OPT_FREE)
safefree(val->value.tab);
val->value.tab = NULL;
return 1;
}
}
/* check the values, and compute the overall width for all but the last
* term.
*/
for (i = 0, width = total = 0L; ; i++)
{
if (elvdigit(newval[i]))
width = width * 10 + newval[i] - '0';
else if (newval[i] == ',' || !newval[i])
{
if (width < 1 || (short)width != (long)width)
{
msg(MSG_ERROR, "[s]bad width in $1 list", desc->longname);
return -1;
}
if (!newval[i])
break;
total += width;
width = 0L;
}
else
{
msg(MSG_ERROR, "[s]value of $1 should be comma-delimited numbers", desc->longname);
return -1;
}
}
/* free the old list, if any, and allocate a new list */
if (val->value.tab && (val->flags & OPT_FREE))
safefree(val->value.tab);
tab = val->value.tab = (short *)safekept((int)(2 + total), sizeof(short));
val->flags |= OPT_FREE;
/* stuff the values into the array */
tab[0] = (short)total;
tab[1] = (short)width;
for (i = 0, width = total = 0L; newval[i]; i++)
{
if (elvdigit(newval[i]))
width = width * 10 + newval[i] - '0';
else
{
total += width;
tab[2 + (int)total - 1] = 1;
width = 0L;
}
}
for (i = tab[0]; --i >= 0;)
{
if (tab[2 + i] == 1)
j = 1;
else
tab[2 + i] = ++j;
}
return 1;
}
/* convert a "number" value to a string */
CHAR *optnstring(desc, val)
OPTDESC *desc; /* description of the option */
OPTVAL *val; /* value of the option */
{
static char buf[30];
/* convert the value to a string */
sprintf(buf, "%ld", val->value.number);
return toCHAR(buf);
}
/* convert a "string" value to a string. For NULL strings, return "". */
CHAR *optsstring(desc, val)
OPTDESC *desc; /* description of the option */
OPTVAL *val; /* value of the option */
{
if (val->value.string)
return val->value.string;
else
return toCHAR("");
}
/* convert a "one of" value to a string */
CHAR *opt1string(desc, val)
OPTDESC *desc; /* description of the option */
OPTVAL *val; /* value of the option */
{
static CHAR buf[30], *build;
char *scan;
/* locate the current value in the limit string */
scan = desc->limit;
assert(scan != NULL);
while (*scan != val->value.character)
{
scan = strchr(scan, ' ');
assert(scan != NULL);
scan++;
}
/* copy the value to buf, and terminate it with a NUL */
for (build = buf; *scan && *scan != ' '; )
{
*build++ = *scan++;
}
*build = '\0';
return buf;
}
/* convert a "tab" value to a string */
CHAR *opttstring(desc, val)
OPTDESC *desc; /* description of the option */
OPTVAL *val; /* value of the option */
{
static CHAR buf[100];
CHAR *build;
short *tab;
int i;
/* get the value */
tab = val->value.tab;
/* if no value, return "" */
if (!tab)
{
buf[0] = '\0';
return buf;
}
/* build a list of specific tabstops */
for (build = buf, i = 0; i < tab[0]; i += tab[2 + i])
{
if (build != buf)
*build++ = ',';
long2CHAR(build, (long)tab[2 + i]);
while (*++build)
{
}
}
/* add the repeating width */
if (build != buf)
*build++ = ',';
long2CHAR(build, (long)tab[1]);
return buf;
}
/* Delete the options whose values are stored starting at val. */
void optdelete(val)
OPTVAL val[]; /* array of values to delete */
{
OPTDOMAIN *scan, *lag;
assert(head != (OPTDOMAIN *)0);
/* locate the domain in the list */
for (scan = head, lag = (OPTDOMAIN *)0;
scan && scan->val != val;
lag = scan, scan = scan->next)
{
}
/* if not found, then do nothing */
if (!scan)
return;
/* remove the domain from the list */
if (lag)
{
lag->next = scan->next;
}
else
{
head = scan->next;
}
/* free the domain structure */
safefree(scan);
}
/* Add options to the list known to :set. desc is an array of
* option descriptions, as described below. val is a parallel
* array where the option values are stored. nopts is the number
* of options being added.
*
* Descriptions and values are stored separately to support the
* situation multiple items such as buffers must have their own
* values for the same options.
*/
void optinsert(domain, nopts, desc, val)
char *domain; /* name of this set of options */
int nopts; /* number of options being inserted */
OPTDESC desc[]; /* descriptions of options */
OPTVAL val[]; /* values of options */
{
OPTDOMAIN *newp;
/* create a new domain structure */
newp = (OPTDOMAIN *)safekept(1, sizeof(OPTDOMAIN));
assert(newp != (OPTDOMAIN *)0);
newp->name = domain,
newp->nopts = nopts;
newp->desc = desc;
newp->val = val;
/* insert it at the start of the list, so its values take precedence
* over any other options that may have been declared with the same
* name.
*/
newp->next = head;
head = newp;
}
/* This function calls safefree() on any values which have the OPT_FREE
* flag set. This is handy when you intend to free a struct which contains
* some option values.
*/
void optfree(nopts, vals)
int nopts; /* number of options */
OPTVAL *vals; /* values of options */
{
int i;
for (i = 0; i < nopts; i++)
{
if (vals[i].flags & OPT_FREE)
{
safefree(vals[i].value.pointer);
}
}
}
/* This function sets the "show" flag for a given option. Returns ElvTrue if
* successful, or ElvFalse if the option doesn't exist.
*/
static ELVBOOL optshow(name)
char *name; /* name of an option that should be shown */
{
OPTDOMAIN *dom;
ELVBOOL ret = ElvFalse;
int i;
/* for each domain of options... */
for (dom = head; dom; dom = dom->next)
{
/* for each option in that domain... */
for (i = 0; i < dom->nopts; i++)
{
/* if this is the one we're looking for... */
if (!strcmp(dom->desc[i].longname, name)
|| !strcmp(dom->desc[i].shortname, name)
|| !strcmp(dom->name, name))
{
dom->val[i].flags |= OPT_SHOW;
ret = ElvTrue;
}
}
}
/* complain if unknown */
if (!ret)
{
msg(MSG_ERROR, "[s]bad option name $1", name);
}
return ret;
}
/* This function collects option names & values, sorts them, puts them into
* columns, and outputs them. Parameters are:
* domain - include domain names as part of option name?
* all - output all options?
* set - output all options which have been set?
* (If neither "all" nor "set" then only options which were
* touched by a previous optshow() are output.)
*/
static void optoutput(domain, all, set, outbuf, outsize)
ELVBOOL domain; /* if ElvTrue, include domain names */
ELVBOOL all; /* if ElvTrue, output all non-hidden options */
ELVBOOL set; /* if ElvTrue, output all changed options */
CHAR *outbuf; /* where to place the values */
size_t outsize; /* size of outbuf */
{
OPTOUT *out; /* list of options to be output */
OPTOUT *scan, *lag; /* used for scanning through "out" list */
OPTOUT *newp; /* a new OPTOUT to be inserted into list */
OPTDOMAIN *dom; /* used for scanning through domains */
int i, j, k; /* used for scanning through opts in a domain */
int cmp; /* results of comparison */
int nshown; /* number of options to show */
int maxwidth; /* width of widest item */
int ncols, nrows; /* number of columns, and items per column */
struct
{
OPTOUT *opt; /* first option in a column */
int width; /* width of the column */
} colinfo[OPT_MAXCOLS];
CHAR *str, *build;
/* if no output buffer, then do nothing */
if (!outbuf)
return;
/* start with an empty list */
out = (OPTOUT *)0;
nshown = 0;
maxwidth = 0;
*outbuf = '\0';
/* For each domain... */
for (dom = head; dom; dom = dom->next)
{
/* For each option in the domain... */
for (i = 0; i < dom->nopts; dom->val[i++].flags &= ~OPT_SHOW)
{
/* Skip if we aren't supposed to output this option */
if (!all || !set)
{
if ((all || set) && dom->val[i].flags & OPT_HIDE)
continue;
if (!all && !(dom->val[i].flags & (set ? OPT_SET : OPT_SHOW)))
continue;
}
/* See where this should be inserted into the output
* list. Beware of duplicates; keep only first of each.
*/
for (scan = out, lag = (OPTOUT *)0, cmp = 1;
scan && (cmp = strcmp(scan->dom->desc[scan->idx].longname, dom->desc[i].longname)) < 0;
lag = scan, scan = scan->next)
{
}
if (cmp == 0)
{
continue;
}
/* create a new OPTOUT structure for this option */
newp = (OPTOUT *)safealloc(1, sizeof(OPTOUT));
newp->dom = dom;
newp->idx = i;
if (domain) /* including domain name? */
newp->width = strlen(dom->name) + 1 +
strlen(dom->desc[i].shortname);
else
newp->width = strlen(dom->desc[i].longname);
if (dom->desc[i].isvalid) /* non-boolean? */
{
newp->width += 1;
if (dom->desc[i].asstring)
{
cmp = CHARlen((*dom->desc[i].asstring)(&dom->desc[i], &dom->val[i]));
if (newp->width + cmp > o_optionwidth
&& newp->width < o_optionwidth - 3
&& (dom->val[i].flags & OPT_SHOW) == 0)
newp->width = o_optionwidth;
else
newp->width += cmp;
}
}
else if (!dom->val[i].value.boolean)
{
newp->width += 2;
}
/* is this the widest value so far? */
if (newp->width > maxwidth)
{
maxwidth = newp->width;
}
/* insert the new OPTOUT into the list */
if (lag)
{
newp->next = lag->next;
lag->next = newp;
}
else
{
newp->next = out;
out = newp;
}
nshown++;
}
}
/* if nothing to show, then exit */
if (nshown == 0)
{
return;
}
/* try to use as many columns as possible */
for (ncols = (nshown > OPT_MAXCOLS ? OPT_MAXCOLS : nshown); ; ncols--)
{
/* how many options would go in each column? */
nrows = (nshown + ncols - 1) / ncols;
/* figure out the width of each column */
for (scan = out, i = 0; i < ncols; i++)
{
colinfo[i].opt = scan;
colinfo[i].width = 0;
for (j = 0; j < nrows && scan; j++, scan = scan->next)
{
/* if this is the widest so far, widen col */
if (scan->width > colinfo[i].width)
{
colinfo[i].width = scan->width;
}
}
colinfo[i].width += 2;
}
/* if the total width is narrow enough, then use it */
for (j = -2, i = 0; i < ncols; i++)
{
j += colinfo[i].width;
}
if (ncols == 1 || j < (windefault ? o_columns(windefault) : 80) - 1)
{
break;
}
}
/* if the list is too large to fit in the output buffer, then just
* return an empty string.
*/
if ((size_t)j * (size_t)nrows >= outsize)
{
/* free the list */
for (scan = out; scan; scan = lag)
{
lag = scan->next;
safefree(scan);
}
CHARncpy(outbuf, toCHAR("too big!\n"), outsize);
return;
}
/* show 'em */
for (i = 0; i < nrows; i++)
{
for (j = 0; j < ncols && colinfo[j].opt; j++)
{
/* include the domain name, if we're supposed to */
scan = colinfo[j].opt;
if (domain)
{
(void)CHARcat(outbuf, scan->dom->name);
(void)CHARcat(outbuf, ".");
}
/* booleans are special... */
if (!scan->dom->desc[scan->idx].isvalid)
{
if (!scan->dom->val[scan->idx].value.boolean)
{
(void)CHARcat(outbuf, "no");
}
(void)CHARcat(outbuf, domain
? scan->dom->desc[scan->idx].shortname
: scan->dom->desc[scan->idx].longname);
}
else
{
str = (CHAR *)(domain
? scan->dom->desc[scan->idx].shortname
: scan->dom->desc[scan->idx].longname);
(void)CHARcat(outbuf, str);
(void)CHARcat(outbuf, toCHAR("="));
cmp = CHARlen(str) + 1;
str = (*scan->dom->desc[scan->idx].asstring)(
&scan->dom->desc[scan->idx],
&scan->dom->val[scan->idx]);
if (cmp + CHARlen(str) > (unsigned)scan->width)
{
/* value too long, so just show part */
build = &outbuf[CHARlen(outbuf)];
for (; cmp < scan->width - 3; cmp++)
*build++ = *str++;
*build++ = '.';
*build++ = '.';
*build++ = '.';
*build++ = '\0';
}
else
/* show the whole value */
(void)CHARcat(outbuf, str);
}
/* pad to max width, except at end of column */
if (j + 1 == ncols || !colinfo[j + 1].opt)
{
(void)CHARcat(outbuf, "\n");
}
else
{
for (k = colinfo[j].width - scan->width; k > 0; k--)
{
(void)CHARcat(outbuf, " ");
}
}
/* next time, use the next option */
colinfo[j].opt = colinfo[j].opt->next;
}
}
/* free the list */
for (scan = out; scan; scan = lag)
{
lag = scan->next;
safefree(scan);
}
}
/* This function returns the value of an option, as a statically allocated,
* nul-terminated string. For booleans, it returns "true" or "false". For
* invalid option names, it returns a NULL pointer.
*
* If the desc parameter is non-NULL, then the pointer that it refers to will
* be altered to point to the option's OPTDESC struct.
*/
CHAR *optgetstr(name, desc)
CHAR *name; /* NUL-terminated name */
OPTDESC **desc; /* where to store a pointer to the OPTDESC struct */
{
OPTDOMAIN *dom; /* used for scanning through domains */
int i; /* used for scanning through opts in a domain */
/* For each domain... */
for (dom = head; dom; dom = dom->next)
{
/* For each option in the domain... */
for (i = 0; i < dom->nopts; i++)
{
/* skip options with the wrong name */
if (strcmp(dom->desc[i].longname, tochar8(name))
&& strcmp(dom->desc[i].shortname, tochar8(name)))
{
continue;
}
/* if the caller wants to know the OPTDESC, tell it */
if (desc)
*desc = &dom->desc[i];
/* convert it */
if (dom->desc[i].isvalid) /* non-boolean? */
{
if (dom->desc[i].asstring)
{
return (CHAR *)(*dom->desc[i].asstring)(&dom->desc[i], &dom->val[i]);
}
return (CHAR *)"";
}
else if (dom->val[i].value.boolean)
{
return o_true;
}
else
{
return o_false;
}
}
}
/* if we get here, then we didn't find the option */
return (CHAR *)0;
}
#ifdef FEATURE_AUTOCMD
/* Trigger OptChanged and/or OptSet events on an option. You can leave either
* name or desc set to NULL, but not both. If you have the OPTDESC pointer
* handy, then you should pass that and use NULL for the name; otherwise pass
* the option's long name and use NULL for desc. Either way, you must pass a
* valid "val" pointer.
*/
void optautocmd(name, desc, val)
char *name; /* option's long name, or NULL t use desc & val */
OPTDESC *desc; /* description of option, if known */
OPTVAL *val; /* value of option */
{
CHAR noname[30];
/* if we have a name, look it up */
if (name && !optgetstr(toCHAR(name), &desc))
msg(MSG_FATAL, "[s]bufautocmd($1...) called for bad option name", name);
/* if no events for this option, then do nothing */
if (!desc->event)
return;
/* if supposed to send an event, then do that */
if (desc->event)
{
/* "OptChanged" */
(void)auperform(windefault, ElvFalse, NULL, AU_OPTCHANGED,
toCHAR(desc->longname));
/* "OptSet" */
if (!desc->asstring)
{
noname[0] = '\0';
if (!val->value.boolean)
CHARcpy(noname, "no");
CHARcat(noname, desc->longname);
(void)auperform(windefault, ElvFalse, NULL,
AU_OPTSET, noname);
}
}
}
#endif
/* This function assigns a new value to an option. For booleans, it expects
* "true" or "false". For invalid option names or inappropriate values it
* outputs an error message and returns ElvFalse. If the value is NULL it
* returns ElvFalse without issueing an error message, on the assumption that
* whatever caused the NULL pointer already issued a message.
*/
ELVBOOL optputstr(name, value, bang)
CHAR *name; /* NUL-terminated name */
CHAR *value; /* NUL-terminated value */
ELVBOOL bang; /* don't set the OPT_SET flag? */
{
OPTDOMAIN *dom; /* used for scanning through domains */
int i; /* used for scanning through opts in a domain */
ELVBOOL ret; /* return code */
WINDOW w;
/* For each domain... */
for (dom = head; dom; dom = dom->next)
{
/* For each option in the domain... */
for (i = 0; i < dom->nopts; i++)
{
/* skip options with the wrong name */
if (strcmp(dom->desc[i].longname, tochar8(name))
&& strcmp(dom->desc[i].shortname, tochar8(name)))
{
continue;
}
/* if the option is locked, then fail */
if (dom->val[i].flags & OPT_LOCK)
{
if (!bang)
msg(MSG_ERROR, "[S]$1 is locked", name);
return ElvFalse;
}
/* if the option is unsafe and "safer" is set, fail */
if (o_security != 'n' /* normal */
&& (dom->val[i].flags & OPT_UNSAFE) != 0)
{
if (!bang)
msg(MSG_ERROR, "[S]unsafe to change $1", name);
return ElvFalse;
}
/* if we haven't save the default value before, and
* this isn't a :set! command (with a bang) then save
* the old value as the default.
*/
if (!dom->desc[i].dflt && !bang)
{
dom->desc[i].dflt = CHARkdup(optgetstr(toCHAR(dom->desc[i].longname), NULL));
}
/* convert it */
ret = ElvTrue;
if (dom->desc[i].isvalid) /* non-boolean? */
{
/* if the value is valid & different and we need
* to call a store function, then call it.
*/
if ((*dom->desc[i].isvalid)(&dom->desc[i], &dom->val[i], value) == 1
&& dom->desc[i].store)
{
ret = (ELVBOOL)((*dom->desc[i].store)(&dom->desc[i], &dom->val[i], value) >= 0);
}
}
else
{
dom->val[i].value.boolean = calctrue(value);
}
/* set or clear the "set" flag */
if (!bang)
{
if (CHARcmp(optgetstr(toCHAR(dom->desc[i].longname), NULL), dom->desc[i].dflt))
dom->val[i].flags |= OPT_SET;
else
dom->val[i].flags &= ~OPT_SET;
}
else
{
/* store the new value as the default */
dom->desc[i].dflt = CHARkdup(optgetstr(toCHAR(dom->desc[i].longname), NULL));
dom->val[i].flags &= ~OPT_SET;
}
#ifdef FEATURE_AUTOCMD
/* if supposed to send an event, then do that */
optautocmd(NULL, &dom->desc[i], &dom->val[i]);
#endif
/* if the "redraw" flag is set, then force redraw */
if (dom->val[i].flags & (OPT_REDRAW|OPT_SCRATCH))
{
for (w = winofbuf(NULL, NULL); w; w = winofbuf(w, NULL))
{
if (dom->val[i].flags & OPT_SCRATCH)
w->di->logic = DRAW_SCRATCH;
else if (w->di->logic == DRAW_NORMAL)
w->di->logic = DRAW_CHANGED;
}
}
return ret;
}
}
/* if we get here, then we didn't find the option */
if (!bang)
msg(MSG_ERROR, "[S]bad option name $1", name);
return ElvFalse;
}
#ifdef FEATURE_MISC
/* Save an option on the :local stack */
static void savelocal(desc, val, i)
OPTDESC *desc; /* descriptions of the option's domain */
OPTVAL *val; /* values of the option's domain */
int i; /* index of the particular option we want to save */
{
OPTSTK *s;
/* create a stack entry */
s = (OPTSTK *)safealloc(1, sizeof(OPTSTK));
/* store the data into it */
s->desc = desc;
s->val = val;
s->i = i;
s->wasset = (ELVBOOL)((val[i].flags & OPT_SET) != 0);
if (desc[i].isvalid) /* non-boolean? */
{
if (desc[i].asstring)
s->value = (*desc[i].asstring)(&desc[i], &val[i]);
else
s->value = toCHAR("");
}
else if (val[i].value.boolean)
{
s->value = o_true;
}
else
{
s->value = o_false;
}
s->value = CHARdup(s->value);
s->buffer = (bufdefault && val == &bufdefault->filename) ? bufdefault : NULL;
/* insert it onto the stack */
s->next = stack;
stack = s;
}
/* Restore a single option from the :local stack, and then free that item and
* return the one after it.
*/
static OPTSTK *restorelocal(item)
OPTSTK *item;
{
OPTSTK *next;
OPTDESC *desc = &item->desc[item->i];
OPTVAL *val = &item->val[item->i];
OPTDOMAIN *d;
ELVBOOL newbool, changed = ElvFalse;
BUFFER b;
/* verify that the option is still active */
for (d = head; d; d = d->next)
if (d->val == item->val)
break;
/* if not active, maybe it is a buffer option that we can make
* active again temporarily.
*/
b = NULL;
if (!d && item->buffer && bufdefault != item->buffer)
{
/* verify that the buffer still exists */
while ((b = buflist(b)) != NULL)
if (b == item->buffer)
break;
if (b)
{
/* It exists. Make a 1-element group for it */
optinsert("local", 1, desc, val);
}
}
/* if active now, then restore it */
if (d)
{
/* restore the value */
if (desc->isvalid) /* non-boolean? */
{
/* if the value is valid & different and we need
* to call a store function, then call it.
*/
if ((*desc->isvalid)(desc, val, item->value) == 1)
{
if (!desc->store || (*desc->store)(desc, val, stack->value) != 0)
changed = ElvTrue;
}
}
else
{
newbool = calctrue(stack->value);
changed = (ELVBOOL)(newbool != val->value.boolean);
val->value.boolean = newbool;
}
/* restore the "was set" flag */
if (stack->wasset)
val->flags |= OPT_SET;
else
val->flags &= ~OPT_SET;
#ifdef FEATURE_AUTOCMD
/* if supposed to send an event, then do that */
if (changed)
optautocmd(NULL, desc, val);
#endif
}
else
{
msg(MSG_WARNING, "[s]can't restore local $1", desc->longname);
}
/* if we had to insert a temporary local group (to restore a buffer
* option after we've switched buffers) then delete that group now.
*/
if (b)
optdelete(val);
/* free it */
next = item->next;
safefree(item->value);
safefree(item);
/* return the item after it */
return next;
}
/* This function serves two purposes. It should be called with NULL at the
* start of a script or alias, to find the current location of the :local
* stack. It should be called again at the end of the script/alias, with the
* value returned by the first call, to restore all local variables from that
* stack.
*/
void *optlocal(level)
void *level; /* level of stack to pop to */
{
/* if NULL, then return the current stack */
if (!level)
return stack;
/* else we need to restore items until we reach the old stack point */
while ((void *)stack != level)
stack = restorelocal(stack);
return NULL;
}
#endif /* FEATURE_MISC */
/* This function parses the arguments to a ":set" command. Returns ElvTrue if
* successful. For errors, it issues an error message via msg() and returns
* ElvFalse. If any options are to be output, their values will be stored in
* a null-terminated string in outbuf.
*
* If outbuf is NULL, then no options will be output, and any options mentioned
* in the "args" string will be pushed onto the :local stack.
*/
ELVBOOL optset(bang, args, outbuf, outsize)
ELVBOOL bang; /* if ElvTrue, any options displayed will include domain */
CHAR *args; /* arguments of ":set" command */
CHAR *outbuf; /* buffer for storing output string */
size_t outsize; /* size of outbuf */
{
CHAR *name; /* name of option in args */
CHAR *value; /* value of the variable */
CHAR *scan; /* used for moving through strings */
CHAR *build; /* used for copying chars from "scan" */
CHAR *prefix; /* pointer to "neg" or "no" at front of a boolean */
ELVBOOL quote; /* boolean: inside '"' quotes? */
OPTDOMAIN *dom; /* used for scanning through domains list */
ELVBOOL ret; /* return code */
WINDOW w;
ELVBOOL b;
int i;
#ifdef FEATURE_MISC
/* check whether this is for a foreign GUI */
for (i = 0; elvalnum(args[i]); i++)
{
}
if (args[i] == '.')
{
/* this is a GUI-specific setting -- For foreign GUI? */
if (strlen(gui->name) != (size_t)i || CHARncmp(toCHAR(gui->name), args, i))
{
/* This is a foreign GUI setting */
# ifdef FEATURE_MKEXRC
/* save the settings */
OPTFOREIGN *f = (OPTFOREIGN *)safealloc(sizeof(OPTFOREIGN), 1);
f->args = CHARdup(args);
f->next = NULL;
if (foreigntail)
foreigntail->next = f;
else
foreignhead = f;
foreigntail = f;
# endif
/* don't do anything else with foreign settings */
return ElvTrue;
}
else /* setting for this GUI */
{
/* skip past the "gui." part of args */
args += i + 1;
}
}
#endif
/* be optimistic. Begin by assuming this will succeed. */
ret = ElvTrue;
/* initialize "prefix" just to avoid a compiler warning */
prefix = NULL;
/* if no arguments, list values of any set options */
if (!*args)
{
optoutput(bang, ElvFalse, ElvTrue, outbuf, outsize);
return ElvTrue;
}
/* if "all", list values of all options */
if (!CHARcmp(args, toCHAR("all")))
{
optoutput(bang, ElvTrue, ElvFalse, outbuf, outsize);
return ElvTrue;
}
if (!CHARcmp(args, toCHAR("everything")))
{
optoutput(bang, ElvTrue, ElvTrue, outbuf, outsize);
return ElvTrue;
}
/* for each assignment... */
for (name = args; *name; name = scan)
{
/* skip whitespace */
while (*name == ' ' || *name == '\t')
{
name++;
}
if (!*name)
break;
/* after the name, find the value (if any) */
for (scan = name; elvalnum(*scan); scan++)
{
}
if (*scan == '=')
{
*scan++ = '\0';
value = build = scan;
for (quote = ElvFalse; *scan && (quote || !elvspace(*scan)); scan++)
{
if (*scan == '"')
{
quote = (ELVBOOL)!quote;
}
else if (*scan == '\\' && scan[1] && !elvalnum(scan[1]))
{
*build++ = *++scan;
}
else
{
*build++ = *scan;
}
}
if (*scan)
scan++;
*build = '\0';
}
else if (*scan == '?')
{
/* mark the option for showing */
*scan++ = '\0';
if (!optshow(tochar8(name)))
ret = ElvFalse;
continue;
}
else /* no "=" or "?" */
{
if (*scan)
{
*scan++ = '\0';
}
value = NULL;
prefix = name;
if (!CHARcmp(name, toCHAR("novice"))
|| !CHARcmp(name, toCHAR("nonascii")))
/* don't check for a "no" prefix */;
else if (prefix[0] == 'n' && prefix[1] == 'o')
name += 2;
else if (prefix[0] == 'n' && prefix[1] == 'e' && prefix[2] == 'g')
name += 3;
}
/* find the option */
for (dom = head; dom; dom = dom->next)
{
/* check each option in this domain */
for (i = 0; i < dom->nopts; i++)
{
if (!CHARcmp(name, toCHAR(dom->desc[i].longname))
|| !CHARcmp(name, toCHAR(dom->desc[i].shortname)))
{
goto BreakBreak;
}
}
}
BreakBreak:
/* if not found, complain */
if (!dom)
{
msg(MSG_ERROR, "[S]bad option name $1", name);
ret = ElvFalse;
continue;
}
/* if non-boolean & we got no value, then assume '?' */
if (dom->desc[i].isvalid && !value)
{
if (prefix == name)
{
if (outbuf)
optshow(tochar8(name));
#ifdef FEATURE_MISC
else if (dom->val[i].flags & OPT_LOCK)
{
msg(MSG_ERROR, "[S]$1 is locked", name);
name = scan;
ret = ElvFalse;
continue;
}
else
savelocal(dom->desc, dom->val, i);
#endif
}
else
{
msg(MSG_ERROR, "[S]$1 is not a boolean option", name);
ret = ElvFalse;
}
continue;
}
/* if option is locked, then complain */
if (dom->val[i].flags & OPT_LOCK)
{
msg(MSG_ERROR, "[S]$1 is locked", name);
name = scan;
ret = ElvFalse;
continue;
}
/* if unsafe, then complain */
if (o_security != 'n' /* normal */
&& (dom->val[i].flags & OPT_UNSAFE) != 0)
{
msg(MSG_ERROR, "[S]unsafe to change $1", name);
name = scan;
ret = ElvFalse;
continue;
}
/* if boolean & we got a value, then complain */
if (!dom->desc[i].isvalid && value)
{
msg(MSG_ERROR, "[S]$1 is a boolean option", name);
name = scan;
ret = ElvFalse;
continue;
}
/* if :set! and the option was already explicitly set, then
* don't set it now.
*/
if (bang && (dom->val[i].flags & OPT_SET))
{
if (o_verbose >= 3)
msg(MSG_INFO, "[s]skipping \":set! $1\" because already set", dom->desc[i].longname);
name = scan;
continue;
}
#ifdef FEATURE_MISC
/* if :local then save its original value */
if (!outbuf)
savelocal(dom->desc, dom->val, i);
#endif
/* if boolean, set it */
if (!dom->desc[i].isvalid)
{
/* set the value */
if (prefix == name)
b = ElvTrue;
else if (prefix[0] == 'n' && prefix[1] == 'o')
b = ElvFalse;
else /* "neg" */
b = (ELVBOOL)!dom->val[i].value.boolean;
/* if there's a store function, then call it */
if (dom->desc[i].store)
{
if ((*dom->desc[i].store)(&dom->desc[i], &dom->val[i], b ? o_true : o_false) < 0)
ret = ElvFalse;
}
else
dom->val[i].value.boolean = b;
}
else /* non-boolean with a value */
{
/* if the value is valid & different and we need to
* call a store function, then call it.
*/
if ((*dom->desc[i].isvalid)(&dom->desc[i], &dom->val[i], value) == 1
&& dom->desc[i].store)
{
if ((*dom->desc[i].store)(&dom->desc[i], &dom->val[i], value) < 0)
ret = ElvFalse;
}
}
/* set the "set" flag */
if (!bang)
{
/* set or clear the "set" flag */
if (!dom->desc[i].dflt
|| CHARcmp(optgetstr(toCHAR(dom->desc[i].longname), NULL), dom->desc[i].dflt))
dom->val[i].flags |= OPT_SET;
else
dom->val[i].flags &= ~OPT_SET;
#ifdef FEATURE_AUTOCMD
/* if supposed to send an event, then do that */
optautocmd(NULL, &dom->desc[i], &dom->val[i]);
#endif
}
/* If the "redraw" flag is set, then force redrawing (or at
* least regeneration) of all windows.
*/
if (dom->val[i].flags & (OPT_REDRAW|OPT_SCRATCH))
{
for (w = winofbuf(NULL, NULL); w; w = winofbuf(w, NULL))
{
if (dom->val[i].flags & OPT_SCRATCH)
w->di->logic = DRAW_SCRATCH;
else if (w->di->logic == DRAW_NORMAL)
w->di->logic = DRAW_CHANGED;
}
}
}
/* show any options which we're supposed to show */
optoutput(bang, ElvFalse, ElvFalse, outbuf, outsize);
return ret;
}
/* This function returns the OPTVAL struct of an option, given a name.
* If the string is not the name of an option, it returns NULL.
*/
OPTVAL *optval(name)
char *name;
{
OPTDOMAIN *dom;
int i;
/* for each domain of options... */
for (dom = head; dom; dom = dom->next)
{
/* for each option in that domain... */
for (i = 0; i < dom->nopts; i++)
{
/* if this is the one we're looking for... */
if (!strcmp(dom->desc[i].longname, name)
|| !strcmp(dom->desc[i].shortname, name))
{
/* return the value struct */
return &dom->val[i];
}
}
}
return NULL;
}
#ifdef FEATURE_AUTOCMD
/* This function is used for setting the "event" flag of a specific option,
* or clearing the "event" flags for all options. Returns the long name of
* the option -- the one that should be checked for in the pattern.
*/
char *optevent(name)
CHAR *name; /* the option that triggers events, NULL to clear all */
{
OPTDOMAIN *dom;
int i;
static char noname[30];
/* for each domain of options... */
for (dom = head; dom; dom = dom->next)
{
/* for each option in that domain... */
for (i = 0; i < dom->nopts; i++)
{
/* if supposed to clear flags... */
if (!name)
{
/* clear it */
dom->desc[i].event = ElvFalse;
}
else if (!strcmp(dom->desc[i].longname, tochar8(name))
|| !strcmp(dom->desc[i].shortname, tochar8(name)))
{
/* set this particular option's flag */
dom->desc[i].event = ElvTrue;
return dom->desc[i].longname;
}
}
}
/* maybe it has a "no" prefix? */
if (!name || CHARncmp(name, toCHAR("no"), 2))
return NULL;
name += 2;
/* for each domain of options... */
for (dom = head; dom; dom = dom->next)
{
/* for each option in that domain... */
for (i = 0; i < dom->nopts; i++)
{
if (!strcmp(dom->desc[i].longname, tochar8(name))
|| !strcmp(dom->desc[i].shortname, tochar8(name)))
{
/* set this particular option's flag */
dom->desc[i].event = ElvTrue;
noname[0] = 'n';
noname[1] = 'o';
strcpy(noname+2, dom->desc[i].longname);
return noname;
}
}
}
/* not found - return NULL */
return NULL;
}
#endif /* FEATURE_AUTOCMD */
#ifdef FEATURE_MKEXRC
/* This function saves the values of some options. It only does this for
* options whose values have been changed, and which are in the "global",
* "buf", "win", "syntax", or "lp" domains.
*/
void optsave(custom)
BUFFER custom; /* where to stuff the "set" commands */
{
OPTDOMAIN *dom;
int i, j, pass;
CHAR *str;
char *tmp;
/* make two passes - one for universal options, and one for options
* which only apply to this GUI.
*/
for (pass = 1; pass <= 2; pass++)
{
/* for each domain of options... */
for (dom = head; dom; dom = dom->next)
{
if (pass == 1)
{
/* ignore if not "global", "buf", "win", "lp",
* or "syntax"
*/
if (strcmp(dom->name, "global")
&& strcmp(dom->name, "buf")
&& strcmp(dom->name, "win")
&& strcmp(dom->name, "lp")
&& strcmp(dom->name, "syntax"))
continue;
}
else /* pass == 2 */
{
/* ignore if not GUI global options or GUI
* window options.
*/
if (strcmp(dom->name, tochar8(o_gui))
&& gui->optdescs != dom->desc)
continue;
}
/* for each option in that domain... */
for (i = 0; i < dom->nopts; i++)
{
/* if its value has been set... */
if ((dom->val[i].flags & (OPT_SET|OPT_LOCK|OPT_NODFLT)) == OPT_SET)
{
/* then add it to the custom buffer */
if (dom->desc[i].asstring)
{
/* Non-Boolean */
str = (*dom->desc[i].asstring)(&dom->desc[i], &dom->val[i]);
tmp = (char *)safealloc(12 + strlen(gui->name) + strlen(dom->desc[i].longname) + 2 * CHARlen(str), sizeof(char));
if (dom->val[i].flags & OPT_UNSAFE)
strcpy(tmp, "try set ");
else
strcpy(tmp, "set ");
if (pass == 2)
{
strcat(tmp, gui->name);
strcat(tmp, ".");
}
strcat(tmp, dom->desc[i].longname);
strcat(tmp, "=");
for (j = strlen(tmp); *str; )
{
if (*str == ' ' || *str == '\t' || *str == '|' || *str == '\\')
{
tmp[j++] = '\\';
}
tmp[j++] = (char)*str++;
}
tmp[j++] = '\n';
tmp[j] = '\0';
}
else
{
/* Boolean */
tmp = (char *)safealloc(13 + strlen(gui->name) + strlen(dom->desc[i].longname), sizeof(char));
if (pass == 1)
sprintf(tmp, "%sset %s%s\n",
(dom->val[i].flags & OPT_UNSAFE) ? "try " : "",
dom->val[i].value.boolean ? "" : "no",
dom->desc[i].longname);
else
sprintf(tmp, "%sset %s.%s%s\n",
(dom->val[i].flags & OPT_UNSAFE) ? "try " : "",
gui->name,
dom->val[i].value.boolean ? "" : "no",
dom->desc[i].longname);
}
bufappend(custom, toCHAR(tmp), 0);
safefree((void *)tmp);
}
}
}
}
# ifdef FEATURE_MISC
/* we also need to save options for foreign GUIs */
{
OPTFOREIGN *f;
for (f = foreignhead; f; f = f->next)
{
bufappend(custom, toCHAR("set "), 4);
bufappend(custom, f->args, 0);
bufappend(custom, toCHAR("\n"), 1);
}
}
# endif
}
#endif /* FEATURE_MKEXRC */
#ifdef FEATURE_COMPLETE
/* This function is used for completing an option name. It searches
* backward from the provided cursor position to collect the characters
* of a partial option name, and then it looks for any known options
* whose name matches that partial name. It returns a statically-allocated
* string containing any new characters that it could match.
*
* It only complete the long names. Short names aren't worth the effort.
*/
CHAR *optcomplete(win, m)
WINDOW win; /* the window where multiple matches are listed */
MARK m; /* the cursor position (end of partial name) */
{
char partial[20];
static CHAR retbuf[100];
int plen; /* length of partial string */
CHAR *cp;
OPTDOMAIN *dom;
char *name;
int i, j, mlen = 0, mcount;
ELVBOOL isbool = ElvFalse;
ELVBOOL getvalue;
/* if the cursor is located immediately after a '=' then skip back
* before the '=' so we can still get the option name. Also set a
* flag we can test later so we know how to treat that name.
*/
getvalue = ElvFalse;
scanalloc(&cp, m);
if (!scanprev(&cp))
{
retbuf[0] = '\t';
retbuf[1] = '\0';
scanfree(&cp);
return retbuf;
}
if (*cp == '=')
getvalue = ElvTrue;
else
scannext(&cp);
/* collect the characters of the partial name */
partial[0] = '\0';
for (plen = 0; scanprev(&cp) && elvalnum(*cp) && plen < QTY(partial)-1; )
{
memmove(partial + 1, partial, QTY(partial) - 1);
partial[0] = *cp;
plen++;
}
scanfree(&cp);
/* if '=' then just get the value */
if (getvalue)
{
memset(retbuf, 0, sizeof retbuf);
cp = optgetstr(toCHAR(partial), NULL);
if (!cp)
{
retbuf[0] = '\t';
retbuf[1] = '\0';
}
else
{
cp = addquotes(toCHAR("\"|"), cp);
if (CHARchr(cp, ' ') || CHARchr(cp, '\t'))
{
retbuf[0] = '"';
CHARncpy(&retbuf[1], cp, QTY(retbuf) - 3);
CHARcat(retbuf, toCHAR("\""));
}
else
{
CHARncpy(retbuf, cp, QTY(retbuf) - 1);
}
safefree(cp);
}
return retbuf;
}
/* look for matches */
for (mcount = 0, dom = head; dom; dom = dom->next)
{
/* check every option in this domain */
for (i = 0; i < dom->nopts; i++)
{
name = dom->desc[i].longname;
if (!dom->desc[i].asstring /* boolean option */
&& plen >= 2
&& partial[0] == 'n' && partial[1] == 'o'
&& !strncmp(name, partial + 2, plen - 2))
{
mcount++;
isbool = ElvTrue;
if (mcount == 1)
{
mlen = strlen(name) + 2;
strcpy(partial + 2, name);
}
else
{
while (strncmp(partial + 2, name, mlen) != 0)
mlen--;
}
}
else if (!strncmp(name, partial, plen))
{
mcount++;
isbool = (ELVBOOL)(!dom->desc[i].asstring);
if (mcount == 1)
{
mlen = strlen(name);
strcpy(partial, name);
}
else
{
while (strncmp(partial, name, mlen) != 0)
mlen--;
}
}
}
}
/* Copy the new matching chars into the return buffer */
i = 0;
if (mcount >= 1)
for (i = 0, j = plen; j < mlen; )
retbuf[i++] = partial[j++];
/* Unless there are multiple matches, add a space or '=' */
if (mcount <= 1)
retbuf[i++] = isbool ? ' ' : '=';
retbuf[i] = '\0';
/* if no chars could be added, then list all matches */
if (!retbuf[0])
{
j = 0;
for (dom = head; dom; dom = dom->next)
for (i = 0; i < dom->nopts; i++)
{
name = dom->desc[i].longname;
if (!dom->desc[i].asstring /* boolean option */
&& plen >= 2
&& partial[0] == 'n' && partial[1] == 'o'
&& !strncmp(name, partial + 2, plen - 2))
{
mlen = strlen(name);
if (2 + j + 1 + mlen >= o_columns(win))
{
drawextext(win, toCHAR("\n"), 1);
j = 0;
}
else if (j > 0)
{
drawextext(win, blanks, 1);
j++;
}
drawextext(win, toCHAR("no"), 2);
drawextext(win, toCHAR(name), mlen);
j += 2 + mlen;
}
else if (!strncmp(name, partial, plen))
{
mlen = strlen(name);
if (j + 1 + mlen >= o_columns(win))
{
drawextext(win, toCHAR("\n"), 1);
j = 0;
}
else if (j > 0)
{
drawextext(win, blanks, 1);
j++;
}
drawextext(win, toCHAR(name), mlen);
j += mlen;
}
}
if (j > 0)
drawextext(win, toCHAR("\n"), 1);
}
/* return the matching chars */
return retbuf;
}
#endif
Jump to Line
Something went wrong with that request. Please try again.