]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-lib.el
emacs: Add new option notmuch-search-hide-excluded
[notmuch] / emacs / notmuch-lib.el
index 91c9478186590b1e903c4ad626aea78143e3abc1..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
 ;;
 ;;
 ;; Copyright © Carl Worth
 ;;
@@ -22,6 +22,8 @@
 ;;; Code:
 
 (require 'cl-lib)
 ;;; Code:
 
 (require 'cl-lib)
+(require 'pcase)
+(require 'subr-x)
 
 (require 'mm-util)
 (require 'mm-view)
 
 (require 'mm-util)
 (require 'mm-view)
@@ -33,6 +35,8 @@
   (defconst notmuch-emacs-version "unknown"
     "Placeholder variable when notmuch-version.el[c] is not available."))
 
   (defconst notmuch-emacs-version "unknown"
     "Placeholder variable when notmuch-version.el[c] is not available."))
 
+;;; Groups
+
 (defgroup notmuch nil
   "Notmuch mail reader for Emacs."
   :group 'mail)
 (defgroup notmuch nil
   "Notmuch mail reader for Emacs."
   :group 'mail)
@@ -78,6 +82,8 @@
   "Graphical attributes for displaying text"
   :group 'notmuch)
 
   "Graphical attributes for displaying text"
   :group 'notmuch)
 
+;;; Options
+
 (defcustom notmuch-command "notmuch"
   "Name of the notmuch binary.
 
 (defcustom notmuch-command "notmuch"
   "Name of the notmuch binary.
 
@@ -97,6 +103,17 @@ search results. Note that any filtered searches created by
 search."
   :type 'boolean
   :group 'notmuch-search)
 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.
 
 (defcustom notmuch-poll-script nil
   "[Deprecated] Command to run to incorporate new mail into the notmuch database.
@@ -125,11 +142,6 @@ the user's needs:
                 (string :tag "Custom script"))
   :group 'notmuch-external)
 
                 (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.
 
 (defcustom notmuch-archive-tags '("-inbox")
   "List of tag changes to apply to a message or a thread when it is archived.
 
@@ -144,9 +156,15 @@ For example, if you wanted to remove an \"inbox\" tag and add an
   :group 'notmuch-search
   :group 'notmuch-show)
 
   :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)
 (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)
     (define-key map "q" 'notmuch-bury-or-kill-this-buffer)
     (define-key map "s" 'notmuch-search)
     (define-key map "t" 'notmuch-search-by-tag)
@@ -158,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 (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.")
 
     map)
   "Keymap shared by all notmuch modes.")
 
@@ -176,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)))
 
                  (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.
 
 (defun notmuch-command-to-string (&rest args)
   "Synchronously invoke \"notmuch\" with the given list of arguments.
 
@@ -185,8 +206,8 @@ will be signaled.
 
 Otherwise the output will be returned."
   (with-temp-buffer
 
 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)))
 
       (notmuch-check-exit-status status (cons notmuch-command args) output)
       output)))
 
@@ -196,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
 (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)
                                "config" "get" "user.primary_email")))
       (setq notmuch--cli-sane-p (= status 0))))
   notmuch--cli-sane-p)
@@ -218,13 +239,31 @@ on the command line, and then retry your notmuch command")))
        (match-string 2 long-string)
       "unknown")))
 
        (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
 (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)))
 
        (substring val 0 -1)
       val)))
 
@@ -247,6 +286,8 @@ on the command line, and then retry your notmuch command")))
 (defun notmuch-user-emails ()
   (cons (notmuch-user-primary-email) (notmuch-user-other-email)))
 
 (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.
 
 (defun notmuch-poll ()
   "Run \"notmuch new\" or an external script to import mail.
 
@@ -255,8 +296,8 @@ depending on the value of `notmuch-poll-script'."
   (interactive)
   (message "Polling mail...")
   (if (stringp 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"))
   (message "Polling mail...done"))
          (error "Notmuch: poll script `%s' failed!" notmuch-poll-script)))
     (notmuch-call-notmuch-process "new"))
   (message "Polling mail...done"))
