On 2020-06-15 19:37:21 -0700, Kevin J. McCarthy wrote:
> On Mon, Jun 15, 2020 at 12:34:50PM -0500, Corey Minyard wrote:
> > I have a bookmark set to http://www.mutt.org/doc/manual/#patterns
> > because, well, I can't remember all those patterns. I was wondering if
> > a command could be added so help text could be displayed wherever a
> > pattern modifiers is prompted for. It would make my life a little
> > easier.
>
> Thanks for the suggestion. I'll try to think of what might be feasible.
There's Tamo's patch that does that for ~[Tab] to get help on the
patterns.
FYI, I'm using this patch merged together with the addition of
~a and %a pattern modifiers that do:
<row><entry>~a <emphasis>EXPR</emphasis></entry><entry>messages which contain
<emphasis>EXPR</emphasis> in some envelope address field</entry></row>
<row><entry>%a <emphasis>GROUP</emphasis></entry><entry>messages which contain
a member of <emphasis>GROUP</emphasis> in some envelope address
field</entry></row>
I've maintained this patch since 2008 and modified it when need be.
I'm attaching it.
--
Vincent Lefèvre <[email protected]> - Web: <https://www.vinc17.net/>
100% accessible validated (X)HTML - Blog: <https://www.vinc17.net/blog/>
Work: CR INRIA - computer arithmetic / AriC project (LIP, ENS-Lyon)
diff --git a/PATCHES b/PATCHES
index e69de29b..a3e1715f 100644
--- a/PATCHES
+++ b/PATCHES
@@ -0,0 +1 @@
+patch-20200424.tamovl.patterns.1
diff --git a/curs_lib.c b/curs_lib.c
index 92eda9b1..18b169f3 100644
--- a/curs_lib.c
+++ b/curs_lib.c
@@ -248,6 +248,7 @@ int _mutt_buffer_get_field (const char *field, BUFFER
*buffer, int complete, int
mutt_refresh ();
mutt_window_getyx (MuttMessageWindow, NULL, &x);
ret = _mutt_enter_string (buffer->data, buffer->dsize, x, complete,
multiple, files, numfiles, es);
+ complete &= ~(MUTT_CLEAR);
}
while (ret == 1);
diff --git a/curs_main.c b/curs_main.c
index b1891354..7686754a 100644
--- a/curs_main.c
+++ b/curs_main.c
@@ -973,7 +973,7 @@ int mutt_index_menu (void)
CHECK_ATTACH;
mutt_pattern_func (MUTT_DELETE, _("Delete messages matching: "));
- menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
+ menu->redraw |= REDRAW_FULL;
break;
#ifdef USE_POP
@@ -1033,10 +1033,8 @@ int mutt_index_menu (void)
menu->current = 0;
if (Context->msgcount && (Sort & SORT_MASK) == SORT_THREADS)
mutt_draw_tree (Context);
- menu->redraw = REDRAW_FULL;
}
- if (Context->pattern)
- mutt_message _("To view all messages, limit to \"all\".");
+ menu->redraw = REDRAW_FULL;
break;
case OP_QUIT:
@@ -1096,6 +1094,8 @@ int mutt_index_menu (void)
menu->current = menu->oldcurrent;
else
menu->redraw = REDRAW_MOTION;
+ if (op == OP_SEARCH || op == OP_SEARCH_REVERSE)
+ menu->redraw = REDRAW_FULL;
break;
case OP_SORT:
@@ -1151,7 +1151,7 @@ int mutt_index_menu (void)
CHECK_MSGCOUNT;
CHECK_VISIBLE;
mutt_pattern_func (MUTT_TAG, _("Tag messages matching: "));
- menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
+ menu->redraw |= REDRAW_FULL;
break;
case OP_MAIN_UNDELETE_PATTERN:
@@ -1162,16 +1162,16 @@ int mutt_index_menu (void)
/* L10N: CHECK_ACL */
CHECK_ACL(MUTT_ACL_DELETE, _("Cannot undelete message(s)"));
- if (mutt_pattern_func (MUTT_UNDELETE, _("Undelete messages matching:
")) == 0)
- menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
+ mutt_pattern_func (MUTT_UNDELETE, _("Undelete messages matching: "));
+ menu->redraw |= REDRAW_FULL;
break;
case OP_MAIN_UNTAG_PATTERN:
CHECK_MSGCOUNT;
CHECK_VISIBLE;
- if (mutt_pattern_func (MUTT_UNTAG, _("Untag messages matching: ")) == 0)
- menu->redraw |= REDRAW_INDEX | REDRAW_STATUS;
+ mutt_pattern_func (MUTT_UNTAG, _("Untag messages matching: "));
+ menu->redraw |= REDRAW_FULL;
break;
/* --------------------------------------------------------------------
diff --git a/doc/manual.xml.head b/doc/manual.xml.head
index 631692aa..dc5c62af 100644
--- a/doc/manual.xml.head
+++ b/doc/manual.xml.head
@@ -5690,6 +5690,8 @@ shows several ways to select messages.
<row><entry>Pattern modifier</entry><entry>Description</entry></row>
</thead>
<tbody>
+<row><entry>~a <emphasis>EXPR</emphasis></entry><entry>messages which contain
<emphasis>EXPR</emphasis> in some envelope address field</entry></row>
+<row><entry>%a <emphasis>GROUP</emphasis></entry><entry>messages which contain
a member of <emphasis>GROUP</emphasis> in some envelope address
field</entry></row>
<row><entry>~A</entry><entry>all messages</entry></row>
<row><entry>~b <emphasis>EXPR</emphasis></entry><entry>messages which contain
<emphasis>EXPR</emphasis> in the message body ***)</entry></row>
<row><entry>=b <emphasis>STRING</emphasis></entry><entry>If IMAP is enabled,
like ~b but searches for <emphasis>STRING</emphasis> on the server, rather than
downloading each message and searching it locally.</entry></row>
diff --git a/enter.c b/enter.c
index 546754ac..060a9cee 100644
--- a/enter.c
+++ b/enter.c
@@ -619,7 +619,15 @@ int _mutt_enter_string (char *buf, size_t buflen, int col,
}
else if (flags & MUTT_PATTERN && ch == OP_EDITOR_COMPLETE)
{
- for (i = state->curpos; i && state->wbuf[i-1] != '~'; i--)
+ i = state->curpos;
+ if (i && state->wbuf[i - 1] == '~')
+ {
+ if (mutt_ask_pattern (buf, buflen))
+ replace_part (state, i, buf);
+ rv = 1;
+ goto bye;
+ }
+ for (; i && state->wbuf[i - 1] != '~'; i--)
;
if (i && i < state->curpos && state->wbuf[i-1] == '~' &&
state->wbuf[i] == 'y')
{
diff --git a/mutt.h b/mutt.h
index 0d58e276..a54216a9 100644
--- a/mutt.h
+++ b/mutt.h
@@ -264,6 +264,7 @@ enum
MUTT_PERSONAL_RECIP,
MUTT_PERSONAL_FROM,
MUTT_ADDRESS,
+ MUTT_ADDRESS_ALL,
MUTT_CRYPT_SIGN,
MUTT_CRYPT_VERIFIED,
MUTT_CRYPT_ENCRYPT,
diff --git a/pattern.c b/pattern.c
index a995934e..8077d4ec 100644
--- a/pattern.c
+++ b/pattern.c
@@ -26,6 +26,8 @@
#include "mailbox.h"
#include "copy.h"
#include "mime.h"
+#include "mutt_menu.h"
+#include "mutt_curses.h"
#include <string.h>
#include <stdlib.h>
@@ -48,59 +50,109 @@ static int eat_date (pattern_t *pat, int, BUFFER *, BUFFER
*);
static int eat_range (pattern_t *pat, int, BUFFER *, BUFFER *);
static int patmatch (const pattern_t *pat, const char *buf);
+#define EAT_REGEXP 1
+#define EAT_DATE 2
+#define EAT_RANGE 3
static const struct pattern_flags
{
int tag; /* character used to represent this op */
int op; /* operation to perform */
int class;
- int (*eat_arg) (pattern_t *, int, BUFFER *, BUFFER *);
+ int eat_arg;
+ char *desc;
}
Flags[] =
{
- { 'A', MUTT_ALL, 0, NULL },
- { 'b', MUTT_BODY, MUTT_FULL_MSG|MUTT_SEND_MODE_SEARCH, eat_regexp },
- { 'B', MUTT_WHOLE_MSG, MUTT_FULL_MSG|MUTT_SEND_MODE_SEARCH, eat_regexp },
- { 'c', MUTT_CC, 0, eat_regexp },
- { 'C', MUTT_RECIPIENT, 0, eat_regexp },
- { 'd', MUTT_DATE, 0, eat_date },
- { 'D', MUTT_DELETED, 0, NULL },
- { 'e', MUTT_SENDER, 0, eat_regexp },
- { 'E', MUTT_EXPIRED, 0, NULL },
- { 'f', MUTT_FROM, 0, eat_regexp },
- { 'F', MUTT_FLAG, 0, NULL },
- { 'g', MUTT_CRYPT_SIGN, 0, NULL },
- { 'G', MUTT_CRYPT_ENCRYPT, 0, NULL },
- { 'h', MUTT_HEADER, MUTT_FULL_MSG|MUTT_SEND_MODE_SEARCH, eat_regexp },
- { 'H', MUTT_HORMEL, 0, eat_regexp },
- { 'i', MUTT_ID, 0, eat_regexp },
- { 'k', MUTT_PGP_KEY, 0, NULL },
- { 'l', MUTT_LIST, 0, NULL },
- { 'L', MUTT_ADDRESS, 0, eat_regexp },
- { 'm', MUTT_MESSAGE, 0, eat_range },
- { 'M', MUTT_MIMETYPE, MUTT_FULL_MSG, eat_regexp },
- { 'n', MUTT_SCORE, 0, eat_range },
- { 'N', MUTT_NEW, 0, NULL },
- { 'O', MUTT_OLD, 0, NULL },
- { 'p', MUTT_PERSONAL_RECIP, 0, NULL },
- { 'P', MUTT_PERSONAL_FROM, 0, NULL },
- { 'Q', MUTT_REPLIED, 0, NULL },
- { 'r', MUTT_DATE_RECEIVED, 0, eat_date },
- { 'R', MUTT_READ, 0, NULL },
- { 's', MUTT_SUBJECT, 0, eat_regexp },
- { 'S', MUTT_SUPERSEDED, 0, NULL },
- { 't', MUTT_TO, 0, eat_regexp },
- { 'T', MUTT_TAG, 0, NULL },
- { 'u', MUTT_SUBSCRIBED_LIST, 0, NULL },
- { 'U', MUTT_UNREAD, 0, NULL },
- { 'v', MUTT_COLLAPSED, 0, NULL },
- { 'V', MUTT_CRYPT_VERIFIED, 0, NULL },
- { 'x', MUTT_REFERENCE, 0, eat_regexp },
- { 'X', MUTT_MIMEATTACH, 0, eat_range },
- { 'y', MUTT_XLABEL, 0, eat_regexp },
- { 'z', MUTT_SIZE, 0, eat_range },
- { '=', MUTT_DUPLICATED, 0, NULL },
- { '$', MUTT_UNREFERENCED, 0, NULL },
- { 0, 0, 0, NULL }
+ { 'a', MUTT_ADDRESS_ALL, 0, EAT_REGEXP,
+ N_("messages whose some address from the envelope matches EXPR") },
+ { 'A', MUTT_ALL, 0, 0,
+ N_("all messages") },
+ { 'b', MUTT_BODY, MUTT_FULL_MSG|MUTT_SEND_MODE_SEARCH, EAT_REGEXP,
+ N_("messages whose body matches EXPR") },
+ { 'B', MUTT_WHOLE_MSG, MUTT_FULL_MSG|MUTT_SEND_MODE_SEARCH, EAT_REGEXP,
+ N_("messages whose body or headers match EXPR") },
+ { 'c', MUTT_CC, 0, EAT_REGEXP,
+ N_("messages whose CC header matches EXPR") },
+ { 'C', MUTT_RECIPIENT, 0, EAT_REGEXP,
+ N_("messages whose recipient matches EXPR") },
+ { 'd', MUTT_DATE, 0, EAT_DATE,
+ N_("messages sent in DATERANGE") },
+ { 'D', MUTT_DELETED, 0, 0,
+ N_("deleted messages") },
+ { 'e', MUTT_SENDER, 0, EAT_REGEXP,
+ N_("messages whose Sender header matches EXPR") },
+ { 'E', MUTT_EXPIRED, 0, 0,
+ N_("expired messages") },
+ { 'f', MUTT_FROM, 0, EAT_REGEXP,
+ N_("messages whose From header matches EXPR") },
+ { 'F', MUTT_FLAG, 0, 0,
+ N_("flagged messages") },
+ { 'g', MUTT_CRYPT_SIGN, 0, 0,
+ N_("cryptographically signed messages") },
+ { 'G', MUTT_CRYPT_ENCRYPT, 0, 0,
+ N_("cryptographically encrypted messages") },
+ { 'h', MUTT_HEADER, MUTT_FULL_MSG|MUTT_SEND_MODE_SEARCH, EAT_REGEXP,
+ N_("messages whose header matches EXPR") },
+ { 'H', MUTT_HORMEL, 0, EAT_REGEXP,
+ N_("messages whose spam tag matches EXPR") },
+ { 'i', MUTT_ID, 0, EAT_REGEXP,
+ N_("messages whose Message-ID matches EXPR") },
+ { 'k', MUTT_PGP_KEY, 0, 0,
+ N_("messages which contain PGP key") },
+ { 'l', MUTT_LIST, 0, 0,
+ N_("messages addressed to known mailing lists") },
+ { 'L', MUTT_ADDRESS, 0, EAT_REGEXP,
+ N_("messages whose From/Sender/To/CC matches EXPR") },
+ { 'm', MUTT_MESSAGE, 0, EAT_RANGE,
+ N_("messages whose number is in RANGE") },
+ { 'M', MUTT_MIMETYPE, MUTT_FULL_MSG, EAT_REGEXP,
+ N_("messages with a Content-Type matching EXPR") },
+ { 'n', MUTT_SCORE, 0, EAT_RANGE,
+ N_("messages whose score is in RANGE") },
+ { 'N', MUTT_NEW, 0, 0,
+ N_("new messages") },
+ { 'O', MUTT_OLD, 0, 0,
+ N_("old messages") },
+ { 'p', MUTT_PERSONAL_RECIP, 0, 0,
+ N_("messages addressed to you") },
+ { 'P', MUTT_PERSONAL_FROM, 0, 0,
+ N_("messages from you") },
+ { 'Q', MUTT_REPLIED, 0, 0,
+ N_("messages which have been replied to") },
+ { 'r', MUTT_DATE_RECEIVED, 0, EAT_DATE,
+ N_("messages received in DATERANGE") },
+ { 'R', MUTT_READ, 0, 0,
+ N_("already read messages") },
+ { 's', MUTT_SUBJECT, 0, EAT_REGEXP,
+ N_("messages whose Subject header matches EXPR") },
+ { 'S', MUTT_SUPERSEDED, 0, 0,
+ N_("superseded messages") },
+ { 't', MUTT_TO, 0, EAT_REGEXP,
+ N_("messages whose To header matches EXPR") },
+ { 'T', MUTT_TAG, 0, 0,
+ N_("tagged messages") },
+ { 'u', MUTT_SUBSCRIBED_LIST, 0, 0,
+ N_("messages addressed to subscribed mailing lists") },
+ { 'U', MUTT_UNREAD, 0, 0,
+ N_("unread messages") },
+ { 'v', MUTT_COLLAPSED, 0, 0,
+ N_("messages in collapsed threads") },
+ { 'V', MUTT_CRYPT_VERIFIED, 0, 0,
+ N_("cryptographically verified messages") },
+ { 'x', MUTT_REFERENCE, 0, EAT_REGEXP,
+ N_("messages whose References header matches EXPR") },
+ { 'X', MUTT_MIMEATTACH, 0, EAT_RANGE,
+ N_("messages with RANGE attachments") },
+ { 'y', MUTT_XLABEL, 0, EAT_REGEXP,
+ N_("messages whose X-Label header matches EXPR") },
+ { 'z', MUTT_SIZE, 0, EAT_RANGE,
+ N_("messages whose size is in RANGE") },
+ { '=', MUTT_DUPLICATED, 0, 0,
+ N_("duplicated messages") },
+ { '$', MUTT_UNREFERENCED, 0, 0,
+ N_("unreferenced messages") },
+ { 0, 0, 0, 0,
+ NULL }
};
static pattern_t *SearchPattern = NULL; /* current search pattern */
@@ -1118,13 +1170,20 @@ pattern_t *mutt_pattern_comp (/* const */ char *s, int
flags, BUFFER *err)
if (entry->eat_arg)
{
+ int eatrv = 0;
if (!*ps.dptr)
{
snprintf (err->data, err->dsize, "%s", _("missing parameter"));
mutt_pattern_free (&curlist);
return NULL;
}
- if (entry->eat_arg (tmp, flags, &ps, err) == -1)
+ switch (entry->eat_arg)
+ {
+ case EAT_REGEXP: eatrv = eat_regexp (tmp, flags, &ps, err); break;
+ case EAT_DATE: eatrv = eat_date (tmp, flags, &ps, err); break;
+ case EAT_RANGE: eatrv = eat_range (tmp, flags, &ps, err); break;
+ }
+ if (eatrv == -1)
{
mutt_pattern_free (&curlist);
return NULL;
@@ -1478,6 +1537,12 @@ mutt_pattern_exec (struct pattern_t *pat,
pattern_exec_flag flags, CONTEXT *ctx,
return (pat->not ^ match_adrlist (pat, flags & MUTT_MATCH_FULL_ADDRESS,
4,
h->env->from, h->env->sender,
h->env->to, h->env->cc));
+ case MUTT_ADDRESS_ALL:
+ return (pat->not ^ match_adrlist (pat, flags & MUTT_MATCH_FULL_ADDRESS,
8,
+ h->env->from, h->env->sender,
+ h->env->to, h->env->cc, h->env->bcc,
+ h->env->return_path, h->env->reply_to,
+ h->env->mail_followup_to));
case MUTT_RECIPIENT:
return (pat->not ^ match_adrlist (pat, flags & MUTT_MATCH_FULL_ADDRESS,
2, h->env->to, h->env->cc));
@@ -1668,6 +1733,8 @@ int mutt_pattern_func (int op, char *prompt)
!mutt_buffer_len (buf))
{
mutt_buffer_pool_release (&buf);
+ if (op == MUTT_LIMIT && Context->pattern)
+ mutt_message _("To view all messages, limit to \"all\".");
return (-1);
}
@@ -1926,3 +1993,130 @@ int mutt_search_command (int cur, int op)
mutt_error _("Not found.");
return (-1);
}
+
+static void pattern_entry (char *s, size_t l, MUTTMENU * menu, int num)
+{
+ LIST **PatTable = (LIST **) menu->data;
+
+ mutt_format_string (s, l, 0, COLS, 0, ' ', PatTable[num]->data,
+ mutt_strlen (PatTable[num]->data), 0);
+}
+
+static char pattern_menu (LIST *pats)
+{
+ int patmax = 0;
+ LIST **PatTable = NULL;
+ MUTTMENU *menu;
+ int i, done = 0;
+ char helpstr[SHORT_STRING], buf[LONG_STRING];
+ LIST *pat;
+ char rv = 0;
+
+ for (i = 0, pat = pats; pat; pat = pat->next)
+ {
+ if (i == patmax)
+ {
+ patmax += 5;
+ safe_realloc (&PatTable, sizeof (LIST *) * patmax);
+ }
+ PatTable[i++] = pat;
+ }
+
+ helpstr[0] = 0;
+ mutt_make_help (buf, sizeof (buf), _("Exit "), MENU_GENERIC, OP_EXIT);
+ safe_strcat (helpstr, sizeof (helpstr), buf);
+ mutt_make_help (buf, sizeof (buf), _("Select "), MENU_GENERIC,
+ OP_GENERIC_SELECT_ENTRY);
+ safe_strcat (helpstr, sizeof (helpstr), buf);
+ mutt_make_help (buf, sizeof (buf), _("Help"), MENU_GENERIC, OP_HELP);
+ safe_strcat (helpstr, sizeof (helpstr), buf);
+
+ menu = mutt_new_menu (MENU_GENERIC);
+ menu->max = i;
+ menu->make_entry = pattern_entry;
+ menu->help = helpstr;
+ menu->data = PatTable;
+ menu->title = _("Patterns");
+
+ mutt_clear_error ();
+
+ while (!done)
+ {
+ switch (mutt_menuLoop (menu))
+ {
+ case OP_GENERIC_SELECT_ENTRY:
+ rv = PatTable[menu->current]->data[1];
+ done = 1;
+ break;
+
+ case OP_EXIT:
+ rv = 0;
+ done = 1;
+ break;
+ }
+ }
+
+ mutt_menuDestroy (&menu);
+ FREE (&PatTable);
+
+ return (rv);
+}
+
+static LIST *list_patterns ()
+{
+ LIST *first = NULL, *last = NULL, *cur = NULL;
+ int i;
+
+ for (i = 0; Flags[i].tag; i++)
+ {
+ char buf[LONG_STRING];
+ switch (Flags[i].eat_arg)
+ {
+ case EAT_REGEXP:
+ snprintf (buf, sizeof (buf), _("~%c EXPR %s"),
+ (char) Flags[i].tag, _(Flags[i].desc));
+ break;
+ case EAT_RANGE:
+ snprintf (buf, sizeof (buf), _("~%c RANGE %s"),
+ (char) Flags[i].tag, _(Flags[i].desc));
+ break;
+ case EAT_DATE:
+ snprintf (buf, sizeof (buf), _("~%c DATERANGE %s"),
+ (char) Flags[i].tag, _(Flags[i].desc));
+ break;
+ default:
+ snprintf (buf, sizeof (buf), _("~%c %s"),
+ (char) Flags[i].tag, _(Flags[i].desc));
+ }
+ cur = (LIST *) safe_calloc (1, sizeof (LIST));
+ cur->data = safe_strdup (buf);
+ if (!first)
+ first = cur;
+ if (last)
+ last->next = cur;
+ last = cur;
+ }
+ if (cur)
+ cur->next = NULL;
+ return (first);
+}
+
+int mutt_ask_pattern (char *buf, size_t buflen)
+{
+ char c;
+ LIST *l;
+ int rv = 0;
+
+ if (!buf || buflen < 3)
+ return 0;
+ if ((l = list_patterns()))
+ {
+ if ((c = pattern_menu (l)))
+ {
+ sprintf (buf, "%c ", c); /* __SPRINTF_CHECKED__ */
+ rv = 1;
+ }
+ mutt_free_list (&l);
+ }
+ return rv;
+}
diff --git a/protos.h b/protos.h
index e1bfe746..72c9ad57 100644
--- a/protos.h
+++ b/protos.h
@@ -294,6 +294,7 @@ int mutt_alias_complete (char *, size_t);
void mutt_alias_add_reverse (ALIAS *t);
void mutt_alias_delete_reverse (ALIAS *t);
int mutt_alloc_color (int fg, int bg);
+int mutt_ask_pattern (char *, size_t);
int mutt_any_key_to_continue (const char *);
char *mutt_apply_replace (char *, size_t, char *, REPLACE_LIST *);
int mutt_builtin_editor (SEND_CONTEXT *);