]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch-lib.el
emacs: Add new option notmuch-search-hide-excluded
[notmuch] / emacs / notmuch-lib.el
index 21fa25824373ced9c69b0a81953dcc8790f5a0b1..bf9c4a534a24f5d5c088857ce420600604259326 100644 (file)
@@ -1,4 +1,4 @@
-;;; notmuch-lib.el --- common variables, functions and function declarations
+;;; notmuch-lib.el --- common variables, functions and function declarations  -*- lexical-binding: t -*-
 ;;
 ;; Copyright © Carl Worth
 ;;
@@ -22,6 +22,8 @@
 ;;; Code:
 
 (require 'cl-lib)
+(require 'pcase)
+(require 'subr-x)
 
 (require 'mm-util)
 (require 'mm-view)
@@ -33,6 +35,8 @@
   (defconst notmuch-emacs-version "unknown"
     "Placeholder variable when notmuch-version.el[c] is not available."))
 
+;;; Groups
+
 (defgroup notmuch nil
   "Notmuch mail reader for Emacs."
   :group 'mail)
@@ -78,6 +82,8 @@
   "Graphical attributes for displaying text"
   :group 'notmuch)
 
+;;; Options
+
 (defcustom notmuch-command "notmuch"
   "Name of the notmuch binary.
 
@@ -97,6 +103,17 @@ search results. Note that any filtered searches created by
 search."
   :type 'boolean
   :group 'notmuch-search)
+(make-variable-buffer-local 'notmuch-search-oldest-first)
+
+(defcustom notmuch-search-hide-excluded t
+  "Hide mail tagged with a excluded tag.
+
+Excluded tags are defined in the users configuration file under
+the search section. When this variable is true, any mail with
+such a tag will not be shown in the search output."
+  :type 'boolean
+  :group 'notmuch-search)
+(make-variable-buffer-local 'notmuch-search-hide-excluded)
 
 (defcustom notmuch-poll-script nil
   "[Deprecated] Command to run to incorporate new mail into the notmuch database.
@@ -125,11 +142,6 @@ the user's needs:
                 (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.
 
@@ -144,6 +156,11 @@ For example, if you wanted to remove an \"inbox\" tag and add an
   :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)
@@ -159,6 +176,7 @@ For example, if you wanted to remove an \"inbox\" tag and add an
     (define-key map (kbd "M-=") 'notmuch-refresh-all-buffers)
     (define-key map "G" 'notmuch-poll-and-refresh-this-buffer)
     (define-key map "j" 'notmuch-jump-search)
+    (define-key map [remap undo] 'notmuch-tag-undo)
     map)
   "Keymap shared by all notmuch modes.")
 
@@ -177,6 +195,8 @@ For example, if you wanted to remove an \"inbox\" tag and add an
                  (select-window (posn-window (event-start last-input-event)))
                  (button-activate button)))
 
+;;; CLI Utilities
+
 (defun notmuch-command-to-string (&rest args)
   "Synchronously invoke \"notmuch\" with the given list of arguments.
 
@@ -186,8 +206,8 @@ will be signaled.
 
 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)))
 
@@ -197,7 +217,7 @@ Otherwise the output will be returned."
 (defun notmuch-cli-sane-p ()
   "Return t if the cli seems to be configured sanely."
   (unless notmuch--cli-sane-p
-    (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)
@@ -234,13 +254,16 @@ displays both values separately."
               (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)))
 
@@ -263,6 +286,8 @@ displays both values separately."
 (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.
 
@@ -271,8 +296,8 @@ 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"))
   (message "Polling mail...done"))
@@ -287,6 +312,8 @@ it, in which case it is killed."
       (bury-buffer)
     (kill-buffer)))
 
+;;; Describe Key Bindings
+
 (defun notmuch-prefix-key-description (key)
   "Given a prefix key code, return a human-readable string representation.
 
@@ -297,7 +324,6 @@ This is basically just `format-kbd-macro' but we also convert ESC to M-."
        "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.
 
@@ -398,14 +424,14 @@ This is similar to `describe-function' for the current major
 mode, but bindings tables are shown with documentation strings
 rather than command names.  By default, this uses the first line
 of each command's documentation string.  A command can override
-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))
@@ -435,6 +461,8 @@ of its command symbol."
          (insert desc)))
       (pop-to-buffer (help-buffer)))))
 
+;;; Refreshing Buffers
+
 (defvar-local notmuch-buffer-refresh-function nil
   "Function to call to refresh the current buffer.")
 
@@ -466,9 +494,11 @@ be displayed."
        (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))
@@ -509,8 +539,6 @@ This replaces spaces, percents, and double quotes in STR with
   (replace-regexp-in-string
    "[ %\"]" (lambda (match) (format "%%%02x" (aref match 0))) str))
 
-;;
-
 (defun notmuch-common-do-stash (text)
   "Common function to stash text in kill ring, and display in minibuffer."
   (if text
@@ -522,33 +550,47 @@ This replaces spaces, percents, and double quotes in STR with
     (kill-new "")
     (message "Nothing to stash!")))
 
