** .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
#include "context.h"
#include "copy.h"
#include "curs_lib.h"
+#include "filter.h"
#include "globals.h"
#include "group.h"
#include "handler.h"
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
*/
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);
{ '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 },
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;
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);