From 7d97a61a343c6c988fb49cbb6c009b255275191d Mon Sep 17 00:00:00 2001 From: Ian Zimmerman Date: Wed, 14 Nov 2018 09:14:11 -0800 Subject: [PATCH] Add new pattern type ~I for external searches Call external commands, like mairix, to perform searches of the index. --- doc/manual.xml.head | 10 ++++++ globals.h | 1 + init.h | 28 +++++++++++++++ mutt.h | 1 + pattern.c | 84 +++++++++++++++++++++++++++++++++++++++++++-- pattern.h | 2 ++ 6 files changed, 124 insertions(+), 2 deletions(-) diff --git a/doc/manual.xml.head b/doc/manual.xml.head index cd0374532..96df26e47 100644 --- a/doc/manual.xml.head +++ b/doc/manual.xml.head @@ -7305,6 +7305,16 @@ set index_format="%4C %Z %{%b %d} %-15.15L (%?l?%4l&%4c?)%* %s" Message-ID field + + ~I QUERY + + messages whose Message-ID field is included in + the results returned from en external search program, when the program + is run with QUERY as its argument. + This is explained in greater detail in the variable reference entry + , + + ~k diff --git a/globals.h b/globals.h index 4c8a874c8..09a161e51 100644 --- a/globals.h +++ b/globals.h @@ -139,6 +139,7 @@ WHERE char *IndentString; ///< Config: String used to indent 'r WHERE char *PrintCommand; ///< Config: External command to print a message WHERE char *NewMailCommand; ///< Config: External command to run when new mail arrives WHERE char *Realname; ///< Config: Real name of the user +WHERE char *SearchCommand; ///< Config: External search command WHERE char *Shell; ///< Config: External command to run subshells in WHERE char *SimpleSearch; ///< Config: Pattern to search for when search doesn't contain ~'s #ifdef USE_SMTP diff --git a/init.h b/init.h index 8c42be467..b766ba391 100644 --- a/init.h +++ b/init.h @@ -928,6 +928,34 @@ struct ConfigDef MuttVars[] = { ** .pp ** Escape character to use for functions in the built-in editor. */ + { "external_search_command", DT_COMMAND, R_NONE, &SearchCommand, 0 }, + /* + ** .pp + ** If set, contains the name of the external program used by ``~I'' patterns. + ** This will usually be a wrapper script around mairix, mu, or similar + ** indexers other than notmuch (for which there is optional special support). + ** .pp + ** Here is an example how it works. Let's assume $$external_search_command + ** is set to ``mairix_filter'', and mairix_filter is a script which + ** runs the old but well loved mairix indexer with the arguments + ** given to mairix_filter, in the ``raw'' mode of mairix, producing + ** on the standard output a list of Message-IDs, one per line. + ** (This can be the type of clean and simple script called a \fIone-liner\fP.) + ** .pp + ** Now if NeoMutt gets a limit or tag command followed by the pattern + ** ``~I "-t s:bleeping="'', mairix_filter runs mairix with the + ** arguments from inside the quotes (the quotes are needed because + ** of the space after ``-t''), mairix finds all messages with + ** ``bleeping'' in the Subject plus all messages sharing threads + ** with these and outputs their file names, and mairix_filter + ** translates the file names into Message-IDs. Finally, NeoMutt + ** reads the Message-IDs and targets the matching messages with the + ** command given to it. + ** .pp + ** You, the user, still have to rewrite the mairix_filter script to + ** match the behavior of your indexer, but this should help users + ** of indexers other than notmuch to integrate them cleanly with NeoMutt. + */ { "fast_reply", DT_BOOL, R_NONE, &FastReply, false }, /* ** .pp diff --git a/mutt.h b/mutt.h index b9a6e0099..d2eb08862 100644 --- a/mutt.h +++ b/mutt.h @@ -144,6 +144,7 @@ enum MuttMisc MUTT_UNREFERENCED, ///< Message is unreferenced in the thread MUTT_BROKEN, ///< Message is part of a broken thread MUTT_ID, ///< Pattern matches email's Message-Id + MUTT_ID_EXTERNAL, ///< Message-Id is among results from an external query MUTT_BODY, ///< Pattern matches email's body MUTT_HEADER, ///< Pattern matches email's header MUTT_HORMEL, ///< Pattern matches email's spam score diff --git a/pattern.c b/pattern.c index 2b33cc60f..9d4af76f4 100644 --- a/pattern.c +++ b/pattern.c @@ -47,6 +47,7 @@ #include "context.h" #include "copy.h" #include "curs_lib.h" +#include "filter.h" #include "globals.h" #include "group.h" #include "handler.h" @@ -245,6 +246,79 @@ static bool eat_regex(struct Pattern *pat, struct Buffer *s, struct Buffer *err) return true; } +/** + * add_query_msgid - Parse a Message-Id and add it to a list - Implements mutt_file_map_t + * @retval true Always + */ +static bool add_query_msgid(char *line, int line_num, void *user_data) +{ + struct ListHead *msgid_list = (struct ListHead *) (user_data); + char *nows = mutt_str_skip_whitespace(line); + if (!*nows) + return true; + mutt_str_remove_trailing_ws(nows); + mutt_list_insert_tail(msgid_list, mutt_str_strdup(nows)); + return true; +} + +/** + * eat_query - Parse a query for an external search program + * @param pat Pattern to match + * @param s String to parse + * @param err Buffer for error messages + * @retval true If the pattern was read successfully + */ +static bool eat_query(struct Pattern *pat, struct Buffer *s, struct Buffer *err) +{ + struct Buffer cmd_buf; + struct Buffer tok_buf; + FILE *fp = NULL; + + if (!SearchCommand) + { + mutt_buffer_printf(err, "%s", _("No search command defined")); + return false; + } + + mutt_buffer_init(&tok_buf); + char *pexpr = s->dptr; + if (mutt_extract_token(&tok_buf, s, MUTT_TOKEN_PATTERN | MUTT_TOKEN_COMMENT) != 0 || + !tok_buf.data) + { + mutt_buffer_printf(err, _("Error in expression: %s"), pexpr); + return false; + } + if (*tok_buf.data == '\0') + { + mutt_buffer_printf(err, "%s", _("Empty expression")); + FREE(&tok_buf.data); + return false; + } + + mutt_buffer_init(&cmd_buf); + mutt_buffer_addstr(&cmd_buf, SearchCommand); + mutt_buffer_addch(&cmd_buf, ' '); + mutt_buffer_addstr(&cmd_buf, tok_buf.data); + FREE(&tok_buf.data); + + mutt_message(_("Running search command: %s ..."), cmd_buf.data); + pat->ismulti = true; + mutt_list_clear(&pat->p.multi_cases); + pid_t pid = mutt_create_filter(cmd_buf.data, NULL, &fp, NULL); + if (pid < 0) + { + mutt_buffer_printf(err, "unable to fork command: %s\n", cmd_buf.data); + FREE(&cmd_buf.data); + return false; + } + + mutt_file_map_lines(add_query_msgid, &pat->p.multi_cases, fp, 0); + mutt_file_fclose(&fp); + mutt_wait_filter(pid); + FREE(&cmd_buf.data); + return true; +} + /** * get_offset - Calculate a symbolic offset * @param tm Store the time here @@ -980,7 +1054,9 @@ static bool eat_message_range(struct Pattern *pat, struct Buffer *s, struct Buff */ static int patmatch(const struct Pattern *pat, const char *buf) { - if (pat->stringmatch) + if (pat->ismulti) + return (mutt_list_find(&pat->p.multi_cases, buf) == NULL); + else if (pat->stringmatch) return pat->ign_case ? !strcasestr(buf, pat->p.str) : !strstr(buf, pat->p.str); else if (pat->groupmatch) return !mutt_group_match(pat->p.g, buf); @@ -1167,6 +1243,7 @@ static const struct PatternFlags Flags[] = { { 'h', MUTT_HEADER, MUTT_FULL_MSG, eat_regex }, { 'H', MUTT_HORMEL, 0, eat_regex }, { 'i', MUTT_ID, 0, eat_regex }, + { 'I', MUTT_ID_EXTERNAL, 0, eat_query }, { 'k', MUTT_PGP_KEY, 0, NULL }, { 'l', MUTT_LIST, 0, NULL }, { 'L', MUTT_ADDRESS, 0, eat_regex }, @@ -1254,7 +1331,9 @@ void mutt_pattern_free(struct Pattern **pat) tmp = *pat; *pat = (*pat)->next; - if (tmp->stringmatch) + if (tmp->ismulti) + mutt_list_free(&tmp->p.multi_cases); + else if (tmp->stringmatch) FREE(&tmp->p.str); else if (tmp->groupmatch) tmp->p.g = NULL; @@ -1937,6 +2016,7 @@ int mutt_pattern_exec(struct Pattern *pat, enum PatternExecFlag flags, return 0; return pat->not^(e->env->subject &&patmatch(pat, e->env->subject) == 0); case MUTT_ID: + case MUTT_ID_EXTERNAL: if (!e->env) return 0; return pat->not^(e->env->message_id &&patmatch(pat, e->env->message_id) == 0); diff --git a/pattern.h b/pattern.h index d05c0b94f..a29c898c4 100644 --- a/pattern.h +++ b/pattern.h @@ -50,6 +50,7 @@ struct Pattern bool groupmatch : 1; bool ign_case : 1; /**< ignore case for local stringmatch searches */ bool isalias : 1; + bool ismulti : 1; /**< multiple case (only for I pattern now) */ int min; int max; struct Pattern *next; @@ -58,6 +59,7 @@ struct Pattern regex_t *regex; struct Group *g; char *str; + struct ListHead multi_cases; } p; }; -- 2.50.1