]> git.notmuchmail.org Git - notmuch/blobdiff - emacs/notmuch.el
emacs: restore autoload cookie for notmuch-search
[notmuch] / emacs / notmuch.el
index e098bd7bb1b98a8a3d700b2ce8ed69c7452707da..46f14fea1dd0ff112a57e64154cc5e5eeb19ab37 100644 (file)
@@ -1,4 +1,4 @@
-;; notmuch.el --- run notmuch within emacs
+;;; notmuch.el --- run notmuch within emacs
 ;;
 ;; 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>
+;; Homepage: https://notmuchmail.org/
+
+;;; Commentary:
 
 ;; This is an emacs-based interface to the notmuch mail system.
 ;;
 ;; You will first need to have the notmuch program installed and have a
 ;; notmuch database built in order to use this. See
-;; http://notmuchmail.org for details.
+;; https://notmuchmail.org for details.
 ;;
 ;; To install this software, copy it to a directory that is on the
 ;; `load-path' variable within emacs (a good candidate is
@@ -36,7 +39,7 @@
 ;;
 ;; Then, to actually run it, add:
 ;;
-;;     (require 'notmuch)
+;;     (autoload 'notmuch "notmuch" "Notmuch mail" t)
 ;;
 ;; to your ~/.emacs file, and then run "M-x notmuch" from within emacs,
 ;; or run:
@@ -45,7 +48,9 @@
 ;;
 ;; Have fun, and let us know if you have any comment, questions, or
 ;; kudos: Notmuch list <notmuch@notmuchmail.org> (subscription is not
-;; required, but is available from http://notmuchmail.org).
+;; required, but is available from https://notmuchmail.org).
+
+;;; Code:
 
 (eval-when-compile (require 'cl))
 (require 'mm-view)
@@ -54,6 +59,7 @@
 (require 'notmuch-lib)
 (require 'notmuch-tag)
 (require 'notmuch-show)
+(require 'notmuch-tree)
 (require 'notmuch-mua)
 (require 'notmuch-hello)
 (require 'notmuch-maildir-fcc)
@@ -80,6 +86,18 @@ To enter a line break in customize, press \\[quoted-insert] C-j."
   :type '(alist :key-type (string) :value-type (string))
   :group 'notmuch-search)
 
+;; The name of this variable `notmuch-init-file' is consistent with the
+;; convention used in e.g. emacs and gnus. The value, `notmuch-config[.el[c]]'
+;; is consistent with notmuch cli configuration file `~/.notmuch-config'.
+(defcustom notmuch-init-file (locate-user-emacs-file "notmuch-config")
+  "Your Notmuch Emacs-Lisp configuration file name.
+If a file with one of the suffixes defined by `get-load-suffixes' exists,
+it will be read instead.
+This file is read once when notmuch is loaded; the notmuch hooks added
+there will be called at other points of notmuch execution."
+  :type 'file
+  :group 'notmuch)
+
 (defvar notmuch-query-history nil
   "Variable to store minibuffer history for notmuch queries")
 
@@ -119,69 +137,6 @@ To enter a line break in customize, press \\[quoted-insert] C-j."
             (mm-save-part p))))
    mm-handle))
 
-(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 ((desc (format-kbd-macro (vector key))))
-    (if (string= desc "ESC")
-       "M-"
-      (concat desc " "))))
-
-(defun notmuch-describe-keymap (keymap &optional prefix tail)
-  "Return a list of strings, each describing one key in KEYMAP.
-
-Each string gives a human-readable description of the key and the
-first line of documentation for the bound function."
-  (map-keymap
-   (lambda (key binding)
-     (cond ((mouse-event-p key) nil)
-          ((keymapp binding)
-           (setq tail
-                 (notmuch-describe-keymap
-                  binding (notmuch-prefix-key-description key) tail)))
-          (t
-           (push (concat prefix (format-kbd-macro (vector key)) "\t"
-                         (notmuch-documentation-first-line binding))
-                 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* ((keymap-name (substring doc (match-beginning 1) (match-end 1)))
-            (keymap (symbol-value (intern keymap-name)))
-            (desc-list (notmuch-describe-keymap keymap))
-            (desc (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."
-  (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))))
-
 (require 'hl-line)
 
 (defun notmuch-hl-line-mode ()
@@ -199,9 +154,7 @@ first line of documentation for the bound function."
 (defvar notmuch-search-mode-map
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map notmuch-common-keymap)
-    (define-key map "?" 'notmuch-help)
-    (define-key map "q" 'notmuch-kill-this-buffer)
-    (define-key map "x" 'notmuch-kill-this-buffer)
+    (define-key map "x" 'notmuch-bury-or-kill-this-buffer)
     (define-key map (kbd "<DEL>") 'notmuch-search-scroll-down)
     (define-key map "b" 'notmuch-search-scroll-down)
     (define-key map " " 'notmuch-search-scroll-up)
@@ -211,20 +164,17 @@ first line of documentation for the bound function."
     (define-key map "n" 'notmuch-search-next-thread)
     (define-key map "r" 'notmuch-search-reply-to-thread-sender)
     (define-key map "R" 'notmuch-search-reply-to-thread)
-    (define-key map "m" 'notmuch-mua-new-mail)
-    (define-key map "s" 'notmuch-search)
     (define-key map "o" 'notmuch-search-toggle-order)
     (define-key map "c" 'notmuch-search-stash-map)
-    (define-key map "=" 'notmuch-refresh-this-buffer)
-    (define-key map "G" 'notmuch-poll-and-refresh-this-buffer)
     (define-key map "t" 'notmuch-search-filter-by-tag)
-    (define-key map "f" 'notmuch-search-filter)
+    (define-key map "l" 'notmuch-search-filter)
     (define-key map [mouse-1] 'notmuch-search-show-thread)
     (define-key map "*" 'notmuch-search-tag-all)
     (define-key map "a" 'notmuch-search-archive-thread)
     (define-key map "-" 'notmuch-search-remove-tag)
     (define-key map "+" 'notmuch-search-add-tag)
     (define-key map (kbd "RET") 'notmuch-search-show-thread)
+    (define-key map "Z" 'notmuch-tree-from-search-current-query)
     map)
   "Keymap for \"notmuch search\" buffers.")
 (fset 'notmuch-search-mode-map notmuch-search-mode-map)
@@ -232,6 +182,8 @@ first line of documentation for the bound function."
 (defvar notmuch-search-stash-map
   (let ((map (make-sparse-keymap)))
     (define-key map "i" 'notmuch-search-stash-thread-id)
+    (define-key map "q" 'notmuch-stash-query)
+    (define-key map "?" 'notmuch-subkeymap-help)
     map)
   "Submap for stash commands")
 (fset 'notmuch-search-stash-map notmuch-search-stash-map)
@@ -241,6 +193,11 @@ first line of documentation for the bound function."
   (interactive)
   (notmuch-common-do-stash (notmuch-search-find-thread-id)))
 
+(defun notmuch-stash-query ()
+  "Copy current query to kill-ring."
+  (interactive)
+  (notmuch-common-do-stash (notmuch-search-get-query)))
+
 (defvar notmuch-search-query-string)
 (defvar notmuch-search-target-thread)
 (defvar notmuch-search-target-line)
@@ -354,7 +311,27 @@ first line of documentation for the bound function."
   :group 'notmuch-search
   :group 'notmuch-faces)
 
-(defun notmuch-search-mode ()
+(defface notmuch-search-flagged-face
+  '((t
+     (:foreground "blue")))
+  "Face used in search mode face for flagged threads.
+
+This face is the default value for the \"flagged\" tag in
+`notmuch-search-line-faces`."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(defface notmuch-search-unread-face
+  '((t
+     (:weight bold)))
+  "Face used in search mode for unread threads.
+
+This face is the default value for the \"unread\" tag in
+`notmuch-search-line-faces`."
+  :group 'notmuch-search
+  :group 'notmuch-faces)
+
+(define-derived-mode notmuch-search-mode fundamental-mode "notmuch-search"
   "Major mode displaying results of a notmuch search.
 
 This buffer contains the results of a \"notmuch search\" of your
@@ -384,8 +361,6 @@ new, global search.
 Complete list of currently available key bindings:
 
 \\{notmuch-search-mode-map}"
-  (interactive)
-  (kill-all-local-variables)
   (make-local-variable 'notmuch-search-query-string)
   (make-local-variable 'notmuch-search-oldest-first)
   (make-local-variable 'notmuch-search-target-thread)
@@ -393,10 +368,7 @@ Complete list of currently available key bindings:
   (setq notmuch-buffer-refresh-function #'notmuch-search-refresh-view)
   (set (make-local-variable 'scroll-preserve-screen-position) t)
   (add-to-invisibility-spec (cons 'ellipsis t))
-  (use-local-map notmuch-search-mode-map)
   (setq truncate-lines t)
-  (setq major-mode 'notmuch-search-mode
-       mode-name "notmuch-search")
   (setq buffer-read-only t))
 
 (defun notmuch-search-get-result (&optional pos)
@@ -467,14 +439,27 @@ If BARE is set then do not prefix with \"thread:\""
   (let ((thread (plist-get (notmuch-search-get-result) :thread)))
     (when thread (concat (unless bare "thread:") thread))))
 
-(defun notmuch-search-find-thread-id-region (beg end)
-  "Return a list of threads for the current region"
-  (mapcar (lambda (thread) (concat "thread:" thread))
-         (notmuch-search-properties-in-region :thread beg end)))
+(defun notmuch-search-find-stable-query ()
+  "Return the stable queries for the current thread.
 
-(defun notmuch-search-find-thread-id-region-search (beg end)
-  "Return a search string for threads for the current region"
-  (mapconcat 'identity (notmuch-search-find-thread-id-region beg end) " or "))
+This returns a list (MATCHED-QUERY UNMATCHED-QUERY) for the
+matched and unmatched messages in the current thread."
+  (plist-get (notmuch-search-get-result) :query))
+
+(defun notmuch-search-find-stable-query-region (beg end &optional only-matched)
+  "Return the stable query for the current region.
+
+If ONLY-MATCHED is non-nil, include only matched messages.  If it
+is nil, include both matched and unmatched messages. If there are
+no messages in the region then return nil."
+  (let ((query-list nil) (all (not only-matched)))
+    (dolist (queries (notmuch-search-properties-in-region :query beg end))
+      (when (first queries)
+       (push (first queries) query-list))
+      (when (and all (second queries))
+       (push (second queries) query-list)))
+    (when query-list
+      (concat "(" (mapconcat 'identity query-list ") or (") ")"))))
 
 (defun notmuch-search-find-authors ()
   "Return the authors for the current thread"
@@ -492,19 +477,38 @@ If BARE is set then do not prefix with \"thread:\""
   "Return a list of authors for the current region"
   (notmuch-search-properties-in-region :subject beg end))
 
-(defun notmuch-search-show-thread ()
-  "Display the currently selected thread."
-  (interactive)
+(defun notmuch-search-show-thread (&optional elide-toggle)
+  "Display the currently selected thread.
+
+With a prefix argument, invert the default value of
+`notmuch-show-only-matching-messages' when displaying the
+thread."
+  (interactive "P")
   (let ((thread-id (notmuch-search-find-thread-id))
        (subject (notmuch-search-find-subject)))
     (if (> (length thread-id) 0)
        (notmuch-show thread-id
+                     elide-toggle
                      (current-buffer)
                      notmuch-search-query-string
                      ;; Name the buffer based on the subject.
                      (concat "*" (truncate-string-to-width subject 30 nil nil t) "*"))
       (message "End of search results."))))
 
