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 <vinc...@vinc17.net> - 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 *);