]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-lib.el
emacs: notmuch-poll: Let the user know we are polling
[notmuch] / emacs / notmuch-lib.el
index fd25f7c9e6279b528c81b1908948ff7554a12015..6ff351d71bb1a0e7b4658b0a62dbd3e20233c54c 100644 (file)
@@ -1,4 +1,4 @@
-;; notmuch-lib.el --- common variables, functions and function declarations
+;;; notmuch-lib.el --- common variables, functions and function declarations
 ;;
 ;; Copyright © Carl Worth
 ;;
 ;; General Public License for more details.
 ;;
 ;; You should have received a copy of the GNU General Public License
-;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
+;; along with Notmuch.  If not, see <https://www.gnu.org/licenses/>.
 ;;
 ;; 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 'mm-util)
 (require 'mm-view)
 (require 'mm-decode)
-(require 'cl)
+
+(require 'notmuch-compat)
+
+(unless (require 'notmuch-version nil t)
+  (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)
 
 (custom-add-to-group 'notmuch-send 'message 'custom-group)
 
+(defgroup notmuch-tag nil
+  "Tags and tagging in Notmuch."
+  :group 'notmuch)
+
 (defgroup notmuch-crypto nil
   "Processing and display of cryptographic MIME parts."
   :group 'notmuch)
   "Running external commands from within Notmuch."
   :group 'notmuch)
 
+(defgroup notmuch-address nil
+  "Address completion."
+  :group 'notmuch)
+
 (defgroup notmuch-faces nil
   "Graphical attributes for displaying text"
   :group 'notmuch)
@@ -137,9 +155,13 @@ For example, if you wanted to remove an \"inbox\" tag and add an
     (define-key map "?" 'notmuch-help)
     (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 "z" 'notmuch-tree)
+    (define-key map "u" 'notmuch-unthreaded)
     (define-key map "m" 'notmuch-mua-new-mail)
+    (define-key map "g" 'notmuch-refresh-this-buffer)
     (define-key map "=" 'notmuch-refresh-this-buffer)
+    (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)
     map)
@@ -167,7 +189,7 @@ If notmuch exits with a non-zero status, output from the process
 will appear in a buffer named \"*Notmuch errors*\" and an error
 will be signaled.
 
-Otherwise the output will be returned"
+Otherwise the output will be returned."
   (with-temp-buffer
     (let* ((status (apply #'call-process notmuch-command nil t nil args))
           (output (buffer-string)))
@@ -189,11 +211,11 @@ Otherwise the output will be returned"
   (unless (notmuch-cli-sane-p)
     (notmuch-logged-error
      "notmuch cli seems misconfigured or unconfigured."
-"Perhaps you haven't run \"notmuch setup\" yet? Try running this
+     "Perhaps you haven't run \"notmuch setup\" yet? Try running this
 on the command line, and then retry your notmuch command")))
 
-(defun notmuch-version ()
-  "Return a string with the notmuch version number."
+(defun notmuch-cli-version ()
+  "Return a string with the notmuch cli command version number."
   (let ((long-string
         ;; Trim off the trailing newline.
         (substring (notmuch-command-to-string "--version") 0 -1)))
@@ -228,16 +250,22 @@ on the command line, and then retry your notmuch command")))
   "Return the user.other_email value (as a list) from the notmuch configuration."
   (split-string (notmuch-config-get "user.other_email") "\n" t))
 
+(defun notmuch-user-emails ()
+  (cons (notmuch-user-primary-email) (notmuch-user-other-email)))
+
 (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 "")
-       (call-process notmuch-poll-script nil nil))
-    (call-process notmuch-command nil nil nil "new")))
+       (unless (equal (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"))
 
 (defun notmuch-bury-or-kill-this-buffer ()
   "Undisplay the current buffer.
@@ -273,10 +301,10 @@ This is basically just `format-kbd-macro' but we also convert ESC to M-."
 
 
 (defun notmuch-describe-key (actual-key binding prefix ua-keys tail)
-  "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL
+  "Prepend cons cells describing prefix-arg ACTUAL-KEY and ACTUAL-KEY to TAIL.
 
 It does not prepend if ACTUAL-KEY is already listed in TAIL."
-  (let ((key-string (concat prefix (format-kbd-macro actual-key))))
+  (let ((key-string (concat prefix (key-description actual-key))))
     ;; We don't include documentation if the key-binding is
     ;; over-ridden. Note, over-riding a binding automatically hides the
     ;; prefixed version too.
@@ -290,10 +318,12 @@ It does not prepend if ACTUAL-KEY is already listed in TAIL."
                tail)))
       ;; Documentation for command
       (push (cons key-string
-                 (or (and (symbolp binding) (get binding 'notmuch-doc))
-                     (notmuch-documentation-first-line binding)))
+                 (or (and (symbolp binding)
+                          (get binding 'notmuch-doc))
+                     (and (functionp binding)
+                          (notmuch-documentation-first-line binding))))
            tail)))
-    tail)
+  tail)
 
 (defun notmuch-describe-remaps (remap-keymap ua-keys base-keymap prefix tail)
   ;; Remappings are represented as a binding whose first "event" is
@@ -301,13 +331,13 @@ It does not prepend if ACTUAL-KEY is already listed in TAIL."
   ;; binding whose "key" is 'remap, and whose "binding" is itself a
   ;; keymap that maps not from keys to commands, but from old (remapped)
   ;; functions to the commands to use in their stead.
-  (map-keymap
-   (lambda (command binding)
-     (mapc
-      (lambda (actual-key)
-       (setq tail (notmuch-describe-key actual-key binding prefix ua-keys tail)))
-      (where-is-internal command base-keymap)))
-   remap-keymap)
+  (map-keymap (lambda (command binding)
+               (mapc (lambda (actual-key)
+                       (setq tail
+                             (notmuch-describe-key actual-key binding
+                                                   prefix ua-keys tail)))
+                     (where-is-internal command base-keymap)))
+             remap-keymap)
   tail)
 
 (defun notmuch-describe-keymap (keymap ua-keys base-keymap &optional prefix tail)
@@ -330,9 +360,13 @@ prefix argument.  PREFIX and TAIL are used internally."
                      (notmuch-describe-remaps
                       binding ua-keys base-keymap prefix tail)
                    (notmuch-describe-keymap
-                    binding ua-keys base-keymap (notmuch-prefix-key-description key) tail))))
+                    binding ua-keys base-keymap
+                    (notmuch-prefix-key-description key)
+                    tail))))
           (binding
-           (setq tail (notmuch-describe-key (vector key) binding prefix ua-keys tail)))))
+           (setq tail
+                 (notmuch-describe-key (vector key)
+                                       binding prefix ua-keys tail)))))
    keymap)
   tail)
 
@@ -342,11 +376,15 @@ prefix argument.  PREFIX and TAIL are used internally."
     (while (string-match "\\\\{\\([^}[:space:]]*\\)}" doc beg)
       (let ((desc
             (save-match-data
-              (let* ((keymap-name (substring doc (match-beginning 1) (match-end 1)))
+              (let* ((keymap-name (substring doc
+                                             (match-beginning 1)
+                                             (match-end 1)))
                      (keymap (symbol-value (intern keymap-name)))
                      (ua-keys (where-is-internal 'universal-argument keymap t))
                      (desc-alist (notmuch-describe-keymap keymap ua-keys keymap))
-                     (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg))) desc-alist)))
+                     (desc-list (mapcar (lambda (arg)
+                                          (concat (car arg) "\t" (cdr arg)))
+                                        desc-alist)))
                 (mapconcat #'identity desc-list "\n")))))
        (setq doc (replace-match desc 1 1 doc)))
       (setq beg (match-end 0)))
@@ -365,7 +403,8 @@ 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)))))
+        (doc (substitute-command-keys
+              (notmuch-substitute-command-keys (documentation mode t)))))
     (with-current-buffer (generate-new-buffer "*notmuch-help*")
       (insert doc)
       (goto-char (point-min))
@@ -376,17 +415,18 @@ of its command symbol."
   "Show help for a subkeymap."
   (interactive)
   (let* ((key (this-command-keys-vector))
-       (prefix (make-vector (1- (length key)) nil))
-       (i 0))
+        (prefix (make-vector (1- (length key)) nil))
+        (i 0))
     (while (< i (length prefix))
       (aset prefix i (aref key i))
       (setq i (1+ i)))
-
     (let* ((subkeymap (key-binding prefix))
           (ua-keys (where-is-internal 'universal-argument nil t))
           (prefix-string (notmuch-prefix-key-description prefix))
-          (desc-alist (notmuch-describe-keymap subkeymap ua-keys subkeymap prefix-string))
-          (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg))) desc-alist))
+          (desc-alist (notmuch-describe-keymap
+                       subkeymap ua-keys subkeymap prefix-string))
+          (desc-list (mapcar (lambda (arg) (concat (car arg) "\t" (cdr arg)))
+                             desc-alist))
           (desc (mapconcat #'identity desc-list "\n")))
       (with-help-window (help-buffer)
        (with-current-buffer standard-output
@@ -402,10 +442,8 @@ of its command symbol."
   "Refresh the current buffer."
   (interactive)
   (when notmuch-buffer-refresh-function
-    (if (commandp notmuch-buffer-refresh-function)
-       ;; Pass prefix argument, etc.
-       (call-interactively notmuch-buffer-refresh-function)
-      (funcall notmuch-buffer-refresh-function))))
+    ;; Pass prefix argument, etc.
+    (call-interactively notmuch-buffer-refresh-function)))
 
 (defun notmuch-poll-and-refresh-this-buffer ()
   "Invoke `notmuch-poll' to import mail, then refresh the current buffer."
@@ -413,6 +451,21 @@ of its command symbol."
   (notmuch-poll)
   (notmuch-refresh-this-buffer))
 
+(defun notmuch-refresh-all-buffers ()
+  "Invoke `notmuch-refresh-this-buffer' on all notmuch major-mode buffers.
+
+The buffers are silently refreshed, i.e. they are not forced to
+be displayed."
+  (interactive)
+  (dolist (buffer (buffer-list))
+    (let ((buffer-mode (buffer-local-value 'major-mode buffer)))
+      (when (memq buffer-mode '(notmuch-show-mode
+                               notmuch-tree-mode
+                               notmuch-search-mode
+                               notmuch-hello-mode))
+       (with-current-buffer buffer
+         (notmuch-refresh-this-buffer))))))
+
 (defun notmuch-prettify-subject (subject)
   ;; This function is used by `notmuch-search-process-filter' which
   ;; requires that we not disrupt its' matching state.
@@ -434,7 +487,6 @@ This includes newlines, tabs, and other funny characters."
 The caller is responsible for prepending the term prefix and a
 colon.  This performs minimal escaping in order to produce
 user-friendly queries."
-
   (save-match-data
     (if (or (equal term "")
            ;; To be pessimistic, only pass through terms composed
@@ -477,7 +529,7 @@ This replaces spaces, percents, and double quotes in STR with
   (let (out)
     (while list
       (when (funcall predicate (car list))
-        (push (car list) out))
+       (push (car list) out))
       (setq list (cdr list)))
     (nreverse out)))
 
@@ -491,11 +543,11 @@ This replaces spaces, percents, and double quotes in STR with
     (cdr xplist)))
 
 (defun notmuch-split-content-type (content-type)
-  "Split content/type into 'content' and 'type'"
+  "Split content/type into 'content' and 'type'."
   (split-string content-type "/"))
 
 (defun notmuch-match-content-type (t1 t2)
-  "Return t if t1 and t2 are matching content types, taking wildcards into account"
+  "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) "*")
@@ -505,18 +557,29 @@ This replaces spaces, percents, and double quotes in STR with
       (string= (downcase t1) (downcase t2)))))
 
 (defvar notmuch-multipart/alternative-discouraged
-  '(
-    ;; Avoid HTML parts.
+  '(;; Avoid HTML parts.
     "text/html"
-    ;; multipart/related usually contain a text/html part and some associated graphics.
-    "multipart/related"
-    ))
-
-(defun notmuch-multipart/alternative-choose (types)
-  "Return a list of preferred types from the given list of types"
+    ;; multipart/related usually contain a text/html part and some
+    ;; associated graphics.
+    "multipart/related"))
+
+(defun notmuch-multipart/alternative-determine-discouraged (msg)
+  "Return the discouraged alternatives for the specified message."
+  ;; If a function, return the result of calling it.
+  (if (functionp notmuch-multipart/alternative-discouraged)
+      (funcall notmuch-multipart/alternative-discouraged msg)
+    ;; Otherwise simply return the value of the variable, which is
+    ;; assumed to be a list of discouraged alternatives. This is the
+    ;; default behaviour.
+    notmuch-multipart/alternative-discouraged))
+
+(defun notmuch-multipart/alternative-choose (msg types)
+  "Return a list of preferred types from the given list of types
+for this message, if present."
   ;; Based on `mm-preferred-alternative-precedence'.
-  (let ((seq types))
-    (dolist (pref (reverse notmuch-multipart/alternative-discouraged))
+  (let ((discouraged (notmuch-multipart/alternative-determine-discouraged msg))
+       (seq types))
+    (dolist (pref (reverse discouraged))
       (dolist (elem (copy-sequence seq))
        (when (string-match pref elem)
          (setq seq (nconc (delete elem seq) (list elem))))))
@@ -525,29 +588,76 @@ This replaces spaces, percents, and double quotes in STR with
 (defun notmuch-parts-filter-by-type (parts type)
   "Given a list of message parts, return a list containing the ones matching
 the given type."
-  (remove-if-not
+  (cl-remove-if-not
    (lambda (part) (notmuch-match-content-type (plist-get part :content-type) type))
    parts))
 
-;; Helper for parts which are generally not included in the default
-;; SEXP output.
-(defun notmuch-get-bodypart-internal (query part-number process-crypto)
-  (let ((args '("show" "--format=raw"))
-       (part-arg (format "--part=%s" part-number)))
-    (setq args (append args (list part-arg)))
-    (if process-crypto
-       (setq args (append args '("--decrypt"))))
-    (setq args (append args (list query)))
-    (with-temp-buffer
-      (let ((coding-system-for-read 'no-conversion))
-       (progn
-         (apply 'call-process (append (list notmuch-command nil (list t nil) nil) args))
-         (buffer-string))))))
-
-(defun notmuch-get-bodypart-content (msg part process-crypto)
-  (or (plist-get part :content)
-      (notmuch-get-bodypart-internal (notmuch-id-to-query (plist-get msg :id))
-                                    (plist-get part :id) process-crypto)))
+(defun notmuch--get-bodypart-raw (msg part process-crypto binaryp cache)
+  (let* ((plist-elem (if binaryp :content-binary :content))
+        (data (or (plist-get part plist-elem)
+                  (with-temp-buffer
+                    ;; Emacs internally uses a UTF-8-like multibyte string
+                    ;; representation by default (regardless of the coding
+                    ;; system, which only affects how it goes from outside data
+                    ;; to this internal representation).  This *almost* never
+                    ;; matters.  Annoyingly, it does matter if we use this data
+                    ;; in an image descriptor, since Emacs will use its internal
+                    ;; data buffer directly and this multibyte representation
+                    ;; corrupts binary image formats.  Since the caller is
+                    ;; asking for binary data, a unibyte string is a more
+                    ;; appropriate representation anyway.
+                    (when binaryp
+                      (set-buffer-multibyte nil))
+                    (let ((args `("show" "--format=raw"
+                                  ,(format "--part=%s" (plist-get part :id))
+                                  ,@(and process-crypto '("--decrypt=true"))
+                                  ,(notmuch-id-to-query (plist-get msg :id))))
+                          (coding-system-for-read
+                           (if binaryp
+                               'no-conversion
+                             (let ((coding-system
+                                    (mm-charset-to-coding-system
+                                     (plist-get part :content-charset))))
+                               ;; Sadly,
+                               ;; `mm-charset-to-coding-system' seems
+                               ;; to return things that are not
+                               ;; considered acceptable values for
+                               ;; `coding-system-for-read'.
+                               (if (coding-system-p coding-system)
+                                   coding-system
+                                 ;; RFC 2047 says that the default
+                                 ;; charset is US-ASCII. RFC6657
+                                 ;; complicates this somewhat.
+                                 'us-ascii)))))
+                      (apply #'call-process
+                             notmuch-command nil '(t nil) nil args)
+                      (buffer-string))))))
+    (when (and cache data)
+      (plist-put part plist-elem data))
+    data))
+
+(defun notmuch-get-bodypart-binary (msg part process-crypto &optional cache)
+  "Return the unprocessed content of PART in MSG as a unibyte string.
+
+This returns the \"raw\" content of the given part after content
+transfer decoding, but with no further processing (see the
+discussion of --format=raw in man notmuch-show).  In particular,
+this does no charset conversion.
+
+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 t cache))
+
+(defun notmuch-get-bodypart-text (msg part process-crypto &optional cache)
+  "Return the text content of PART in MSG.
+
+This returns the content of the given part as a multibyte Lisp
+string after performing content transfer decoding and any
+necessary charset decoding.
+
+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
@@ -557,29 +667,34 @@ the given type."
 ;; 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)))
+(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."
   (let ((display-buffer (current-buffer)))
     (with-temp-buffer
-      ;; In case there is :content, the content string is already converted
-      ;; into emacs internal format. `gnus-decoded' is a fake charset,
-      ;; which means no further decoding (to be done by mm- functions).
-      (let* ((charset (if (plist-member part :content)
+      ;; In case we already have :content, use it and tell mm-* that
+      ;; it's already been charset-decoded by using the fake
+      ;; `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
                        (plist-get part :content-charset)))
-            (handle (mm-make-handle (current-buffer) `(,content-type (charset . ,charset)))))
+            (handle (mm-make-handle (current-buffer)
+                                    `(,content-type (charset . ,charset)))))
        ;; If the user wants the part inlined, insert the content and
        ;; test whether we are able to inline it (which includes both
        ;; capability and suitability tests).
        (when (mm-inlined-p handle)
-         (insert (notmuch-get-bodypart-content msg part process-crypto))
+         (if have-content
+             (insert (notmuch-get-bodypart-text msg part process-crypto))
+           (insert (notmuch-get-bodypart-binary msg part process-crypto)))
          (when (mm-inlinable-p handle)
            (set-buffer display-buffer)
            (mm-display-part handle)
@@ -589,8 +704,8 @@ current buffer, if possible."
 ;; have symbols of the form :Header as keys, and the resulting alist will have
 ;; symbols of the form 'Header as keys.
 (defun notmuch-headers-plist-to-alist (plist)
-  (loop for (key value . rest) on plist by #'cddr
-       collect (cons (intern (substring (symbol-name key) 1)) value)))
+  (cl-loop for (key value . rest) on plist by #'cddr
+          collect (cons (intern (substring (symbol-name key) 1)) value)))
 
 (defun notmuch-face-ensure-list-form (face)
   "Return FACE in face list form.
@@ -616,7 +731,6 @@ must be a face name (a symbol or string), a property list of face
 attributes, or a list of these.  If START and/or END are omitted,
 they default to the beginning/end of OBJECT.  For convenience
 when applied to strings, this returns OBJECT."
-
   ;; A face property can have three forms: a face name (a string or
   ;; symbol), a property list, or a list of these two forms.  In the
   ;; list case, the faces will be combined, with the earlier faces
@@ -659,7 +773,6 @@ This logs MSG and EXTRA to the *Notmuch errors* buffer and
 signals MSG as an error.  If EXTRA is non-nil, text referring the
 user to the *Notmuch errors* buffer will be appended to the
 signaled error.  This function does not return."
-
   (with-current-buffer (get-buffer-create "*Notmuch errors*")
     (goto-char (point-max))
     (unless (bobp)
@@ -672,26 +785,27 @@ 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"
+        (concat msg (and extra " (see *Notmuch errors* for more details)"))))
 
-(defun notmuch-check-async-exit-status (proc msg &optional command err-file)
+(defun notmuch-check-async-exit-status (proc msg &optional command err)
   "If PROC exited abnormally, pop up an error buffer and signal an error.
 
 This is a wrapper around `notmuch-check-exit-status' for
 asynchronous process sentinels.  PROC and MSG must be the
-arguments passed to the sentinel.  COMMAND and ERR-FILE, if
-provided, are passed to `notmuch-check-exit-status'.  If COMMAND
-is not provided, it is taken from `process-command'."
+arguments passed to the sentinel.  COMMAND and ERR, if provided,
+are passed to `notmuch-check-exit-status'.  If COMMAND is not
+provided, it is taken from `process-command'."
   (let ((exit-status
-        (case (process-status proc)
+        (cl-case (process-status proc)
           ((exit) (process-exit-status proc))
           ((signal) msg))))
     (when exit-status
-      (notmuch-check-exit-status exit-status (or command (process-command proc))
-                                nil err-file))))
+      (notmuch-check-exit-status exit-status
+                                (or command (process-command proc))
+                                nil err))))
 
-(defun notmuch-check-exit-status (exit-status command &optional output err-file)
+(defun notmuch-check-exit-status (exit-status command &optional output err)
   "If EXIT-STATUS is non-zero, pop up an error buffer and signal an error.
 
 If EXIT-STATUS is non-zero, pop up a notmuch error buffer
@@ -700,10 +814,9 @@ be a number indicating the exit status code of a process or a
 string describing the signal that terminated the process (such as
 returned by `call-process').  COMMAND must be a list giving the
 command and its arguments.  OUTPUT, if provided, is a string
-giving the output of command.  ERR-FILE, if provided, is the name
-of a file containing the error output of command.  OUTPUT and the
-contents of ERR-FILE will be included in the error message."
-
+giving the output of command.  ERR, if provided, is the error
+output of command.  OUTPUT and ERR will be included in the error
+message."
   (cond
    ((eq exit-status 0) t)
    ((eq exit-status 20)
@@ -715,32 +828,31 @@ 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* ((err (when err-file
-                 (with-temp-buffer
-                   (insert-file-contents err-file)
-                   (unless (eobp)
-                     (buffer-string)))))
+    (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: " (mapconcat #'shell-quote-argument command " ") "\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)))))
-       (if err
-           ;; We have an error message straight from the CLI.
-           (notmuch-logged-error
-            (replace-regexp-in-string "[ \n\r\t\f]*\\'" "" err) extra)
-         ;; 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)
-                               extra))
-       ;; `notmuch-logged-error' does not return.
-       ))))
+           (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
+          (replace-regexp-in-string "[ \n\r\t\f]*\\'" "" err) extra)
+       ;; 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)
+                             extra))
+      ;; `notmuch-logged-error' does not return.
+      ))))
 
 (defun notmuch-call-notmuch--helper (destination args)
   "Helper for synchronous notmuch invocation commands.
@@ -748,12 +860,11 @@ You may need to restart Emacs or upgrade your notmuch package."))
 This wraps `call-process'.  DESTINATION has the same meaning as
 for `call-process'.  ARGS is as described for
 `notmuch-call-notmuch-process'."
-
   (let (stdin-string)
     (while (keywordp (car args))
-      (case (car args)
-       (:stdin-string (setq stdin-string (cadr args)
-                            args (cddr args)))
+      (cl-case (car args)
+       (:stdin-string (setq stdin-string (cadr args))
+                      (setq args (cddr args)))
        (otherwise
         (error "Unknown keyword argument: %s" (car args)))))
     (if (null stdin-string)
@@ -786,13 +897,16 @@ notmuch's output as an S-expression and returns the parsed value.
 Like `notmuch-call-notmuch-process', if notmuch exits with a
 non-zero status, this will report its output and signal an
 error."
-
   (with-temp-buffer
     (let ((err-file (make-temp-file "nmerr")))
       (unwind-protect
-         (let ((status (notmuch-call-notmuch--helper (list t err-file) args)))
+         (let ((status (notmuch-call-notmuch--helper (list t err-file) args))
+               (err (with-temp-buffer
+                      (insert-file-contents err-file)
+                      (unless (eobp)
+                        (buffer-string)))))
            (notmuch-check-exit-status status (cons notmuch-command args)
-                                      (buffer-string) err-file)
+                                      (buffer-string) err)
            (goto-char (point-min))
            (read (current-buffer)))
        (delete-file err-file)))))
@@ -810,31 +924,56 @@ 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."
-
-  ;; There is no way (as of Emacs 24.3) 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.
-  (let* ((err-file (make-temp-file "nmerr"))
-        ;; Use a pipe
-        (process-connection-type nil)
-        ;; Find notmuch using Emacs' `exec-path'
-        (command (or (executable-find notmuch-command)
-                     (error "command not found: %s" notmuch-command)))
-        (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 (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))
     (process-put proc 'sub-sentinel sentinel)
     (process-put proc 'real-command (cons notmuch-command args))
     (set-process-sentinel proc #'notmuch-start-notmuch-sentinel)
     proc))
 
 (defun notmuch-start-notmuch-sentinel (proc event)
-  (let ((err-file (process-get proc 'err-file))
-       (sub-sentinel (process-get proc 'sub-sentinel))
-       (real-command (process-get proc 'real-command)))
+  "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)))
+                  (with-current-buffer err-buffer (buffer-string))))
+        (sub-sentinel (process-get proc 'sub-sentinel))
+        (real-command (process-get proc 'real-command)))
     (condition-case err
        (progn
          ;; Invoke the sub-sentinel, if any
@@ -846,34 +985,56 @@ 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-file))
+           (notmuch-check-async-exit-status proc event real-command err))
          ;; If that didn't signal an error, then any error output was
          ;; really warning output.  Show warnings, if any.
          (let ((warnings
-                (with-temp-buffer
-                  (unless (= (second (insert-file-contents err-file)) 0)
-                    (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))))
-    (ignore-errors (delete-file err-file))))
+    (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)
 
+(defun notmuch-interactive-region ()
+  "Return the bounds of the current interactive region.
+
+This returns (BEG END), where BEG and END are the bounds of the
+region if the region is active, or both `point' otherwise."
+  (if (region-active-p)
+      (list (region-beginning) (region-end))
+    (list (point) (point))))
+
+(define-obsolete-function-alias
+  'notmuch-search-interactive-region
+  'notmuch-interactive-region
+  "notmuch 0.29")
+
 (provide 'notmuch-lib)
 
-;; Local Variables:
-;; byte-compile-warnings: (not cl-functions)
-;; End:
+;;; notmuch-lib.el ends here