--- /dev/null
+Return-Path: <pieter@praet.org>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by olra.theworths.org (Postfix) with ESMTP id 24935431FD0\r
+ for <notmuch@notmuchmail.org>; Mon, 11 Jul 2011 13:43:26 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -0.7\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
+ tests=[RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+ by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id koDr0Kmwy0I5 for <notmuch@notmuchmail.org>;\r
+ Mon, 11 Jul 2011 13:43:23 -0700 (PDT)\r
+Received: from mail-wy0-f181.google.com (mail-wy0-f181.google.com\r
+ [74.125.82.181]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
+ (No client certificate requested)\r
+ by olra.theworths.org (Postfix) with ESMTPS id 6E8AA431FB6\r
+ for <notmuch@notmuchmail.org>; Mon, 11 Jul 2011 13:43:23 -0700 (PDT)\r
+Received: by wyh22 with SMTP id 22so3208041wyh.26\r
+ for <notmuch@notmuchmail.org>; Mon, 11 Jul 2011 13:43:22 -0700 (PDT)\r
+Received: by 10.216.235.134 with SMTP id u6mr3358534weq.99.1310417001892;\r
+ Mon, 11 Jul 2011 13:43:21 -0700 (PDT)\r
+Received: from localhost ([109.131.120.63])\r
+ by mx.google.com with ESMTPS id gb1sm10317138wbb.54.2011.07.11.13.43.18\r
+ (version=TLSv1/SSLv3 cipher=OTHER);\r
+ Mon, 11 Jul 2011 13:43:20 -0700 (PDT)\r
+From: Pieter Praet <pieter@praet.org>\r
+To: Austin Clements <amdragon@MIT.EDU>\r
+Subject: [PATCH v2] emacs: bad regexp @ `notmuch-search-process-filter'\r
+Date: Mon, 11 Jul 2011 22:43:13 +0200\r
+Message-Id: <1310416993-31031-1-git-send-email-pieter@praet.org>\r
+X-Mailer: git-send-email 1.7.5.4\r
+In-Reply-To: <20110705214234.GA15360@mit.edu>\r
+References: <20110705214234.GA15360@mit.edu>\r
+Cc: Notmuch Mail <notmuch@notmuchmail.org>\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Mon, 11 Jul 2011 20:43:26 -0000\r
+\r
+\r
+TL;DR: I can haz regex pl0x?\r
+\r
+\r
+I've updated the regex a bit to prevent it from choking on the whole\r
+"parens in subject vs. parens around tags vs. parens around matching\r
+Message-Id's" deal, but it still causes errors in the search buffer due\r
+to the list of Message-Id's being cut off at a seemingly arbitrary point,\r
+and this for *different* results on pretty much every refresh.\r
+\r
+All tests pass, except for the ones I've mentioned before [1] (which\r
+don't test anything tagging-related, just `notmuch-show'), though even\r
+those fail to fail consistently :<. This variability can't be related to\r
+residual files, as I always run the test suite like this:\r
+\r
+ rm -rf /dev/shm/notmuch/* && make test OPTIONS="--root=/dev/shm/notmuch"\r
+\r
+\r
+So, to keep this on track for 0.7 whilst keeping myself from having to\r
+send spammerific amounts of patches, I've squashed the whole deal in\r
+this single patch. Don't worry though, it's all quite grokkable:\r
+\r
+\r
+notmuch-search.c\r
+\r
+- Make `notmuch search' *only* append results with their Message-Id's\r
+ when supplied with the "--output=summary-ids" option, to prevent a\r
+ slew of failing tests obscuring the relevant ones.\r
+\r
+\r
+emacs/notmuch.el\r
+\r
+- Make `notmuch-search' run the notmuch binary with the "--output=summary-ids"\r
+ option, to receive search results appended with their lists of Message-Id's.\r
+\r
+- Update the regex @ `notmuch-search-process-filter' to include a new atom,\r
+ which matches the list of Message-Id's at the end of every search result\r
+ returned by the notmuch binary.\r
+ To each individual result in the search buffer, this matched string is\r
+ added as a text property called `notmuch-search-msgids'.\r
+\r
+- Add 2 functions to return the `notmuch-search-msgids' property of search\r
+ results: `notmuch-search-find-msgids', `notmuch-search-find-msgids-region'.\r
+\r
+- Add a function to stash the Message-Id's of (a region of) search results:\r
+ `notmuch-search-stash-msgids', bound to "m" in `notmuch-search-stash-map'.\r
+ Mainly for testing purposes.\r
+\r
+- Merge `notmuch-call-notmuch-process' into `notmuch-tag':\r
+ `notmuch-tag' was the only thing making use of `notmuch-call-notmuch-process',\r
+ and the extra layer of abstraction would complicate making `notmuch-tag' send\r
+ arguments on stdin (see next point).\r
+\r
+- Make `notmuch-tag' send its query string on stdin:\r
+ Instead of providing the query string as a (potentially very long) command\r
+ line argument, `notmuch-tag' now dumps it into a temporary buffer, which\r
+ `call-process-region' sends to `notmuch-command' on stdin.\r
+ This is needed to circumvent "$command: arg list too long" errors due\r
+ to command line argument length limitations imposed by the kernel (ARG_MAX).\r
+\r
+- Fix the actual bug(s) this patch series is intended to address by making\r
+ the tagging functions procure their targets from the `notmuch-search-msgids'\r
+ property with `notmuch-search-find-msgids-region', instead of using x:\r
+ - `notmuch-search-add-tag-region' (x = `notmuch-search-find-thread-id-region')\r
+ - `notmuch-search-remove-tag-region' (x = `notmuch-search-find-thread-id-region')\r
+ - `notmuch-search-remove-tag' (x = `notmuch-search-find-thread-id-region')\r
+ - `notmuch-search-operate-all' (x = `notmuch-search-query-string')\r
+\r
+\r
+test/emacs, test/emacs-search-operate-all, test/notmuch-test\r
+\r
+- Expand the test suite to also cover:\r
+ - Tagging messages with `notmuch-search-operate-all'.\r
+ - Tagging messages to which a reply is sent. For this I also needed to\r
+ correct the title of an existing test, and add a test for sending\r
+ replies from within Emacs.\r
+\r
+\r
+Side note:\r
+ After playing around with Austin's new patch for a bit, I've come to the\r
+ conclusion that making a clear distinction between matched and unmatched\r
+ messages in the binary's output *is* the way to go, but in the case of\r
+ `notmuch-search-operate-all', this capability shouldn't be leveraged.\r
+\r
+ The way `notmuch-search-operate-all' currently works, i.e. operate on\r
+ matched *messages* instead of matched *threads*, is not only counter-\r
+ intuitive (same as it would be for `notmuch-search-add-tag' and\r
+ `notmuch-search-remove-tag' [2]), but semantically incorrect as well:\r
+ Its name implies operating on *all* that is visible in the current\r
+ buffer, instead of only a subset.\r
+\r
+\r
+Peace\r
+\r
+[1] id:"1310307099-25197-1-git-send-email-pieter@praet.org"\r
+[2] id:"e8c5fbf4-4dfa-461a-8f5c-6c696291a270@email.android.com"\r
+\r
+\r
+Signed-off-by: Pieter Praet <pieter@praet.org>\r
+---\r
+ emacs/notmuch.el | 93 ++++++++++++++++++++++++++++-------------\r
+ notmuch-search.c | 6 ++-\r
+ test/emacs | 49 +++++++++++++++++++++-\r
+ test/emacs-search-operate-all | 29 +++++++++++++\r
+ test/notmuch-test | 1 +\r
+ 5 files changed, 147 insertions(+), 31 deletions(-)\r
+ create mode 100755 test/emacs-search-operate-all\r
+\r
+diff --git a/emacs/notmuch.el b/emacs/notmuch.el\r
+index f11ec24..400adcc 100644\r
+--- a/emacs/notmuch.el\r
++++ b/emacs/notmuch.el\r
+@@ -226,6 +226,7 @@ For a mouse binding, return nil."\r
+ (defvar notmuch-search-stash-map\r
+ (let ((map (make-sparse-keymap)))\r
+ (define-key map "i" 'notmuch-search-stash-thread-id)\r
++ (define-key map "m" 'notmuch-search-stash-msgids)\r
+ map)\r
+ "Submap for stash commands")\r
+ (fset 'notmuch-search-stash-map notmuch-search-stash-map)\r
+@@ -235,6 +236,19 @@ For a mouse binding, return nil."\r
+ (interactive)\r
+ (notmuch-common-do-stash (notmuch-search-find-thread-id)))\r
+ \r
++(defun notmuch-search-stash-msgids ()\r
++ "Copy all Message-ID's in currently selected thread(s) to kill-ring."\r
++ (interactive)\r
++ (save-excursion\r
++ (if (region-active-p)\r
++ (let* ((beg (region-beginning))\r
++ (end (region-end)))\r
++ (notmuch-common-do-stash\r
++ (mapconcat 'identity\r
++ (notmuch-search-find-msgids-region beg end)\r
++ " or ")))\r
++ (notmuch-common-do-stash (notmuch-search-find-msgids)))))\r
++\r
+ (defvar notmuch-search-query-string)\r
+ (defvar notmuch-search-target-thread)\r
+ (defvar notmuch-search-target-line)\r
+@@ -402,6 +416,14 @@ Complete list of currently available key bindings:\r
+ "Return a list of threads for the current region"\r
+ (notmuch-search-properties-in-region 'notmuch-search-thread-id beg end))\r
+ \r
++(defun notmuch-search-find-msgids ()\r
++ "Return all Message-Id's for the current thread"\r
++ (get-text-property (point) 'notmuch-search-msgids))\r
++\r
++(defun notmuch-search-find-msgids-region (beg end)\r
++ "Return a list of all Message-Id's for the threads in the current region"\r
++ (notmuch-search-properties-in-region 'notmuch-search-msgids beg end))\r
++\r
+ (defun notmuch-search-find-authors ()\r
+ "Return the authors for the current thread"\r
+ (get-text-property (point) 'notmuch-search-authors))\r
+@@ -448,23 +470,6 @@ Complete list of currently available key bindings:\r
+ (let ((message-id (notmuch-search-find-thread-id)))\r
+ (notmuch-mua-new-reply message-id prompt-for-sender)))\r
+ \r
+-(defun notmuch-call-notmuch-process (&rest args)\r
+- "Synchronously invoke \"notmuch\" with the given list of arguments.\r
+-\r
+-Output from the process will be presented to the user as an error\r
+-and will also appear in a buffer named \"*Notmuch errors*\"."\r
+- (let ((error-buffer (get-buffer-create "*Notmuch errors*")))\r
+- (with-current-buffer error-buffer\r
+- (erase-buffer))\r
+- (if (eq (apply 'call-process notmuch-command nil error-buffer nil args) 0)\r
+- (point)\r
+- (progn\r
+- (with-current-buffer error-buffer\r
+- (let ((beg (point-min))\r
+- (end (- (point-max) 1)))\r
+- (error (buffer-substring beg end))\r
+- ))))))\r
+-\r
+ (defun notmuch-tag (query &rest tags)\r
+ "Add/remove tags in TAGS to messages matching QUERY.\r
+ \r
+@@ -476,8 +481,32 @@ messages instead of running (notmuch-call-notmuch-process \"tag\" ..)\r
+ directly, so that hooks specified in notmuch-before-tag-hook and\r
+ notmuch-after-tag-hook will be run."\r
+ (run-hooks 'notmuch-before-tag-hook)\r
+- (apply 'notmuch-call-notmuch-process\r
+- (append (list "tag") tags (list "--" query)))\r
++\r
++ (let ((query-buffer (get-buffer-create "*Notmuch query*"))\r
++ (error-buffer (get-buffer-create "*Notmuch errors*")))\r
++ (with-current-buffer error-buffer\r
++ (erase-buffer))\r
++\r
++ (with-current-buffer query-buffer\r
++ (erase-buffer)\r
++ (insert query)\r
++\r
++ (if (eq\r
++ (apply 'call-process-region\r
++ (append\r
++ (list (point-min) (point-max) notmuch-command nil error-buffer nil)\r
++ (list "tag" "--stdin") tags))\r
++ 0)\r
++ (point)\r
++ (progn\r
++ (with-current-buffer error-buffer\r
++ (let ((beg (point-min))\r
++ (end (- (point-max) 1)))\r
++ (error (buffer-substring beg end))\r
++ )))))\r
++\r
++ (kill-buffer query-buffer))\r
++\r
+ (run-hooks 'notmuch-after-tag-hook))\r
+ \r
+ (defcustom notmuch-before-tag-hook nil\r
+@@ -541,7 +570,7 @@ the messages that were tagged"\r
+ (notmuch-search-add-tag-region tag (point) (point)))\r
+ \r
+ (defun notmuch-search-add-tag-region (tag beg end)\r
+- (let ((search-id-string (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or ")))\r
++ (let ((search-id-string (mapconcat 'identity (notmuch-search-find-msgids-region beg end) " or ")))\r
+ (notmuch-tag search-id-string (concat "+" tag))\r
+ (save-excursion\r
+ (let ((last-line (line-number-at-pos end))\r
+@@ -555,7 +584,7 @@ the messages that were tagged"\r
+ (notmuch-search-remove-tag-region tag (point) (point)))\r
+ \r
+ (defun notmuch-search-remove-tag-region (tag beg end)\r
+- (let ((search-id-string (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or ")))\r
++ (let ((search-id-string (mapconcat 'identity (notmuch-search-find-msgids-region beg end) " or ")))\r
+ (notmuch-tag search-id-string (concat "-" tag))\r
+ (save-excursion\r
+ (let ((last-line (line-number-at-pos end))\r
+@@ -589,9 +618,9 @@ thread or threads in the current region."\r
+ "Tag to remove: "\r
+ (if (region-active-p)\r
+ (mapconcat 'identity\r
+- (notmuch-search-find-thread-id-region (region-beginning) (region-end))\r
++ (notmuch-search-find-msgids-region (region-beginning) (region-end))\r
+ " ")\r
+- (notmuch-search-find-thread-id)))))\r
++ (notmuch-search-find-msgids)))))\r
+ (save-excursion\r
+ (if (region-active-p)\r
+ (let* ((beg (region-beginning))\r
+@@ -801,13 +830,14 @@ non-authors is found, assume that all of the authors match."\r
+ (while more\r
+ (while (and (< line (length string)) (= (elt string line) ?\n))\r
+ (setq line (1+ line)))\r
+- (if (string-match "^\\(thread:[0-9A-Fa-f]*\\) \\([^][]*\\) \\(\\[[0-9/]*\\]\\) \\([^;]*\\); \\(.*\\) (\\([^()]*\\))$" string line)\r
++ (if (string-match "^\\(thread:[0-9A-Fa-f]*\\) \\([^][]*\\) \\(\\[[0-9/]*\\]\\) \\([^;]*\\); \\(.*\\) (\\([^()]*\\)) \\((.*\\(or.*\\)*\\)$" string line)\r
+ (let* ((thread-id (match-string 1 string))\r
+ (date (match-string 2 string))\r
+ (count (match-string 3 string))\r
+ (authors (match-string 4 string))\r
+ (subject (match-string 5 string))\r
+ (tags (match-string 6 string))\r
++ (msgids (match-string 7 string))\r
+ (tag-list (if tags (save-match-data (split-string tags)))))\r
+ (goto-char (point-max))\r
+ (if (/= (match-beginning 1) line)\r
+@@ -816,6 +846,7 @@ non-authors is found, assume that all of the authors match."\r
+ (notmuch-search-show-result date count authors subject tags)\r
+ (notmuch-search-color-line beg (point-marker) tag-list)\r
+ (put-text-property beg (point-marker) 'notmuch-search-thread-id thread-id)\r
++ (put-text-property beg (point-marker) 'notmuch-search-msgids msgids)\r
+ (put-text-property beg (point-marker) 'notmuch-search-authors authors)\r
+ (put-text-property beg (point-marker) 'notmuch-search-subject subject)\r
+ (if (string= thread-id notmuch-search-target-thread)\r
+@@ -834,10 +865,10 @@ non-authors is found, assume that all of the authors match."\r
+ (delete-process proc))))\r
+ \r
+ (defun notmuch-search-operate-all (action)\r
+- "Add/remove tags from all matching messages.\r
++ "Add/remove tags to/from all threads in current search buffer.\r
+ \r
+-This command adds or removes tags from all messages matching the\r
+-current search terms. When called interactively, this command\r
++This command adds or removes tags from all threads displayed in\r
++the current search buffer. When called interactively, this command\r
+ will prompt for tags to be added or removed. Tags prefixed with\r
+ '+' will be added and tags prefixed with '-' will be removed.\r
+ \r
+@@ -845,7 +876,10 @@ Each character of the tag name may consist of alphanumeric\r
+ characters as well as `_.+-'.\r
+ "\r
+ (interactive "sOperation (+add -drop): notmuch tag ")\r
+- (let ((action-split (split-string action " +")))\r
++ (let ((action-split (split-string action " +"))\r
++ (msgids (mapconcat 'identity\r
++ (notmuch-search-find-msgids-region (point-min) (- (point-max) 2))\r
++ " or ")))\r
+ ;; Perform some validation\r
+ (let ((words action-split))\r
+ (when (null words) (error "No operation given"))\r
+@@ -853,7 +887,7 @@ characters as well as `_.+-'.\r
+ (unless (string-match-p "^[-+][-+_.[:word:]]+$" (car words))\r
+ (error "Action must be of the form `+thistag -that_tag'"))\r
+ (setq words (cdr words))))\r
+- (apply 'notmuch-tag notmuch-search-query-string action-split)))\r
++ (apply 'notmuch-tag msgids action-split)))\r
+ \r
+ (defun notmuch-search-buffer-title (query)\r
+ "Returns the title for a buffer with notmuch search results."\r
+@@ -913,6 +947,7 @@ The optional parameters are used as follows:\r
+ (let ((proc (start-process\r
+ "notmuch-search" buffer\r
+ notmuch-command "search"\r
++ "--output=summary-ids"\r
+ (if oldest-first\r
+ "--sort=oldest-first"\r
+ "--sort=newest-first")\r
+diff --git a/notmuch-search.c b/notmuch-search.c\r
+index 2288eb7..b3af88b 100644\r
+--- a/notmuch-search.c\r
++++ b/notmuch-search.c\r
+@@ -22,6 +22,7 @@\r
+ \r
+ typedef enum {\r
+ OUTPUT_SUMMARY,\r
++ OUTPUT_SUMMARY_IDS,\r
+ OUTPUT_THREADS,\r
+ OUTPUT_MESSAGES,\r
+ OUTPUT_FILES,\r
+@@ -274,7 +275,7 @@ do_search_threads (const search_format_t *format,\r
+ \r
+ fputs (format->tag_end, stdout);\r
+ \r
+- if (format == &format_text) {\r
++ if (format == &format_text && output == OUTPUT_SUMMARY_IDS) {\r
+ notmuch_messages_t *toplevel;\r
+ const char *first;\r
+ \r
+@@ -462,6 +463,8 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\r
+ opt = argv[i] + sizeof ("--output=") - 1;\r
+ if (strcmp (opt, "summary") == 0) {\r
+ output = OUTPUT_SUMMARY;\r
++ } else if (strcmp (opt, "summary-ids") == 0) {\r
++ output = OUTPUT_SUMMARY_IDS;\r
+ } else if (strcmp (opt, "threads") == 0) {\r
+ output = OUTPUT_THREADS;\r
+ } else if (strcmp (opt, "messages") == 0) {\r
+@@ -513,6 +516,7 @@ notmuch_search_command (void *ctx, int argc, char *argv[])\r
+ switch (output) {\r
+ default:\r
+ case OUTPUT_SUMMARY:\r
++ case OUTPUT_SUMMARY_IDS:\r
+ case OUTPUT_THREADS:\r
+ ret = do_search_threads (format, query, sort, output);\r
+ break;\r
+diff --git a/test/emacs b/test/emacs\r
+index 53f455a..6479c4e 100755\r
+--- a/test/emacs\r
++++ b/test/emacs\r
+@@ -239,7 +239,7 @@ Subject:\r
+ EOF\r
+ test_expect_equal_file OUTPUT EXPECTED\r
+ \r
+-test_begin_subtest "Reply within emacs"\r
++test_begin_subtest "Compose reply in emacs"\r
+ test_emacs '(notmuch-search "subject:\"testing message sent via SMTP\"")\r
+ (notmuch-test-wait)\r
+ (notmuch-search-reply-to-thread)\r
+@@ -257,6 +257,53 @@ On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> w\r
+ EOF\r
+ test_expect_equal_file OUTPUT EXPECTED\r
+ \r
++test_begin_subtest "Send reply from within Emacs"\r
++$TEST_DIRECTORY/smtp-dummy sent_message &\r
++smtp_dummy_pid=$!\r
++test_emacs \\r
++'(let ((message-send-mail-function '\''message-smtpmail-send-it)\r
++ (smtpmail-smtp-server "localhost")\r
++ (smtpmail-smtp-service "25025"))\r
++ (notmuch-search "subject:\"testing message sent via SMTP\"")\r
++ (notmuch-test-wait)\r
++ (notmuch-search-reply-to-thread)\r
++ (message-goto-to)\r
++ (message-goto-body)\r
++ (end-of-buffer)\r
++ (newline)\r
++ (insert "Reply to a message via Emacs with fake SMTP")\r
++ (message-send-and-exit))' >/dev/null 2>&1\r
++wait ${smtp_dummy_pid}\r
++notmuch new >/dev/null\r
++sed \\r
++ -e s',^User-Agent: Notmuch/.* Emacs/.*,User-Agent: Notmuch/XXX Emacs/XXX,' \\r
++ -e s',^Message-ID: <.*>$,Message-ID: <XXX>,' \\r
++ -e s',^In-Reply-To: <.*>$,In-Reply-To: <XXX>,' \\r
++ -e s',^References: <.*>$,References: <XXX>,' \\r
++ -e s',^Date: .*$,Date: Fri\, 29 Mar 1974 10:05:00 -0000,' < sent_message >OUTPUT\r
++cat <<EOF >EXPECTED\r
++From: Notmuch Test Suite <test_suite@notmuchmail.org>\r
++To: user@example.com\r
++Subject: Re: Testing message sent via SMTP\r
++In-Reply-To: <XXX>\r
++References: <XXX>\r
++User-Agent: Notmuch/XXX Emacs/XXX\r
++Date: Fri, 29 Mar 1974 10:05:00 -0000\r
++Message-ID: <XXX>\r
++MIME-Version: 1.0\r
++Content-Type: text/plain; charset=us-ascii\r
++\r
++On 01 Jan 2000 12:00:00 -0000, Notmuch Test Suite <test_suite@notmuchmail.org> wrote:\r
++> This is a test that messages are sent via SMTP\r
++\r
++Reply to a message via Emacs with fake SMTP\r
++EOF\r
++test_expect_equal_file OUTPUT EXPECTED\r
++\r
++test_begin_subtest "Verify that 'replied' tag is added to reply's parent message."\r
++output=$(notmuch search 'tag:replied' | notmuch_search_sanitize)\r
++test_expect_equal "$output" "thread:XXX 2000-01-01 [1/2] Notmuch Test Suite; Testing message sent via SMTP (inbox replied)"\r
++\r
+ test_begin_subtest "Save attachment from within emacs using notmuch-show-save-attachments"\r
+ # save as archive to test that Emacs does not re-compress .gz\r
+ test_emacs '(let ((standard-input "\"attachment1.gz\""))\r
+diff --git a/test/emacs-search-operate-all b/test/emacs-search-operate-all\r
+new file mode 100755\r
+index 0000000..48326c8\r
+--- /dev/null\r
++++ b/test/emacs-search-operate-all\r
+@@ -0,0 +1,29 @@\r
++#!/usr/bin/env bash\r
++\r
++test_description="emacs interface"\r
++. test-lib.sh\r
++\r
++EXPECTED=$TEST_DIRECTORY/emacs.expected-output\r
++\r
++add_email_corpus\r
++\r
++test_begin_subtest "Add/remove tags to/from all matching threads."\r
++test_emacs '(notmuch-search "tag:inbox AND tags")\r
++ (notmuch-test-wait)\r
++ (notmuch-search-operate-all "+matching -inbox")\r
++ (notmuch-search "tag:matching AND NOT tag:inbox")\r
++ (notmuch-test-wait)\r
++ (test-output)'\r
++cat <<EOF >EXPECTED\r
++ 2009-11-18 [2/2] Ingmar Vanhassel, Carl Worth [notmuch] [PATCH] Typsos (matching unread)\r
++ 2009-11-18 [3/3] Adrian Perez de Castro, Keith Packard, Carl Worth [notmuch] Introducing myself (matching signed unread)\r
++ 2009-11-18 [3/3] Israel Herraiz, Keith Packard, Carl Worth [notmuch] New to the list (matching unread)\r
++ 2009-11-18 [2/2] Keith Packard, Carl Worth [notmuch] [PATCH] Make notmuch-show 'X' (and 'x') commands remove inbox (and unread) tags (matching unread)\r
++ 2009-11-18 [2/2] Keith Packard, Alexander Botero-Lowry [notmuch] [PATCH] Create a default notmuch-show-hook that highlights URLs and uses word-wrap (matching unread)\r
++ 2009-11-18 [1/1] Jan Janak [notmuch] [PATCH] notmuch new: Support for conversion of spool subdirectories into tags (matching unread)\r
++ 2009-11-18 [1/1] Stewart Smith [notmuch] [PATCH] Fix linking with gcc to use g++ to link in C++ libs. (matching unread)\r
++End of search results.\r
++EOF\r
++test_expect_equal_file OUTPUT EXPECTED\r
++\r
++test_done\r
+diff --git a/test/notmuch-test b/test/notmuch-test\r
+index 79e6267..bafbcb1 100755\r
+--- a/test/notmuch-test\r
++++ b/test/notmuch-test\r
+@@ -38,6 +38,7 @@ TESTS="\r
+ encoding\r
+ emacs\r
+ emacs-large-search-buffer\r
++ emacs-search-operate-all\r
+ maildir-sync\r
+ crypto\r
+ symbol-hiding\r
+-- \r
+1.7.5.4\r
+\r