]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-tree.el
Merge tag '0.31.4'
[notmuch] / emacs / notmuch-tree.el
index c00315e8dd40edaadfc369a95b2545fce49affbf..13007a134d0a9f4e8cd40a7a57bb2f9663884ee1 100644 (file)
@@ -1,4 +1,4 @@
-;;; notmuch-tree.el --- displaying notmuch forests.
+;;; notmuch-tree.el --- displaying notmuch forests  -*- lexical-binding: t -*-
 ;;
 ;; Copyright © Carl Worth
 ;; Copyright © David Edmondson
 (require 'notmuch-show)
 (require 'notmuch-tag)
 (require 'notmuch-parser)
+(require 'notmuch-jump)
 
-(eval-when-compile (require 'cl))
-(declare-function notmuch-search "notmuch" (&optional query oldest-first target-thread target-line))
+(declare-function notmuch-search "notmuch"
+                 (&optional query oldest-first target-thread target-line))
 (declare-function notmuch-call-notmuch-process "notmuch" (&rest args))
 (declare-function notmuch-read-query "notmuch" (prompt))
 (declare-function notmuch-search-find-thread-id "notmuch" (&optional bare))
 (declare-function notmuch-search-find-subject "notmuch" ())
 
+;; For `notmuch-tree-next-thread-from-search'.
+(declare-function notmuch-search-next-thread "notmuch" ())
+(declare-function notmuch-search-previous-thread "notmuch" ())
+(declare-function notmuch-tree-from-search-thread "notmuch" ())
+
 ;; the following variable is defined in notmuch.el
 (defvar notmuch-search-query-string)
 
+;; this variable distinguishes the unthreaded display from the normal tree display
+(defvar-local notmuch-tree-unthreaded nil
+  "A buffer local copy of argument unthreaded to the function notmuch-tree.")
+
+;;; Options
+
 (defgroup notmuch-tree nil
   "Showing message and thread structure."
   :group 'notmuch)
   :type 'boolean
   :group 'notmuch-tree)
 
+(defcustom notmuch-unthreaded-show-out t
+  "View selected messages in new window rather than split-pane."
+  :type 'boolean
+  :group 'notmuch-tree)
+
+(defun notmuch-tree-show-out ()
+  (if notmuch-tree-unthreaded
+      notmuch-unthreaded-show-out
+    notmuch-tree-show-out))
+
 (defcustom notmuch-tree-result-format
   `(("date" . "%12s  ")
     ("authors" . "%-20s")
     ((("tree" . "%s")("subject" . "%s")) ." %-54s ")
     ("tags" . "(%s)"))
-  "Result formatting for Tree view. Supported fields are: date,
-        authors, subject, tree, tags.  Tree means the thread tree
-        box graphics. The field may also be a list in which case
-        the formatting rules are applied recursively and then the
-        output of all the fields in the list is inserted
-        according to format-string.
+  "Result formatting for tree view. Supported fields are: date,
+authors, subject, tree, tags.  Tree means the thread tree
+box graphics. The field may also be a list in which case
+the formatting rules are applied recursively and then the
+output of all the fields in the list is inserted
+according to format-string.
+
+Note the author string should not contain
+whitespace (put it in the neighbouring fields instead).
+For example:
+        (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\)
+                                          \(\"subject\" . \"%s\"\)\)\)"
+  :type '(alist :key-type (string) :value-type (string))
+  :group 'notmuch-tree)
+
+(defcustom notmuch-unthreaded-result-format
+  `(("date" . "%12s  ")
+    ("authors" . "%-20s")
+    ((("subject" . "%s")) ." %-54s ")
+    ("tags" . "(%s)"))
+  "Result formatting for unthreaded tree view. Supported fields are: date,
+authors, subject, tree, tags.  Tree means the thread tree
+box graphics. The field may also be a list in which case
+the formatting rules are applied recursively and then the
+output of all the fields in the list is inserted
+according to format-string.
 
 Note the author string should not contain
-        whitespace (put it in the neighbouring fields instead).
-        For example:
+whitespace (put it in the neighbouring fields instead).
+For example:
         (setq notmuch-tree-result-format \(\(\"authors\" . \"%-40s\"\)
-                                             \(\"subject\" . \"%s\"\)\)\)"
+                                          \(\"subject\" . \"%s\"\)\)\)"
   :type '(alist :key-type (string) :value-type (string))
   :group 'notmuch-tree)
 
-;; Faces for messages that match the query.
+(defun notmuch-tree-result-format ()
+  (if notmuch-tree-unthreaded
+      notmuch-unthreaded-result-format
+    notmuch-tree-result-format))
+
+;;; Faces
+;;;; Faces for messages that match the query
+
 (defface notmuch-tree-match-face
   '((t :inherit default))
   "Default face used in tree mode face for matching messages"
@@ -122,10 +171,11 @@ Note the author string should not contain
   :group 'notmuch-tree
   :group 'notmuch-faces)
 
-;; Faces for messages that do not match the query.
+;;;; Faces for messages that do not match the query
+
 (defface notmuch-tree-no-match-face
   '((t (:foreground "gray")))
-  "Default face used in tree mode face for non-matching messages"
+  "Default face used in tree mode face for non-matching messages."
   :group 'notmuch-tree
   :group 'notmuch-faces)
 
@@ -159,101 +209,125 @@ Note the author string should not contain
   :group 'notmuch-tree
   :group 'notmuch-faces)
 
-(defvar notmuch-tree-previous-subject
-  "The subject of the most recent result shown during the async display")
-(make-variable-buffer-local 'notmuch-tree-previous-subject)
+;;; Variables
+
+(defvar-local notmuch-tree-previous-subject
+  "The subject of the most recent result shown during the async display.")
+
+(defvar-local notmuch-tree-basic-query nil
+  "A buffer local copy of argument query to the function notmuch-tree.")
 
-(defvar notmuch-tree-basic-query nil
-  "A buffer local copy of argument query to the function notmuch-tree")
-(make-variable-buffer-local 'notmuch-tree-basic-query)
+(defvar-local notmuch-tree-query-context nil
+  "A buffer local copy of argument query-context to the function notmuch-tree.")
 
-(defvar notmuch-tree-query-context nil
-  "A buffer local copy of argument query-context to the function notmuch-tree")
-(make-variable-buffer-local 'notmuch-tree-query-context)
+(defvar-local notmuch-tree-target-msg nil
+  "A buffer local copy of argument target to the function notmuch-tree.")
 
-(defvar notmuch-tree-target-msg nil
-  "A buffer local copy of argument target to the function notmuch-tree")
-(make-variable-buffer-local 'notmuch-tree-target-msg)
+(defvar-local notmuch-tree-open-target nil
+  "A buffer local copy of argument open-target to the function notmuch-tree.")
 
-(defvar notmuch-tree-open-target nil
-  "A buffer local copy of argument open-target to the function notmuch-tree")
-(make-variable-buffer-local 'notmuch-tree-open-target)
+(defvar-local notmuch-tree-parent-buffer nil)
 
-(defvar notmuch-tree-message-window nil
+(defvar-local notmuch-tree-message-window nil
   "The window of the message pane.
 
 It is set in both the tree buffer and the child show buffer. It
 is used to try and close the message pane when quitting tree view
 or the child show buffer.")
-(make-variable-buffer-local 'notmuch-tree-message-window)
 (put 'notmuch-tree-message-window 'permanent-local t)
 
-(defvar notmuch-tree-message-buffer nil
+(defvar-local notmuch-tree-message-buffer nil
   "The buffer name of the show buffer in the message pane.
 
 This is used to try and make sure we don't close the message pane
 if the user has loaded a different buffer in that window.")
-(make-variable-buffer-local 'notmuch-tree-message-buffer)
 (put 'notmuch-tree-message-buffer 'permanent-local t)
 
-(defun notmuch-tree-to-message-pane (func)
-  "Execute FUNC in message pane.
+;;; Tree wrapper commands
 
-This function returns a function (so can be used as a keybinding)
-which executes function FUNC in the message pane if it is
-open (if the message pane is closed it does nothing)."
-  `(lambda ()
-      ,(concat "(In message pane) " (documentation func t))
+(defmacro notmuch-tree--define-do-in-message-window (name cmd)
+  "Define NAME as a command that calls CMD interactively in the message window.
+If the message pane is closed then this command does nothing.
+Avoid using this macro in new code; it will be removed."
+  `(defun ,name ()
+     ,(concat "(In message window) " (documentation cmd t))
      (interactive)
      (when (window-live-p notmuch-tree-message-window)
        (with-selected-window notmuch-tree-message-window
-        (call-interactively #',func)))))
-
-(defun notmuch-tree-inherit-from-message-pane (sym)
-  "Return value of SYM in message-pane if open, or tree-pane if not"
+        (call-interactively #',cmd)))))
+
+(notmuch-tree--define-do-in-message-window
+ notmuch-tree-previous-message-button
+ notmuch-show-previous-button)
+(notmuch-tree--define-do-in-message-window
+ notmuch-tree-next-message-button
+ notmuch-show-next-button)
+(notmuch-tree--define-do-in-message-window
+ notmuch-tree-toggle-message-process-crypto
+ notmuch-show-toggle-process-crypto)
+
+(defun notmuch-tree--message-process-crypto ()
+  "Return value of `notmuch-show-process-crypto' in the message window.
+If that window isn't alive, then return the current value.
+Avoid using this function in new code; it will be removed."
   (if (window-live-p notmuch-tree-message-window)
       (with-selected-window notmuch-tree-message-window
-       (symbol-value sym))
-    (symbol-value sym)))
-
-(defun notmuch-tree-button-activate (&optional button)
-  "Activate BUTTON or button at point
-
-This function does not give an error if there is no button."
-  (interactive)
-  (let ((button (or button (button-at (point)))))
-    (when button (button-activate button))))
-
-(defun notmuch-tree-close-message-pane-and (func)
-  "Close message pane and execute FUNC.
-
-This function returns a function (so can be used as a keybinding)
-which closes the message pane if open and then executes function
-FUNC."
-  `(lambda ()
-      ,(concat "(Close message pane and) " (documentation func t))
+       notmuch-show-process-crypto)
+    notmuch-show-process-crypto))
+
+(defmacro notmuch-tree--define-close-message-window-and (name cmd)
+  "Define NAME as a variant of CMD.
+
+NAME determines the value of `notmuch-show-process-crypto' in the
+message window, closes the window, and then call CMD interactively
+with that value let-bound.  If the message window does not exist,
+then NAME behaves like CMD."
+  `(defun ,name ()
+     ,(concat "(Close message pane and) " (documentation cmd t))
      (interactive)
      (let ((notmuch-show-process-crypto
-           (notmuch-tree-inherit-from-message-pane 'notmuch-show-process-crypto)))
+           (notmuch-tree--message-process-crypto)))
        (notmuch-tree-close-message-window)
-       (call-interactively #',func))))
+       (call-interactively #',cmd))))
+
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-help
+ notmuch-help)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-new-mail
+ notmuch-mua-new-mail)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-jump-search
+ notmuch-jump-search)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-forward-message
+ notmuch-show-forward-message)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-reply-sender
+ notmuch-show-reply-sender)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-reply
+ notmuch-show-reply)
+(notmuch-tree--define-close-message-window-and
+ notmuch-tree-view-raw-message
+ notmuch-show-view-raw-message)
+
+;;; Keymap
 
 (defvar notmuch-tree-mode-map
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map notmuch-common-keymap)
-    ;; The following override the global keymap.
-    ;; Override because we want to close message pane first.
-    (define-key map [remap notmuch-help] (notmuch-tree-close-message-pane-and #'notmuch-help))
-    ;; Override because we first close message pane and then close tree buffer.
+    ;; These bindings shadow common bindings with variants
+    ;; that additionally close the message window.
     (define-key map [remap notmuch-bury-or-kill-this-buffer] 'notmuch-tree-quit)
-    ;; Override because we close message pane after the search query is entered.
-    (define-key map [remap notmuch-search] 'notmuch-tree-to-search)
-    ;; Override because we want to close message pane first.
-    (define-key map [remap notmuch-mua-new-mail] (notmuch-tree-close-message-pane-and #'notmuch-mua-new-mail))
-    ;; Override because we want to close message pane first.
-    (define-key map [remap notmuch-jump-search] (notmuch-tree-close-message-pane-and #'notmuch-jump-search))
+    (define-key map [remap notmuch-search]       'notmuch-tree-to-search)
+    (define-key map [remap notmuch-help]         'notmuch-tree-help)
+    (define-key map [remap notmuch-mua-new-mail] 'notmuch-tree-new-mail)
+    (define-key map [remap notmuch-jump-search]  'notmuch-tree-jump-search)
 
     (define-key map "S" 'notmuch-search-from-tree-current-query)
+    (define-key map "U" 'notmuch-unthreaded-from-tree-current-query)
+    (define-key map "Z" 'notmuch-tree-from-unthreaded-current-query)
 
     ;; these use notmuch-show functions directly
     (define-key map "|" 'notmuch-show-pipe-message)
@@ -263,22 +337,23 @@ FUNC."
     (define-key map "b" 'notmuch-show-resend-message)
 
     ;; these apply to the message pane
-    (define-key map (kbd "M-TAB") (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
-    (define-key map (kbd "<backtab>")  (notmuch-tree-to-message-pane #'notmuch-show-previous-button))
-    (define-key map (kbd "TAB") (notmuch-tree-to-message-pane #'notmuch-show-next-button))
-    (define-key map "$" (notmuch-tree-to-message-pane #'notmuch-show-toggle-process-crypto))
+    (define-key map (kbd "M-TAB")     'notmuch-tree-previous-message-button)
+    (define-key map (kbd "<backtab>") 'notmuch-tree-previous-message-button)
+    (define-key map (kbd "TAB")       'notmuch-tree-next-message-button)
+    (define-key map "$" 'notmuch-tree-toggle-message-process-crypto)
 
     ;; bindings from show (or elsewhere) but we close the message pane first.
-    (define-key map "f" (notmuch-tree-close-message-pane-and #'notmuch-show-forward-message))
-    (define-key map "r" (notmuch-tree-close-message-pane-and #'notmuch-show-reply-sender))
-    (define-key map "R" (notmuch-tree-close-message-pane-and #'notmuch-show-reply))
-    (define-key map "V" (notmuch-tree-close-message-pane-and #'notmuch-show-view-raw-message))
+    (define-key map "f" 'notmuch-tree-forward-message)
+    (define-key map "r" 'notmuch-tree-reply-sender)
+    (define-key map "R" 'notmuch-tree-reply)
+    (define-key map "V" 'notmuch-tree-view-raw-message)
 
     ;; The main tree view bindings
     (define-key map (kbd "RET") 'notmuch-tree-show-message)
     (define-key map [mouse-1] 'notmuch-tree-show-message)
-    (define-key map "x" 'notmuch-tree-quit)
-    (define-key map "A" 'notmuch-tree-archive-thread)
+    (define-key map "x" 'notmuch-tree-archive-message-then-next-or-exit)
+    (define-key map "X" 'notmuch-tree-archive-thread-then-exit)
+    (define-key map "A" 'notmuch-tree-archive-thread-then-next)
     (define-key map "a" 'notmuch-tree-archive-message-then-next)
     (define-key map "z" 'notmuch-tree-to-tree)
     (define-key map "n" 'notmuch-tree-next-matching-message)
@@ -294,15 +369,17 @@ FUNC."
     (define-key map " " 'notmuch-tree-scroll-or-next)
     (define-key map (kbd "DEL") 'notmuch-tree-scroll-message-window-back)
     (define-key map "e" 'notmuch-tree-resume-message)
-    map))
-(fset 'notmuch-tree-mode-map notmuch-tree-mode-map)
+    map)
+  "Keymap for \"notmuch tree\" buffers.")
+
+;;; Message properties
 
 (defun notmuch-tree-get-message-properties ()
   "Return the properties of the current message as a plist.
 
 Some useful entries are:
 :headers - Property list containing the headers :Date, :Subject, :From, etc.
-:tags - Tags for this message"
+:tags - Tags for this message."
   (save-excursion
     (beginning-of-line)
     (get-text-property (point) :notmuch-message-properties)))
@@ -310,7 +387,9 @@ Some useful entries are:
 (defun notmuch-tree-set-message-properties (props)
   (save-excursion
     (beginning-of-line)
-    (put-text-property (point) (+ (point) 1) :notmuch-message-properties props)))
+    (put-text-property (point)
+                      (+ (point) 1)
+                      :notmuch-message-properties props)))
 
 (defun notmuch-tree-set-prop (prop val &optional props)
   (let ((inhibit-read-only t)
@@ -320,9 +399,8 @@ Some useful entries are:
     (notmuch-tree-set-message-properties props)))
 
 (defun notmuch-tree-get-prop (prop &optional props)
-  (let ((props (or props
-                  (notmuch-tree-get-message-properties))))
-    (plist-get props prop)))
+  (plist-get (or props (notmuch-tree-get-message-properties))
+            prop))
 
 (defun notmuch-tree-set-tags (tags)
   "Set the tags of the current message."
@@ -343,9 +421,10 @@ Some useful entries are:
 
 (defun notmuch-tree-get-match ()
   "Return whether the current message is a match."
-  (interactive)
   (notmuch-tree-get-prop :match))
 
+;;; Update display
+
 (defun notmuch-tree-refresh-result ()
   "Redisplay the current message line.
 
@@ -363,7 +442,8 @@ updated."
     ;; from overwriting the buffer local copy of
     ;; notmuch-tree-previous-subject if this is called while the
     ;; buffer is displaying.
-    (let ((notmuch-tree-previous-subject (notmuch-tree-get-prop :previous-subject)))
+    (let ((notmuch-tree-previous-subject
+          (notmuch-tree-get-prop :previous-subject)))
       (delete-region (point) (1+ (line-end-position)))
       (notmuch-tree-insert-msg msg))
     (let ((new-end (line-end-position)))
@@ -387,8 +467,10 @@ NOT change the database."
          (when (string= tree-msg-id (notmuch-show-get-message-id))
            (notmuch-show-update-tags new-tags)))))))
 
+;;; Commands (and some helper functions used by them)
+
 (defun notmuch-tree-tag (tag-changes)
-  "Change tags for the current message"
+  "Change tags for the current message."
   (interactive
    (list (notmuch-read-tag-changes (notmuch-tree-get-tags) "Tag message")))
   (notmuch-tag (notmuch-tree-get-message-id) tag-changes)
@@ -428,14 +510,32 @@ NOT change the database."
     (notmuch-search query)))
 
 (defun notmuch-tree-to-tree ()
-  "Run a query and display results in Tree view"
+  "Run a query and display results in tree view."
   (interactive)
   (let ((query (notmuch-read-query "Notmuch tree view search: ")))
     (notmuch-tree-close-message-window)
     (notmuch-tree query)))
 
+(defun notmuch-tree-archive-thread-then-next ()
+  "Archive all messages in the current buffer, then show next thread from search."
+  (interactive)
+  (notmuch-tree-archive-thread)
+  (notmuch-tree-next-thread))
+
+(defun notmuch-unthreaded-from-tree-current-query ()
+  "Switch from tree view to unthreaded view."
+  (interactive)
+  (unless notmuch-tree-unthreaded
+    (notmuch-tree-refresh-view 'unthreaded)))
+
+(defun notmuch-tree-from-unthreaded-current-query ()
+  "Switch from unthreaded view to tree view."
+  (interactive)
+  (when notmuch-tree-unthreaded
+    (notmuch-tree-refresh-view 'tree)))
+
 (defun notmuch-search-from-tree-current-query ()
-  "Call notmuch search with the current query"
+  "Call notmuch search with the current query."
   (interactive)
   (notmuch-tree-close-message-window)
   (notmuch-search (notmuch-tree-get-query)))
@@ -445,9 +545,10 @@ NOT change the database."
   (let ((buffer (current-buffer)))
     (when (and (window-live-p notmuch-tree-message-window)
               (eq (window-buffer notmuch-tree-message-window) buffer))
-      ;; We do not want an error if this is the sole window in the
-      ;; frame and I do not know how to test for that in emacs pre
-      ;; 24. Hence we just ignore-errors.
+      ;; We could check whether this is the only window in its frame,
+      ;; but simply ignoring the error that is thrown otherwise is
+      ;; what we had to do for Emacs 24 and we stick to that because
+      ;; it is still the simplest approach.
       (ignore-errors
        (delete-window notmuch-tree-message-window)))))
 
@@ -471,9 +572,14 @@ NOT change the database."
       (setq notmuch-tree-message-window
            (split-window-vertically (/ (window-height) 4)))
       (with-selected-window notmuch-tree-message-window
-       ;; Since we are only displaying one message do not indent.
-       (let ((notmuch-show-indent-messages-width 0)
-             (notmuch-show-only-matching-messages t))
+       (let (;; Since we are only displaying one message do not indent.
+             (notmuch-show-indent-messages-width 0)
+             (notmuch-show-only-matching-messages t)
+             ;; Ensure that `pop-to-buffer-same-window' uses the
+             ;; window we want it to use.
+             (display-buffer-overriding-action
+                '((display-buffer-same-window)
+                  (inhibit-same-window . nil))))
          (setq buffer (notmuch-show id))))
       ;; We need the `let' as notmuch-tree-message-window is buffer local.
       (let ((window notmuch-tree-message-window))
@@ -488,8 +594,7 @@ NOT change the database."
   "Show the current message (in whole window)."
   (interactive)
   (let ((id (notmuch-tree-get-message-id))
-       (inhibit-read-only t)
-       buffer)
+       (inhibit-read-only t))
     (when id
       ;; We close the window to kill off un-needed buffers.
       (notmuch-tree-close-message-window)
@@ -501,13 +606,13 @@ NOT change the database."
 Shows in split pane or whole window according to value of
 `notmuch-tree-show-out'. A prefix argument reverses the choice."
   (interactive "P")
-  (if (or (and notmuch-tree-show-out  (not arg))
-         (and (not notmuch-tree-show-out) arg))
+  (if (or (and (notmuch-tree-show-out) (not arg))
+         (and (not (notmuch-tree-show-out)) arg))
       (notmuch-tree-show-message-out)
     (notmuch-tree-show-message-in)))
 
 (defun notmuch-tree-scroll-message-window ()
-  "Scroll the message window (if it exists)"
+  "Scroll the message window (if it exists)."
   (interactive)
   (when (window-live-p notmuch-tree-message-window)
     (with-selected-window notmuch-tree-message-window
@@ -516,7 +621,7 @@ Shows in split pane or whole window according to value of
        (scroll-up)))))
 
 (defun notmuch-tree-scroll-message-window-back ()
-  "Scroll the message window back(if it exists)"
+  "Scroll the message window back (if it exists)."
   (interactive)
   (when (window-live-p notmuch-tree-message-window)
     (with-selected-window notmuch-tree-message-window
@@ -525,22 +630,24 @@ Shows in split pane or whole window according to value of
        (scroll-down)))))
 
 (defun notmuch-tree-scroll-or-next ()
-  "Scroll the message window. If it at end go to next message."
+  "Scroll the message window.
+If it at end go to next message."
   (interactive)
   (when (notmuch-tree-scroll-message-window)
     (notmuch-tree-next-matching-message)))
 
-(defun notmuch-tree-quit ()
+(defun notmuch-tree-quit (&optional kill-both)
   "Close the split view or exit tree."
-  (interactive)
-  (unless (notmuch-tree-close-message-window)
+  (interactive "P")
+  (when (or (not (notmuch-tree-close-message-window)) kill-both)
     (kill-buffer (current-buffer))))
 
 (defun notmuch-tree-close-message-window ()
   "Close the message-window. Return t if close succeeds."
   (interactive)
   (when (and (window-live-p notmuch-tree-message-window)
-            (eq (window-buffer notmuch-tree-message-window) notmuch-tree-message-buffer))
+            (eq (window-buffer notmuch-tree-message-window)
+                notmuch-tree-message-buffer))
     (delete-window notmuch-tree-message-window)
     (unless (get-buffer-window-list notmuch-tree-message-buffer)
       (kill-buffer notmuch-tree-message-buffer))
@@ -555,7 +662,8 @@ message will be \"unarchived\", i.e. the tag changes in
 `notmuch-archive-tags' will be reversed."
   (interactive "P")
   (when notmuch-archive-tags
-    (notmuch-tree-tag (notmuch-tag-change-list notmuch-archive-tags unarchive))))
+    (notmuch-tree-tag
+     (notmuch-tag-change-list notmuch-archive-tags unarchive))))
 
 (defun notmuch-tree-archive-message-then-next (&optional unarchive)
   "Archive the current message and move to next matching message."
@@ -563,6 +671,21 @@ message will be \"unarchived\", i.e. the tag changes in
   (notmuch-tree-archive-message unarchive)
   (notmuch-tree-next-matching-message))
 
+(defun notmuch-tree-archive-thread-then-exit ()
+  "Archive all messages in the current buffer, then exit notmuch-tree."
+  (interactive)
+  (notmuch-tree-archive-thread)
+  (notmuch-tree-quit t))
+
+(defun notmuch-tree-archive-message-then-next-or-exit ()
+  "Archive current message, then show next open message in current thread.
+
+If at the last open message in the current thread, then exit back
+to search results."
+  (interactive)
+  (notmuch-tree-archive-message)
+  (notmuch-tree-next-matching-message t))
+
 (defun notmuch-tree-next-message ()
   "Move to next message."
   (interactive)
@@ -577,63 +700,116 @@ message will be \"unarchived\", i.e. the tag changes in
   (when (window-live-p notmuch-tree-message-window)
     (notmuch-tree-show-message-in)))
 
-(defun notmuch-tree-prev-matching-message ()
+(defun notmuch-tree-goto-matching-message (&optional prev)
+  "Move to the next or previous matching message.
+
+Returns t if there was a next matching message in the thread to show,
+nil otherwise."
+  (let ((dir (if prev -1 nil))
+       (eobfn (if prev #'bobp #'eobp)))
+    (while (and (not (funcall eobfn))
+               (not (notmuch-tree-get-match)))
+      (forward-line dir))
+    (not (funcall eobfn))))
+
+(defun notmuch-tree-matching-message (&optional prev pop-at-end)
+  "Move to the next or previous matching message."
+  (interactive "P")
+  (forward-line (if prev -1 nil))
+  (if (and (not (notmuch-tree-goto-matching-message prev)) pop-at-end)
+      (notmuch-tree-quit pop-at-end)
+    (when (window-live-p notmuch-tree-message-window)
+      (notmuch-tree-show-message-in))))
+
+(defun notmuch-tree-prev-matching-message (&optional pop-at-end)
   "Move to previous matching message."
-  (interactive)
-  (forward-line -1)
-  (while (and (not (bobp)) (not (notmuch-tree-get-match)))
-    (forward-line -1))
-  (when (window-live-p notmuch-tree-message-window)
-    (notmuch-tree-show-message-in)))
+  (interactive "P")
+  (notmuch-tree-matching-message t pop-at-end))
 
-(defun notmuch-tree-next-matching-message ()
+(defun notmuch-tree-next-matching-message (&optional pop-at-end)
   "Move to next matching message."
-  (interactive)
-  (forward-line)
-  (while (and (not (eobp)) (not (notmuch-tree-get-match)))
-    (forward-line))
-  (when (window-live-p notmuch-tree-message-window)
-    (notmuch-tree-show-message-in)))
+  (interactive "P")
+  (notmuch-tree-matching-message nil pop-at-end))
 
-(defun notmuch-tree-refresh-view ()
+(defun notmuch-tree-refresh-view (&optional view)
   "Refresh view."
   (interactive)
   (when (get-buffer-process (current-buffer))
     (error "notmuch tree process already running for current buffer"))
   (let ((inhibit-read-only t)
        (basic-query notmuch-tree-basic-query)
+       (unthreaded (cond ((eq view 'unthreaded) t)
+                         ((eq view 'tree) nil)
+                         (t notmuch-tree-unthreaded)))
        (query-context notmuch-tree-query-context)
        (target (notmuch-tree-get-message-id)))
     (erase-buffer)
     (notmuch-tree-worker basic-query
                         query-context
-                        target)))
+                        target
+                        nil
+                        unthreaded)))
 
 (defun notmuch-tree-thread-top ()
   (when (notmuch-tree-get-message-properties)
     (while (not (or (notmuch-tree-get-prop :first) (eobp)))
       (forward-line -1))))
 
-(defun notmuch-tree-prev-thread ()
+(defun notmuch-tree-prev-thread-in-tree ()
+  "Move to the previous thread in the current tree"
   (interactive)
   (forward-line -1)
-  (notmuch-tree-thread-top))
+  (notmuch-tree-thread-top)
+  (not (bobp)))
 
-(defun notmuch-tree-next-thread ()
+(defun notmuch-tree-next-thread-in-tree ()
+  "Get the next thread in the current tree. Returns t if a thread was
+found or nil if not."
   (interactive)
   (forward-line 1)
   (while (not (or (notmuch-tree-get-prop :first) (eobp)))
-    (forward-line 1)))
+    (forward-line 1))
+  (not (eobp)))
+
+(defun notmuch-tree-next-thread-from-search (&optional previous)
+  "Move to the next thread in the parent search results, if any.
+
+If PREVIOUS is non-nil, move to the previous item in the
+search results instead."
+  (interactive "P")
+  (let ((parent-buffer notmuch-tree-parent-buffer))
+    (notmuch-tree-quit t)
+    (when (buffer-live-p parent-buffer)
+      (switch-to-buffer parent-buffer)
+      (if previous
+         (notmuch-search-previous-thread)
+       (notmuch-search-next-thread))
+      (notmuch-tree-from-search-thread))))
+
+(defun notmuch-tree-next-thread (&optional previous)
+  "Move to the next thread in the current tree or parent search results.
+
+If PREVIOUS is non-nil, move to the previous thread in the tree or
+search results instead."
+  (interactive)
+  (unless (if previous (notmuch-tree-prev-thread-in-tree)
+           (notmuch-tree-next-thread-in-tree))
+    (notmuch-tree-next-thread-from-search previous)))
+
+(defun notmuch-tree-prev-thread ()
+  "Move to the previous thread in the current tree or parent search results."
+  (interactive)
+  (notmuch-tree-next-thread t))
 
 (defun notmuch-tree-thread-mapcar (function)
-  "Iterate through all messages in the current thread
- and call FUNCTION for side effects."
+  "Call FUNCTION for each message in the current thread.
+FUNCTION is called for side effects only."
   (save-excursion
     (notmuch-tree-thread-top)
-    (loop collect (funcall function)
-         do (forward-line)
-         while (and (notmuch-tree-get-message-properties)
-                    (not (notmuch-tree-get-prop :first))))))
+    (cl-loop collect (funcall function)
+            do (forward-line)
+            while (and (notmuch-tree-get-message-properties)
+                       (not (notmuch-tree-get-prop :first))))))
 
 (defun notmuch-tree-get-messages-ids-thread-search ()
   "Return a search string for all message ids of messages in the current thread."
@@ -642,7 +818,7 @@ message will be \"unarchived\", i.e. the tag changes in
             " or "))
 
 (defun notmuch-tree-tag-thread (tag-changes)
-  "Tag all messages in the current thread"
+  "Tag all messages in the current thread."
   (interactive
    (let ((tags (apply #'append (notmuch-tree-thread-mapcar
                                (lambda () (notmuch-tree-get-tags))))))
@@ -669,7 +845,7 @@ buffer."
     (notmuch-tree-tag-thread
      (notmuch-tag-change-list notmuch-archive-tags unarchive))))
 
-;; Functions below here display the tree buffer itself.
+;;; Functions for displaying the tree buffer itself
 
 (defun notmuch-tree-clean-address (address)
   "Try to clean a single email ADDRESS for display. Return
@@ -683,7 +859,7 @@ unchanged ADDRESS if parsing fails."
     (or p-name p-address)))
 
 (defun notmuch-tree-format-field (field format-string msg)
-  "Format a FIELD of MSG according to FORMAT-STRING and return string"
+  "Format a FIELD of MSG according to FORMAT-STRING and return string."
   (let* ((headers (plist-get msg :headers))
         (match (plist-get msg :match)))
     (cond
@@ -694,7 +870,8 @@ unchanged ADDRESS if parsing fails."
       (let ((face (if match
                      'notmuch-tree-match-date-face
                    'notmuch-tree-no-match-date-face)))
-       (propertize (format format-string (plist-get msg :date_relative)) 'face face)))
+       (propertize (format format-string (plist-get msg :date_relative))
+                   'face face)))
 
      ((string-equal field "tree")
       (let ((tree-status (plist-get msg :tree-status))
@@ -739,7 +916,7 @@ unchanged ADDRESS if parsing fails."
        (format format-string (notmuch-tag-format-tags tags orig-tags face)))))))
 
 (defun notmuch-tree-format-field-list (field-list msg)
-  "Format fields of MSG according to FIELD-LIST and return string"
+  "Format fields of MSG according to FIELD-LIST and return string."
   (let ((face (if (plist-get msg :match)
                  'notmuch-tree-match-face
                'notmuch-tree-no-match-face))
@@ -750,17 +927,17 @@ unchanged ADDRESS if parsing fails."
     (notmuch-apply-face result-string face t)))
 
 (defun notmuch-tree-insert-msg (msg)
-  "Insert the message MSG according to notmuch-tree-result-format"
+  "Insert the message MSG according to notmuch-tree-result-format."
   ;; We need to save the previous subject as it will get overwritten
   ;; by the insert-field calls.
   (let ((previous-subject notmuch-tree-previous-subject))
-    (insert (notmuch-tree-format-field-list notmuch-tree-result-format msg))
+    (insert (notmuch-tree-format-field-list (notmuch-tree-result-format) msg))
     (notmuch-tree-set-message-properties msg)
     (notmuch-tree-set-prop :previous-subject previous-subject)
     (insert "\n")))
 
 (defun notmuch-tree-goto-and-insert-msg (msg)
-  "Insert msg at the end of the buffer. Move point to msg if it is the target"
+  "Insert msg at the end of the buffer. Move point to msg if it is the target."
   (save-excursion
     (goto-char (point-max))
     (notmuch-tree-insert-msg msg))
@@ -781,43 +958,41 @@ A message tree is another name for a single sub-thread: i.e., a
 message together with all its descendents."
   (let ((msg (car tree))
        (replies (cadr tree)))
-
-      (cond
-       ((and (< 0 depth) (not last))
-       (push "├" tree-status))
-       ((and (< 0 depth) last)
-       (push "╰" tree-status))
-       ((and (eq 0 depth) first last)
-;;       (push "─" tree-status)) choice between this and next line is matter of taste.
-       (push " " tree-status))
-       ((and (eq 0 depth) first (not last))
-         (push "┬" tree-status))
-       ((and (eq 0 depth) (not first) last)
-       (push "╰" tree-status))
-       ((and (eq 0 depth) (not first) (not last))
-       (push "├" tree-status)))
-
-      (push (concat (if replies "┬" "─") "►") tree-status)
-      (setq msg (plist-put msg :first (and first (eq 0 depth))))
-      (setq msg (plist-put msg :tree-status tree-status))
-      (setq msg (plist-put msg :orig-tags (plist-get msg :tags)))
-      (notmuch-tree-goto-and-insert-msg msg)
-      (pop tree-status)
-      (pop tree-status)
-
-      (if last
-         (push " " tree-status)
-       (push "│" tree-status))
-
+    (cond
+     ((and (< 0 depth) (not last))
+      (push "├" tree-status))
+     ((and (< 0 depth) last)
+      (push "╰" tree-status))
+     ((and (eq 0 depth) first last)
+      ;; Choice between these two variants is a matter of taste.
+      ;; (push "─" tree-status))
+      (push " " tree-status))
+     ((and (eq 0 depth) first (not last))
+      (push "┬" tree-status))
+     ((and (eq 0 depth) (not first) last)
+      (push "╰" tree-status))
+     ((and (eq 0 depth) (not first) (not last))
+      (push "├" tree-status)))
+    (push (concat (if replies "┬" "─") "►") tree-status)
+    (setq msg (plist-put msg :first (and first (eq 0 depth))))
+    (setq msg (plist-put msg :tree-status tree-status))
+    (setq msg (plist-put msg :orig-tags (plist-get msg :tags)))
+    (notmuch-tree-goto-and-insert-msg msg)
+    (pop tree-status)
+    (pop tree-status)
+    (if last
+       (push " " tree-status)
+      (push "│" tree-status))
     (notmuch-tree-insert-thread replies (1+ depth) tree-status)))
 
 (defun notmuch-tree-insert-thread (thread depth tree-status)
   "Insert the collection of sibling sub-threads THREAD at depth DEPTH in the current forest."
   (let ((n (length thread)))
-    (loop for tree in thread
-         for count from 1 to n
-
-         do (notmuch-tree-insert-tree tree depth tree-status (eq count 1) (eq count n)))))
+    (cl-loop for tree in thread
+            for count from 1 to n
+            do (notmuch-tree-insert-tree tree depth tree-status
+                                         (eq count 1)
+                                         (eq count n)))))
 
 (defun notmuch-tree-insert-forest-thread (forest-thread)
   "Insert a single complete thread."
@@ -846,51 +1021,47 @@ Pressing \\[notmuch-tree-show-message] on any line displays that message.
 Complete list of currently available key bindings:
 
 \\{notmuch-tree-mode-map}"
-
   (setq notmuch-buffer-refresh-function #'notmuch-tree-refresh-view)
   (hl-line-mode 1)
-  (setq buffer-read-only t
-       truncate-lines t))
+  (setq buffer-read-only t)
+  (setq truncate-lines t))
 
-(defun notmuch-tree-process-sentinel (proc msg)
-  "Add a message to let user know when \"notmuch tree\" exits"
+(defun notmuch-tree-process-sentinel (proc _msg)
+  "Add a message to let user know when \"notmuch tree\" exits."
   (let ((buffer (process-buffer proc))
        (status (process-status proc))
-       (exit-status (process-exit-status proc))
-       (never-found-target-thread nil))
+       (exit-status (process-exit-status proc)))
     (when (memq status '(exit signal))
-        (kill-buffer (process-get proc 'parse-buf))
-       (if (buffer-live-p buffer)
-           (with-current-buffer buffer
-             (save-excursion
-               (let ((inhibit-read-only t)
-                     (atbob (bobp)))
-                 (goto-char (point-max))
-                 (if (eq status 'signal)
-                     (insert "Incomplete search results (tree view process was killed).\n"))
-                 (when (eq status 'exit)
-                   (insert "End of search results.")
-                   (unless (= exit-status 0)
-                     (insert (format " (process returned %d)" exit-status)))
-                   (insert "\n")))))))))
+      (kill-buffer (process-get proc 'parse-buf))
+      (when (buffer-live-p buffer)
+       (with-current-buffer buffer
+         (save-excursion
+           (let ((inhibit-read-only t))
+             (goto-char (point-max))
+             (when (eq status 'signal)
+               (insert "Incomplete search results (tree view process was killed).\n"))
+             (when (eq status 'exit)
+               (insert "End of search results.")
+               (unless (= exit-status 0)
+                 (insert (format " (process returned %d)" exit-status)))
+               (insert "\n")))))))))
 
 (defun notmuch-tree-process-filter (proc string)
-  "Process and filter the output of \"notmuch show\" for tree view"
+  "Process and filter the output of \"notmuch show\" for tree view."
   (let ((results-buf (process-buffer proc))
-        (parse-buf (process-get proc 'parse-buf))
-        (inhibit-read-only t)
-        done)
+       (parse-buf (process-get proc 'parse-buf))
+       (inhibit-read-only t))
     (if (not (buffer-live-p results-buf))
-        (delete-process proc)
+       (delete-process proc)
       (with-current-buffer parse-buf
-        ;; Insert new data
-        (save-excursion
-          (goto-char (point-max))
-          (insert string))
+       ;; Insert new data
+       (save-excursion
+         (goto-char (point-max))
+         (insert string))
        (notmuch-sexp-parse-partial-list 'notmuch-tree-insert-forest-thread
                                         results-buf)))))
 
-(defun notmuch-tree-worker (basic-query &optional query-context target open-target)
+(defun notmuch-tree-worker (basic-query &optional query-context target open-target unthreaded)
   "Insert the tree view of the search in the current buffer.
 
 This is is a helper function for notmuch-tree. The arguments are
@@ -898,10 +1069,12 @@ the same as for the function notmuch-tree."
   (interactive)
   (notmuch-tree-mode)
   (add-hook 'post-command-hook #'notmuch-tree-command-hook t t)
+  (setq notmuch-tree-unthreaded unthreaded)
   (setq notmuch-tree-basic-query basic-query)
   (setq notmuch-tree-query-context (if (or (string= query-context "")
                                           (string= query-context "*"))
-                                      nil query-context))
+                                      nil
+                                    query-context))
   (setq notmuch-tree-target-msg target)
   (setq notmuch-tree-open-target open-target)
   ;; Set the default value for `notmuch-show-process-crypto' in this
@@ -909,15 +1082,14 @@ the same as for the function notmuch-tree."
   ;; (such as reply) do. It is a buffer local variable so setting it
   ;; will not affect genuine show buffers.
   (setq notmuch-show-process-crypto notmuch-crypto-process-mime)
-
   (erase-buffer)
   (goto-char (point-min))
   (let* ((search-args (concat basic-query
-                      (if query-context (concat " and (" query-context ")"))
-                      ))
-        (message-arg "--entire-thread"))
-    (if (equal (car (process-lines notmuch-command "count" search-args)) "0")
-       (setq search-args basic-query))
+                             (and query-context
+                                  (concat " and (" query-context ")"))))
+        (message-arg (if unthreaded "--unthreaded" "--entire-thread")))
+    (when (equal (car (process-lines notmuch-command "count" search-args)) "0")
+      (setq search-args basic-query))
     (notmuch-tag-clear-cache)
     (let ((proc (notmuch-start-notmuch
                 "notmuch-tree" (current-buffer) #'notmuch-tree-process-sentinel
@@ -932,7 +1104,7 @@ the same as for the function notmuch-tree."
       (set-process-query-on-exit-flag proc nil))))
 
 (defun notmuch-tree-get-query ()
-  "Return the current query in this tree buffer"
+  "Return the current query in this tree buffer."
   (if notmuch-tree-query-context
       (concat notmuch-tree-basic-query
              " and ("
@@ -940,8 +1112,8 @@ the same as for the function notmuch-tree."
              ")")
     notmuch-tree-basic-query))
 
-(defun notmuch-tree (&optional query query-context target buffer-name open-target)
-  "Display threads matching QUERY in Tree View.
+(defun notmuch-tree (&optional query query-context target buffer-name open-target unthreaded parent-buffer)
+  "Display threads matching QUERY in tree view.
 
 The arguments are:
   QUERY: the main query. This can be any query but in many cases will be
@@ -953,25 +1125,31 @@ The arguments are:
       current if it appears in the tree view results.
   BUFFER-NAME: the name of the buffer to display the tree view. If
       it is nil \"*notmuch-tree\" followed by QUERY is used.
-  OPEN-TARGET: If TRUE open the target message in the message pane."
+  OPEN-TARGET: If TRUE open the target message in the message pane.
+  UNTHREADED: If TRUE only show matching messages in an unthreaded view."
   (interactive)
-  (if (null query)
-      (setq query (notmuch-read-query "Notmuch tree view search: ")))
+  (unless query
+    (setq query (notmuch-read-query (concat "Notmuch "
+                                           (if unthreaded "unthreaded " "tree ")
+                                           "view search: "))))
   (let ((buffer (get-buffer-create (generate-new-buffer-name
                                    (or buffer-name
-                                       (concat "*notmuch-tree-" query "*")))))
+                                       (concat "*notmuch-"
+                                               (if unthreaded "unthreaded-" "tree-")
+                                               query "*")))))
        (inhibit-read-only t))
-
-    (switch-to-buffer buffer))
+    (pop-to-buffer-same-window buffer))
   ;; Don't track undo information for this buffer
-  (set 'buffer-undo-list t)
-
-  (notmuch-tree-worker query query-context target open-target)
-
+  (setq buffer-undo-list t)
+  (notmuch-tree-worker query query-context target open-target unthreaded)
+  (setq notmuch-tree-parent-buffer parent-buffer)
   (setq truncate-lines t))
 
+(defun notmuch-unthreaded (&optional query query-context target buffer-name open-target)
+  (interactive)
+  (notmuch-tree query query-context target buffer-name open-target t))
 
-;;
+;;; _
 
 (provide 'notmuch-tree)