[PATCH v2] emacs: display tags in notmuch-show with links
authorDamien Cassou <damien.cassou@gmail.com>
Sat, 10 Nov 2012 16:41:59 +0000 (17:41 +0100)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:50:29 +0000 (09:50 -0800)
fc/ddcc671b99b4265d9064361a1709876574f48c [new file with mode: 0644]

diff --git a/fc/ddcc671b99b4265d9064361a1709876574f48c b/fc/ddcc671b99b4265d9064361a1709876574f48c
new file mode 100644 (file)
index 0000000..9b81cff
--- /dev/null
@@ -0,0 +1,534 @@
+Return-Path: <damien.cassou@gmail.com>\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 EA7A3431FB6\r
+       for <notmuch@notmuchmail.org>; Sat, 10 Nov 2012 08:42:35 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: 1.7\r
+X-Spam-Level: *\r
+X-Spam-Status: No, score=1.7 tagged_above=-999 required=5\r
+       tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
+       FREEMAIL_FROM=0.001, FREEMAIL_REPLY=2.499, RCVD_IN_DNSWL_LOW=-0.7]\r
+       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 0sUuV0LU3a+5 for <notmuch@notmuchmail.org>;\r
+       Sat, 10 Nov 2012 08:42:34 -0800 (PST)\r
+Received: from mail-wi0-f179.google.com (mail-wi0-f179.google.com\r
+       [209.85.212.179]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
+       (No client certificate requested)\r
+       by olra.theworths.org (Postfix) with ESMTPS id C4770431FAF\r
+       for <notmuch@notmuchmail.org>; Sat, 10 Nov 2012 08:42:33 -0800 (PST)\r
+Received: by mail-wi0-f179.google.com with SMTP id hm6so1027954wib.2\r
+       for <notmuch@notmuchmail.org>; Sat, 10 Nov 2012 08:42:32 -0800 (PST)\r
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113;\r
+       h=from:to:cc:subject:date:message-id:x-mailer:mime-version\r
+       :content-type:content-transfer-encoding;\r
+       bh=SokKGH5nL2A7mAwfJwWFkf3dGi1c6ohOxYnwTr9Cr/w=;\r
+       b=TK6sze1FbLOckGUlw+NMdx2Kytmz0sBCuYCrluwNfVBZtIjRLC10MvmbT37OLUYfHH\r
+       5oX7RoMPbJBP1sD3KuYedWkRdMb+dmtwiyRjw1Bpxatqvg3CGOofB0LlXP1FwWTqI29Y\r
+       2P4gbvN9XezWDiAbyWMlhIRJGPuWAo8x9cXwu/OFQgmwvO0lRf//N49n3BWwwv643krb\r
+       j9PssL9rGAtaaGu5LK9Ha80zfoTbxZ799/bfCMT9n9WrGqBTd9NyBY7n2cem1gFxCTvX\r
+       hGb43DiHsjvaFfr13x2hhBjSFc2wMw0dQ1uWvvjXqJ9vPzg/VqbhNh5m45o+yGUcHScV\r
+       KBWA==\r
+Received: by 10.180.97.35 with SMTP id dx3mr7785198wib.14.1352565752453;\r
+       Sat, 10 Nov 2012 08:42:32 -0800 (PST)\r
+Received: from localhost.localdomain (ble59-4-82-228-190-150.fbx.proxad.net.\r
+       [82.228.190.150])\r
+       by mx.google.com with ESMTPS id i6sm6952373wix.5.2012.11.10.08.42.31\r
+       (version=TLSv1/SSLv3 cipher=OTHER);\r
+       Sat, 10 Nov 2012 08:42:31 -0800 (PST)\r
+From: Damien Cassou <damien.cassou@gmail.com>\r
+To: notmuch mailing list <notmuch@notmuchmail.org>\r
+Subject: [PATCH v2] emacs: display tags in notmuch-show with links\r
+Date: Sat, 10 Nov 2012 17:41:59 +0100\r
+Message-Id: <1352565719-12397-1-git-send-email-damien.cassou@gmail.com>\r
+X-Mailer: git-send-email 1.7.10.4\r
+MIME-Version: 1.0\r
+Content-Type: text/plain; charset=UTF-8\r
+Content-Transfer-Encoding: 8bit\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: Sat, 10 Nov 2012 16:42:36 -0000\r
+\r
+This patch obsoletes\r
+id:1352234344-28119-1-git-send-email-damien.cassou@gmail.com\r
+\r
+This patch makes clickable all tags that appear in notmuch-show\r
+buffers. Each tag is a link to open a new notmuch-search buffer for\r
+this tag. Additionally, the buffer's header-line now shows the\r
+thread's tags (also clickable).\r
+\r
+This patch is the first one of an upcoming series whose goal is to\r
+integrate notmuch-labeler into notmuch. See the following for more\r
+details:\r
+https://github.com/DamienCassou/notmuch-labeler\r
+\r
+This patch includes header-button.el, a package contributed by Jonas\r
+Bernoulli that fixes a limitation of the button.el Emacs library.\r
+Jonas gave me the authorization to include the package in notmuch, but\r
+only if the package is first searched in the existing `load-path'. See\r
+this thread:\r
+id:CA+y5ggiGrAcicQLeskaXFoxYyJQVVXZ1VRX=XS8zPFR9_mBFxA@mail.gmail.com\r
+\r
+With respect to v1, I took care of the comments you made:\r
+- Renamed tager to tagger;\r
+- Avoided an additional call to notmuch by reading existing data in\r
+  the buffer with `notmuch-show-mapc';\r
+- As a result of previous point, a thread's tags now equals the union\r
+  of the emails' tags that are visible;\r
+- Stopped stripping "thread:" from the thread-id to add it back\r
+  later.\r
+\r
+With respect to v1, I added the following:\r
+- Each label on each message is now clickable;\r
+- Moved header-button.el to fallback-libs/ and only load this one when\r
+  it is not already in the `load-path'.\r
+\r
+You can follow this patch series on\r
+https://github.com/DamienCassou/notmuch/tree/labeler-integration\r
+\r
+Signed-off-by: Damien Cassou <damien.cassou@gmail.com>\r
+---\r
+ emacs/fallback-libs/.nosearch        |    1 +\r
+ emacs/fallback-libs/header-button.el |  138 ++++++++++++++++++++++++++++++++++\r
+ emacs/notmuch-show.el                |   33 ++++++--\r
+ emacs/notmuch-tagger.el              |  129 +++++++++++++++++++++++++++++++\r
+ test/emacs                           |   61 +++++++++++++++\r
+ 5 files changed, 355 insertions(+), 7 deletions(-)\r
+ create mode 100644 emacs/fallback-libs/.nosearch\r
+ create mode 100644 emacs/fallback-libs/header-button.el\r
+ create mode 100644 emacs/notmuch-tagger.el\r
+\r
+diff --git a/emacs/fallback-libs/.nosearch b/emacs/fallback-libs/.nosearch\r
+new file mode 100644\r
+index 0000000..0a01dc9\r
+--- /dev/null\r
++++ b/emacs/fallback-libs/.nosearch\r
+@@ -0,0 +1 @@\r
++This file prevents Emacs from adding the directory to the `load-path'.\r
+diff --git a/emacs/fallback-libs/header-button.el b/emacs/fallback-libs/header-button.el\r
+new file mode 100644\r
+index 0000000..05f6f32\r
+--- /dev/null\r
++++ b/emacs/fallback-libs/header-button.el\r
+@@ -0,0 +1,138 @@\r
++;;; header-button.el --- clickable buttons in header lines\r
++\r
++;; Copyright (C) 2010-2012  Jonas Bernoulli\r
++\r
++;; Author: Jonas Bernoulli <jonas@bernoul.li>\r
++;; Created: 20100604\r
++;; Version: 0.2.2\r
++;; Homepage: https://github.com/tarsius/header-button\r
++;; Keywords: extensions\r
++\r
++;; This file is not part of GNU Emacs.\r
++\r
++;; This file is free software; you can redistribute it and/or modify\r
++;; it under the terms of the GNU General Public License as published by\r
++;; the Free Software Foundation; either version 3, or (at your option)\r
++;; any later version.\r
++\r
++;; This file is distributed in the hope that it will be useful,\r
++;; but WITHOUT ANY WARRANTY; without even the implied warranty of\r
++;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
++;; GNU General Public License for more details.\r
++\r
++;; You should have received a copy of the GNU General Public License\r
++;; along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
++\r
++;;; Commentary:\r
++\r
++;; This package extends `button' by adding support for adding buttons to\r
++;; the header line.  Since the header line is very limited compared to a\r
++;; buffer most of the functionality provided by `button' is not available\r
++;; for buttons in the header line.\r
++\r
++;; While `button' provides the function `insert-button' (as well as\r
++;; others) to insert a button into a buffer at point, something similar\r
++;; can't be done here, due to the lack of point in header lines.\r
++\r
++;; Instead use `header-button-format' like this:\r
++;;\r
++;; (setq header-line-format\r
++;;       (concat "Here's a button: "\r
++;;               (header-button-format "Click me!" :action 'my-action)))\r
++\r
++;; Like with `button' you can create your own derived button types:\r
++;;\r
++;; (define-button-type 'my-header\r
++;;   :supertype 'header\r
++;;   :action 'my-action)\r
++;;\r
++;; (setq header-line-format\r
++;;       (concat (header-button-format "Click me!" :action 'my-action) " "\r
++;;               (header-button-format "No me!" :type 'my-header)))\r
++\r
++;; The function associated with `:action' is called with the button plist\r
++;; as only argument.  Do no use `plist-get' to extract a value from it.\r
++;; Instead use `header-button-get' which will also extract values stored\r
++;; in it's type.\r
++;;\r
++;; (defun my-action (button)\r
++;;   (message "This button labeled `%s' belongs to category `%s'"\r
++;;            (header-button-label button)\r
++;;            (header-button-get button 'category)))\r
++\r
++;;; Code:\r
++\r
++(require 'button)\r
++\r
++(defvar header-button-map\r
++  (let ((map (make-sparse-keymap)))\r
++    ;; $$$ follow-link does not work here\r
++    (define-key map [header-line mouse-1] 'header-button-push)\r
++    (define-key map [header-line mouse-2] 'header-button-push)\r
++    map)\r
++  "Keymap used by buttons in header lines.")\r
++\r
++(define-button-type 'header\r
++  'keymap header-button-map\r
++  'help-echo (purecopy "mouse-1: Push this button"))\r
++\r
++(defun header-button-get (button prop)\r
++  "Get the property of header button BUTTON named PROP."\r
++  (let ((entry (plist-member button prop)))\r
++    (if entry\r
++        (cadr entry)\r
++      (get (plist-get button 'category) prop))))\r
++\r
++(defun header-button-label (button)\r
++  "Return header button BUTTON's text label."\r
++  (plist-get button 'label))\r
++\r
++(defun header-button-format (label &rest properties)\r
++  "Format a header button string with the label LABEL.\r
++The remaining arguments form a sequence of PROPERTY VALUE pairs,\r
++specifying properties to add to the button.\r
++In addition, the keyword argument :type may be used to specify a\r
++button-type from which to inherit other properties; see\r
++`define-button-type'.\r
++\r
++To actually create the header button set the value of variable\r
++`header-line-format' to the string returned by this function\r
++\(or a string created by concatenating that string with others."\r
++  (let ((type-entry (or (plist-member properties 'type)\r
++                        (plist-member properties :type))))\r
++    (when (plist-get properties 'category)\r
++      (error "Button `category' property may not be set directly"))\r
++    (if (null type-entry)\r
++        (setq properties\r
++              (cons 'category\r
++                    (cons (button-category-symbol 'header) properties)))\r
++      (setcar type-entry 'category)\r
++      (setcar (cdr type-entry)\r
++              (button-category-symbol (car (cdr type-entry)))))\r
++    (apply #'propertize label\r
++           (nconc (list 'button (list t) 'label label) properties))))\r
++\r
++(defun header-button-activate (button)\r
++  "Call header button BUTTON's `:action' property."\r
++  ;; Older versions only supported `:action' but button.el uses\r
++  ;; `action' instead.  Now we support both and query `:action'\r
++  ;; first because `action' defaults to function `ignore'.\r
++  (funcall (or (header-button-get button :action)\r
++               (header-button-get button 'action))\r
++           button))\r
++\r
++(defun header-button-push ()\r
++  "Perform the action specified by the pressed header button."\r
++  (interactive)\r
++  (let* ((posn (event-start last-command-event))\r
++         (object (posn-object posn))\r
++         (buffer (window-buffer (posn-window posn)))\r
++         (button (text-properties-at (cdr object) (car object))))\r
++    (with-current-buffer buffer\r
++      (header-button-activate button))))\r
++\r
++(provide 'header-button)\r
++;; Local Variables:\r
++;; indent-tabs-mode: nil\r
++;; End:\r
++;;; header-button.el ends here\r
+diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el\r
+index d061367..6f38381 100644\r
+--- a/emacs/notmuch-show.el\r
++++ b/emacs/notmuch-show.el\r
+@@ -36,6 +36,7 @@\r
+ (require 'notmuch-mua)\r
+ (require 'notmuch-crypto)\r
+ (require 'notmuch-print)\r
++(require 'notmuch-tagger)\r
+ \r
+ (declare-function notmuch-call-notmuch-process "notmuch" (&rest args))\r
+ (declare-function notmuch-fontify-headers "notmuch" nil)\r
+@@ -430,10 +431,11 @@ message at DEPTH in the current thread."\r
+           (notmuch-show-clean-address (plist-get headers :From))\r
+           " ("\r
+           date\r
+-          ") ("\r
+-          (propertize (mapconcat 'identity tags " ")\r
+-                      'face 'notmuch-tag-face)\r
+-          ")\n")\r
++          ") "\r
++          (propertize\r
++           (format-mode-line (notmuch-tagger-present-tags tags))\r
++           'face 'notmuch-tag-face)\r
++          "\n")\r
+     (overlay-put (make-overlay start (point)) 'face 'notmuch-message-summary-face)))\r
+ \r
+ (defun notmuch-show-insert-header (header header-value)\r
+@@ -1082,11 +1084,28 @@ function is used."\r
+ \r
+       (jit-lock-register #'notmuch-show-buttonise-links)\r
+ \r
+-      ;; Set the header line to the subject of the first message.\r
+-      (setq header-line-format (notmuch-show-strip-re (notmuch-show-get-subject)))\r
+-\r
++      (notmuch-show-update-header-line)\r
+       (run-hooks 'notmuch-show-hook))))\r
+ \r
++(defun notmuch-show-thread-tags ()\r
++  "Return the list of tags for the current thread."\r
++  (let ((tags (list)))\r
++    (notmuch-show-mapc (lambda ()\r
++                       (mapcar (lambda (elt)\r
++                                 ;; Avoid adding duplicate tags\r
++                                 (add-to-list 'tags elt))\r
++                               (notmuch-show-get-tags))))\r
++    tags))\r
++\r
++(defun notmuch-show-update-header-line ()\r
++  "Make the header-line show the thread's subject and tags."\r
++  (let ((thread-subject (notmuch-show-strip-re (notmuch-show-get-subject))))\r
++    (setq header-line-format\r
++        (list\r
++         thread-subject\r
++         " "\r
++         (notmuch-tagger-present-tags (notmuch-show-thread-tags) t)))))\r
++\r
+ (defun notmuch-show-capture-state ()\r
+   "Capture the state of the current buffer.\r
+ \r
+diff --git a/emacs/notmuch-tagger.el b/emacs/notmuch-tagger.el\r
+new file mode 100644\r
+index 0000000..e825df5\r
+--- /dev/null\r
++++ b/emacs/notmuch-tagger.el\r
+@@ -0,0 +1,129 @@\r
++;; notmuch-tagger.el --- Library to show labels as links\r
++;;\r
++;; Copyright © Damien Cassou\r
++;;\r
++;; This file is part of Notmuch.\r
++;;\r
++;; Notmuch is free software: you can redistribute it and/or modify it\r
++;; under the terms of the GNU General Public License as published by\r
++;; the Free Software Foundation, either version 3 of the License, or\r
++;; (at your option) any later version.\r
++;;\r
++;; Notmuch is distributed in the hope that it will be useful, but\r
++;; WITHOUT ANY WARRANTY; without even the implied warranty of\r
++;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
++;; General Public License for more details.\r
++;;\r
++;; You should have received a copy of the GNU General Public License\r
++;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.\r
++;;\r
++;; Authors: Damien Cassou <damien.cassou@gmail.com>\r
++;;; Commentary:\r
++;;\r
++;;; Code:\r
++;;\r
++\r
++(require 'button)\r
++\r
++(or (require 'header-button nil t)\r
++    (let ((load-path\r
++           (cons (expand-file-name\r
++                  "fallback-libs"\r
++                  (file-name-directory (or load-file-name buffer-file-name)))\r
++                 load-path)))\r
++      (require 'header-button)))\r
++\r
++(defun notmuch-tagger-separate-elems (list sep)\r
++  "Return a list with all elements of LIST separated by SEP."\r
++  (let ((first t)\r
++        (res nil))\r
++    (dolist (elt (reverse list) res)\r
++      (unless first\r
++        (push sep res))\r
++      (setq first nil)\r
++      (push elt res))))\r
++\r
++(defun notmuch-tagger-goto-target (target)\r
++  "Show a `notmuch-search' buffer for the TARGET tag."\r
++  (notmuch-search (concat "tag:" target)))\r
++\r
++(defun notmuch-tagger-headerline-button-action (button)\r
++  "Open `notmuch-search' for the tag referenced by BUTTON."\r
++  (let ((tag (header-button-get button 'notmuch-tagger-tag)))\r
++    (notmuch-tagger-goto-target tag)))\r
++\r
++(defun notmuch-tagger-body-button-action (button)\r
++  "Open `notmuch-search' for the tag referenced by BUTTON."\r
++  (let ((tag (button-get button 'notmuch-tagger-tag)))\r
++    (notmuch-tagger-goto-target tag)))\r
++\r
++(define-button-type 'notmuch-tagger-headerline-button-type\r
++  'supertype 'header\r
++  'action    #'notmuch-tagger-headerline-button-action\r
++  'follow-link t)\r
++\r
++(define-button-type 'notmuch-tagger-body-button-type\r
++  'action    #'notmuch-tagger-body-button-action\r
++  'follow-link t)\r
++\r
++(defun notmuch-tagger-make-headerline-link (target)\r
++  "Return a property list that presents a link to TARGET.\r
++\r
++TARGET is a notmuch tag.\r
++The returned property list will only work in the header-line."\r
++  (header-button-format\r
++   target\r
++   :type 'notmuch-tagger-headerline-button-type\r
++   'notmuch-tagger-tag target\r
++   'help-echo (format "%s: Search other messages like this" target)))\r
++\r
++(defun notmuch-tagger-make-body-link (target)\r
++  "Return a property list that presents a link to TARGET.\r
++\r
++TARGET is a notmuch tag.\r
++The returned property list will work everywhere except in the\r
++header-line."\r
++  (let ((button (copy-sequence target)))\r
++    (make-text-button\r
++     button nil\r
++     'type 'notmuch-tagger-body-button-type\r
++     'notmuch-tagger-tag target\r
++     'help-echo (format "%s: Search other messages like this" target))\r
++    button))\r
++\r
++(defun notmuch-tagger-make-link (target headerline)\r
++"Return a property list that presents a link to TARGET.\r
++\r
++TARGET is a notmuch tag.\r
++\r
++If HEADERLINE is non-nil the returned list will be ready for\r
++inclusion in the buffer's header-line. HEADERLINE must be nil in\r
++all other cases."\r
++  (if headerline\r
++      (notmuch-tagger-make-headerline-link target)\r
++    (notmuch-tagger-make-body-link target)))\r
++\r
++(defun notmuch-tagger-format-tags (tags &optional headerline)\r
++  "Return a format list for TAGS suitable for use in header line.\r
++See Info node `(elisp)Mode Line Format' for more information.\r
++\r
++If HEADERLINE is non-nil the returned list will be ready for\r
++inclusion in the buffer's header-line. HEADERLINE must be nil in\r
++all other cases."\r
++  (mapcar\r
++   (lambda (tag) (notmuch-tagger-make-link tag headerline))\r
++   tags))\r
++\r
++(defun notmuch-tagger-present-tags (tags &optional headerline)\r
++  "Return a property list which nicely presents all TAGS.\r
++\r
++If HEADERLINE is non-nil the returned list will be ready for\r
++inclusion in the buffer's header-line. HEADERLINE must be nil in\r
++all other cases."\r
++  (list\r
++   "("\r
++   (notmuch-tagger-separate-elems (notmuch-tagger-format-tags tags headerline) " ")\r
++   ")"))\r
++\r
++(provide 'notmuch-tagger)\r
++;;; notmuch-tagger.el ends here\r
+diff --git a/test/emacs b/test/emacs\r
+index 44f641e..ecdc841 100755\r
+--- a/test/emacs\r
++++ b/test/emacs\r
+@@ -820,5 +820,66 @@ Date: Fri, 05 Jan 2001 15:43:57 +0000\r
+ EOF\r
+ test_expect_equal_file OUTPUT EXPECTED\r
+ \r
++test_begin_subtest "Extracting all tags from a thread"\r
++add_message \\r
++    '[subject]="Extracting all tags from a thread"' \\r
++    '[body]="body 1"'\r
++parent=${gen_msg_id}\r
++add_message \\r
++    '[subject]="Extracting all tags from a thread"' \\r
++    '[body]="body 2"' \\r
++    "[in-reply-to]=\<$parent\>"\r
++add_message \\r
++    '[subject]="Extracting all tags from a thread"' \\r
++    '[body]="body 3"' \\r
++    "[in-reply-to]=\<$parent\>"\r
++latest=${gen_msg_id}\r
++# Extract the thread-id from one of the emails\r
++thread_id=$(notmuch search id:${latest} | sed -e "s/thread:\([a-f0-9]*\).*/\1/")\r
++# Add tag "mytagfoo" to one of the emails\r
++notmuch tag +mytagfoo id:${latest}\r
++test_emacs_expect_t \\r
++    "(notmuch-show \"thread:${thread_id}\")\r
++     (let ((output (notmuch-show-thread-tags))\r
++           (expected '(\"inbox\" \"mytagfoo\" \"unread\")))\r
++      (notmuch-test-expect-equal\r
++         (sort output #'string<)\r
++         (sort expected #'string<)))"\r
++\r
++test_begin_subtest "The tags appear in the header-line of notmuch-show"\r
++add_message \\r
++    '[subject]="foo bar"' \\r
++    '[body]="body 1"'\r
++parent=${gen_msg_id}\r
++# Add tag "mytagfoo" to one of the emails\r
++notmuch tag +mytagfoo id:${parent}\r
++# Extract the thread-id from one of the emails\r
++thread_id=$(notmuch search id:${latest} | sed -e "s/thread:\([a-f0-9]*\).*/\1/")\r
++test_emacs_expect_t \\r
++    "(notmuch-show \"thread:${thread_id}\")\r
++     (if (string-match-p \"mytagfoo\" (format-mode-line header-line-format))\r
++         t\r
++       \"The tag mytagfoo was not in the header-line-format\")"\r
++\r
++test_begin_subtest "The tags of notmuch-show emails are clickable"\r
++add_message \\r
++    '[subject]="foo bar"' \\r
++    '[body]="body 1"'\r
++parent=${gen_msg_id}\r
++# Add tag "mytagfoo" to one of the emails\r
++notmuch tag +mytagfoo id:${parent}\r
++# Extract the thread-id from one of the emails\r
++thread_id=$(notmuch search id:${latest} | sed -e "s/thread:\([a-f0-9]*\).*/\1/")\r
++test_emacs_expect_t \\r
++    "(notmuch-show \"thread:${thread_id}\")\r
++    (goto-char (point-min))\r
++    (re-search-forward \"mytagfoo\")\r
++    (backward-char) ;; to be 'in' the tag\r
++    (unless (eq major-mode 'notmuch-show-mode)\r
++       (error \"We must be in notmch-show at this point but we are in %s.\" major-mode))\r
++    (push-button) ;; simulate a press on the RET key\r
++    (if (eq major-mode 'notmuch-search-mode)\r
++        t\r
++       (format \"We must be in notmch-search at this point but we are in %s.\" major-mode))"\r
+ \r
+ test_done\r
+-- \r
+1.7.10.4\r
+\r