tl;dr This diagram is all you need to know
Key |
Configuration begins with the ConfigSet. It contains:
First we create a ConfigSet and register some Types. Each Type is responsible for one data type. Each Type has:
Name | Human-readable name |
---|---|
setter/getter | Set/get the variable as a string |
nsetter/ngetter | Setter/getter as a native type, e.g. struct Address
|
resetter | Reset variable to factory settings |
destructor | Release the resources |
Each Config Type implements the struct ConfigSetType
interface
int string_set (const struct ConfigSet *cs, void *var, struct ConfigDef *cdef, const char *value, struct Buffer *err);
int string_get (const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef, struct Buffer *result);
int native_set (const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef, intptr_t value, struct Buffer *err);
intptr_t native_get (const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef, struct Buffer *err);
int string_plus_equals (const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef, const char *value, struct Buffer *err);
int string_minus_equals(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef, const char *value, struct Buffer *err);
int reset (const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef, struct Buffer *err);
void destroy (const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef);
Note that the word “string” in these functions doesn’t refer to a String Config Type, but rather a user-entered string that will be converted to the Config Type.
NeoMutt’s Types are:
Name | Description | Source | C Type |
---|---|---|---|
Address | Email address | config/address.c | struct Address |
Bool | Boolean | config/bool.c | bool |
Enum | Enumeration | config/enum.c | unsigned char |
Long | Long | config/long.c | long |
Mbtable | Multi-byte character table | config/mbtable.c | struct MbTable |
Number | Number | config/number.c | short |
Path | Path | config/path.c | char * |
Quad | Quad-option | config/quad.c | unsigned char |
Regex | Regular expression | config/regex.c | struct Regex |
Slist | List of strings | config/slist.c | struct Slist |
Sort | Sorting | config/sort.c | short |
String | String | config/string.c | char * |
Each Config Name is represented by a ConfigDef. This links the user-facing Name of the Item to a registered Type and a global Variable.
Here are some examples:
// Name Type|Flags Initial, Data, Validator
{ "timeout", DT_NUMBER|DT_NOT_NEGATIVE, 600, 0, NULL,
{ "move", DT_QUAD, MUTT_NO, 0, NULL,
{ "print_command", DT_STRING|DT_COMMAND, "lpr", 0, NULL,
{ "sort", DT_SORT|DT_SORT_REVERSE, SORT_DATE, SortMethods, pager_validator,
The Config Set implements a notification system using struct Notify
. This
means that other code can register Observers of the Config and be notified when
anything changes.
This takes a fictional Config item “$foo_percentage”
NeoMutt defines the Number Type: DT_NUMBER which is backed by a short int.
The ‘foo’ module registers its variable:
// Name Type|Flags Initial, Data, Validator, Docs
{ "foo_percentage", DT_NUMBER, 42, 0, foo_pct_validator, "Amount of foo" },
NeoMutt will set the value of the Variable (explanation below). Note: The initial value is Type-specific.
and an observer:
int foo_config_observer(struct NotifyCallback *nc);
Now we read the config file:
set foo_percentage = 96 # Dangerously high
mutt_parse_rc_line()
reads the config filemutt_parse_rc_buffer()
identifies the ‘set’ commandparse_set()
splits the line into
DT_NUMBER
DT_NUMBER
in the Type definitionsThe “Number” Type has a ‘setter’ function to convert a string to data. If any of the steps fail, the user will be notified and the Variable won’t be changed.
SHRT_MIN
?SHRT_MAX
?Validator:
This variable has a validator function, which is now called. In this case, it checks that the value is in the range 0-100. If it’s not, it returns false and an error message.
If the validator succeeds, then the Variable is set to the new value. Finally, the ConfigSet sends out a notification to all the Observers.
foo_config_observer()
is calledThis takes a fictional Config item “$home_address”
NeoMutt defines the Address Type: DT_ADDRESS
which is backed by a struct Address
pointer.
The ‘foo’ module registers its variable:
// Name Type|Flags Initial, Data, Validator, Docs
{ "home_address", DT_ADDRESS, "jim@example.com", NULL, NULL, "Home address" },
DT_ADDRESS
Address Type:
On success:
On failure:
This variable doesn’t have a validator function.
Some Types will allow an empty value to be set. By default, NeoMutt stores empty strings as NULL.
Setting an Address Type to an empty string will release the old address.
There are ~460 Config Names in NeoMutt. A third of these are defined in
mutt_config.c
.
The rest of the Config Items have been moved into libraries.
This allows the libraries to reduce the scope of their Config Variables.
For example, the Sidebar
registers its
Config Items
in sidebar/config.c
// Name Type|Flags Initial, Data, Validator,
{ "sidebar_delim_chars", DT_STRING, "/.", 0, NULL,
{ "sidebar_divider_char", DT_STRING, 0, 0, NULL,
{ "sidebar_folder_indent", DT_BOOL, false, 0, NULL,
The Config Variables are still global – there’s only one value for each variable.
This is why when you switch Accounts you need to use account-
and folder-hook
s to set variables.
The Config System supports inheritance which will allow us to create Account- and Mailbox-specific config.
This leads to my favourite diagram at the top of this page. Understanding this will lead to Enlightenment :-)
The Config System can be accessed using a handle: struct ConfigSubset *sub
Initially, this will be the global scope, NeoMutt->sub
.
Over time, this will be changed to Account->sub
when Account-specific config is introduced, then eventually Mailbox->sub
.
For dialogs, the ConfigSubset
should be passed in as a parameter.
There’s a helper function for each config type.
These are strictly type-checked and will assert()
on failure.
Here’s an example of each function.
const struct Address *c_envelope_from_address = cs_subset_address(sub, "envelope_from_address");
const bool c_fast_reply = cs_subset_bool (sub, "fast_reply");
const unsigned char c_use_threads = cs_subset_enum (sub, "use_threads");
const long c_imap_fetch_chunk_size = cs_subset_long (sub, "imap_fetch_chunk_size");
const struct MbTable *c_from_chars = cs_subset_mbtable(sub, "from_chars");
const short c_connect_timeout = cs_subset_number (sub, "connect_timeout");
const char *c_debug_file = cs_subset_path (sub, "debug_file");
const enum QuadOption c_fcc_attach = cs_subset_quad (sub, "fcc_attach");
const struct Regex *c_gecos_mask = cs_subset_regex (sub, "gecos_mask");
const struct Slist *c_hidden_tags = cs_subset_slist (sub, "hidden_tags");
const short c_pgp_sort_keys = cs_subset_sort (sub, "pgp_sort_keys");
const char *c_pattern_format = cs_subset_string (sub, "pattern_format");
Each variable is named to match the Config Variable and is const to discourage the coder from changing it (which would have no effect on the actual config).