Major tidy up and rework of options tree and set-option/show-options

commands this pushes more of the code into options.c and ties it more
closely to the options table rather than having an unnecessary
split. Also add support for array options (will be used later). Only
(intentional) user visible change is that show-options output is now
passed through vis(3) with VIS_DQ so quotes are escaped.
This commit is contained in:
nicm
2017-01-15 20:48:41 +00:00
parent 404214b0ac
commit 2b0bc9f1c5
8 changed files with 898 additions and 819 deletions

673
options.c
View File

@ -18,6 +18,7 @@
#include <sys/types.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
@ -29,18 +30,72 @@
* a red-black tree.
*/
struct options {
RB_HEAD(options_tree, options_entry) tree;
struct options *parent;
struct option {
struct options *owner;
const char *name;
const struct options_table_entry *tableentry;
union {
char *string;
long long number;
struct grid_cell style;
struct {
const char **array;
u_int arraysize;
};
};
RB_ENTRY(option) entry;
};
static int options_cmp(struct options_entry *, struct options_entry *);
RB_GENERATE_STATIC(options_tree, options_entry, entry, options_cmp);
struct options {
RB_HEAD(options_tree, option) tree;
struct options *parent;
};
static struct option *options_add(struct options *, const char *);
#define OPTIONS_ARRAY_LIMIT 1000
#define OPTIONS_IS_STRING(o) \
((o)->tableentry == NULL || \
(o)->tableentry->type == OPTIONS_TABLE_STRING)
#define OPTIONS_IS_NUMBER(o) \
((o)->tableentry != NULL && \
((o)->tableentry->type == OPTIONS_TABLE_NUMBER || \
(o)->tableentry->type == OPTIONS_TABLE_KEY || \
(o)->tableentry->type == OPTIONS_TABLE_COLOUR || \
(o)->tableentry->type == OPTIONS_TABLE_ATTRIBUTES || \
(o)->tableentry->type == OPTIONS_TABLE_FLAG || \
(o)->tableentry->type == OPTIONS_TABLE_CHOICE))
#define OPTIONS_IS_STYLE(o) \
((o)->tableentry != NULL && \
(o)->tableentry->type == OPTIONS_TABLE_STYLE)
#define OPTIONS_IS_ARRAY(o) \
((o)->tableentry != NULL && \
(o)->tableentry->type == OPTIONS_TABLE_ARRAY)
static int options_cmp(struct option *, struct option *);
RB_GENERATE_STATIC(options_tree, option, entry, options_cmp);
static int
options_cmp(struct options_entry *o1, struct options_entry *o2)
options_cmp(struct option *lhs, struct option *rhs)
{
return (strcmp(o1->name, o2->name));
return (strcmp(lhs->name, rhs->name));
}
static const struct options_table_entry *
options_parent_table_entry(struct options *oo, const char *s)
{
struct option *o;
if (oo->parent == NULL)
fatalx("no parent options for %s", s);
o = options_get_only(oo->parent, s);
if (o == NULL)
fatalx("%s not in parent options", s);
return (o->tableentry);
}
struct options *
@ -54,181 +109,549 @@ options_create(struct options *parent)
return (oo);
}
static void
options_free1(struct options *oo, struct options_entry *o)
{
RB_REMOVE(options_tree, &oo->tree, o);
free((char *)o->name);
if (o->type == OPTIONS_STRING)
free(o->str);
free(o);
}
static struct options_entry *
options_new(struct options *oo, const char *name)
{
struct options_entry *o;
if ((o = options_find1(oo, name)) == NULL) {
o = xmalloc(sizeof *o);
o->name = xstrdup(name);
RB_INSERT(options_tree, &oo->tree, o);
memcpy(&o->style, &grid_default_cell, sizeof o->style);
} else if (o->type == OPTIONS_STRING)
free(o->str);
return (o);
}
void
options_free(struct options *oo)
{
struct options_entry *o, *o1;
struct option *o, *tmp;
RB_FOREACH_SAFE (o, options_tree, &oo->tree, o1)
options_free1(oo, o);
RB_FOREACH_SAFE (o, options_tree, &oo->tree, tmp)
options_remove(o);
free(oo);
}
struct options_entry *
struct option *
options_first(struct options *oo)
{
return (RB_MIN(options_tree, &oo->tree));
}
struct options_entry *
options_next(struct options_entry *o)
struct option *
options_next(struct option *o)
{
return (RB_NEXT(options_tree, &oo->tree, o));
}
struct options_entry *
options_find1(struct options *oo, const char *name)
struct option *
options_get_only(struct options *oo, const char *name)
{
struct options_entry p;
struct option o;
p.name = (char *)name;
return (RB_FIND(options_tree, &oo->tree, &p));
o.name = name;
return (RB_FIND(options_tree, &oo->tree, &o));
}
struct options_entry *
options_find(struct options *oo, const char *name)
struct option *
options_get(struct options *oo, const char *name)
{
struct options_entry *o, p;
struct option *o;
p.name = (char *)name;
o = RB_FIND(options_tree, &oo->tree, &p);
o = options_get_only(oo, name);
while (o == NULL) {
oo = oo->parent;
if (oo == NULL)
break;
o = RB_FIND(options_tree, &oo->tree, &p);
o = options_get_only(oo, name);
}
return (o);
}
struct option *
options_empty(struct options *oo, const struct options_table_entry *oe)
{
struct option *o;
o = options_add(oo, oe->name);
o->tableentry = oe;
return (o);
}
struct option *
options_default(struct options *oo, const struct options_table_entry *oe)
{
struct option *o;
char *cp, *copy, *next;
u_int idx = 0;
o = options_empty(oo, oe);
if (oe->type == OPTIONS_TABLE_ARRAY) {
copy = cp = xstrdup(oe->default_str);
while ((next = strsep(&cp, ",")) != NULL) {
options_array_set(o, idx, next);
idx++;
}
free(copy);
return (o);
}
if (oe->type == OPTIONS_TABLE_STRING)
o->string = xstrdup(oe->default_str);
else if (oe->type == OPTIONS_TABLE_STYLE) {
memcpy(&o->style, &grid_default_cell, sizeof o->style);
style_parse(&grid_default_cell, &o->style, oe->default_str);
} else
o->number = oe->default_num;
return (o);
}
static struct option *
options_add(struct options *oo, const char *name)
{
struct option *o;
o = options_get_only(oo, name);
if (o != NULL)
options_remove(o);
o = xcalloc(1, sizeof *o);
o->owner = oo;
o->name = xstrdup(name);
RB_INSERT(options_tree, &oo->tree, o);
return (o);
}
void
options_remove(struct options *oo, const char *name)
options_remove(struct option *o)
{
struct options_entry *o;
struct options *oo = o->owner;
u_int i;
if ((o = options_find1(oo, name)) != NULL)
options_free1(oo, o);
}
struct options_entry *
options_set_string(struct options *oo, const char *name, int append,
const char *fmt, ...)
{
struct options_entry *o;
va_list ap;
char *s, *value;
va_start(ap, fmt);
xvasprintf(&s, fmt, ap);
va_end(ap);
o = options_find1(oo, name);
if (o == NULL || !append)
value = s;
else {
xasprintf(&value, "%s%s", o->str, s);
free(s);
if (OPTIONS_IS_STRING(o))
free((void *)o->string);
else if (OPTIONS_IS_ARRAY(o)) {
for (i = 0; i < o->arraysize; i++)
free((void *)o->array[i]);
free(o->array);
}
o = options_new(oo, name);
o->type = OPTIONS_STRING;
o->str = value;
RB_REMOVE(options_tree, &oo->tree, o);
free(o);
}
const char *
options_name(struct option *o)
{
return (o->name);
}
const struct options_table_entry *
options_table_entry(struct option *o)
{
return (o->tableentry);
}
const char *
options_array_get(struct option *o, u_int idx)
{
if (!OPTIONS_IS_ARRAY(o))
return (NULL);
if (idx >= o->arraysize)
return (NULL);
return (o->array[idx]);
}
int
options_array_set(struct option *o, u_int idx, const char *value)
{
u_int i;
if (!OPTIONS_IS_ARRAY(o))
return (-1);
if (idx >= OPTIONS_ARRAY_LIMIT)
return (-1);
if (idx >= o->arraysize) {
o->array = xreallocarray(o->array, idx + 1, sizeof *o->array);
for (i = o->arraysize; i < idx + 1; i++)
o->array[i] = NULL;
o->arraysize = idx + 1;
}
if (o->array[idx] != NULL)
free((void *)o->array[idx]);
if (value != NULL)
o->array[idx] = xstrdup(value);
else
o->array[idx] = NULL;
return (0);
}
int
options_array_size(struct option *o, u_int *size)
{
if (!OPTIONS_IS_ARRAY(o))
return (-1);
if (size != NULL)
*size = o->arraysize;
return (0);
}
int
options_isstring(struct option *o)
{
if (o->tableentry == NULL)
return (1);
return (OPTIONS_IS_STRING(o) || OPTIONS_IS_ARRAY(o));
}
const char *
options_tostring(struct option *o, int idx)
{
static char s[1024];
const char *tmp;
if (OPTIONS_IS_ARRAY(o)) {
if (idx == -1)
return (NULL);
if ((u_int)idx >= o->arraysize || o->array[idx] == NULL)
return ("");
return (o->array[idx]);
}
if (OPTIONS_IS_STYLE(o))
return (style_tostring(&o->style));
if (OPTIONS_IS_NUMBER(o)) {
tmp = NULL;
switch (o->tableentry->type) {
case OPTIONS_TABLE_NUMBER:
xsnprintf(s, sizeof s, "%lld", o->number);
break;
case OPTIONS_TABLE_KEY:
tmp = key_string_lookup_key(o->number);
break;
case OPTIONS_TABLE_COLOUR:
tmp = colour_tostring(o->number);
break;
case OPTIONS_TABLE_ATTRIBUTES:
tmp = attributes_tostring(o->number);
break;
case OPTIONS_TABLE_FLAG:
tmp = (o->number ? "on" : "off");
break;
case OPTIONS_TABLE_CHOICE:
tmp = o->tableentry->choices[o->number];
break;
case OPTIONS_TABLE_STRING:
case OPTIONS_TABLE_STYLE:
case OPTIONS_TABLE_ARRAY:
break;
}
if (tmp != NULL)
xsnprintf(s, sizeof s, "%s", tmp);
return (s);
}
if (OPTIONS_IS_STRING(o))
return (o->string);
return (NULL);
}
char *
options_parse(const char *name, int *idx)
{
char *copy, *cp, *end;
if (*name == '\0')
return (NULL);
copy = xstrdup(name);
if ((cp = strchr(copy, '[')) == NULL) {
*idx = -1;
return (copy);
}
end = strchr(cp + 1, ']');
if (end == NULL || end[1] != '\0' || !isdigit((u_char)end[-1])) {
free(copy);
return (NULL);
}
if (sscanf(cp, "[%d]", idx) != 1 || *idx < 0) {
free(copy);
return (NULL);
}
*cp = '\0';
return (copy);
}
struct option *
options_parse_get(struct options *oo, const char *s, int *idx, int only)
{
struct option *o;
char *name;
name = options_parse(s, idx);
if (name == NULL)
return (NULL);
if (only)
o = options_get_only(oo, name);
else
o = options_get(oo, name);
free(name);
if (o != NULL) {
if (OPTIONS_IS_ARRAY(o) && *idx == -1)
return (NULL);
if (!OPTIONS_IS_ARRAY(o) && *idx != -1)
return (NULL);
}
return (o);
}
char *
options_match(const char *s, int *idx, int* ambiguous)
{
const struct options_table_entry *oe, *found;
char *name;
size_t namelen;
name = options_parse(s, idx);
namelen = strlen(name);
found = NULL;
for (oe = options_table; oe->name != NULL; oe++) {
if (strcmp(oe->name, name) == 0) {
found = oe;
break;
}
if (strncmp(oe->name, name, namelen) == 0) {
if (found != NULL) {
*ambiguous = 1;
free(name);
return (NULL);
}
found = oe;
}
}
free(name);
if (found == NULL) {
*ambiguous = 0;
return (NULL);
}
return (xstrdup(found->name));
}
struct option *
options_match_get(struct options *oo, const char *s, int *idx, int only,
int* ambiguous)
{
char *name;
struct option *o;
name = options_match(s, idx, ambiguous);
if (name == NULL)
return (NULL);
*ambiguous = 0;
if (only)
o = options_get_only(oo, name);
else
o = options_get(oo, name);
free(name);
if (o != NULL) {
if (OPTIONS_IS_ARRAY(o) && *idx == -1)
return (NULL);
if (!OPTIONS_IS_ARRAY(o) && *idx != -1)
return (NULL);
}
return (o);
}
const char *
options_get_string(struct options *oo, const char *name)
{
struct options_entry *o;
struct option *o;
if ((o = options_find(oo, name)) == NULL)
o = options_get(oo, name);
if (o == NULL)
fatalx("missing option %s", name);
if (o->type != OPTIONS_STRING)
fatalx("option %s not a string", name);
return (o->str);
}
struct options_entry *
options_set_number(struct options *oo, const char *name, long long value)
{
struct options_entry *o;
o = options_new(oo, name);
o->type = OPTIONS_NUMBER;
o->num = value;
return (o);
if (!OPTIONS_IS_STRING(o))
fatalx("option %s is not a string", name);
return (o->string);
}
long long
options_get_number(struct options *oo, const char *name)
{
struct options_entry *o;
struct option *o;
if ((o = options_find(oo, name)) == NULL)
o = options_get(oo, name);
if (o == NULL)
fatalx("missing option %s", name);
if (o->type != OPTIONS_NUMBER)
fatalx("option %s not a number", name);
return (o->num);
}
struct options_entry *
options_set_style(struct options *oo, const char *name, int append,
const char *value)
{
struct options_entry *o;
struct grid_cell tmpgc;
o = options_find1(oo, name);
if (o == NULL || !append)
memcpy(&tmpgc, &grid_default_cell, sizeof tmpgc);
else
memcpy(&tmpgc, &o->style, sizeof tmpgc);
if (style_parse(&grid_default_cell, &tmpgc, value) == -1)
return (NULL);
o = options_new(oo, name);
o->type = OPTIONS_STYLE;
memcpy(&o->style, &tmpgc, sizeof o->style);
return (o);
if (!OPTIONS_IS_NUMBER(o))
fatalx("option %s is not a number", name);
return (o->number);
}
const struct grid_cell *
options_get_style(struct options *oo, const char *name)
{
struct options_entry *o;
struct option *o;
if ((o = options_find(oo, name)) == NULL)
o = options_get(oo, name);
if (o == NULL)
fatalx("missing option %s", name);
if (o->type != OPTIONS_STYLE)
fatalx("option %s not a style", name);
if (!OPTIONS_IS_STYLE(o))
fatalx("option %s is not a style", name);
return (&o->style);
}
struct option *
options_set_string(struct options *oo, const char *name, int append,
const char *fmt, ...)
{
struct option *o;
va_list ap;
char *s, *value;
va_start(ap, fmt);
xvasprintf(&s, fmt, ap);
va_end(ap);
o = options_get_only(oo, name);
if (o != NULL && append && OPTIONS_IS_STRING(o)) {
xasprintf(&value, "%s%s", o->string, s);
free(s);
} else
value = s;
if (o == NULL && *name == '@')
o = options_add(oo, name);
else if (o == NULL) {
o = options_default(oo, options_parent_table_entry(oo, name));
if (o == NULL)
return (NULL);
}
if (!OPTIONS_IS_STRING(o))
fatalx("option %s is not a string", name);
free(o->string);
o->string = value;
return (o);
}
struct option *
options_set_number(struct options *oo, const char *name, long long value)
{
struct option *o;
if (*name == '@')
fatalx("user option %s must be a string", name);
o = options_get_only(oo, name);
if (o == NULL) {
o = options_default(oo, options_parent_table_entry(oo, name));
if (o == NULL)
return (NULL);
}
if (!OPTIONS_IS_NUMBER(o))
fatalx("option %s is not a number", name);
o->number = value;
return (o);
}
struct option *
options_set_style(struct options *oo, const char *name, int append,
const char *value)
{
struct option *o;
struct grid_cell gc;
if (*name == '@')
fatalx("user option %s must be a string", name);
o = options_get_only(oo, name);
if (o != NULL && append && OPTIONS_IS_STYLE(o))
memcpy(&gc, &o->style, sizeof gc);
else
memcpy(&gc, &grid_default_cell, sizeof gc);
if (style_parse(&grid_default_cell, &gc, value) == -1)
return (NULL);
if (o == NULL) {
o = options_default(oo, options_parent_table_entry(oo, name));
if (o == NULL)
return (NULL);
}
if (!OPTIONS_IS_STYLE(o))
fatalx("option %s is not a style", name);
memcpy(&o->style, &gc, sizeof o->style);
return (o);
}
enum options_table_scope
options_scope_from_flags(struct args *args, int window,
struct cmd_find_state *fs, struct options **oo, char **cause)
{
struct session *s = fs->s;
struct winlink *wl = fs->wl;
const char *target= args_get(args, 't');
if (args_has(args, 's')) {
*oo = global_options;
return (OPTIONS_TABLE_SERVER);
}
if (window || args_has(args, 'w')) {
if (args_has(args, 'g')) {
*oo = global_w_options;
return (OPTIONS_TABLE_WINDOW);
}
if (wl == NULL) {
if (target != NULL)
xasprintf(cause, "no such window: %s", target);
else
xasprintf(cause, "no current window");
return (OPTIONS_TABLE_NONE);
}
*oo = wl->window->options;
return (OPTIONS_TABLE_WINDOW);
} else {
if (args_has(args, 'g')) {
*oo = global_s_options;
return (OPTIONS_TABLE_SESSION);
}
if (s == NULL) {
if (target != NULL)
xasprintf(cause, "no such session: %s", target);
else
xasprintf(cause, "no current session");
return (OPTIONS_TABLE_NONE);
}
*oo = s->options;
return (OPTIONS_TABLE_SESSION);
}
}
void
options_style_update_new(struct options *oo, struct option *o)
{
const char *newname = o->tableentry->style;
struct option *new;
if (newname == NULL)
return;
new = options_get_only(oo, newname);
if (new == NULL)
new = options_set_style(oo, newname, 0, "default");
if (strstr(o->name, "-bg") != NULL)
new->style.bg = o->number;
else if (strstr(o->name, "-fg") != NULL)
new->style.fg = o->number;
else if (strstr(o->name, "-attr") != NULL)
new->style.attr = o->number;
}
void
options_style_update_old(struct options *oo, struct option *o)
{
char newname[128];
int size;
size = strrchr(o->name, '-') - o->name;
xsnprintf(newname, sizeof newname, "%.*s-bg", size, o->name);
options_set_number(oo, newname, o->style.bg);
xsnprintf(newname, sizeof newname, "%.*s-fg", size, o->name);
options_set_number(oo, newname, o->style.fg);
xsnprintf(newname, sizeof newname, "%.*s-attr", size, o->name);
options_set_number(oo, newname, o->style.attr);
}