+(defun notmuch-tree-from-search-current-query ()
+  "Call notmuch tree with the current query"
+  (interactive)
+  (notmuch-tree notmuch-search-query-string))
+
+(defun notmuch-tree-from-search-thread ()
+  "Show the selected thread with notmuch-tree"
+  (interactive)
+  (notmuch-tree (notmuch-search-find-thread-id)
+                notmuch-search-query-string
+               nil
+                (notmuch-prettify-subject (notmuch-search-find-subject))
+               t))
+
 (defun notmuch-search-reply-to-thread (&optional prompt-for-sender)
   "Begin composing a reply-all to the entire current thread in a new buffer."
   (interactive "P")
@@ -517,17 +521,6 @@ If BARE is set then do not prefix with \"thread:\""
   (let ((message-id (notmuch-search-find-thread-id)))
     (notmuch-mua-new-reply message-id prompt-for-sender nil)))
 
-(defun notmuch-call-notmuch-process (&rest args)
-  "Synchronously invoke \"notmuch\" with the given list of arguments.
-
-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 (apply #'call-process notmuch-command nil t nil args)))
-      (notmuch-check-exit-status status (cons notmuch-command args)
-                                (buffer-string)))))
-
 (defun notmuch-search-set-tags (tags &optional pos)
   (let ((new-result (plist-put (notmuch-search-get-result pos) :tags tags)))
     (notmuch-search-update-result new-result pos)))
