]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-lib.el
emacs: Add new option notmuch-search-hide-excluded
[notmuch] / emacs / notmuch-lib.el
index f8958a9116605e1c98d92cdd3aa8cb8d3a497140..bf9c4a534a24f5d5c088857ce420600604259326 100644 (file)
@@ -1,4 +1,4 @@
-;;; notmuch-lib.el --- common variables, functions and function declarations
+;;; notmuch-lib.el --- common variables, functions and function declarations  -*- lexical-binding: t -*-
 ;;
 ;; Copyright © Carl Worth
 ;;
 ;;
 ;; Authors: Carl Worth <cworth@cworth.org>
 
-;; This is an part of an emacs-based interface to the notmuch mail system.
-
 ;;; Code:
 
 (require 'cl-lib)
+(require 'pcase)
+(require 'subr-x)
 
 (require 'mm-util)
 (require 'mm-view)
@@ -35,8 +35,7 @@
   (defconst notmuch-emacs-version "unknown"
     "Placeholder variable when notmuch-version.el[c] is not available."))
 
-(autoload 'notmuch-jump-search "notmuch-jump"
-  "Jump to a saved search by shortcut key." t)
+;;; Groups
 
 (defgroup notmuch nil
   "Notmuch mail reader for Emacs."
@@ -56,9 +55,8 @@
 
 (defgroup notmuch-send nil
   "Sending messages from Notmuch."
-  :group 'notmuch)
-
-(custom-add-to-group 'notmuch-send 'message 'custom-group)
+  :group 'notmuch
+  :group 'message)
 
 (defgroup notmuch-tag nil
   "Tags and tagging in Notmuch."
@@ -84,6 +82,8 @@
   "Graphical attributes for displaying text"
   :group 'notmuch)
 
+;;; Options
+
 (defcustom notmuch-command "notmuch"
   "Name of the notmuch binary.
 
@@ -103,6 +103,17 @@ search results. Note that any filtered searches created by
 search."
   :type 'boolean
   :group 'notmuch-search)
+(make-variable-buffer-local 'notmuch-search-oldest-first)
+
+(defcustom notmuch-search-hide-excluded t
+  "Hide mail tagged with a excluded tag.
+
+Excluded tags are defined in the users configuration file under
+the search section. When this variable is true, any mail with
+such a tag will not be shown in the search output."
+  :type 'boolean
+  :group 'notmuch-search)
+(make-variable-buffer-local 'notmuch-search-hide-excluded)
 
 (defcustom notmuch-poll-script nil
   "[Deprecated] Command to run to incorporate new mail into the notmuch database.
@@ -131,11 +142,6 @@ the user's needs:
                 (string :tag "Custom script"))
   :group 'notmuch-external)
 
-;;
-
-(defvar notmuch-search-history nil
-  "Variable to store notmuch searches history.")
-
 (defcustom notmuch-archive-tags '("-inbox")
   "List of tag changes to apply to a message or a thread when it is archived.
 
@@ -150,9 +156,15 @@ For example, if you wanted to remove an \"inbox\" tag and add an
   :group 'notmuch-search
   :group 'notmuch-show)
 
+;;; Variables
+
+(defvar notmuch-search-history nil
+  "Variable to store notmuch searches history.")
+
 (defvar notmuch-common-keymap
   (let ((map (make-sparse-keymap)))
     (define-key map "?" 'notmuch-help)
+    (define-key map "v" 'notmuch-version)
     (define-key map "q" 'notmuch-bury-or-kill-this-buffer)
     (define-key map "s" 'notmuch-search)
     (define-key map "t" 'notmuch-search-by-tag)
@@ -164,6 +176,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an
     (define-key map (kbd "M-=") 'notmuch-refresh-all-buffers)
     (define-key map "G" 'notmuch-poll-and-refresh-this-buffer)
     (define-key map "j" 'notmuch-jump-search)
+    (define-key map [remap undo] 'notmuch-tag-undo)
     map)
   "Keymap shared by all notmuch modes.")
 
@@ -182,6 +195,8 @@ For example, if you wanted to remove an \"inbox\" tag and add an
                  (select-window (posn-window (event-start last-input-event)))
                  (button-activate button)))
 
+;;; CLI Utilities
+
 (defun notmuch-command-to-string (&rest args)
   "Synchronously invoke \"notmuch\" with the given list of arguments.
 
@@ -191,8 +206,8 @@ will be signaled.
 
 Otherwise the output will be returned."
   (with-temp-buffer
-    (let* ((status (apply #'call-process notmuch-command nil t nil args))
-          (output (buffer-string)))
+    (let ((status (apply #'notmuch--call-process notmuch-command nil t nil args))
+         (output (buffer-string)))
       (notmuch-check-exit-status status (cons notmuch-command args) output)
       output)))
 
@@ -202,7 +217,7 @@ Otherwise the output will be returned."
 (defun notmuch-cli-sane-p ()
   "Return t if the cli seems to be configured sanely."
   (unless notmuch--cli-sane-p
-    (let ((status (call-process notmuch-command nil nil nil
+    (let ((status (notmuch--call-process notmuch-command nil nil nil
                                "config" "get" "user.primary_email")))
       (setq notmuch--cli-sane-p (= status 0))))
   notmuch--cli-sane-p)
@@ -224,13 +239,31 @@ on the command line, and then retry your notmuch command")))
        (match-string 2 long-string)
       "unknown")))
 
+(defvar notmuch-emacs-version)
+
+(defun notmuch-version ()
+  "Display the notmuch version.
+The versions of the Emacs package and the `notmuch' executable
+should match, but if and only if they don't, then this command
+displays both values separately."
+  (interactive)
+  (let ((cli-version (notmuch-cli-version)))
+    (message "notmuch version %s"
+            (if (string= notmuch-emacs-version cli-version)
+                cli-version
+              (concat cli-version
+                      " (emacs mua version " notmuch-emacs-version ")")))))
+
+;;; Notmuch Configuration
+
 (defun notmuch-config-get (item)
   "Return a value from the notmuch configuration."
   (let* ((val (notmuch-command-to-string "config" "get" item))
         (len (length val)))
     ;; Trim off the trailing newline (if the value is empty or not
-    ;; configured, there will be no newline)
-    (if (and (> len 0) (= (aref val (- len 1)) ?\n))
+    ;; configured, there will be no newline).
+    (if (and (> len 0)
+            (= (aref val (- len 1)) ?\n))
        (substring val 0 -1)
       val)))
 
@@ -253,17 +286,21 @@ on the command line, and then retry your notmuch command")))
 (defun notmuch-user-emails ()
   (cons (notmuch-user-primary-email) (notmuch-user-other-email)))
 
+;;; Commands
+
 (defun notmuch-poll ()
   "Run \"notmuch new\" or an external script to import mail.
 
 Invokes `notmuch-poll-script', \"notmuch new\", or does nothing
 depending on the value of `notmuch-poll-script'."
   (interactive)
+  (message "Polling mail...")
   (if (stringp notmuch-poll-script)
-      (unless (string= notmuch-poll-script "")
-       (unless (equal (call-process notmuch-poll-script nil nil) 0)
+      (unless (string-empty-p notmuch-poll-script)
+       (unless (equal (notmuch--call-process notmuch-poll-script nil nil) 0)
          (error "Notmuch: poll script `%s' failed!" notmuch-poll-script)))
-    (notmuch-call-notmuch-process "new")))
+    (notmuch-call-notmuch-process "new"))
+  (message "Polling mail...done"))
 
 (defun notmuch-bury-or-kill-this-buffer ()
   "Undisplay the current buffer.
@@ -275,17 +312,7 @@ it, in which case it is killed."
       (bury-buffer)
     (kill-buffer)))
 
-(defun notmuch-documentation-first-line (symbol)
-  "Return the first line of the documentation string for SYMBOL."
-  (let ((doc (documentation symbol)))
-    (if doc
-       (with-temp-buffer
-         (insert (documentation symbol t))
-         (goto-char (point-min))
-         (let ((beg (point)))
-           (end-of-line)
-           (buffer-substring beg (point))))
-      "")))
+;;; Describe Key Bindings
 
 (defun notmuch-prefix-key-description (key)
   "Given a prefix key code, return a human-readable string representation.
@@ -297,7 +324,6 @@ This is basically just `format-kbd-macro' but we also convert ESC to M-."
        "M-"
       (concat desc " "))))
 
-
 (defun notmuch-describe-key (actual-key binding prefix ua-keys tail)
   "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL.
 
@@ -319,7 +345,10 @@ It does not prepend if ACTUAL-KEY is already listed in TAIL."
                  (or (and (symbolp binding)
                           (get binding 'notmuch-doc))
                      (and (functionp binding)
-                          (notmuch-documentation-first-line binding))))
+                          (let ((doc (documentation binding)))
+                            (and doc
+                                 (string-match "\\`.+" doc)
+                                 (match-string 0 doc))))))
            tail)))
   tail)
 
@@ -395,14 +424,14 @@ This is similar to `describe-function' for the current major
 mode, but bindings tables are shown with documentation strings
 rather than command names.  By default, this uses the first line
 of each command's documentation string.  A command can override
-this by setting the 'notmuch-doc property of its command symbol.
+this by setting the \\='notmuch-doc property of its command symbol.
 A command that supports a prefix argument can explicitly document
-its prefixed behavior by setting the 'notmuch-prefix-doc property
+its prefixed behavior by setting the \\='notmuch-prefix-doc property
 of its command symbol."
   (interactive)
-  (let* ((mode major-mode)
-        (doc (substitute-command-keys
-              (notmuch-substitute-command-keys (documentation mode t)))))
+  (let ((doc (substitute-command-keys
+             (notmuch-substitute-command-keys
+              (documentation major-mode t)))))
     (with-current-buffer (generate-new-buffer "*notmuch-help*")
       (insert doc)
       (goto-char (point-min))
@@ -417,7 +446,7 @@ of its command symbol."
         (i 0))
     (while (< i (length prefix))
       (aset prefix i (aref key i))
-      (setq i (1+ i)))
+      (cl-incf i))
     (let* ((subkeymap (key-binding prefix))
           (ua-keys (where-is-internal 'universal-argument nil t))
           (prefix-string (notmuch-prefix-key-description prefix))
@@ -432,9 +461,10 @@ of its command symbol."
          (insert desc)))
       (pop-to-buffer (help-buffer)))))
 
-(defvar notmuch-buffer-refresh-function nil
+;;; Refreshing Buffers
+
+(defvar-local notmuch-buffer-refresh-function nil
   "Function to call to refresh the current buffer.")
-(make-variable-buffer-local 'notmuch-buffer-refresh-function)
 
 (defun notmuch-refresh-this-buffer ()
   "Refresh the current buffer."
@@ -464,9 +494,11 @@ be displayed."
        (with-current-buffer buffer
          (notmuch-refresh-this-buffer))))))
 
+;;; String Utilities
+
 (defun notmuch-prettify-subject (subject)
-  ;; This function is used by `notmuch-search-process-filter' which
-  ;; requires that we not disrupt its' matching state.
+  ;; This function is used by `notmuch-search-process-filter',
+  ;; which requires that we not disrupt its matching state.
   (save-match-data
     (if (and subject
             (string-match "^[ \t]*$" subject))
@@ -507,8 +539,6 @@ This replaces spaces, percents, and double quotes in STR with
   (replace-regexp-in-string
    "[ %\"]" (lambda (match) (format "%%%02x" (aref match 0))) str))
 
-;;
-
 (defun notmuch-common-do-stash (text)
   "Common function to stash text in kill ring, and display in minibuffer."
   (if text
@@ -520,46 +550,47 @@ This replaces spaces, percents, and double quotes in STR with
     (kill-new "")
     (message "Nothing to stash!")))
 
-;;
-
-(defun notmuch-remove-if-not (predicate list)
-  "Return a copy of LIST with all items not satisfying PREDICATE removed."
-  (let (out)
-    (while list
-      (when (funcall predicate (car list))
-       (push (car list) out))
-      (setq list (cdr list)))
-    (nreverse out)))
+;;; Generic Utilities
 
 (defun notmuch-plist-delete (plist property)
-  (let* ((xplist (cons nil plist))
-        (pred xplist))
-    (while (cdr pred)
-      (when (eq (cadr pred) property)
-       (setcdr pred (cdddr pred)))
-      (setq pred (cddr pred)))
-    (cdr xplist)))
-
-(defun notmuch-split-content-type (content-type)
-  "Split content/type into 'content' and 'type'."
-  (split-string content-type "/"))
+  (let (p)
+    (while plist
+      (unless (eq property (car plist))
+       (setq p (plist-put p (car plist) (cadr plist))))
+      (setq plist (cddr plist)))
+    p))
+
+;;; MML Utilities
 
 (defun notmuch-match-content-type (t1 t2)
-  "Return t if t1 and t2 are matching content types, taking wildcards into account."
-  (let ((st1 (notmuch-split-content-type t1))
-       (st2 (notmuch-split-content-type t2)))
-    (if (or (string= (cadr st1) "*")
-           (string= (cadr st2) "*"))
-       ;; Comparison of content types should be case insensitive.
-       (string= (downcase (car st1)) (downcase (car st2)))
-      (string= (downcase t1) (downcase t2)))))
-
-(defvar notmuch-multipart/alternative-discouraged
+  "Return t if t1 and t2 are matching content types.
+Take wildcards into account."
+  (and (stringp t1)
+       (stringp t2)
+       (let ((st1 (split-string t1 "/"))
+            (st2 (split-string t2 "/")))
+        (if (or (string= (cadr st1) "*")
+                (string= (cadr st2) "*"))
+            ;; Comparison of content types should be case insensitive.
+            (string= (downcase (car st1))
+                     (downcase (car st2)))
+          (string= (downcase t1)
+                   (downcase t2))))))
+
+(defcustom notmuch-multipart/alternative-discouraged
   '(;; Avoid HTML parts.
     "text/html"
     ;; multipart/related usually contain a text/html part and some
     ;; associated graphics.
-    "multipart/related"))
+    "multipart/related")
+  "Which mime types to hide by default for multipart messages.
+
+Can either be a list of mime types (as strings) or a function
+mapping a plist representing the current message to such a list.
+See Info node `(notmuch-emacs) notmuch-show' for a sample function."
+  :group 'notmuch-show
+  :type '(radio (repeat :tag "MIME Types" string)
+               (function :tag "Function")))
 
 (defun notmuch-multipart/alternative-determine-discouraged (msg)
   "Return the discouraged alternatives for the specified message."
@@ -608,10 +639,11 @@ the given type."
                       (set-buffer-multibyte nil))
                     (let ((args `("show" "--format=raw"
                                   ,(format "--part=%s" (plist-get part :id))
-                                  ,@(when process-crypto '("--decrypt=true"))
+                                  ,@(and process-crypto '("--decrypt=true"))
                                   ,(notmuch-id-to-query (plist-get msg :id))))
                           (coding-system-for-read
-                           (if binaryp 'no-conversion
+                           (if binaryp
+                               'no-conversion
                              (let ((coding-system
                                     (mm-charset-to-coding-system
                                      (plist-get part :content-charset))))
@@ -626,7 +658,7 @@ the given type."
                                  ;; charset is US-ASCII. RFC6657
                                  ;; complicates this somewhat.
                                  'us-ascii)))))
-                      (apply #'call-process
+                      (apply #'notmuch--call-process
                              notmuch-command nil '(t nil) nil args)
                       (buffer-string))))))
     (when (and cache data)
@@ -656,20 +688,6 @@ If CACHE is non-nil, the content of this part will be saved in
 MSG (if it isn't already)."
   (notmuch--get-bodypart-raw msg part process-crypto nil cache))
 
-;; Workaround: The call to `mm-display-part' below triggers a bug in
-;; Emacs 24 if it attempts to use the shr renderer to display an HTML
-;; part with images in it (demonstrated in 24.1 and 24.2 on Debian and
-;; Fedora 17, though unreproducable in other configurations).
-;; `mm-shr' references the variable `gnus-inhibit-images' without
-;; first loading gnus-art, which defines it, resulting in a
-;; void-variable error.  Hence, we advise `mm-shr' to ensure gnus-art
-;; is loaded.
-(if (>= emacs-major-version 24)
-    (defadvice mm-shr (before load-gnus-arts activate)
-      (require 'gnus-art nil t)
-      (ad-disable-advice 'mm-shr 'before 'load-gnus-arts)
-      (ad-activate 'mm-shr)))
-
 (defun notmuch-mm-display-part-inline (msg part content-type process-crypto)
   "Use the mm-decode/mm-view functions to display a part in the
 current buffer, if possible."
@@ -680,7 +698,8 @@ current buffer, if possible."
       ;; `gnus-decoded' charset.  Otherwise, we'll fetch the binary
       ;; part content and let mm-* decode it.
       (let* ((have-content (plist-member part :content))
-            (charset (if have-content 'gnus-decoded
+            (charset (if have-content
+                         'gnus-decoded
                        (plist-get part :content-charset)))
             (handle (mm-make-handle (current-buffer)
                                     `(,content-type (charset . ,charset)))))
@@ -694,8 +713,11 @@ current buffer, if possible."
          (when (mm-inlinable-p handle)
            (set-buffer display-buffer)
            (mm-display-part handle)
+           (plist-put part :undisplayer (mm-handle-undisplayer handle))
            t))))))
 
+;;; Generic Utilities
+
 ;; Converts a plist of headers to an alist of headers. The input plist should
 ;; have symbols of the form :Header as keys, and the resulting alist will have
 ;; symbols of the form 'Header as keys.
@@ -714,7 +736,7 @@ single element face list."
     (list face)))
 
 (defun notmuch-apply-face (object face &optional below start end)
-  "Combine FACE into the 'face text property of OBJECT between START and END.
+  "Combine FACE into the \\='face text property of OBJECT between START and END.
 
 This function combines FACE with any existing faces between START
 and END in OBJECT.  Attributes specified by FACE take precedence
@@ -762,6 +784,8 @@ returned by FUNC."
       (put-text-property start next prop (funcall func value) object)
       (setq start next))))
 
+;;; Running Notmuch
+
 (defun notmuch-logged-error (msg &optional extra)
   "Log MSG and EXTRA to *Notmuch errors* and signal MSG.
 
@@ -781,8 +805,7 @@ signaled error.  This function does not return."
        (insert extra)
        (unless (bolp)
          (newline)))))
-  (error "%s" (concat msg (when extra
-                           " (see *Notmuch errors* for more details)"))))
+  (error "%s%s" msg (if extra " (see *Notmuch errors* for more details)" "")))
 
 (defun notmuch-check-async-exit-status (proc msg &optional command err)
   "If PROC exited abnormally, pop up an error buffer and signal an error.
@@ -824,22 +847,27 @@ You may need to restart Emacs or upgrade your notmuch Emacs package."))
 Emacs requested a newer output format than supported by the notmuch CLI.
 You may need to restart Emacs or upgrade your notmuch package."))
    (t
-    (let* ((command-string
-           (mapconcat (lambda (arg)
-                        (shell-quote-argument
-                         (cond ((stringp arg) arg)
-                               ((symbolp arg) (symbol-name arg))
-                               (t "*UNKNOWN ARGUMENT*"))))
-                      command " "))
-          (extra
-           (concat "command: " command-string "\n"
-                   (if (integerp exit-status)
-                       (format "exit status: %s\n" exit-status)
-                     (format "exit signal: %s\n" exit-status))
-                   (when err
-                     (concat "stderr:\n" err))
-                   (when output
-                     (concat "stdout:\n" output)))))
+    (pcase-let*
+       ((`(,command . ,args) command)
+        (command (if (equal (file-name-nondirectory command)
+                            notmuch-command)
+                     notmuch-command
+                   command))
+        (command-string
+         (mapconcat (lambda (arg)
+                      (shell-quote-argument
+                       (cond ((stringp arg) arg)
+                             ((symbolp arg) (symbol-name arg))
+                             (t "*UNKNOWN ARGUMENT*"))))
+                    (cons command args)
+                    " "))
+        (extra
+         (concat "command: " command-string "\n"
+                 (if (integerp exit-status)
+                     (format "exit status: %s\n" exit-status)
+                   (format "exit signal: %s\n" exit-status))
+                 (and err    (concat "stderr:\n" err))
+                 (and output (concat "stdout:\n" output)))))
       (if err
          ;; We have an error message straight from the CLI.
          (notmuch-logged-error
@@ -847,11 +875,37 @@ You may need to restart Emacs or upgrade your notmuch package."))
        ;; We only have combined output from the CLI; don't inundate
        ;; the user with it.  Mimic `process-lines'.
        (notmuch-logged-error (format "%s exited with status %s"
-                                     (car command) exit-status)
+                                     command exit-status)
                              extra))
       ;; `notmuch-logged-error' does not return.
       ))))
 
+(defmacro notmuch--apply-with-env (func &rest args)
+  `(let ((default-directory "~"))
+     (apply ,func ,@args)))
+
+(defun notmuch--process-lines (program &rest args)
+  "Wrap process-lines, binding DEFAULT-DIRECTORY to a safe
+default"
+  (notmuch--apply-with-env #'process-lines program args))
+
+(defun notmuch--make-process (&rest args)
+  "Wrap make-process, binding DEFAULT-DIRECTORY to a safe
+default"
+  (notmuch--apply-with-env #'make-process args))
+
+(defun notmuch--call-process-region (start end program
+                                          &optional delete buffer display
+                                          &rest args)
+  "Wrap call-process-region, binding DEFAULT-DIRECTORY to a safe
+default"
+  (notmuch--apply-with-env
+   #'call-process-region start end program delete buffer display args))
+
+(defun notmuch--call-process (program &optional infile destination display &rest args)
+  "Wrap call-process, binding DEFAULT-DIRECTORY to a safe default"
+  (notmuch--apply-with-env #'call-process program infile destination display args))
+
 (defun notmuch-call-notmuch--helper (destination args)
   "Helper for synchronous notmuch invocation commands.
 
@@ -861,14 +915,14 @@ for `call-process'.  ARGS is as described for
   (let (stdin-string)
     (while (keywordp (car args))
       (cl-case (car args)
-       (:stdin-string (setq stdin-string (cadr args)
-                            args (cddr args)))
+       (:stdin-string (setq stdin-string (cadr args))
+                      (setq args (cddr args)))
        (otherwise
         (error "Unknown keyword argument: %s" (car args)))))
     (if (null stdin-string)
-       (apply #'call-process notmuch-command nil destination nil args)
+       (apply #'notmuch--call-process notmuch-command nil destination nil args)
       (insert stdin-string)
-      (apply #'call-process-region (point-min) (point-max)
+      (apply #'notmuch--call-process-region (point-min) (point-max)
             notmuch-command t destination nil args))))
 
 (defun notmuch-call-notmuch-process (&rest args)
@@ -922,56 +976,29 @@ when the process exits, or nil for none.  The caller must *not*
 invoke `set-process-sentinel' directly on the returned process,
 as that will interfere with the handling of stderr and the exit
 status."
-  (let (err-file err-buffer proc err-proc
-                ;; Find notmuch using Emacs' `exec-path'
-                (command (or (executable-find notmuch-command)
-                             (error "Command not found: %s" notmuch-command))))
-    (if (fboundp 'make-process)
-       (progn
-         (setq err-buffer (generate-new-buffer " *notmuch-stderr*"))
-         ;; Emacs 25 and newer has `make-process', which allows
-         ;; redirecting stderr independently from stdout to a
-         ;; separate buffer. As this allows us to avoid using a
-         ;; temporary file and shell invocation, use it when
-         ;; available.
-         (setq proc (make-process
-                     :name name
-                     :buffer buffer
-                     :command (cons command args)
-                     :connection-type 'pipe
-                     :stderr err-buffer)
-               err-proc (get-buffer-process err-buffer))
-         (process-put proc 'err-buffer err-buffer)
-
-         (process-put err-proc 'err-file err-file)
-         (process-put err-proc 'err-buffer err-buffer)
-         (set-process-sentinel err-proc #'notmuch-start-notmuch-error-sentinel))
-      ;; On Emacs versions before 25, there is no way to capture
-      ;; stdout and stderr separately for asynchronous processes, or
-      ;; even to redirect stderr to a file, so we use a trivial shell
-      ;; wrapper to send stderr to a temporary file and clean things
-      ;; up in the sentinel.
-      (setq err-file (make-temp-file "nmerr"))
-      (let ((process-connection-type nil)) ;; Use a pipe
-       (setq proc (apply #'start-process name buffer
-                         "/bin/sh" "-c"
-                         "exec 2>\"$1\"; shift; exec \"$0\" \"$@\""
-                         command err-file args)))
-      (process-put proc 'err-file err-file))
+  (let* ((command (or (executable-find notmuch-command)
+                     (error "Command not found: %s" notmuch-command)))
+        (err-buffer (generate-new-buffer " *notmuch-stderr*"))
+        (proc (notmuch--make-process
+               :name name
+               :buffer buffer
+               :command (cons command args)
+               :connection-type 'pipe
+               :stderr err-buffer))
+        (err-proc (get-buffer-process err-buffer)))
+    (process-put proc 'err-buffer err-buffer)
     (process-put proc 'sub-sentinel sentinel)
-    (process-put proc 'real-command (cons notmuch-command args))
     (set-process-sentinel proc #'notmuch-start-notmuch-sentinel)
+    (set-process-sentinel err-proc #'notmuch-start-notmuch-error-sentinel)
     proc))
 
 (defun notmuch-start-notmuch-sentinel (proc event)
   "Process sentinel function used by `notmuch-start-notmuch'."
-  (let* ((err-file (process-get proc 'err-file))
-        (err-buffer (or (process-get proc 'err-buffer)
-                        (find-file-noselect err-file)))
-        (err (when (not (zerop (buffer-size err-buffer)))
-               (with-current-buffer err-buffer (buffer-string))))
-        (sub-sentinel (process-get proc 'sub-sentinel))
-        (real-command (process-get proc 'real-command)))
+  (let* ((err-buffer (process-get proc 'err-buffer))
+        (err (and (buffer-live-p err-buffer)
+                  (not (zerop (buffer-size err-buffer)))
+                  (with-current-buffer err-buffer (buffer-string))))
+        (sub-sentinel (process-get proc 'sub-sentinel)))
     (condition-case err
        (progn
          ;; Invoke the sub-sentinel, if any
@@ -983,40 +1010,51 @@ status."
          ;; and there's no point in telling the user that (but we
          ;; still check for and report stderr output below).
          (when (buffer-live-p (process-buffer proc))
-           (notmuch-check-async-exit-status proc event real-command err))
+           (notmuch-check-async-exit-status proc event nil err))
          ;; If that didn't signal an error, then any error output was
          ;; really warning output.  Show warnings, if any.
          (let ((warnings
-                (when err
-                  (with-current-buffer err-buffer
-                    (goto-char (point-min))
-                    (end-of-line)
-                    ;; Show first line; stuff remaining lines in the
-                    ;; errors buffer.
-                    (let ((l1 (buffer-substring (point-min) (point))))
-                      (skip-chars-forward "\n")
-                      (cons l1 (unless (eobp)
-                                 (buffer-substring (point) (point-max)))))))))
+                (and err
+                     (with-current-buffer err-buffer
+                       (goto-char (point-min))
+                       (end-of-line)
+                       ;; Show first line; stuff remaining lines in the
+                       ;; errors buffer.
+                       (let ((l1 (buffer-substring (point-min) (point))))
+                         (skip-chars-forward "\n")
+                         (cons l1 (and (not (eobp))
+                                       (buffer-substring (point)
+                                                         (point-max)))))))))
            (when warnings
              (notmuch-logged-error (car warnings) (cdr warnings)))))
       (error
        ;; Emacs behaves strangely if an error escapes from a sentinel,
        ;; so turn errors into messages.
-       (message "%s" (error-message-string err))))
-    (when err-file (ignore-errors (delete-file err-file)))))
-
-(defun notmuch-start-notmuch-error-sentinel (proc event)
-  (let* ((err-file (process-get proc 'err-file))
-        ;; When `make-process' is available, use the error buffer
-        ;; associated with the process, otherwise the error file.
-        (err-buffer (or (process-get proc 'err-buffer)
-                        (find-file-noselect err-file))))
-    (when err-buffer (kill-buffer err-buffer))))
-
-;; This variable is used only buffer local, but it needs to be
-;; declared globally first to avoid compiler warnings.
-(defvar notmuch-show-process-crypto nil)
-(make-variable-buffer-local 'notmuch-show-process-crypto)
+       (message "%s" (error-message-string err))))))
+
+(defun notmuch-start-notmuch-error-sentinel (proc _event)
+  (unless (process-live-p proc)
+    (let ((buffer (process-buffer proc)))
+      (when (buffer-live-p buffer)
+       (kill-buffer buffer)))))
+
+(defvar-local notmuch-show-process-crypto nil)
+
+(defun notmuch--run-show (search-terms &optional duplicate)
+  "Return a list of threads of messages matching SEARCH-TERMS.
+
+A thread is a forest or list of trees. A tree is a two element
+list where the first element is a message, and the second element
+is a possibly empty forest of replies."
+  (let ((args '("show" "--format=sexp" "--format-version=5")))
+    (when notmuch-show-process-crypto
+      (setq args (append args '("--decrypt=true"))))
+    (when duplicate
+      (setq args (append args (list (format "--duplicate=%d" duplicate)))))
+    (setq args (append args search-terms))
+    (apply #'notmuch-call-notmuch-sexp args)))
+
+;;; Generic Utilities
 
 (defun notmuch-interactive-region ()
   "Return the bounds of the current interactive region.
@@ -1032,6 +1070,16 @@ region if the region is active, or both `point' otherwise."
   'notmuch-interactive-region
   "notmuch 0.29")
 
+(defun notmuch--inline-override-types ()
+  "Override mm-inline-override-types to stop application/*
+parts from being displayed unless the user has customized
+it themselves."
+  (if (equal mm-inline-override-types
+            (eval (car (get 'mm-inline-override-types 'standard-value))))
+      (cons "application/.*" mm-inline-override-types)
+    mm-inline-override-types))
+;;; _
+
 (provide 'notmuch-lib)
 
 ;;; notmuch-lib.el ends here