Gmail's IMAP support has an extension, described at: http://code.google.com/apis/gmail/imap/ which lets you use the "X-GM-RAW" search attribute. This will interpret its string as a Gmail search, exposing (most of) the advanced search operators via IMAP.
[disclosure: I am a former Google employee; I get nothing for adding this support to mutt, it's purely for personal convenience] This patch adds the =/ operator to invoke this. Mnemonic: '=' for server-side search, '/' for the Gmail keyboard shortcut to move to the search box. And it was one of the few options still available. :) Note that to get the nearest equivalence to typing into the search box, you first need to navigate to the "[Gmail]/All Mail" folder; by default, the IMAP server-side implementation seems to add folder constraints, which is obviously correct for IMAP -- you can't return message references that don't make sense in a given context. I've tested it against my Gmail account, seems to work for me. So: =/ "some arbitrary search string" =/ "has:attachment from:spodhuis.org" etc etc etc. See also: http://mail.google.com/support/bin/answer.py?answer=7190 It appears that chats are not integrated into IMAP, so is:chat returns no results. "is:starred" works, "has:<colour>-<startype>" does not. Doubtless there are other differences, but that's not mutt's problem. I'm not sure of the best reaction to a ~/ search operator or how to cleanly reject that syntax. At present, it fails to match but no error message is shown, so this would benefit from having someone more current with the search guts looking over that bit to clean it up. This is slightly complicated by Gmail's documentation at the above URL documenting a capability string of "X-GM-EXT1" but real connections seeing a capability string of "X-GM-EXT-1". I added logic to handle capability aliases so that mutt can support both. Ah, the joys of site-local extensions. Regards, -Phil
# HG changeset patch # User Phil Pennock <[email protected]> # Date 1312196976 14400 # Branch HEAD # Node ID d70e93aaef8f5a4177321d5adb0767d9288c1080 # Parent a1e4667211b4c5f2dcca06ad8f352cb86dac94c3 Support Gmail's X-GM-RAW server-side search, letting Gmail search filters be used from mutt with =/ diff --git a/doc/manual.xml.head b/doc/manual.xml.head --- a/doc/manual.xml.head +++ b/doc/manual.xml.head @@ -5002,6 +5002,7 @@ shows several ways to select messages. <row><entry>~X [<emphasis>MIN</emphasis>]-[<emphasis>MAX</emphasis>]</entry><entry>messages with <emphasis>MIN</emphasis> to <emphasis>MAX</emphasis> attachments *)</entry></row> <row><entry>~y <emphasis>EXPR</emphasis></entry><entry>messages which contain <emphasis>EXPR</emphasis> in the <quote>X-Label</quote> field</entry></row> <row><entry>~z [<emphasis>MIN</emphasis>]-[<emphasis>MAX</emphasis>]</entry><entry>messages with a size in the range <emphasis>MIN</emphasis> to <emphasis>MAX</emphasis> *) **)</entry></row> +<row><entry>=/ <emphasis>STRING</emphasis></entry><entry>IMAP custom server-side search for <emphasis>STRING</emphasis>. Currently only defined for Gmail.</entry></row> <row><entry>~=</entry><entry>duplicated messages (see <link linkend="duplicate-threads">$duplicate_threads</link>)</entry></row> <row><entry>~$</entry><entry>unreferenced messages (requires threaded view)</entry></row> <row><entry>~(<emphasis>PATTERN</emphasis>)</entry><entry>messages in threads diff --git a/imap/command.c b/imap/command.c --- a/imap/command.c +++ b/imap/command.c @@ -66,10 +66,22 @@ static char *Capabilities[] = { "LOGINDISABLED", "IDLE", "SASL-IR", + "X-GM-EXT1", NULL }; +/* Gmail document one string but use another. Support both. */ +struct Capability_Alias { + char *name; + unsigned int value; +}; +static struct Capability_Alias Capability_Aliases[] = { + { "X-GM-EXT-1", X_GM_EXT1 }, + { NULL, 0 } +}; + + /* imap_cmd_start: Given an IMAP command, send it to the server. * If cmdstr is NULL, sends queued commands. */ int imap_cmd_start (IMAP_DATA* idata, const char* cmdstr) @@ -556,7 +568,7 @@ static int cmd_handle_untagged (IMAP_DAT * response */ static void cmd_parse_capability (IMAP_DATA* idata, char* s) { - int x; + int x, found; char* bracket; dprint (3, (debugfile, "Handling CAPABILITY\n")); @@ -571,12 +583,25 @@ static void cmd_parse_capability (IMAP_D while (*s) { + found = 0; for (x = 0; x < CAPMAX; x++) if (imap_wordcasecmp(Capabilities[x], s) == 0) { mutt_bit_set (idata->capabilities, x); + dprint (4, (debugfile, " Found capability \"%s\": %d\n", Capabilities[x], x)); + found = 1; break; } + if (!found) + for (x = 0; Capability_Aliases[x].name != NULL; x++) + if (imap_wordcasecmp(Capability_Aliases[x].name, s) == 0) + { + mutt_bit_set (idata->capabilities, Capability_Aliases[x].value); + dprint (4, (debugfile, " Found capability \"%s\": %d\n", + Capability_Aliases[x].name, Capability_Aliases[x].value)); + found = 1; + break; + } s = imap_next_word (s); } } diff --git a/imap/imap.c b/imap/imap.c --- a/imap/imap.c +++ b/imap/imap.c @@ -1678,6 +1678,9 @@ static int do_search (const pattern_t* s if (pat->stringmatch) rc++; break; + case M_SERVERSEARCH: + rc++; + break; default: if (pat->child && do_search (pat->child, 1)) rc++; @@ -1693,7 +1696,7 @@ static int do_search (const pattern_t* s /* convert mutt pattern_t to IMAP SEARCH command containing only elements * that require full-text search (mutt already has what it needs for most * match types, and does a better job (eg server doesn't support regexps). */ -static int imap_compile_search (const pattern_t* pat, BUFFER* buf) +static int imap_compile_search (CONTEXT* ctx, const pattern_t* pat, BUFFER* buf) { if (! do_search (pat, 0)) return 0; @@ -1719,7 +1722,7 @@ static int imap_compile_search (const pa mutt_buffer_addstr (buf, "OR "); clauses--; - if (imap_compile_search (clause, buf) < 0) + if (imap_compile_search (ctx, clause, buf) < 0) return -1; if (clauses) @@ -1770,6 +1773,19 @@ static int imap_compile_search (const pa imap_quote_string (term, sizeof (term), pat->p.str); mutt_buffer_addstr (buf, term); break; + case M_SERVERSEARCH: + { + IMAP_DATA* idata = (IMAP_DATA*)ctx->data; + if (!mutt_bit_isset (idata->capabilities, X_GM_EXT1)) + { + mutt_error(_("Server-side custom search not supported: %s"), pat->p.str); + return -1; + } + } + mutt_buffer_addstr (buf, "X-GM-RAW "); + imap_quote_string (term, sizeof (term), pat->p.str); + mutt_buffer_addstr (buf, term); + break; } } @@ -1790,7 +1806,7 @@ int imap_search (CONTEXT* ctx, const pat memset (&buf, 0, sizeof (buf)); mutt_buffer_addstr (&buf, "UID SEARCH "); - if (imap_compile_search (pat, &buf) < 0) + if (imap_compile_search (ctx, pat, &buf) < 0) { FREE (&buf.data); return -1; diff --git a/imap/imap_private.h b/imap/imap_private.h --- a/imap/imap_private.h +++ b/imap/imap_private.h @@ -114,6 +114,7 @@ enum LOGINDISABLED, /* LOGINDISABLED */ IDLE, /* RFC 2177: IDLE */ SASL_IR, /* SASL initial response draft */ + X_GM_EXT1, /* http://code.google.com/apis/gmail/imap/ */ CAPMAX }; diff --git a/mutt.h b/mutt.h --- a/mutt.h +++ b/mutt.h @@ -226,6 +226,7 @@ enum M_CRYPT_ENCRYPT, M_PGP_KEY, M_XLABEL, + M_SERVERSEARCH, M_MIMEATTACH, /* Options for Mailcap lookup */ diff --git a/pattern.c b/pattern.c --- a/pattern.c +++ b/pattern.c @@ -98,6 +98,7 @@ Flags[] = { 'z', M_SIZE, 0, eat_range }, { '=', M_DUPLICATED, 0, NULL }, { '$', M_UNREFERENCED, 0, NULL }, + { '/', M_SERVERSEARCH, 0, eat_regexp }, { 0, 0, 0, NULL } }; @@ -1142,6 +1143,22 @@ mutt_pattern_exec (struct pattern_t *pat return (h->matched); #endif return (pat->not ^ msg_search (ctx, pat, h->msgno)); + case M_SERVERSEARCH: +#ifdef USE_IMAP + if (!ctx) + return 0; + if (ctx->magic == M_IMAP) + { + if (pat->stringmatch) + return (h->matched); + return 0; + } + mutt_error (_("error: server custom search only supported with IMAP.")); + return 0; +#else + mutt_error (_("error: server custom search only supported with IMAP.")); + return (-1); +#endif case M_SENDER: return (pat->not ^ match_adrlist (pat, flags & M_MATCH_FULL_ADDRESS, 1, h->env->sender));
