:type 'boolean
:group 'notmuch-search)
+(defcustom notmuch-poll-script nil
+ "An external script to incorporate new mail into the notmuch database.
+
+This variable controls the action invoked by
+`notmuch-poll-and-refresh-this-buffer' (bound by default to 'G')
+to incorporate new mail into the notmuch database.
+
+If set to nil (the default), new mail is processed by invoking
+\"notmuch new\". Otherwise, this should be set to a string that
+gives the name of an external script that processes new mail. If
+set to the empty string, no command will be run.
+
+The external script could do any of the following depending on
+the user's needs:
+
+1. Invoke a program to transfer mail to the local mail store
+2. Invoke \"notmuch new\" to incorporate the new mail
+3. Invoke one or more \"notmuch tag\" commands to classify the mail
+
+Note that the recommended way of achieving the same is using
+\"notmuch new\" hooks."
+ :type '(choice (const :tag "notmuch new" nil)
+ (const :tag "Disabled" "")
+ (string :tag "Custom script"))
+ :group 'notmuch-external)
+
;;
(defvar notmuch-search-history nil
"Variable to store notmuch searches history.")
-(defcustom notmuch-saved-searches '(("inbox" . "tag:inbox")
- ("unread" . "tag:unread"))
- "A list of saved searches to display."
- :type '(alist :key-type string :value-type string)
- :group 'notmuch-hello)
-
(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)
+(defvar notmuch-common-keymap
+ (let ((map (make-sparse-keymap)))
+ (define-key map "?" 'notmuch-help)
+ (define-key map "q" 'notmuch-kill-this-buffer)
+ (define-key map "s" 'notmuch-search)
+ (define-key map "z" 'notmuch-tree)
+ (define-key map "m" 'notmuch-mua-new-mail)
+ (define-key map "=" 'notmuch-refresh-this-buffer)
+ (define-key map "G" 'notmuch-poll-and-refresh-this-buffer)
+ map)
+ "Keymap shared by all notmuch modes.")
+
;; By default clicking on a button does not select the window
;; containing the button (as opposed to clicking on a widget which
;; does). This means that the button action is then executed in the
(notmuch-check-exit-status status (cons notmuch-command args) output)
output)))
+(defvar notmuch--cli-sane-p nil
+ "Cache whether the CLI seems to be configured sanely.")
+
+(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
+ "config" "get" "user.primary_email")))
+ (setq notmuch--cli-sane-p (= status 0))))
+ notmuch--cli-sane-p)
+
+(defun notmuch-assert-cli-sane ()
+ (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
+on the command line, and then retry your notmuch command")))
+
(defun notmuch-version ()
"Return a string with the notmuch version number."
(let ((long-string
(defun notmuch-config-get (item)
"Return a value from the notmuch configuration."
- ;; Trim off the trailing newline
- (substring (notmuch-command-to-string "config" "get" item) 0 -1))
+ (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))
+ (substring val 0 -1)
+ val)))
(defun notmuch-database-path ()
"Return the database.path value from the notmuch configuration."
(defun notmuch-user-other-email ()
"Return the user.other_email value (as a list) from the notmuch configuration."
- (split-string (notmuch-config-get "user.other_email") "\n"))
+ (split-string (notmuch-config-get "user.other_email") "\n" t))
+
+(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)
+ (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")))
(defun notmuch-kill-this-buffer ()
"Kill the current buffer."
(interactive)
(kill-buffer (current-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))))
+ "")))
+
+(defun notmuch-prefix-key-description (key)
+ "Given a prefix key code, return a human-readable string representation.
+
+This is basically just `format-kbd-macro' but we also convert ESC to M-."
+ (let* ((key-vector (if (vectorp key) key (vector key)))
+ (desc (format-kbd-macro key-vector)))
+ (if (string= desc "ESC")
+ "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
+
+It does not prepend if ACTUAL-KEY is already listed in TAIL."
+ (let ((key-string (concat prefix (format-kbd-macro 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.
+ (unless (assoc key-string tail)
+ (when (and ua-keys (symbolp binding)
+ (get binding 'notmuch-prefix-doc))
+ ;; Documentation for prefixed command
+ (let ((ua-desc (key-description ua-keys)))
+ (push (cons (concat ua-desc " " prefix (format-kbd-macro actual-key))
+ (get binding 'notmuch-prefix-doc))
+ tail)))
+ ;; Documentation for command
+ (push (cons key-string
+ (or (and (symbolp binding) (get binding 'notmuch-doc))
+ (notmuch-documentation-first-line binding)))
+ tail)))
+ tail)
+
+(defun notmuch-describe-remaps (remap-keymap ua-keys base-keymap prefix tail)
+ ;; Remappings are represented as a binding whose first "event" is
+ ;; 'remap. Hence, if the keymap has any remappings, it will have a
+ ;; 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)
+ tail)
+
+(defun notmuch-describe-keymap (keymap ua-keys base-keymap &optional prefix tail)
+ "Return a list of cons cells, each describing one binding in KEYMAP.
+
+Each cons cell consists of a string giving a human-readable
+description of the key, and a one-line description of the bound
+function. See `notmuch-help' for an overview of how this
+documentation is extracted.
+
+UA-KEYS should be a key sequence bound to `universal-argument'.
+It will be used to describe bindings of commands that support a
+prefix argument. PREFIX and TAIL are used internally."
+ (map-keymap
+ (lambda (key binding)
+ (cond ((mouse-event-p key) nil)
+ ((keymapp binding)
+ (setq tail
+ (if (eq key 'remap)
+ (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
+ (setq tail (notmuch-describe-key (vector key) binding prefix ua-keys tail)))))
+ keymap)
+ tail)
+
+(defun notmuch-substitute-command-keys (doc)
+ "Like `substitute-command-keys' but with documentation, not function names."
+ (let ((beg 0))
+ (while (string-match "\\\\{\\([^}[:space:]]*\\)}" doc beg)
+ (let ((desc
+ (save-match-data
+ (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)))
+ (mapconcat #'identity desc-list "\n")))))
+ (setq doc (replace-match desc 1 1 doc)))
+ (setq beg (match-end 0)))
+ doc))
+
+(defun notmuch-help ()
+ "Display help for the current notmuch mode.
+
+This is similar to `describe-function' for the current major
+mode, but bindings tables are shown with documentation strings
+rather than command names. By default, this uses the first line
+of each command's documentation string. A command can override
+this by setting the 'notmuch-doc property of its command symbol.
+A command that supports a prefix argument can explicitly document
+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)))))
+ (with-current-buffer (generate-new-buffer "*notmuch-help*")
+ (insert doc)
+ (goto-char (point-min))
+ (set-buffer-modified-p nil)
+ (view-buffer (current-buffer) 'kill-buffer-if-not-modified))))
+
+(defun notmuch-subkeymap-help ()
+ "Show help for a subkeymap."
+ (interactive)
+ (let* ((key (this-command-keys-vector))
+ (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 (mapconcat #'identity desc-list "\n")))
+ (with-help-window (help-buffer)
+ (with-current-buffer standard-output
+ (insert "\nPress 'q' to quit this window.\n\n")
+ (insert desc)))
+ (pop-to-buffer (help-buffer)))))
+
+(defvar 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."
+ (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))))
+
+(defun notmuch-poll-and-refresh-this-buffer ()
+ "Invoke `notmuch-poll' to import mail, then refresh the current buffer."
+ (interactive)
+ (notmuch-poll)
+ (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.
"[No Subject]"
subject)))
+(defun notmuch-sanitize (str)
+ "Sanitize control character in STR.
+
+This includes newlines, tabs, and other funny characters."
+ (replace-regexp-in-string "[[:cntrl:]\x7f\u2028\u2029]+" " " str))
+
(defun notmuch-escape-boolean-term (term)
"Escape a boolean term for use in a query.
(save-match-data
(if (or (equal term "")
- (string-match "[ ()]\\|^\"" term))
+ ;; To be pessimistic, only pass through terms composed
+ ;; entirely of ASCII printing characters other than ", (,
+ ;; and ).
+ (string-match "[^!#-'*-~]" term))
;; Requires escaping
(concat "\"" (replace-regexp-in-string "\"" "\"\"" term t t) "\"")
term)))
"Return a query that matches the message with id ID."
(concat "id:" (notmuch-escape-boolean-term id)))
+(defun notmuch-hex-encode (str)
+ "Hex-encode STR (e.g., as used by batch tagging).
+
+This replaces spaces, percents, and double quotes in STR with
+%NN where NN is the hexadecimal value of the character."
+ (replace-regexp-in-string
+ "[ %\"]" (lambda (match) (format "%%%02x" (aref match 0))) str))
+
;;
(defun notmuch-common-do-stash (text)
(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-disable-advice 'mm-shr 'before 'load-gnus-arts)
+ (ad-activate 'mm-shr)))
(defun notmuch-mm-display-part-inline (msg part nth content-type process-crypto)
"Use the mm-decode/mm-view functions to display a part in the
face
(list face)))
-(defun notmuch-combine-face-text-property (start end face &optional below object)
- "Combine FACE into the 'face text property between START and END.
+(defun notmuch-apply-face (object face &optional below start 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 (which defaults to the current buffer).
-Attributes specified by FACE take precedence over existing
-attributes unless BELOW is non-nil. FACE must be a face name (a
-symbol or string), a property list of face attributes, or a list
-of these. For convenience when applied to strings, this returns
-OBJECT."
+and END in OBJECT. Attributes specified by FACE take precedence
+over existing attributes unless BELOW is non-nil.
+
+OBJECT may be a string, a buffer, or nil (which means the current
+buffer). If object is a string, START and END are 0-based;
+otherwise they are buffer positions (integers or markers). FACE
+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
;; taking precedent. Here we canonicalize everything to list form
;; to make it easy to combine.
- (let ((pos start)
+ (let ((pos (cond (start start)
+ ((stringp object) 0)
+ (t 1)))
+ (end (cond (end end)
+ ((stringp object) (length object))
+ (t (1+ (buffer-size object)))))
(face-list (notmuch-face-ensure-list-form face)))
(while (< pos end)
(let* ((cur (get-text-property pos 'face object))
(setq pos next))))
object)
-(defun notmuch-combine-face-text-property-string (string face &optional below)
- (notmuch-combine-face-text-property
- 0
- (length string)
- face
- below
- string))
-
(defun notmuch-map-text-property (start end prop func &optional object)
"Transform text property PROP using FUNC.
;; `notmuch-logged-error' does not return.
))))
+(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)))
+ (otherwise
+ (error "Unknown keyword argument: %s" (car args)))))
+ (if (null stdin-string)
+ (apply #'call-process notmuch-command nil destination nil args)
+ (insert stdin-string)
+ (apply #'call-process-region (point-min) (point-max)
+ notmuch-command t destination nil args))))
+
+(defun notmuch-call-notmuch-process (&rest args)
+ "Synchronously invoke `notmuch-command' with ARGS.
+
+The caller may provide keyword arguments before ARGS. Currently
+supported keyword arguments are:
+
+ :stdin-string STRING - Write STRING to stdin
+
+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."
+ (with-temp-buffer
+ (let ((status (notmuch-call-notmuch--helper t args)))
+ (notmuch-check-exit-status status (cons notmuch-command args)
+ (buffer-string)))))
+
(defun notmuch-call-notmuch-sexp (&rest args)
"Invoke `notmuch-command' with ARGS and return the parsed S-exp output.
-If notmuch exits with a non-zero status, this will pop up a
-buffer containing notmuch's output and signal an error."
+This is equivalent to `notmuch-call-notmuch-process', but parses
+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 (apply #'call-process
- notmuch-command nil (list t err-file) nil args)))
+ (let ((status (notmuch-call-notmuch--helper (list t err-file) args)))
(notmuch-check-exit-status status (cons notmuch-command args)
(buffer-string) err-file)
(goto-char (point-min))
(defvar notmuch-show-process-crypto nil)
(make-variable-buffer-local 'notmuch-show-process-crypto)
-
(provide 'notmuch-lib)
;; Local Variables: