-;;; 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)
(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)
+;;; Groups
(defgroup notmuch nil
"Notmuch mail reader for Emacs."
(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."
"Graphical attributes for displaying text"
:group 'notmuch)
+;;; Options
+
(defcustom notmuch-command "notmuch"
"Name of the notmuch binary.
search."
:type 'boolean
:group 'notmuch-search)
+(make-variable-buffer-local 'notmuch-search-oldest-first)
(defcustom notmuch-poll-script nil
"[Deprecated] Command to run to incorporate new mail into the notmuch database.
(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.
: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)
(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)
+ (define-key map [remap undo] 'notmuch-tag-undo)
map)
"Keymap shared by all notmuch modes.")
(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.
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)))
+ (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)))
(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)
(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-cli-version ()
(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)))
(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.
(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.
"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
+ "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.
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)
+ (let ((doc (documentation binding)))
+ (and doc
+ (string-match "\\`.+" doc)
+ (match-string 0 doc))))))
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
;; 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)
(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)
(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)))
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))
"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)))
-
+ (cl-incf 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
(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."
(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))
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
(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
(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
- '(
- ;; Avoid HTML parts.
+ "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 usually contain a text/html part and some
+ ;; associated graphics.
+ "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-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))
(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
- (let ((coding-system (mm-charset-to-coding-system
- (plist-get part :content-charset))))
+ (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
;; charset is US-ASCII. RFC6657
;; complicates this somewhat.
'us-ascii)))))
- (apply #'call-process notmuch-command nil '(t nil) nil args)
+ (apply #'notmuch--call-process
+ notmuch-command nil '(t nil) nil args)
(buffer-string))))))
(when (and cache data)
(plist-put part plist-elem data))
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."
;; `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)))))
+ (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-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.
(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.
(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
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
(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.
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)
(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.
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))
+ (notmuch-check-exit-status exit-status
+ (or command (process-command proc))
nil err))))
(defun notmuch-check-exit-status (exit-status command &optional output err)
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)
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)))))
- (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.
- ))))
+ (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
+ (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"
+ 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.
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)
- (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)
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
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
- ;; 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))
- (process-put proc 'err-buffer err-buffer)
- ;; Silence "Process NAME stderr finished" in stderr by adding a
- ;; no-op sentinel to the fake stderr process object
- (set-process-sentinel (get-buffer-process err-buffer) #'ignore))
-
- ;; 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
;; 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-buffer (kill-buffer err-buffer))
- (when err-file (ignore-errors (delete-file err-file)))))
-
-;; 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.
+
+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")
+
+(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)
-;; Local Variables:
-;; byte-compile-warnings: (not cl-functions)
-;; End:
-
;;; notmuch-lib.el ends here