X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=notmuch.el;h=c504f46de22aa89a3a3c68faf75db530f7b7205d;hp=72224742b1d4cfc8c840f27d151dd5c3f0e430c3;hb=ea4cb3cbdc8b3c6e450b971f52e880223e943ba0;hpb=86992aeb857b58c16440e6d890b68ee9b3c1a55f diff --git a/notmuch.el b/notmuch.el index 72224742..c504f46d 100644 --- a/notmuch.el +++ b/notmuch.el @@ -55,7 +55,6 @@ (let ((map (make-sparse-keymap))) (define-key map "?" 'notmuch-help) (define-key map "q" 'kill-this-buffer) - (define-key map "x" 'kill-this-buffer) (define-key map (kbd "C-p") 'notmuch-show-previous-line) (define-key map (kbd "C-n") 'notmuch-show-next-line) (define-key map (kbd "M-TAB") 'notmuch-show-previous-button) @@ -70,6 +69,8 @@ (define-key map "v" 'notmuch-show-view-all-mime-parts) (define-key map "-" 'notmuch-show-remove-tag) (define-key map "+" 'notmuch-show-add-tag) + (define-key map "X" 'notmuch-show-mark-read-then-archive-then-exit) + (define-key map "x" 'notmuch-show-archive-thread-then-exit) (define-key map "A" 'notmuch-show-mark-read-then-archive-thread) (define-key map "a" 'notmuch-show-archive-thread) (define-key map "p" 'notmuch-show-previous-message) @@ -110,7 +111,7 @@ pattern can still test against the entire line).") (defvar notmuch-show-marker-regexp "\f\\(message\\|header\\|body\\|attachment\\|part\\)[{}].*$") (defvar notmuch-show-id-regexp "\\(id:[^ ]*\\)") -(defvar notmuch-show-depth-regexp " depth:\\([0-9]*\\) ") +(defvar notmuch-show-depth-match-regexp " depth:\\([0-9]*\\).*match:\\([01]\\) ") (defvar notmuch-show-filename-regexp "filename:\\(.*\\)$") (defvar notmuch-show-tags-regexp "(\\([^)]*\\))$") @@ -158,7 +159,7 @@ Unlike builtin `next-line' this version accepts no arguments." By advancing forward until reaching a visible character. -Unlike builtin `next-line' this version accepts no arguments." +Unlike builtin `previous-line' this version accepts no arguments." (interactive) (set 'this-command 'previous-line) (call-interactively 'previous-line) @@ -273,6 +274,18 @@ buffer." (interactive) (notmuch-show-archive-thread-maybe-mark-read nil)) +(defun notmuch-show-archive-thread-then-exit () + "Archive each message in thread, then exit back to search results." + (interactive) + (notmuch-show-archive-thread) + (kill-this-buffer)) + +(defun notmuch-show-mark-read-then-archive-then-exit () + "Remove unread tags from thread, then archive and exit to search results." + (interactive) + (notmuch-show-mark-read-then-archive-thread) + (kill-this-buffer)) + (defun notmuch-show-view-raw-message () "View the raw email of the current message." (interactive) @@ -378,7 +391,7 @@ point either forward or backward to the next visible character when a command ends with point on an invisible character). Emits an error if point is not within a valid message, (that is -not pattern of `notmuch-show-message-begin-regexp' could be found +no pattern of `notmuch-show-message-begin-regexp' could be found by searching backward)." (beginning-of-line) (if (not (looking-at notmuch-show-message-begin-regexp)) @@ -395,22 +408,35 @@ by searching backward)." (not (re-search-forward notmuch-show-message-begin-regexp nil t))))) (defun notmuch-show-message-unread-p () - "Preficate testing whether current message is unread." + "Predicate testing whether current message is unread." (member "unread" (notmuch-show-get-tags))) +(defun notmuch-show-message-open-p () + "Predicate testing whether current message is open (body is visible)." + (let ((btn (previous-button (point) t))) + (while (not (button-has-type-p btn 'notmuch-button-body-toggle-type)) + (setq btn (previous-button (button-start btn)))) + (not (invisible-p (button-get btn 'invisibility-spec))))) + (defun notmuch-show-next-message () "Advance to the beginning of the next message in the buffer. Moves to the last visible character of the current message if -already on the last message in the buffer." +already on the last message in the buffer. + +Returns nil if already on the last message in the buffer." (interactive) (notmuch-show-move-to-current-message-summary-line) (if (re-search-forward notmuch-show-message-begin-regexp nil t) - (notmuch-show-move-to-current-message-summary-line) + (progn + (notmuch-show-move-to-current-message-summary-line) + (recenter 0) + t) (goto-char (- (point-max) 1)) (while (point-invisible-p) - (backward-char))) - (recenter 0)) + (backward-char)) + (recenter 0) + nil)) (defun notmuch-show-find-next-message () "Returns the position of the next message in the buffer. @@ -438,14 +464,9 @@ there are no more unread messages past the current point." (notmuch-show-next-message))) (defun notmuch-show-next-open-message () - "Advance to the next message which is not hidden. - -If read messages are currently hidden, advance to the next unread -message. Otherwise, advance to the next message." - (if (or (memq 'notmuch-show-body-read buffer-invisibility-spec) - (assq 'notmuch-show-body-read buffer-invisibility-spec)) - (notmuch-show-next-unread-message) - (notmuch-show-next-message))) + "Advance to the next open message (that is, body is not invisible)." + (while (and (notmuch-show-next-message) + (not (notmuch-show-message-open-p))))) (defun notmuch-show-previous-message () "Backup to the beginning of the previous message in the buffer. @@ -545,7 +566,7 @@ which this thread was originally shown." (goto-char (button-start (previous-button (point))))) (defun notmuch-toggle-invisible-action (cite-button) - (let ((invis-spec (button-get button 'invisibility-spec))) + (let ((invis-spec (button-get cite-button 'invisibility-spec))) (if (invisible-p invis-spec) (remove-from-invisibility-spec invis-spec) (add-to-invisibility-spec invis-spec) @@ -553,14 +574,19 @@ which this thread was originally shown." (force-window-update) (redisplay t)) -(define-button-type 'notmuch-button-invisibility-toggle-type 'action 'notmuch-toggle-invisible-action 'follow-link t) +(define-button-type 'notmuch-button-invisibility-toggle-type + 'action 'notmuch-toggle-invisible-action + 'follow-link t + 'face "default") (define-button-type 'notmuch-button-citation-toggle-type 'help-echo "mouse-1, RET: Show citation" :supertype 'notmuch-button-invisibility-toggle-type) (define-button-type 'notmuch-button-signature-toggle-type 'help-echo "mouse-1, RET: Show signature" :supertype 'notmuch-button-invisibility-toggle-type) (define-button-type 'notmuch-button-headers-toggle-type 'help-echo "mouse-1, RET: Show headers" :supertype 'notmuch-button-invisibility-toggle-type) -(define-button-type 'notmuch-button-body-toggle-type 'help-echo "mouse-1, RET: Show message" +(define-button-type 'notmuch-button-body-toggle-type + 'help-echo "mouse-1, RET: Show message" + 'face 'notmuch-message-summary-face :supertype 'notmuch-button-invisibility-toggle-type) (defun notmuch-show-markup-citations-region (beg end depth) @@ -658,7 +684,20 @@ which this thread was originally shown." (notmuch-show-markup-part beg end depth mime-message)))))) -(defun notmuch-show-markup-body (depth btn) +(defun notmuch-show-markup-body (depth match btn) + "Markup a message body, (indenting, buttonizing citations, +etc.), and conditionally hiding the body itself if the message +has been read and does not match the current search. + +DEPTH specifies the depth at which this message appears in the +tree of the current thread, (the top-level messages have depth 0 +and each reply increases depth by 1). MATCH indicates whether +this message is regarded as matching the current search. BTN is +the button which is used to toggle the visibility of this +message. + +When this function is called, point must be within the message, but +before the delimiter marking the beginning of the body." (re-search-forward notmuch-show-body-begin-regexp) (forward-line) (let ((beg (point-marker))) @@ -669,86 +708,95 @@ which this thread was originally shown." (overlay-put (make-overlay beg end) 'invisible invis-spec) (button-put btn 'invisibility-spec invis-spec) - (if (not (notmuch-show-message-unread-p)) + (if (not (or (notmuch-show-message-unread-p) match)) (add-to-invisibility-spec invis-spec))) (set-marker beg nil) (set-marker end nil) ))) + (defun notmuch-fontify-headers () - (progn - (if (looking-at "[Tt]o:") - (progn - (overlay-put (make-overlay (point) (re-search-forward ":")) - 'face 'message-header-name) - (overlay-put (make-overlay (point) (re-search-forward ".*$")) - 'face 'message-header-to)) + (while (looking-at "[[:space:]]") + (forward-char)) + (if (looking-at "[Tt]o:") + (progn + (overlay-put (make-overlay (point) (re-search-forward ":")) + 'face 'message-header-name) + (overlay-put (make-overlay (point) (re-search-forward ".*$")) + 'face 'message-header-to)) (if (looking-at "[B]?[Cc][Cc]:") (progn (overlay-put (make-overlay (point) (re-search-forward ":")) - 'face 'message-header-name) - (overlay-put (make-overlay (point) (re-search-forward ".*$")) - 'face 'message-header-cc)) - (if (looking-at "[Ss]ubject:") - (progn - (overlay-put (make-overlay (point) (re-search-forward ":")) - 'face 'message-header-name) - (overlay-put (make-overlay (point) (re-search-forward ".*$")) - 'face 'message-header-subject)) - (if (looking-at "[Ff]rom:") - (progn - (overlay-put (make-overlay (point) (re-search-forward ":")) - 'face 'message-header-name) - (overlay-put (make-overlay (point) (re-search-forward ".*$")) - 'face 'message-header-other)))))))) - -(defun notmuch-show-markup-header (depth) + 'face 'message-header-name) + (overlay-put (make-overlay (point) (re-search-forward ".*$")) + 'face 'message-header-cc)) + (if (looking-at "[Ss]ubject:") + (progn + (overlay-put (make-overlay (point) (re-search-forward ":")) + 'face 'message-header-name) + (overlay-put (make-overlay (point) (re-search-forward ".*$")) + 'face 'message-header-subject)) + (if (looking-at "[Ff]rom:") + (progn + (overlay-put (make-overlay (point) (re-search-forward ":")) + 'face 'message-header-name) + (overlay-put (make-overlay (point) (re-search-forward ".*$")) + 'face 'message-header-other))))))) + +(defun notmuch-show-markup-header (message-begin depth) + "Buttonize and decorate faces in a message header. + +MESSAGE-BEGIN is the position of the absolute first character in +the message (including all delimiters that will end up being +invisible etc.). This is to allow a button to reliably extend to +the beginning of the message even if point is positioned at an +invisible character (such as the beginning of the buffer). + +DEPTH specifies the depth at which this message appears in the +tree of the current thread, (the top-level messages have depth 0 +and each reply increases depth by 1)." (re-search-forward notmuch-show-header-begin-regexp) (forward-line) (let ((beg (point-marker)) + (summary-end (copy-marker (line-beginning-position 2))) + (subject-end (copy-marker (line-end-position 2))) + (invis-spec (make-symbol "notmuch-show-header")) (btn nil)) - (end-of-line) - ; Inverse video for subject - (overlay-put (make-overlay beg (point)) 'face '(:inverse-video t)) - (setq btn (make-button beg (point) :type 'notmuch-button-body-toggle-type)) - (forward-line 1) - (end-of-line) - (let ((beg-hidden (point-marker))) - (re-search-forward notmuch-show-header-end-regexp) - (beginning-of-line) - (let ((end (point-marker))) - (goto-char beg) - (forward-line) - (while (looking-at "[A-Za-z][-A-Za-z0-9]*:") - (beginning-of-line) - (notmuch-fontify-headers) - (forward-line) - ) - (indent-rigidly beg end depth) - (let ((invis-spec (make-symbol "notmuch-show-header"))) - (add-to-invisibility-spec (cons invis-spec t)) - (overlay-put (make-overlay beg-hidden end) - 'invisible invis-spec) - (goto-char beg) - (forward-line) - (make-button (line-beginning-position) (line-end-position) - 'invisibility-spec (cons invis-spec t) - :type 'notmuch-button-headers-toggle-type)) - (goto-char end) - (insert "\n") - (set-marker beg nil) - (set-marker beg-hidden nil) - (set-marker end nil) - )) - btn)) + (re-search-forward notmuch-show-header-end-regexp) + (beginning-of-line) + (let ((end (point-marker))) + (indent-rigidly beg end depth) + (goto-char beg) + (setq btn (make-button message-begin summary-end :type 'notmuch-button-body-toggle-type)) + (forward-line) + (add-to-invisibility-spec invis-spec) + (overlay-put (make-overlay subject-end end) + 'invisible invis-spec) + (make-button (line-beginning-position) subject-end + 'invisibility-spec invis-spec + :type 'notmuch-button-headers-toggle-type) + (while (looking-at "[[:space:]]*[A-Za-z][-A-Za-z0-9]*:") + (beginning-of-line) + (notmuch-fontify-headers) + (forward-line) + ) + (goto-char end) + (insert "\n") + (set-marker beg nil) + (set-marker summary-end nil) + (set-marker subject-end nil) + (set-marker end nil) + ) + btn)) (defun notmuch-show-markup-message () (if (re-search-forward notmuch-show-message-begin-regexp nil t) - (progn - (re-search-forward notmuch-show-depth-regexp) + (let ((message-begin (match-beginning 0))) + (re-search-forward notmuch-show-depth-match-regexp) (let ((depth (string-to-number (buffer-substring (match-beginning 1) (match-end 1)))) + (match (string= "1" (buffer-substring (match-beginning 2) (match-end 2)))) (btn nil)) - (setq btn (notmuch-show-markup-header depth)) - (notmuch-show-markup-body depth btn))) + (setq btn (notmuch-show-markup-header message-begin depth)) + (notmuch-show-markup-body depth match btn))) (goto-char (point-max)))) (defun notmuch-show-hide-markers () @@ -826,9 +874,13 @@ For a mouse binding, return nil." (defun notmuch-help () "Display help for the current notmuch mode." (interactive) - (let ((mode major-mode)) - (with-help-window (help-buffer) - (princ (substitute-command-keys (notmuch-substitute-command-keys (documentation mode t))))))) + (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)))) ;;;###autoload (defun notmuch-show-mode () @@ -904,7 +956,8 @@ The optional PARENT-BUFFER is the notmuch-search buffer from which this notmuch-show command was executed, (so that the next thread from that buffer can be show when done with this one)." (interactive "sNotmuch show: ") - (let ((buffer (get-buffer-create (concat "*notmuch-show-" thread-id "*")))) + (let ((query notmuch-search-query-string) + (buffer (get-buffer-create (concat "*notmuch-show-" thread-id "*")))) (switch-to-buffer buffer) (notmuch-show-mode) (set (make-local-variable 'notmuch-show-parent-buffer) parent-buffer) @@ -916,30 +969,13 @@ thread from that buffer can be show when done with this one)." (erase-buffer) (goto-char (point-min)) (save-excursion - (call-process notmuch-command nil t nil "show" thread-id) + (call-process notmuch-command nil t nil "show" "--entire-thread" thread-id "and (" query ")") (notmuch-show-markup-messages) ) (run-hooks 'notmuch-show-hook) - ; Move straight to the first unread message - (if (not (notmuch-show-message-unread-p)) - (progn - (notmuch-show-next-unread-message) - ; But if there are no unread messages, go back to the - ; beginning of the buffer, and open up the bodies of all - ; read message. - (if (not (notmuch-show-message-unread-p)) - (progn - (goto-char (point-min)) - (let ((btn (forward-button 1))) - (while btn - (if (button-has-type-p btn 'notmuch-button-body-toggle-type) - (push-button)) - (condition-case err - (setq btn (forward-button 1)) - (error (setq btn nil))) - )) - (goto-char (point-min)) - )))) + ; Move straight to the first open message + (if (not (notmuch-show-message-open-p)) + (notmuch-show-next-open-message)) ))) (defvar notmuch-search-authors-width 40 @@ -978,6 +1014,8 @@ thread from that buffer can be show when done with this one)." (defvar notmuch-search-oldest-first t "Show the oldest mail first in the search-mode") +(defvar notmuch-search-disjunctive-regexp "\\<[oO][rR]\\>") + (defun notmuch-search-scroll-up () "Move forward through search results by one window's worth." (interactive) @@ -1003,12 +1041,12 @@ thread from that buffer can be show when done with this one)." (defun notmuch-search-next-thread () "Select the next thread in the search results." (interactive) - (next-line)) + (forward-line 1)) (defun notmuch-search-previous-thread () "Select the previous thread in the search results." (interactive) - (previous-line)) + (forward-line -1)) (defun notmuch-search-last-thread () "Select the last thread in the search results." @@ -1021,6 +1059,12 @@ thread from that buffer can be show when done with this one)." (interactive) (goto-char (point-min))) +(defface notmuch-message-summary-face + '((((class color) (background light)) (:background "#f0f0f0")) + (((class color) (background dark)) (:background "#303030"))) + "Face for the single-line message summary in notmuch-show-mode." + :group 'notmuch) + (defface notmuch-tag-face '((((class color) (background dark)) @@ -1049,15 +1093,15 @@ number of matched messages and total messages in the thread, participants in the thread, a representative subject line, and any tags). -By default, pressing RET on any line displays that thread. The -'+' and '-' keys can be used to add or remove tags from a -thread. The 'a' key is a convenience key for archiving a -thread (removing the \"inbox\" tag). The '*' key can be used to -add or remove a tag from all threads in the current buffer. +Pressing \\[notmuch-search-show-thread] on any line displays that thread. The '\\[notmuch-search-add-tag]' and '\\[notmuch-search-remove-tag]' +keys can be used to add or remove tags from a thread. The '\\[notmuch-search-archive-thread]' key +is a convenience for archiving a thread (removing the \"inbox\" +tag). The '\\[notmuch-search-operate-all]' key can be used to add or remove a tag from all +threads in the current buffer. -Other useful commands are 'f' for filtering the current search -based on an additional query string, 't' for filtering to include -only messages with a given tag, and 's' to execute a new, global +Other useful commands are '\\[notmuch-search-filter]' for filtering the current search +based on an additional query string, '\\[notmuch-search-filter-by-tag]' for filtering to include +only messages with a given tag, and '\\[notmuch-search]' to execute a new, global search. Complete list of currently available key bindings: @@ -1077,12 +1121,11 @@ Complete list of currently available key bindings: (if (not notmuch-tag-face-alist) (add-to-list 'notmuch-search-font-lock-keywords (list "(\\([^)]*\\))$" '(1 'notmuch-tag-face))) - (progn - (setq notmuch-search-tags (mapcar 'car notmuch-tag-face-alist)) - (loop for notmuch-search-tag in notmuch-search-tags - do (add-to-list 'notmuch-search-font-lock-keywords (list - (concat "([^)]*\\(" notmuch-search-tag "\\)[^)]*)$") - `(1 ,(cdr (assoc notmuch-search-tag notmuch-tag-face-alist)))))))) + (let ((notmuch-search-tags (mapcar 'car notmuch-tag-face-alist))) + (loop for notmuch-search-tag in notmuch-search-tags + do (add-to-list 'notmuch-search-font-lock-keywords (list + (concat "([^)]*\\(" notmuch-search-tag "\\)[^)]*)$") + `(1 ,(cdr (assoc notmuch-search-tag notmuch-tag-face-alist)))))))) (set (make-local-variable 'font-lock-defaults) '(notmuch-search-font-lock-keywords t))) @@ -1311,7 +1354,8 @@ search." Runs a new search matching only messages that match both the current search results AND the additional query string provided." (interactive "sFilter search: ") - (notmuch-search (concat notmuch-search-query-string " and " query) notmuch-search-oldest-first)) + (let ((grouped-query (if (string-match-p notmuch-search-disjunctive-regexp query) (concat "( " query " )") query))) + (notmuch-search (concat notmuch-search-query-string " and " grouped-query) notmuch-search-oldest-first))) (defun notmuch-search-filter-by-tag (tag) "Filter the current search results based on a single tag.