]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-lib.el
emacs: renamed function notmuch-version to notmuch-cli-version
[notmuch] / emacs / notmuch-lib.el
index 49fe64457b3a03e34241fb6741ebf82f19d9c7cd..e16a1b971bd5e29bb3b78ba9f3a823c0b39acb85 100644 (file)
@@ -25,8 +25,8 @@
 (require 'mm-decode)
 (require 'cl)
 
-(defvar notmuch-command "notmuch"
-  "Command to run the notmuch binary.")
+(autoload 'notmuch-jump-search "notmuch-jump"
+  "Jump to a saved search by shortcut key." t)
 
 (defgroup notmuch nil
   "Notmuch mail reader for Emacs."
   "Graphical attributes for displaying text"
   :group 'notmuch)
 
+(defcustom notmuch-command "notmuch"
+  "Name of the notmuch binary.
+
+This can be a relative or absolute path to the notmuch binary.
+If this is a relative path, it will be searched for in all of the
+directories given in `exec-path' (which is, by default, based on
+$PATH)."
+  :type 'string
+  :group 'notmuch-external)
+
 (defcustom notmuch-search-oldest-first t
   "Show the oldest mail first when searching.
 
@@ -77,7 +87,11 @@ search."
   :group 'notmuch-search)
 
 (defcustom notmuch-poll-script nil
-  "An external script to incorporate new mail into the notmuch database.
+  "[Deprecated] Command to run to incorporate new mail into the notmuch database.
+
+This option has been deprecated in favor of \"notmuch new\"
+hooks (see man notmuch-hooks).  To change the path to the notmuch
+binary, customize `notmuch-command'.
 
 This variable controls the action invoked by
 `notmuch-poll-and-refresh-this-buffer' (bound by default to 'G')
@@ -93,10 +107,7 @@ 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."
+3. Invoke one or more \"notmuch tag\" commands to classify the mail"
   :type '(choice (const :tag "notmuch new" nil)
                 (const :tag "Disabled" "")
                 (string :tag "Custom script"))
@@ -107,12 +118,6 @@ Note that the recommended way of achieving the same is using
 (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.
 
@@ -130,12 +135,13 @@ For example, if you wanted to remove an \"inbox\" tag and add an
 (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 "q" 'notmuch-bury-or-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)
+    (define-key map "j" 'notmuch-jump-search)
     map)
   "Keymap shared by all notmuch modes.")
 
@@ -168,8 +174,26 @@ Otherwise the output will be returned"
       (notmuch-check-exit-status status (cons notmuch-command args) output)
       output)))
 
-(defun notmuch-version ()
-  "Return a string with the notmuch version number."
+(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-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)))
@@ -180,8 +204,13 @@ Otherwise the output will be returned"
 
 (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."
@@ -197,7 +226,7 @@ Otherwise the output will be returned"
 
 (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.
@@ -210,10 +239,15 @@ depending on the value of `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."
+(defun notmuch-bury-or-kill-this-buffer ()
+  "Undisplay the current buffer.
+
+Bury the current buffer, unless there is only one window showing
+it, in which case it is killed."
   (interactive)
-  (kill-buffer (current-buffer)))
+  (if (> (length (get-buffer-window-list nil nil t)) 1)
+      (bury-buffer)
+    (kill-buffer)))
 
 (defun notmuch-documentation-first-line (symbol)
   "Return the first line of the documentation string for SYMBOL."
@@ -231,7 +265,8 @@ depending on the value of `notmuch-poll-script'."
   "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 ((desc (format-kbd-macro (vector key))))
+  (let* ((key-vector (if (vectorp key) key (vector key)))
+        (desc (format-kbd-macro key-vector)))
     (if (string= desc "ESC")
        "M-"
       (concat desc " "))))
@@ -337,6 +372,28 @@ of its command symbol."
       (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)
@@ -380,7 +437,10 @@ user-friendly queries."
 
   (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)))
@@ -421,6 +481,15 @@ This replaces spaces, percents, and double quotes in STR with
       (setq list (cdr list)))
     (nreverse out)))
 
+(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 "/"))
@@ -460,24 +529,67 @@ the given type."
    (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 nth process-crypto)
-  (or (plist-get part :content)
-      (notmuch-get-bodypart-internal (notmuch-id-to-query (plist-get msg :id)) nth process-crypto)))
+(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)."
+  (let ((data (plist-get part :binary-content)))
+    (when (not data)
+      (let ((args `("show" "--format=raw"
+                   ,(format "--part=%d" (plist-get part :id))
+                   ,@(when process-crypto '("--decrypt"))
+                   ,(notmuch-id-to-query (plist-get msg :id)))))
+       (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.
+         (set-buffer-multibyte nil)
+         (let ((coding-system-for-read 'no-conversion))
+           (apply #'call-process notmuch-command nil '(t nil) nil args)
+           (setq data (buffer-string)))))
+      (when cache
+       ;; Cheat.  part is non-nil, and `plist-put' always modifies
+       ;; the list in place if it's non-nil.
+       (plist-put part :binary-content data)))
+    data))
+
+(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.  It is an error to use this for
+non-text/* parts.
+
+If CACHE is non-nil, the content of this part will be saved in
+MSG (if it isn't already)."
+  (let ((content (plist-get part :content)))
+    (when (not content)
+      ;; Use show --format=sexp to fetch decoded content
+      (let* ((args `("show" "--format=sexp" "--include-html"
+                    ,(format "--part=%s" (plist-get part :id))
+                    ,@(when process-crypto '("--decrypt"))
+                    ,(notmuch-id-to-query (plist-get msg :id))))
+            (npart (apply #'notmuch-call-notmuch-sexp args)))
+       (setq content (plist-get npart :content))
+       (when (not content)
+         (error "Internal error: No :content from %S" args)))
+      (when cache
+       (plist-put part :content content)))
+    content))
 
 ;; 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
@@ -490,25 +602,29 @@ the given type."
 (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)
+(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)
-                         'gnus-decoded
+      ;; 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)))))
        ;; 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 nth 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)
@@ -531,23 +647,32 @@ single element face list."
       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))
@@ -560,14 +685,6 @@ 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.