Refactoring the Expandos (%X) #3937
Replies: 11 comments 12 replies
-
Summary of ExpandosThis is a list of all the expando functions. Last updated: 2023-07-20 alias_format_str()Source: alias/dlg_alias.c:113:60: Config:
attach_format_str()Source: attach/dlg_attach.c:131:66: Config:
autocrypt_format_str()Source: autocrypt/dlg_autocrypt.c:114:76: Config:
compose_format_str()Source: compose/cbar.c:95:73: Config:
compress_format_str()Source: compmbox/compress.c:248:69: Hooks:
crypt_format_str()Source: ncrypt/dlg_gpgme.c:339:68: **Config:
folder_format_str()Source: browser/dlg_browser.c:190:65: Config:
greeting_format_str()Source: send/send.c:671:54: Config:
group_index_format_str()Source: nntp/browse.c:43:70: Config:
history_format_str()Source: history/dlg_history.c:86:64: This function is used with a fixed string of
index_format_str()Source: hdrline.c:338:60: Config:
Hooks:
mix_format_str()Source: mixmaster/win_hosts.c:110:61: Config:
nntp_format_str()Source: nntp/newsrc.c:916:51: Config:
pattern_format_str()Source: pattern/dlg_pattern.c:99:75: Config:
pgp_command_format_str()Source: ncrypt/pgpinvoke.c:69:59: Config:
pgp_entry_format_str()Source: ncrypt/dlg_pgp.c:319:75: Config:
query_format_str()Source: alias/dlg_query.c:140:60: Config:
sidebar_format_str()Source: sidebar/window.c:330:59: Config:
smime_command_format_str()Source: ncrypt/smime.c:210:57: Config:
status_format_str()Source: status.c:72:55: Config:
|
Beta Was this translation helpful? Give feedback.
-
Every ExpandoBasic FormatsThe simplest of the expandos work like
There are a couple of extras to consider:
Date FormatsSome of the format strings display dates.
The
Padding FormatsNeoMutt supports three forms of "smart" padding.
The hard and soft fill behave differently when there's too little space for everything. Hard fill: Decreasing amounts of space; the right-hand-side is truncated to fit.
Soft fill: Decreasing amounts of space; the left-hand-side is truncated to fit.
Conditional Formats (1)Mutt's original style of conditional formatting was:
The An original NeoMutt patch stole the unused
The big benefit of this new format was that it was nestable.
Conditional Formats (2)More recently, Mutt introduced the First, define a set of hooks with a name, Config
When the Index comes to display the
The Index renders the payload Note, the final 'banana' hook has a catch-all pattern ConfigThe style of some expandos is dependent on config variables.
|
Beta Was this translation helpful? Give feedback.
-
Thank you for the help!
|
Beta Was this translation helpful? Give feedback.
-
That's looking very polymorphic :-) I suggest adding a dedicated You could do the same for While {
"x", // Key letter(s)
x_parser, // Custom parser function
flags, // Flags
data, // Private data
} Many expandos would just call a simple parser that expects I hope this makes some sense :-) You can always find me on IRC if you want a more interactive discussion. |
Beta Was this translation helpful? Give feedback.
-
Dear @flatcap ! I'm using the parser to validate |
Beta Was this translation helpful? Give feedback.
-
I'm starting to understand the new expando code :-) Config types define and encapsulate a config variable. The type is defined by That file defines the required functions for a type: We'll also need: With the type defined, we can convert some variables, e.g. { "index_format", DT_EXPANDO|DT_NOT_EMPTY, IP "%4C %Z %{%b %d} %-15.15L (%<l?%4l&%4c>) %s", IP &ExpandoIndexData, expando_validator,
"printf-like format string for the index menu (emails)"
},
Moving to this config model means we'll be able to eliminate the global data from |
Beta Was this translation helpful? Give feedback.
-
Expandos
Define an Expando
This is an example for // ...
{ "a", "author", EF_TYPE_STRING, EF_ENV_AUTHOR, EF_FLAGS_NONE },
{ "K", "mailing-list", EF_TYPE_STRING, EF_ENV_MAILING_LIST, EF_FLAGS_OPTIONAL },
{ "g", "tags", EF_TYPE_STRING, EF_EMAIL_TAGS, EF_FLAGS_NONE },
{ "l", "lines", EF_TYPE_STRING, EF_EMAIL_LINES, EF_FLAGS_OPTIONAL },
{ "c", "index-number", EF_TYPE_NUMBER, EF_MV_INDEX_NUMBER, EF_FLAGS_NONE },
{ "M", "thread-number-hidden", EF_TYPE_NUMBER, EF_THREAD_NUMBER_HIDDEN, EF_FLAGS_OPTIONAL },
// ... The UID is named for the data's domain, e.g. This should be enough information for the default parser to work with.
Add Custom ParserSome expandos need special handling:
It might also be useful to consider "dates" as custom. This is an example for // ...
{ "@", NULL, EF_TYPE_STRING, EF_EMAIL_HOOK, EF_FLAGS_NONE, ef_parser_index_format_hook },
{ "G", NULL, EF_TYPE_STRING, EF_EMAIL_NOTMUCH, EF_FLAGS_NONE, ef_parser_notmuch_tags },
{ "{", NULL, EF_TYPE_STRING, EF_EMAIL_DATE, EF_FLAGS_NONE, ef_parser_date },
{ "[", NULL, EF_TYPE_STRING, EF_EMAIL_DATE_LOCAL, EF_FLAGS_NONE, ef_parser_date },
{ "(", NULL, EF_TYPE_STRING, EF_EMAIL_DATE_RECEIVED, EF_FLAGS_NONE, ef_parser_date },
// ... Notes:
The custom parser will be given:
It will return:
Define a Format StringNow that we've defined each expando, we can define some format strings. First the data: struct Expando IndexFormatData[] = {
// ...
{ "@", NULL, EF_TYPE_STRING, EF_EMAIL_HOOK, EF_FLAGS_NONE, ef_parser_index_format_hook },
{ "a", "author", EF_TYPE_STRING, EF_ENV_AUTHOR, EF_FLAGS_NONE, NULL },
{ "c", "index-number", EF_TYPE_NUMBER, EF_MV_INDEX_NUMBER, EF_FLAGS_NONE, NULL },
{ "g", "tags", EF_TYPE_STRING, EF_EMAIL_TAGS, EF_FLAGS_NONE, NULL },
{ "G", NULL, EF_TYPE_STRING, EF_EMAIL_NOTMUCH, EF_FLAGS_NONE, ef_parser_notmuch_tags },
{ "K", "mailing-list", EF_TYPE_STRING, EF_ENV_MAILING_LIST, EF_FLAGS_OPTIONAL, NULL },
{ "l", "lines", EF_TYPE_STRING, EF_EMAIL_LINES, EF_FLAGS_OPTIONAL, NULL },
{ "M", "thread-number-hidden", EF_TYPE_NUMBER, EF_THREAD_NUMBER_HIDDEN, EF_FLAGS_OPTIONAL, NULL },
{ "(", NULL, EF_TYPE_STRING, EF_EMAIL_DATE_RECEIVED, EF_FLAGS_NONE, ef_parser_date },
{ "[", NULL, EF_TYPE_STRING, EF_EMAIL_DATE_LOCAL, EF_FLAGS_NONE, ef_parser_date },
{ "{", NULL, EF_TYPE_STRING, EF_EMAIL_DATE, EF_FLAGS_NONE, ef_parser_date },
// ...
}; Then the config: static struct ConfigDef MainVars[] = {
// ...
{ "index_format", DT_EXPANDO|DT_NOT_EMPTY, IP "%4C %Z %{%b %d} %-15.15L (%<l?%4l&%4c>) %s", IndexFormatData, expando_validator,
"printf-like format string for the index menu (emails)"
},
// ...
Parse a Format StringAt startup, and when the user sets a value, the format string must be parsed. // ...
{ "a", "author", EF_TYPE_STRING, EF_ENV_AUTHOR, EF_FLAGS_NONE },
{ "g", "tags", EF_TYPE_STRING, EF_EMAIL_TAGS, EF_FLAGS_NONE },
{ "c", "index-number", EF_TYPE_NUMBER, EF_MV_INDEX_NUMBER, EF_FLAGS_NONE },
// ... Using For simple expandos, the parser can do the work itself: parse any formatting, For custom expandos, the parser may need to try more than one callback to find a match. When the parsing is successful, the resulting parse tree will be stored in Render a Format StringThe final stage is to render a format string. The renderer needs:
A simple approach is to link the UID to a callback function: struct ExpandoCallbacks ec_data[] = {
// ...
{ EF_ENV_AUTHOR, index_a },
{ EF_ENV_MAILING_LIST, index_K },
{ EF_EMAIL_TAGS, index_g },
{ EF_EMAIL_LINES, index_l },
{ EF_MV_INDEX_NUMBER, index_c },
{ EF_THREAD_NUMBER_HIDDEN, index_M },
// ...
}; The caller might look like: int rc = expando_render(buf, max_width, ec_data, callback_data); The same Now, the render traverses the tree filling the buffer. Currently, the callbacks do the formatting. const char *index_i(const struct ExpandoNode *self, intptr_t data)
{
const struct HdrFormatInfo *hfi = (const struct HdrFormatInfo *) data;
struct Email *email = hfi->email;
if (email->env->message_id)
return email->env->message_id;
return "<no.id>";
} |
Beta Was this translation helpful? Give feedback.
-
There are 18 expando formatting functions.
|
Beta Was this translation helpful? Give feedback.
-
Each Expando Definition, defines a number of expandos. AliasFormatData
AttachFormatData
AutocryptFormatData
ComposeFormatData
FolderFormatData
GreetingFormatData
GroupIndexFormatData
HistoryFormatData
IndexFormatData
MixFormatData
NntpFormatData
PatternFormatData
PgpCommandFormatData
PgpEntryFormatData
QueryFormatData
SidebarFormatData
SmimeCommandFormatData
StatusFormatData
|
Beta Was this translation helpful? Give feedback.
-
There are 51 config variables.
|
Beta Was this translation helpful? Give feedback.
-
Part one merged! ca6645a By the magic of Doxygen, the various new Expando APIs are now documented. |
Beta Was this translation helpful? Give feedback.
-
The expandos are
printf()
-like strings used throughout NeoMutt, e.g.$index_format
The expandos make NeoMutt's display very flexible, but the code is very complicated and inefficient.
Below are five steps to improving the code and making expandos even more useful.
They start simple and get progressively more mind-bending.
mutt_expando_format()
%n
) to words (%{name}
)My raw notes are available here.
1 Create a new parser
Parse
set index_format = "%4C %Z %{%b %d} %-15.15L (%<l?%4l&%4c>) %s"
into a tree.The tree needs to represent the entire string:
%C
,%Z
-15.15L
%<l? ... & ... >
Once written, we can create some unit-tests for it.
2 Use the parser as a validator function
The first use of the parser is as a validator function for the config.
Creating format strings can be hard.
Having a validators means NeoMutt can tell the user exactly why a format string is invalid.
When the user does,
set index_format = "..."
, the validator will check both the syntax and the expandos used.Each of the twenty config strings will need a data field -- an array of valid expando characters.
3 Integrate parser into
mutt_expando_format()
At the core of expandos is
mutt_expando_format()
.It currently does the parsing and expansion of format strings.
Changing this will be a big job, but it'll make the formatting much more efficient and extendable.
The new function will be given the parse-tree and a set of callback functions.
Using the tree, calculating the layout will be easy.
4 Change key letters (
%n
) to words (%{name}
)Remembering which letter means what is hard.
Once the new parser and formatter are in place, we can move to a new syntax.
Using
%{name}
rather than%n
makes the config more verbose, but much easier to understand.The new format was chosen so that it would be clear and distinct from the current format.
The step will also introduce new syntax for conditionals, perhaps
%{cond:size} ... %{else} ... %{end}
5 Increase domains of data available
In the Index, there's a lot of information available about each Email, that isn't accessible to the user.
If we defined a new object:
header
we could allow the user to pick custom fields, e.g.%{header:x-my-field}
Beta Was this translation helpful? Give feedback.
All reactions