@@ -271,17 +312,7 @@ it, in which case it is killed."
       (bury-buffer)
     (kill-buffer)))
 
       (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.
 
 (defun notmuch-prefix-key-description (key)
   "Given a prefix key code, return a human-readable string representation.
@@ -293,7 +324,6 @@ This is basically just `format-kbd-macro' but we also convert ESC to M-."
        "M-"
       (concat desc " "))))
 
        "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.
 
 (defun notmuch-describe-key (actual-key binding prefix ua-keys tail)
   "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL.
 
@@ -315,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)
                  (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)
 
            tail)))
   tail)
 
@@ -391,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
 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
 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)
 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))
     (with-current-buffer (generate-new-buffer "*notmuch-help*")
       (insert doc)
       (goto-char (point-min))
@@ -428,9 +461,10 @@ of its command symbol."
          (insert desc)))
       (pop-to-buffer (help-buffer)))))
 
          (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.")
   "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."
 
 (defun notmuch-refresh-this-buffer ()
   "Refresh the current buffer."
@@ -460,9 +494,11 @@ be displayed."
        (with-current-buffer buffer
          (notmuch-refresh-this-buffer))))))
 
        (with-current-buffer buffer
          (notmuch-refresh-this-buffer))))))
 
+;;; String Utilities
+
 (defun notmuch-prettify-subject (subject)
 (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))
   (save-match-data
     (if (and subject
             (string-match "^[ \t]*$" subject))
@@ -503,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))
 
   (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
 (defun notmuch-common-do-stash (text)
   "Common function to stash text in kill ring, and display in minibuffer."
   (if text
@@ -516,46 +550,47 @@ This replaces spaces, percents, and double quotes in STR with
     (kill-new "")
     (message "Nothing to stash!")))
 
     (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)
 
 (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)
 
 (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.
   '(;; 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."
 
 (defun notmuch-multipart/alternative-determine-discouraged (msg)
   "Return the discouraged alternatives for the specified message."
@@ -623,7 +658,7 @@ the given type."
                                  ;; charset is US-ASCII. RFC6657
                                  ;; complicates this somewhat.
                                  'us-ascii)))))
                                  ;; 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)
                              notmuch-command nil '(t nil) nil args)
                       (buffer-string))))))
     (when (and cache data)
@@ -653,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))
 
 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 unreproducible 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.
-(when (>= 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."
 (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."
@@ -692,8 +713,11 @@ current buffer, if possible."
          (when (mm-inlinable-p handle)
            (set-buffer display-buffer)
            (mm-display-part handle)
          (when (mm-inlinable-p handle)
            (set-buffer display-buffer)
            (mm-display-part handle)
+           (plist-put part :undisplayer (mm-handle-undisplayer handle))
            t))))))
 
            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.
 ;; 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.
@@ -712,7 +736,7 @@ single element face list."
     (list face)))
 
 (defun notmuch-apply-face (object face &optional below start end)
     (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
 
 This function combines FACE with any existing faces between START
 and END in OBJECT.  Attributes specified by FACE take precedence
@@ -760,6 +784,8 @@ returned by FUNC."
       (put-text-property start next prop (funcall func value) object)
       (setq start next))))
 
       (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.
 
 (defun notmuch-logged-error (msg &optional extra)
   "Log MSG and EXTRA to *Notmuch errors* and signal MSG.
 
@@ -821,20 +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
 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))
-                   (and err    (concat "stderr:\n" err))
-                   (and 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
       (if err
          ;; We have an error message straight from the CLI.
          (notmuch-logged-error
@@ -842,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"
        ;; 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.
       ))))
 
                              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.
 
 (defun notmuch-call-notmuch--helper (destination args)
   "Helper for synchronous notmuch invocation commands.
 
@@ -861,9 +920,9 @@ for `call-process'.  ARGS is as described for
        (otherwise
         (error "Unknown keyword argument: %s" (car args)))))
     (if (null stdin-string)
        (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)
       (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)
             notmuch-command t destination nil args))))
 
 (defun notmuch-call-notmuch-process (&rest args)
@@ -917,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."
 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))
-         (setq 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 'sub-sentinel sentinel)
-    (process-put proc 'real-command (cons notmuch-command args))
     (set-process-sentinel proc #'notmuch-start-notmuch-sentinel)
     (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'."
     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 (and (not (zerop (buffer-size err-buffer)))
+  (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))))
                   (with-current-buffer err-buffer (buffer-string))))
-        (sub-sentinel (process-get proc 'sub-sentinel))
-        (real-command (process-get proc 'real-command)))
+        (sub-sentinel (process-get proc 'sub-sentinel)))
     (condition-case err
        (progn
          ;; Invoke the sub-sentinel, if any
     (condition-case err
        (progn
          ;; Invoke the sub-sentinel, if any
@@ -978,7 +1010,7 @@ 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))
          ;; 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
          ;; If that didn't signal an error, then any error output was
          ;; really warning output.  Show warnings, if any.
          (let ((warnings
@@ -998,21 +1030,31 @@ status."
       (error
        ;; Emacs behaves strangely if an error escapes from a sentinel,
        ;; so turn errors into messages.
       (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.
 
 (defun notmuch-interactive-region ()
   "Return the bounds of the current interactive region.
@@ -1028,6 +1070,16 @@ region if the region is active, or both `point' otherwise."
   'notmuch-interactive-region
   "notmuch 0.29")
 
   '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
 (provide 'notmuch-lib)
 
 ;;; notmuch-lib.el ends here