-;;
+;;; 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)))
+  (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 (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)))))
-
-(defvar notmuch-multipart/alternative-discouraged
+  "Return t if t1 and t2 are matching content types.
+Take wildcards into account."
+  (and (stringp t1)
+       (stringp t2)
+       (let ((st1 (split-string t1 "/"))
+            (st2 (split-string t2 "/")))
+        (if (or (string= (cadr st1) "*")
+                (string= (cadr st2) "*"))
+            ;; Comparison of content types should be case insensitive.
+            (string= (downcase (car st1))
+                     (downcase (car st2)))
+          (string= (downcase t1)
+                   (downcase t2))))))
+
+(defcustom notmuch-multipart/alternative-discouraged
   '(;; Avoid HTML parts.
     "text/html"
     ;; multipart/related usually contain a text/html part and some
     ;; associated graphics.
-    "multipart/related"))
+    "multipart/related")
+  "Which mime types to hide by default for multipart messages.
+
+Can either be a list of mime types (as strings) or a function
+mapping a plist representing the current message to such a list.
+See Info node `(notmuch-emacs) notmuch-show' for a sample function."
+  :group 'notmuch-show
+  :type '(radio (repeat :tag "MIME Types" string)
+               (function :tag "Function")))
 
 (defun notmuch-multipart/alternative-determine-discouraged (msg)
   "Return the discouraged alternatives for the specified message."
@@ -616,7 +658,7 @@ the given type."
                                  ;; charset is US-ASCII. RFC6657
                                  ;; complicates this somewhat.
                                  'us-ascii)))))
-                      (apply #'call-process
+                      (apply #'notmuch--call-process
                              notmuch-command nil '(t nil) nil args)
                       (buffer-string))))))
     (when (and cache data)
@@ -671,8 +713,11 @@ current buffer, if possible."
          (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.
@@ -691,7 +736,7 @@ single element face list."
     (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
@@ -739,6 +784,8 @@ returned by FUNC."
       (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.
 
@@ -800,20 +847,27 @@ You may need to restart Emacs or upgrade your notmuch Emacs package."))
 Emacs requested a newer output format than supported by the notmuch CLI.
 You may need to restart Emacs or upgrade your notmuch package."))
    (t
-    (let* ((command-string
-           (mapconcat (lambda (arg)
-                        (shell-quote-argument
-                         (cond ((stringp arg) arg)
-                               ((symbolp arg) (symbol-name arg))
-                               (t "*UNKNOWN ARGUMENT*"))))
-                      command " "))
-          (extra
-           (concat "command: " command-string "\n"
-                   (if (integerp exit-status)
-                       (format "exit status: %s\n" exit-status)
-                     (format "exit signal: %s\n" exit-status))
-                   (and err    (concat "stderr:\n" err))
-                   (and output (concat "stdout:\n" output)))))
+    (pcase-let*
+       ((`(,command . ,args) command)
+        (command (if (equal (file-name-nondirectory command)
+                            notmuch-command)
+                     notmuch-command
+                   command))
+        (command-string
+         (mapconcat (lambda (arg)
+                      (shell-quote-argument
+                       (cond ((stringp arg) arg)
+                             ((symbolp arg) (symbol-name arg))
+                             (t "*UNKNOWN ARGUMENT*"))))
+                    (cons command args)
+                    " "))
+        (extra
+         (concat "command: " command-string "\n"
+                 (if (integerp exit-status)
+                     (format "exit status: %s\n" exit-status)
+                   (format "exit signal: %s\n" exit-status))
+                 (and err    (concat "stderr:\n" err))
+                 (and output (concat "stdout:\n" output)))))
       (if err
          ;; We have an error message straight from the CLI.
          (notmuch-logged-error
@@ -821,11 +875,37 @@ You may need to restart Emacs or upgrade your notmuch package."))
        ;; We only have combined output from the CLI; don't inundate
        ;; the user with it.  Mimic `process-lines'.
        (notmuch-logged-error (format "%s exited with status %s"
-                                     (car command) exit-status)
+                                     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.
 
@@ -840,9 +920,9 @@ for `call-process'.  ARGS is as described for
        (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)
@@ -899,28 +979,26 @@ status."
   (let* ((command (or (executable-find notmuch-command)
                      (error "Command not found: %s" notmuch-command)))
         (err-buffer (generate-new-buffer " *notmuch-stderr*"))
-        (proc (make-process
+        (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 err-proc 'err-buffer err-buffer)
-    (set-process-sentinel err-proc #'notmuch-start-notmuch-error-sentinel)
     (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-buffer (process-get proc 'err-buffer))
-        (err (and (not (zerop (buffer-size 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))
-        (real-command (process-get proc 'real-command)))
+        (sub-sentinel (process-get proc 'sub-sentinel)))
     (condition-case err
        (progn
          ;; Invoke the sub-sentinel, if any
@@ -932,7 +1010,7 @@ status."
          ;; and there's no point in telling the user that (but we
          ;; still check for and report stderr output below).
          (when (buffer-live-p (process-buffer proc))
-           (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
@@ -954,13 +1032,30 @@ status."
        ;; so turn errors into messages.
        (message "%s" (error-message-string err))))))
 
-(defun notmuch-start-notmuch-error-sentinel (proc event)
-  (let ((buffer (process-get proc 'err-buffer)))
-    (when (buffer-live-p buffer)
-      (kill-buffer buffer))))
+(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.
 
@@ -975,6 +1070,16 @@ region if the region is active, or both `point' otherwise."
   'notmuch-interactive-region
   "notmuch 0.29")
 
+(defun notmuch--inline-override-types ()
+  "Override mm-inline-override-types to stop application/*
+parts from being displayed unless the user has customized
+it themselves."
+  (if (equal mm-inline-override-types
+            (eval (car (get 'mm-inline-override-types 'standard-value))))
+      (cons "application/.*" mm-inline-override-types)
+    mm-inline-override-types))
+;;; _
+
 (provide 'notmuch-lib)
 
 ;;; notmuch-lib.el ends here