]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-tree.el
emacs: Use 'and' instead of 'when' when the return value matters
[notmuch] / emacs / notmuch-tree.el
index 7bebdbabb6cc492997f6afee04e0e1077365efae..37a5d1c843015c6226ec217f2d59bda414473914 100644 (file)
@@ -24,6 +24,8 @@
 
 ;;; Code:
 
+(eval-when-compile (require 'cl-lib))
+
 (require 'mail-parse)
 
 (require 'notmuch-lib)
@@ -32,8 +34,8 @@
 (require 'notmuch-tag)
 (require 'notmuch-parser)
 
-(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))
 ;; 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 notmuch-tree-unthreaded nil
+  "A buffer local copy of argument unthreaded to the function notmuch-tree.")
+(make-variable-buffer-local 'notmuch-tree-unthreaded)
+
 (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)
 
+(defun notmuch-tree-result-format ()
+  (if notmuch-tree-unthreaded
+      notmuch-unthreaded-result-format
+    notmuch-tree-result-format))
+
 ;; Faces for messages that match the query.
 (defface notmuch-tree-match-face
   '((t :inherit default))
@@ -125,7 +167,7 @@ Note the author string should not contain
 ;; 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)
 
@@ -160,23 +202,23 @@ Note the author string should not contain
   :group 'notmuch-faces)
 
 (defvar notmuch-tree-previous-subject
-  "The subject of the most recent result shown during the async display")
+  "The subject of the most recent result shown during the async display.")
 (make-variable-buffer-local 'notmuch-tree-previous-subject)
 
 (defvar notmuch-tree-basic-query nil
-  "A buffer local copy of argument query to the function notmuch-tree")
+  "A buffer local copy of argument query to the function notmuch-tree.")
 (make-variable-buffer-local 'notmuch-tree-basic-query)
 
 (defvar notmuch-tree-query-context nil
-  "A buffer local copy of argument query-context to the function notmuch-tree")
+  "A buffer local copy of argument query-context to the function notmuch-tree.")
 (make-variable-buffer-local 'notmuch-tree-query-context)
 
 (defvar notmuch-tree-target-msg nil
-  "A buffer local copy of argument target to the function notmuch-tree")
+  "A buffer local copy of argument target to the function notmuch-tree.")
 (make-variable-buffer-local 'notmuch-tree-target-msg)
 
 (defvar notmuch-tree-open-target nil
-  "A buffer local copy of argument open-target to the function notmuch-tree")
+  "A buffer local copy of argument open-target to the function notmuch-tree.")
 (make-variable-buffer-local 'notmuch-tree-open-target)
 
 (defvar notmuch-tree-message-window nil
@@ -203,21 +245,21 @@ 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))
+     ,(concat "(In message pane) " (documentation func 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"
+  "Return value of SYM in message-pane if open, or tree-pane if not."
   (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
+  "Activate BUTTON or button at point.
 
 This function does not give an error if there is no button."
   (interactive)
@@ -231,7 +273,7 @@ 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))
+     ,(concat "(Close message pane and) " (documentation func t))
      (interactive)
      (let ((notmuch-show-process-crypto
            (notmuch-tree-inherit-from-message-pane 'notmuch-show-process-crypto)))
@@ -243,17 +285,22 @@ FUNC."
     (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))
+    (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.
     (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))
+    (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-jump-search]
+      (notmuch-tree-close-message-pane-and #'notmuch-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,21 +310,30 @@ 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-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))
 
     ;; 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-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))
 
     ;; 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 "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)
     (define-key map "a" 'notmuch-tree-archive-message-then-next)
     (define-key map "z" 'notmuch-tree-to-tree)
@@ -302,7 +358,7 @@ FUNC."
 
 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 +366,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)
@@ -363,7 +421,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)))
@@ -388,7 +447,7 @@ NOT change the database."
            (notmuch-show-update-tags new-tags)))))))
 
 (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 +487,26 @@ 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-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)))
@@ -501,13 +572,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 +587,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
@@ -530,17 +601,18 @@ Shows in split pane or whole window according to value of
   (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 +627,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 +636,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,35 +665,55 @@ 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)
@@ -618,20 +726,23 @@ message will be \"unarchived\", i.e. the tag changes in
   (notmuch-tree-thread-top))
 
 (defun notmuch-tree-next-thread ()
+  "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-thread-mapcar (function)
   "Iterate through all messages in the current thread
  and call FUNCTION for side effects."
   (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."
@@ -640,7 +751,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))))))
@@ -681,7 +792,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
@@ -692,7 +803,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))
@@ -737,7 +849,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))
@@ -748,17 +860,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))
@@ -779,43 +891,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."
@@ -832,7 +942,7 @@ passed to it by notmuch-tree-process-filter."
   (mapc 'notmuch-tree-insert-forest-thread forest))
 
 (define-derived-mode notmuch-tree-mode fundamental-mode "notmuch-tree"
-  "Major mode displaying messages (as opposed to threads) of of a notmuch search.
+  "Major mode displaying messages (as opposed to threads) of a notmuch search.
 
 This buffer contains the results of a \"notmuch tree\" of your
 email archives. Each line in the buffer represents a single
@@ -844,51 +954,50 @@ 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"
+  "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))
     (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))
+      (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")))))))))
 
 (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)
+       done)
     (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
@@ -896,8 +1005,11 @@ 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 query-context)
+  (setq notmuch-tree-query-context (if (or (string= query-context "")
+                                          (string= 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
@@ -905,19 +1017,18 @@ 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"))
+                             (and query-context
+                                  (concat " and (" query-context ")"))))
+        (message-arg (if unthreaded "--unthreaded" "--entire-thread")))
     (if (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
-                "show" "--body=false" "--format=sexp"
+                "show" "--body=false" "--format=sexp" "--format-version=4"
                 message-arg search-args))
          ;; Use a scratch buffer to accumulate partial output.
          ;; This buffer will be killed by the sentinel, which
@@ -928,7 +1039,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 ("
@@ -936,8 +1047,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)
+  "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
@@ -949,23 +1060,28 @@ 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: ")))
+      (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))
   ;; Don't track undo information for this buffer
   (set 'buffer-undo-list t)
-
-  (notmuch-tree-worker query query-context target open-target)
-
+  (notmuch-tree-worker query query-context target open-target unthreaded)
   (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))
 
 ;;