}
/**
- * format_s_x - Format a string like snprintf()
+ * mutt_format_s_x - Format a string like snprintf()
* @param[out] buf Buffer in which to save string
* @param[in] buflen Buffer length
* @param[in] prec Field precision, e.g. "-3.4"
* except that the numbers in the conversion specification refer to
* the number of character cells when printed.
*/
-static void format_s_x(char *buf, size_t buflen, const char *prec, const char *s, bool arboreal)
+void mutt_format_s_x(char *buf, size_t buflen, const char *prec, const char *s, bool arboreal)
{
enum FormatJustify justify = JUSTIFY_RIGHT;
char *p = NULL;
*/
void mutt_format_s(char *buf, size_t buflen, const char *prec, const char *s)
{
- format_s_x(buf, buflen, prec, s, false);
+ mutt_format_s_x(buf, buflen, prec, s, false);
}
/**
*/
void mutt_format_s_tree(char *buf, size_t buflen, const char *prec, const char *s)
{
- format_s_x(buf, buflen, prec, s, true);
+ mutt_format_s_x(buf, buflen, prec, s, true);
}
/**
void mutt_flush_unget_to_endcond(void);
void mutt_format_s(char *buf, size_t buflen, const char *prec, const char *s);
void mutt_format_s_tree(char *buf, size_t buflen, const char *prec, const char *s);
+void mutt_format_s_x(char *buf, size_t buflen, const char *prec, const char *s, bool arboreal);
void mutt_getch_timeout(int delay);
struct Event mutt_getch(void);
int mutt_get_field_full(const char *field, char *buf, size_t buflen, CompletionFlags complete, bool multiple, char ***files, int *numfiles);
</para>
</sect1>
+ <sect1 id="index-format-hook">
+ <title>Dynamically Changing $index_format using Patterns</title>
+ <para>Usage:</para>
+ <cmdsynopsis>
+ <command>index-format-hook</command>
+ <arg choice="plain">
+ <replaceable class="parameter">name</replaceable>
+ </arg>
+ <arg choice="plain">
+ <replaceable class="parameter">[!]pattern</replaceable>
+ </arg>
+ <arg choice="plain">
+ <replaceable class="parameter">format-string</replaceable>
+ </arg>
+ </cmdsynopsis>
+ <para>
+ This command is used to inject format strings dynamically into <link
+ linkend="index-format">$index_format</link>based on pattern matching
+ against the current message.
+ </para>
+ <para>
+ The <link linkend="index-format">$index_format</link>expando
+ <emphasis>%@name@</emphasis>specifies a placeholder for the injection.
+ Index-format-hooks with the same <emphasis>name</emphasis>are matched using
+ <link linkend="patterns"> <emphasis>pattern</emphasis> </link>against the
+ current message. Matching is done in the order specified in the .muttrc,
+ with the first match being used. The hook's
+ <emphasis>format-string</emphasis>is then substituted and evaluated.
+ </para>
+ <para>
+ Because the first match is used, best practice is to put a catch-all
+ <emphasis>~A</emphasis>pattern as the last hook. Here is an example showing
+ how to implement dynamic date formatting:
+ </para>
+<screen>
+set index_format="%4C %-6@date@ %-15.15F %Z (%4c) %s"
+
+index-format-hook date "~d<1d" "%[%H:%M]"
+index-format-hook date "~d<1m" "%[%a %d]"
+index-format-hook date "~d<1y" "%[%b %d]"
+index-format-hook date "~A" "%[%m/%y]"
+</screen>
+ <para>
+ Another example, showing a way to prepend to
+ the subject. Note that without a catch-all ~A
+ pattern, no match results in the expando being
+ replaced with an empty string.
+ </para>
+<screen>
+set index_format="%4C %@subj_flags@%s"
+
+index-format-hook subj_flags "~f boss@example.com" "** BOSS ** "
+index-format-hook subj_flags "~f spouse@example.com" ":-) "
+</screen>
+ </sect1>
+
<sect1 id="push">
<title>Adding Key Sequences to the Keyboard Buffer</title>
<para>
<link linkend="iconv-hook"><command>iconv-hook</command></link>
</para>
</listitem>
+ <listitem>
+ <para>
+ <link linkend="index-format-hook"><command>index-format-hook</command></link>
+ </para>
+ </listitem>
<listitem>
<para>
<link linkend="crypt-hook"><command>crypt-hook</command></link>
Hooks that act upon messages (<command>message-hook</command>,
<command>reply-hook</command>, <command>send-hook</command>,
<command>send2-hook</command>, <command>save-hook</command>,
- <command>fcc-hook</command>) are evaluated in a slightly different
+ <command>fcc-hook</command>, <command>index-format-hook</command>)
+ are evaluated in a slightly different
manner. For the other types of hooks, a
<link linkend="regex">regular expression</link> is sufficient. But in
dealing with messages a finer grain of control is needed for matching
</group>
</cmdsynopsis>
</listitem>
+ <listitem>
+ <cmdsynopsis>
+ <command>
+ <link linkend="index-format-hook">index-format-hook</link>
+ </command>
+ <arg choice="plain">
+ <replaceable class="parameter">name</replaceable>
+ </arg>
+ <arg choice="plain">
+ <replaceable class="parameter">[!]pattern</replaceable>
+ </arg>
+ <arg choice="plain">
+ <replaceable class="parameter">format-string</replaceable>
+ </arg>
+ </cmdsynopsis>
+ </listitem>
<listitem>
<cmdsynopsis>
<command>
#include "curs_lib.h"
#include "format_flags.h"
#include "globals.h"
+#include "hook.h"
#include "mailbox.h"
#include "mutt_curses.h"
#include "mutt_menu.h"
add_index_color(buf + colorlen, buflen - colorlen, flags, MT_COLOR_INDEX);
break;
+ case '@':
+ {
+ const char *end = src;
+ static unsigned char recurse = 0;
+
+ while ((*end != '\0') && (*end != '@'))
+ end++;
+ if ((*end == '@') && (recurse < 20))
+ {
+ recurse++;
+ mutt_str_substr_cpy(tmp, src, end, sizeof(tmp));
+ mutt_expando_format(tmp, sizeof(tmp), col, cols,
+ NONULL(mutt_idxfmt_hook(tmp, m, e)),
+ index_format_str, data, flags);
+ mutt_format_s_x(buf, buflen, prec, tmp, true);
+ recurse--;
+
+ src = end + 1;
+ break;
+ }
+ }
+ /* fallthrough */
+
default:
snprintf(buf, buflen, "%%%s%c", prec, op);
break;
struct PatternHead *pattern; ///< Used for fcc,save,send-hook
TAILQ_ENTRY(Hook) entries;
};
-static TAILQ_HEAD(, Hook) Hooks = TAILQ_HEAD_INITIALIZER(Hooks);
+TAILQ_HEAD(HookList, Hook);
+static struct HookList Hooks = TAILQ_HEAD_INITIALIZER(Hooks);
+
+static struct Hash *IdxFmtHooks = NULL;
static HookFlags current_hook_type = MUTT_HOOK_NO_FLAGS;
/**
}
}
+/**
+ * delete_idxfmt_hooklist - Delete a index-format-hook from the Hash Table
+ * @param type Type of Hash Element
+ * @param obj Pointer to Hashed object
+ * @param data Private data
+ */
+static void delete_idxfmt_hooklist(int type, void *obj, intptr_t data)
+{
+ struct HookList *hl = obj;
+ struct Hook *h = NULL;
+ struct Hook *tmp = NULL;
+
+ TAILQ_FOREACH_SAFE(h, hl, entries, tmp)
+ {
+ TAILQ_REMOVE(hl, h, entries);
+ delete_hook(h);
+ }
+
+ FREE(&hl);
+}
+
+/**
+ * delete_idxfmt_hooks - Delete all the index-format-hooks
+ */
+static void delete_idxfmt_hooks(void)
+{
+ mutt_hash_free(&IdxFmtHooks);
+}
+
+/**
+ * mutt_parse_idxfmt_hook - Parse the 'index-format-hook' command - Implements ::command_t
+ */
+int mutt_parse_idxfmt_hook(struct Buffer *buf, struct Buffer *s,
+ unsigned long data, struct Buffer *err)
+{
+ enum CommandResult rc = MUTT_CMD_ERROR;
+ bool not = false;
+
+ struct Buffer *name = mutt_buffer_pool_get();
+ struct Buffer *pattern = mutt_buffer_pool_get();
+ struct Buffer *fmtstring = mutt_buffer_pool_get();
+
+ if (!IdxFmtHooks)
+ {
+ IdxFmtHooks = mutt_hash_new(30, MUTT_HASH_STRDUP_KEYS);
+ mutt_hash_set_destructor(IdxFmtHooks, delete_idxfmt_hooklist, 0);
+ }
+
+ if (!MoreArgs(s))
+ {
+ mutt_str_strfcpy(err->data, _("too few arguments"), err->dsize);
+ goto out;
+ }
+ mutt_extract_token(name, s, MUTT_TOKEN_NO_FLAGS);
+ struct HookList *hl = mutt_hash_find(IdxFmtHooks, mutt_b2s(name));
+
+ if (*s->dptr == '!')
+ {
+ s->dptr++;
+ SKIPWS(s->dptr);
+ not = true;
+ }
+ mutt_extract_token(pattern, s, MUTT_TOKEN_NO_FLAGS);
+
+ if (!MoreArgs(s))
+ {
+ mutt_str_strfcpy(err->data, _("too few arguments"), err->dsize);
+ goto out;
+ }
+ mutt_extract_token(fmtstring, s, MUTT_TOKEN_NO_FLAGS);
+
+ if (MoreArgs(s))
+ {
+ mutt_str_strfcpy(err->data, _("too many arguments"), err->dsize);
+ goto out;
+ }
+
+ if (C_DefaultHook)
+ {
+ mutt_buffer_increase_size(pattern, 8192);
+ mutt_check_simple(pattern->data, pattern->dsize, C_DefaultHook);
+ mutt_buffer_fix_dptr(pattern); /* not necessary, but to be safe */
+ }
+
+ /* check to make sure that a matching hook doesn't already exist */
+ struct Hook *hook = NULL;
+ if (hl)
+ {
+ TAILQ_FOREACH(hook, hl, entries)
+ {
+ if ((hook->regex.not == not) &&
+ (mutt_str_strcmp(mutt_b2s(pattern), hook->regex.pattern) == 0))
+ {
+ mutt_str_replace(&hook->command, mutt_b2s(fmtstring));
+ rc = MUTT_CMD_SUCCESS;
+ goto out;
+ }
+ }
+ }
+
+ struct PatternHead *pat = mutt_pattern_comp(pattern->data, MUTT_FULL_MSG, err);
+ if (!pat)
+ goto out;
+
+ hook = mutt_mem_calloc(1, sizeof(struct Hook));
+ hook->type = data;
+ hook->command = mutt_str_strdup(mutt_b2s(fmtstring));
+ hook->pattern = pat;
+ hook->regex.pattern = mutt_str_strdup(mutt_b2s(pattern));
+ hook->regex.regex = NULL;
+ hook->regex.not = not;
+
+ if (!hl)
+ {
+ hl = mutt_mem_calloc(1, sizeof(*hl));
+ TAILQ_INIT(hl);
+ mutt_hash_insert(IdxFmtHooks, mutt_b2s(name), hl);
+ }
+
+ TAILQ_INSERT_TAIL(hl, hook, entries);
+ rc = MUTT_CMD_SUCCESS;
+
+out:
+ mutt_buffer_pool_release(&name);
+ mutt_buffer_pool_release(&pattern);
+ mutt_buffer_pool_release(&fmtstring);
+
+ return rc;
+}
+
/**
* mutt_parse_unhook - Parse the 'unhook' command - Implements ::command_t
*/
return MUTT_CMD_WARNING;
}
mutt_delete_hooks(MUTT_HOOK_NO_FLAGS);
+ delete_idxfmt_hooks();
mutt_ch_lookup_remove();
}
else
buf->data, buf->data);
return MUTT_CMD_WARNING;
}
- mutt_delete_hooks(type);
+ if (type == MUTT_IDXFMTHOOK)
+ delete_idxfmt_hooks();
+ else
+ mutt_delete_hooks(type);
}
}
return MUTT_CMD_SUCCESS;
}
FREE(&token.data);
}
+
+/**
+ * mutt_idxfmt_hook - Get index-format-hook format string
+ * @param name Hook name
+ * @param m Mailbox
+ * @param e Email
+ * @retval ptr printf(3)-like format string
+ * @retval NULL No matching hook
+ */
+const char *mutt_idxfmt_hook(const char *name, struct Mailbox *m, struct Email *e)
+{
+ if (!IdxFmtHooks)
+ return NULL;
+
+ struct HookList *hl = mutt_hash_find(IdxFmtHooks, name);
+ if (!hl)
+ return NULL;
+
+ current_hook_type = MUTT_IDXFMTHOOK;
+
+ struct PatternCache cache = { 0 };
+ const char *fmtstring = NULL;
+ struct Hook *hook = NULL;
+
+ TAILQ_FOREACH(hook, hl, entries)
+ {
+ struct Pattern *pat = SLIST_FIRST(hook->pattern);
+ if ((mutt_pattern_exec(pat, 0, m, e, &cache) > 0) ^ hook->regex.not)
+ {
+ fmtstring = hook->command;
+ break;
+ }
+ }
+
+ current_hook_type = MUTT_HOOK_NO_FLAGS;
+
+ return fmtstring;
+}
#define MUTT_APPEND_HOOK (1 << 13) ///< append-hook: append to a compressed mailbox
#define MUTT_CLOSE_HOOK (1 << 14) ///< close-hook: write to a compressed mailbox
#endif
-#define MUTT_TIMEOUT_HOOK (1 << 15) ///< timeout-hook: run a command periodically
-#define MUTT_STARTUP_HOOK (1 << 16) ///< startup-hook: run when starting NeoMutt
-#define MUTT_SHUTDOWN_HOOK (1 << 17) ///< shutdown-hook: run when leaving NeoMutt
-#define MUTT_GLOBAL_HOOK (1 << 18) ///< Hooks which don't take a regex
+#define MUTT_IDXFMTHOOK (1 << 15) ///< index-format-hook: customise the format of the index
+#define MUTT_TIMEOUT_HOOK (1 << 16) ///< timeout-hook: run a command periodically
+#define MUTT_STARTUP_HOOK (1 << 17) ///< startup-hook: run when starting NeoMutt
+#define MUTT_SHUTDOWN_HOOK (1 << 18) ///< shutdown-hook: run when leaving NeoMutt
+#define MUTT_GLOBAL_HOOK (1 << 19) ///< Hooks which don't take a regex
void mutt_account_hook(const char *url);
void mutt_crypt_hook(struct ListHead *list, struct Address *addr);
void mutt_delete_hooks(HookFlags type);
char *mutt_find_hook(HookFlags type, const char *pat);
void mutt_folder_hook(const char *path, const char *desc);
+const char *mutt_idxfmt_hook(const char *name, struct Mailbox *m, struct Email *e);
void mutt_message_hook(struct Mailbox *m, struct Email *e, HookFlags type);
+enum CommandResult mutt_parse_idxfmt_hook(struct Buffer *buf, struct Buffer *s, unsigned long data, struct Buffer *err);
enum CommandResult mutt_parse_hook(struct Buffer *buf, struct Buffer *s, unsigned long data, struct Buffer *err);
enum CommandResult mutt_parse_unhook(struct Buffer *buf, struct Buffer *s, unsigned long data, struct Buffer *err);
void mutt_select_fcc(char *path, size_t pathlen, struct Email *e);
int mutt_get_hook_type(const char *name)
{
for (const struct Command *c = Commands; c->name; c++)
- if ((c->func == mutt_parse_hook) && (mutt_str_strcasecmp(c->name, name) == 0))
+ {
+ if (((c->func == mutt_parse_hook) || (c->func == mutt_parse_idxfmt_hook)) &&
+ (mutt_str_strcasecmp(c->name, name) == 0))
+ {
return c->data;
+ }
+ }
return MUTT_CMD_SUCCESS;
}
** .dt %zc .dd Message crypto flags
** .dt %zs .dd Message status flags
** .dt %zt .dd Message tag flags
+ ** .dt %@name@ .dd insert and evaluate format-string from the matching
+ ** ``$index-format-hook'' command
** .dt %{fmt} .dd the date and time of the message is converted to sender's
** time zone, and "fmt" is expanded by the library function
** \fCstrftime(3)\fP; a leading bang disables locales
{ "ifdef", parse_ifdef, 0 },
{ "ifndef", parse_ifdef, 1 },
{ "ignore", parse_ignore, 0 },
+ { "index-format-hook", mutt_parse_idxfmt_hook, MUTT_IDXFMTHOOK },
{ "lists", parse_lists, 0 },
#ifdef USE_LUA
{ "lua", mutt_lua_parse, 0 },