@@ -542,37 +535,65 @@ will be signaled."
        (setq output (append output (notmuch-search-get-tags pos)))))
     output))
 
-(defun notmuch-search-tag-region (beg end &optional tag-changes)
-  "Change tags for threads in the given region."
-  (let ((search-string (notmuch-search-find-thread-id-region-search beg end)))
-    (setq tag-changes (notmuch-tag search-string tag-changes))
+(defun notmuch-search-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))))
+
+(defun notmuch-search-interactive-tag-changes (&optional initial-input)
+  "Prompt for tag changes for the current thread or region.
+
+Returns (TAG-CHANGES REGION-BEGIN REGION-END)."
+  (let* ((region (notmuch-search-interactive-region))
+        (beg (first region)) (end (second region))
+        (prompt (if (= beg end) "Tag thread" "Tag region")))
+    (cons (notmuch-read-tag-changes
+          (notmuch-search-get-tags-region beg end) prompt initial-input)
+         region)))
+
+(defun notmuch-search-tag (tag-changes &optional beg end only-matched)
+  "Change tags for the currently selected thread or region.
+
+See `notmuch-tag' for information on the format of TAG-CHANGES.
+When called interactively, this uses the region if the region is
+active.  When called directly, BEG and END provide the region.
+If these are nil or not provided, this applies to the thread at
+point.
+
+If ONLY-MATCHED is non-nil, only tag matched messages."
+  (interactive (notmuch-search-interactive-tag-changes))
+  (unless (and beg end) (setq beg (point) end (point)))
+  (let ((search-string (notmuch-search-find-stable-query-region
+                       beg end only-matched)))
+    (notmuch-tag search-string tag-changes)
     (notmuch-search-foreach-result beg end
       (lambda (pos)
        (notmuch-search-set-tags
         (notmuch-update-tags (notmuch-search-get-tags pos) tag-changes)
         pos)))))
 
-(defun notmuch-search-tag (&optional tag-changes)
-  "Change tags for the currently selected thread or region.
+(defun notmuch-search-add-tag (tag-changes &optional beg end)
+  "Change tags for the current thread or region (defaulting to add).
 
-See `notmuch-tag' for information on the format of TAG-CHANGES."
-  (interactive)
-  (let* ((beg (if (region-active-p) (region-beginning) (point)))
-        (end (if (region-active-p) (region-end) (point))))
-    (notmuch-search-tag-region beg end tag-changes)))
+Same as `notmuch-search-tag' but sets initial input to '+'."
+  (interactive (notmuch-search-interactive-tag-changes "+"))
+  (notmuch-search-tag tag-changes beg end))
 
-(defun notmuch-search-add-tag ()
-  "Same as `notmuch-search-tag' but sets initial input to '+'."
-  (interactive)
-  (notmuch-search-tag "+"))
+(defun notmuch-search-remove-tag (tag-changes &optional beg end)
+  "Change tags for the current thread or region (defaulting to remove).
 
-(defun notmuch-search-remove-tag ()
-  "Same as `notmuch-search-tag' but sets initial input to '-'."
-  (interactive)
-  (notmuch-search-tag "-"))
+Same as `notmuch-search-tag' but sets initial input to '-'."
+  (interactive (notmuch-search-interactive-tag-changes "-"))
+  (notmuch-search-tag tag-changes beg end))
 
-(defun notmuch-search-archive-thread (&optional unarchive)
-  "Archive the currently selected thread.
+(put 'notmuch-search-archive-thread 'notmuch-prefix-doc
+     "Un-archive the currently selected thread.")
+(defun notmuch-search-archive-thread (&optional unarchive beg end)
+  "Archive the currently selected thread or region.
 
 Archive each message in the currently selected thread by applying
 the tag changes in `notmuch-archive-tags' to each (remove the
@@ -581,11 +602,12 @@ messages will be \"unarchived\" (i.e. the tag changes in
 `notmuch-archive-tags' will be reversed).
 
 This function advances the next thread when finished."
-  (interactive "P")
+  (interactive (cons current-prefix-arg (notmuch-search-interactive-region)))
   (when notmuch-archive-tags
     (notmuch-search-tag
-     (notmuch-tag-change-list notmuch-archive-tags unarchive)))
-  (notmuch-search-next-thread))
+     (notmuch-tag-change-list notmuch-archive-tags unarchive) beg end))
+  (when (eq beg end)
+    (notmuch-search-next-thread)))
 
 (defun notmuch-search-update-result (result &optional pos)
   "Replace the result object of the thread at POS (or point) by
@@ -647,34 +669,47 @@ of the result."
                  (goto-char (point-min))
                  (forward-line (1- notmuch-search-target-line)))))))))
 
-(defcustom notmuch-search-line-faces '(("unread" :weight bold)
-                                      ("flagged" :foreground "blue"))
-  "Tag/face mapping for line highlighting in notmuch-search.
+(define-widget 'notmuch--custom-face-edit 'lazy
+  "Custom face edit with a tag Edit Face"
+  ;; I could not persuage custom-face-edit to respect the :tag
+  ;; property so create a widget specially
+  :tag "Manually specify face"
+  :type 'custom-face-edit)
+
+(defcustom notmuch-search-line-faces
+  '(("unread" . notmuch-search-unread-face)
+    ("flagged" . notmuch-search-flagged-face))
+  "Alist of tags to faces for line highlighting in notmuch-search.
+Each element looks like (TAG . FACE).
+A thread with TAG will have FACE applied.
 
 Here is an example of how to color search results based on tags.
  (the following text would be placed in your ~/.emacs file):
 
- (setq notmuch-search-line-faces '((\"deleted\" . (:foreground \"red\"
-                                                 :background \"blue\"))
-                                   (\"unread\" . (:foreground \"green\"))))
-
-The attributes defined for matching tags are merged, with later
-attributes overriding earlier. A message having both \"deleted\"
-and \"unread\" tags with the above settings would have a green
-foreground and blue background."
-  :type '(alist :key-type (string) :value-type (custom-face-edit))
+ (setq notmuch-search-line-faces '((\"unread\" . (:foreground \"green\"))
+                                   (\"deleted\" . (:foreground \"red\"
+                                                 :background \"blue\"))))
+
+The FACE must be a face name (a symbol or string), a property
+list of face attributes, or a list of these.  The faces for
+matching tags are merged, with earlier attributes overriding
+later. A message having both \"deleted\" and \"unread\" tags with
+the above settings would have a green foreground and blue
+background."
+  :type '(alist :key-type (string)
+               :value-type (radio (face :tag "Face name")
+                                   (notmuch--custom-face-edit)))
   :group 'notmuch-search
   :group 'notmuch-faces)
 
 (defun notmuch-search-color-line (start end line-tag-list)
   "Colorize lines in `notmuch-show' based on tags."
-  (mapc (lambda (elem)
-         (let ((tag (car elem))
-               (attributes (cdr elem)))
-           (when (member tag line-tag-list)
-             (notmuch-combine-face-text-property start end attributes))))
-       ;; Reverse the list so earlier entries take precedence
-       (reverse notmuch-search-line-faces)))
+  ;; Reverse the list so earlier entries take precedence
+  (dolist (elem (reverse notmuch-search-line-faces))
+    (let ((tag (car elem))
+         (face (cdr elem)))
+      (when (member tag line-tag-list)
+       (notmuch-apply-face nil face nil start end)))))
 
 (defun notmuch-search-author-propertize (authors)
   "Split `authors' into matching and non-matching authors and
@@ -767,31 +802,42 @@ non-authors is found, assume that all of the authors match."
                                        (plist-get result :total)))
                        'face 'notmuch-search-count)))
    ((string-equal field "subject")
-    (insert (propertize (format format-string (plist-get result :subject))
+    (insert (propertize (format format-string
+                               (notmuch-sanitize (plist-get result :subject)))
                        'face 'notmuch-search-subject)))
 
    ((string-equal field "authors")
-    (notmuch-search-insert-authors format-string (plist-get result :authors)))
+    (notmuch-search-insert-authors
+     format-string (notmuch-sanitize (plist-get result :authors))))
 
    ((string-equal field "tags")
-    (let ((tags (plist-get result :tags)))
-      (insert (format format-string (notmuch-tag-format-tags tags)))))))
+    (let ((tags (plist-get result :tags))
+         (orig-tags (plist-get result :orig-tags)))
+      (insert (format format-string (notmuch-tag-format-tags tags orig-tags)))))))
 
