X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=emacs%2Fnotmuch.el;h=43d56f7be499218214d3c5c64e346484e71ac18b;hp=233c784d9a0676733903b9562247b1575fb02cdb;hb=07dff496304d6dc2e8033a18691b095ed9cd212f;hpb=31fc76b782c5ebc38ec4a6d5f4fea12fe141f460 diff --git a/emacs/notmuch.el b/emacs/notmuch.el index 233c784d..43d56f7b 100644 --- a/emacs/notmuch.el +++ b/emacs/notmuch.el @@ -1,4 +1,4 @@ -;; notmuch.el --- run notmuch within emacs +;;; notmuch.el --- run notmuch within emacs ;; ;; Copyright © Carl Worth ;; @@ -15,15 +15,18 @@ ;; General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License -;; along with Notmuch. If not, see . +;; along with Notmuch. If not, see . ;; ;; Authors: Carl Worth +;; 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 @@ -45,7 +48,9 @@ ;; ;; Have fun, and let us know if you have any comment, questions, or ;; kudos: Notmuch list (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) @@ -149,7 +154,7 @@ there will be called at other points of notmuch execution." (defvar notmuch-search-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map notmuch-common-keymap) - (define-key map "x" 'notmuch-kill-this-buffer) + (define-key map "x" 'notmuch-bury-or-kill-this-buffer) (define-key map (kbd "") 'notmuch-search-scroll-down) (define-key map "b" 'notmuch-search-scroll-down) (define-key map " " 'notmuch-search-scroll-up) @@ -162,7 +167,7 @@ there will be called at other points of notmuch execution." (define-key map "o" 'notmuch-search-toggle-order) (define-key map "c" 'notmuch-search-stash-map) (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) @@ -177,6 +182,7 @@ there will be called at other points of notmuch execution." (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") @@ -187,6 +193,11 @@ there will be called at other points of notmuch execution." (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) @@ -300,6 +311,26 @@ there will be called at other points of notmuch execution." :group 'notmuch-search :group 'notmuch-faces) +(defface notmuch-search-flagged-face + '((t + (:weight bold))) + "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 + (:foreground "blue"))) + "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) + (defun notmuch-search-mode () "Major mode displaying results of a notmuch search. @@ -424,14 +455,16 @@ matched and unmatched messages in the current thread." "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." +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))) - (concat "(" (mapconcat 'identity query-list ") or (") ")"))) + (when query-list + (concat "(" (mapconcat 'identity query-list ") or (") ")")))) (defun notmuch-search-find-authors () "Return the authors for the current thread" @@ -450,7 +483,11 @@ is nil, include both matched and unmatched messages." (notmuch-search-properties-in-region :subject beg end)) (defun notmuch-search-show-thread (&optional elide-toggle) - "Display the currently selected thread." + "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))) @@ -574,7 +611,8 @@ This function advances the next thread when finished." (when notmuch-archive-tags (notmuch-search-tag (notmuch-tag-change-list notmuch-archive-tags unarchive) beg end)) - (notmuch-search-next-thread)) + (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 @@ -636,21 +674,26 @@ 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. +(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\")))) + (setq notmuch-search-line-faces '((\"unread\" . (:foreground \"green\")) + (\"deleted\" . (:foreground \"red\" + :background \"blue\")))) -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." +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 (custom-face-edit)) :group 'notmuch-search :group 'notmuch-faces) @@ -823,14 +866,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 "*")) @@ -848,14 +891,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:" "path:" "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) @@ -873,7 +922,11 @@ 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) ;;;###autoload (put 'notmuch-search 'notmuch-doc "Search for messages.") @@ -946,7 +999,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)))) @@ -959,18 +1012,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. @@ -1031,3 +1094,5 @@ notmuch buffers exist, run `notmuch'." (let ((init-file (locate-file notmuch-init-file '("/") (get-load-suffixes)))) (if init-file (load init-file nil t t)))) + +;;; notmuch.el ends here