-(defun notmuch-search-show-result (result &optional pos)
-  "Insert RESULT at POS or the end of the buffer if POS is null."
+(defun notmuch-search-show-result (result pos)
+  "Insert RESULT at POS."
   ;; Ignore excluded matches
   (unless (= (plist-get result :matched) 0)
-    (let ((beg (or pos (point-max))))
-      (save-excursion
-       (goto-char beg)
-       (dolist (spec notmuch-search-result-format)
-         (notmuch-search-insert-field (car spec) (cdr spec) result))
-       (insert "\n")
-       (notmuch-search-color-line beg (point) (plist-get result :tags))
-       (put-text-property beg (point) 'notmuch-search-result result))
-      (when (string= (plist-get result :thread) notmuch-search-target-thread)
-       (setq notmuch-search-target-thread "found")
-       (goto-char beg)))))
+    (save-excursion
+      (goto-char pos)
+      (dolist (spec notmuch-search-result-format)
+       (notmuch-search-insert-field (car spec) (cdr spec) result))
+      (insert "\n")
+      (notmuch-search-color-line pos (point) (plist-get result :tags))
+      (put-text-property pos (point) 'notmuch-search-result result))))
+
+(defun notmuch-search-append-result (result)
+  "Insert RESULT at the end of the buffer.
+
+This is only called when a result is first inserted so it also
+sets the :orig-tag property."
+  (let ((new-result (plist-put result :orig-tags (plist-get result :tags)))
+       (pos (point-max)))
+    (notmuch-search-show-result new-result pos)
+    (when (string= (plist-get result :thread) notmuch-search-target-thread)
+      (setq notmuch-search-target-thread "found")
+      (goto-char pos))))
 
 (defun notmuch-search-process-filter (proc string)
   "Process and filter the output of \"notmuch search\""
@@ -805,15 +851,17 @@ non-authors is found, assume that all of the authors match."
        (save-excursion
          (goto-char (point-max))
          (insert string))
-       (notmuch-sexp-parse-partial-list 'notmuch-search-show-result
+       (notmuch-sexp-parse-partial-list 'notmuch-search-append-result
                                         results-buf)))))
 
-(defun notmuch-search-tag-all (&optional tag-changes)
+(defun notmuch-search-tag-all (tag-changes)
   "Add/remove tags from all messages in current search buffer.
 
 See `notmuch-tag' for information on the format of TAG-CHANGES."
-  (interactive)
-  (apply 'notmuch-tag notmuch-search-query-string tag-changes))
+  (interactive
+   (list (notmuch-read-tag-changes
+         (notmuch-search-get-tags-region (point-min) (point-max)) "Tag all")))
+  (notmuch-search-tag tag-changes (point-min) (point-max) t))
 
 (defun notmuch-search-buffer-title (query)
   "Returns the title for a buffer with notmuch search results."
@@ -821,14 +869,14 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
          (let (longest
                (longest-length 0))
            (loop for tuple in notmuch-saved-searches
-                 if (let ((quoted-query (regexp-quote (cdr tuple))))
+                 if (let ((quoted-query (regexp-quote (notmuch-saved-search-get tuple :query))))
                       (and (string-match (concat "^" quoted-query) query)
                            (> (length (match-string 0 query))
                               longest-length)))
                  do (setq longest tuple))
            longest))
-        (saved-search-name (car saved-search))
-        (saved-search-query (cdr saved-search)))
+        (saved-search-name (notmuch-saved-search-get saved-search :name))
+        (saved-search-query (notmuch-saved-search-get saved-search :query)))
     (cond ((and saved-search (equal saved-search-query query))
           ;; Query is the same as saved search (ignoring case)
           (concat "*notmuch-saved-search-" saved-search-name "*"))
@@ -846,14 +894,20 @@ See `notmuch-tag' for information on the format of TAG-CHANGES."
   "Read a notmuch-query from the minibuffer with completion.
 
 PROMPT is the string to prompt with."
-  (lexical-let
-      ((completions
-       (append (list "folder:" "thread:" "id:" "date:" "from:" "to:"
-                     "subject:" "attachment:")
-               (mapcar (lambda (tag)
-                         (concat "tag:" (notmuch-escape-boolean-term tag)))
-                       (process-lines notmuch-command "search" "--output=tags" "*")))))
+  (lexical-let*
+      ((all-tags
+        (mapcar (lambda (tag) (notmuch-escape-boolean-term tag))
+                (process-lines notmuch-command "search" "--output=tags" "*")))
+       (completions
+        (append (list "folder:" "path:" "thread:" "id:" "date:" "from:" "to:"
+                      "subject:" "attachment:" "mimetype:")
+                (mapcar (lambda (tag) (concat "tag:" tag)) all-tags)
+                (mapcar (lambda (tag) (concat "is:" tag)) all-tags))))
     (let ((keymap (copy-keymap minibuffer-local-map))
+         (current-query (case major-mode
+                          (notmuch-search-mode (notmuch-search-get-query))
+                          (notmuch-show-mode (notmuch-show-get-query))
+                          (notmuch-tree-mode (notmuch-tree-get-query))))
          (minibuffer-completion-table
           (completion-table-dynamic
            (lambda (string)
@@ -871,19 +925,24 @@ PROMPT is the string to prompt with."
       (define-key keymap (kbd "TAB") 'minibuffer-complete)
       (let ((history-delete-duplicates t))
        (read-from-minibuffer prompt nil keymap nil
-                             'notmuch-search-history nil nil)))))
+                             'notmuch-search-history current-query nil)))))
+
+(defun notmuch-search-get-query ()
+  "Return the current query in this search buffer"
+  notmuch-search-query-string)
 
+(put 'notmuch-search 'notmuch-doc "Search for messages.")
 ;;;###autoload
 (defun notmuch-search (&optional query oldest-first target-thread target-line)
-  "Run \"notmuch search\" with the given `query' and display results.
+  "Display threads matching QUERY in a notmuch-search buffer.
 
-If `query' is nil, it is read interactively from the minibuffer.
+If QUERY is nil, it is read interactively from the minibuffer.
 Other optional parameters are used as follows:
 
-  oldest-first: A Boolean controlling the sort order of returned threads
-  target-thread: A thread ID (without the thread: prefix) that will be made
+  OLDEST-FIRST: A Boolean controlling the sort order of returned threads
+  TARGET-THREAD: A thread ID (without the thread: prefix) that will be made
                  current if it appears in the search results.
-  target-line: The line number to move to if the target thread does not
+  TARGET-LINE: The line number to move to if the target thread does not
                appear in the search results.
 
 When called interactively, this will prompt for a query and use
@@ -894,7 +953,7 @@ the configured default sort order."
     nil
     ;; Use the default search order (if we're doing a search from a
     ;; search buffer, ignore any buffer-local overrides)
-    (default-value notmuch-search-oldest-first)))
+    (default-value 'notmuch-search-oldest-first)))
 
   (let* ((query (or query (notmuch-read-query "Notmuch search: ")))
         (buffer (get-buffer-create (notmuch-search-buffer-title query))))
@@ -906,6 +965,7 @@ the configured default sort order."
     (set 'notmuch-search-oldest-first oldest-first)
     (set 'notmuch-search-target-thread target-thread)
     (set 'notmuch-search-target-line target-line)
+    (notmuch-tag-clear-cache)
     (let ((proc (get-buffer-process (current-buffer)))
          (inhibit-read-only t))
       (if proc
@@ -916,7 +976,7 @@ the configured default sort order."
       (save-excursion
        (let ((proc (notmuch-start-notmuch
                     "notmuch-search" buffer #'notmuch-search-process-sentinel
-                    "search" "--format=sexp" "--format-version=1"
+                    "search" "--format=sexp" "--format-version=2"
                     (if oldest-first
                         "--sort=oldest-first"
                       "--sort=newest-first")
@@ -942,7 +1002,7 @@ same relative position within the new buffer."
        (oldest-first notmuch-search-oldest-first)
        (target-thread (notmuch-search-find-thread-id 'bare))
        (query notmuch-search-query-string))
-    (notmuch-kill-this-buffer)
+    (notmuch-bury-or-kill-this-buffer)
     (notmuch-search query oldest-first target-thread target-line)
     (goto-char (point-min))))
 
@@ -955,18 +1015,28 @@ default sort order is defined by `notmuch-search-oldest-first'."
   (set 'notmuch-search-oldest-first (not notmuch-search-oldest-first))
   (notmuch-search-refresh-view))
 
+(defun notmuch-group-disjunctive-query-string (query-string)
+  "Group query if it contains a complex expression.
+
+Enclose QUERY-STRING in parentheses if it matches
+`notmuch-search-disjunctive-regexp'."
+  (if (string-match-p notmuch-search-disjunctive-regexp query-string)
+      (concat "( " query-string " )")
+    query-string))
+
 (defun notmuch-search-filter (query)
-  "Filter the current search results based on an additional query string.
+  "Filter or LIMIT the current search results based on an additional query string.
 
 Runs a new search matching only messages that match both the
 current search results AND the additional query string provided."
   (interactive (list (notmuch-read-query "Filter search: ")))
-  (let ((grouped-query (if (string-match-p notmuch-search-disjunctive-regexp query)
-                          (concat "( " query " )")
-                        query)))
-    (notmuch-search (if (string= notmuch-search-query-string "*")
+  (let ((grouped-query (notmuch-group-disjunctive-query-string query))
+       (grouped-original-query (notmuch-group-disjunctive-query-string
+                                notmuch-search-query-string)))
+    (notmuch-search (if (string= grouped-original-query "*")
                        grouped-query
-                     (concat notmuch-search-query-string " and " grouped-query)) notmuch-search-oldest-first)))
+                     (concat grouped-original-query " and " grouped-query))
+                   notmuch-search-oldest-first)))
 
 (defun notmuch-search-filter-by-tag (tag)
   "Filter the current search results based on a single tag.
@@ -988,8 +1058,9 @@ current search results AND that are tagged with the given tag."
   (with-current-buffer b
     (memq major-mode '(notmuch-show-mode
                       notmuch-search-mode
+                      notmuch-tree-mode
                       notmuch-hello-mode
-                      message-mode))))
+                      notmuch-message-mode))))
 
 ;;;###autoload
 (defun notmuch-cycle-notmuch-buffers ()
@@ -1021,3 +1092,11 @@ notmuch buffers exist, run `notmuch'."
 (setq mail-user-agent 'notmuch-user-agent)
 
 (provide 'notmuch)
+
+;; After provide to avoid loops if notmuch was require'd via notmuch-init-file.
+(if init-file-user ; don't load init file if the -q option was used.
+    (let ((init-file (locate-file notmuch-init-file '("/")
+                                 (get-load-suffixes))))
+      (if init-file (load init-file nil t t))))
+
+;;; notmuch.el ends here