X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=notmuch.el;h=97914f2cba68b6c879520c9ba7491ec2a0338135;hp=f3adfcf5cb1ed2a7c67e5dd6a9b02e8c493e1272;hb=28ce73848d98d8ee2b661733402e2c10b13418d5;hpb=6945e7e103fdc10cb676571b313eac5abd62d0c6 diff --git a/notmuch.el b/notmuch.el index f3adfcf5..97914f2c 100644 --- a/notmuch.el +++ b/notmuch.el @@ -111,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 "(\\([^)]*\\))$") @@ -322,7 +322,9 @@ buffer." (lambda (p) (let ((disposition (mm-handle-disposition p))) (and (listp disposition) - (equal (car disposition) "attachment") + (or (equal (car disposition) "attachment") + (and (equal (car disposition) "inline") + (assq 'filename disposition))) (incf count)))) mm-handle) count)) @@ -332,7 +334,9 @@ buffer." (lambda (p) (let ((disposition (mm-handle-disposition p))) (and (listp disposition) - (equal (car disposition) "attachment") + (or (equal (car disposition) "attachment") + (and (equal (car disposition) "inline") + (assq 'filename disposition))) (or (not queryp) (y-or-n-p (concat "Save '" (cdr (assq 'filename disposition)) "' "))) @@ -574,14 +578,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) @@ -590,26 +599,27 @@ which this thread was originally shown." (while (< (point) end) (let ((beg-sub (point-marker)) (indent (make-string depth ? )) - (citation "[[:space:]]*>")) + (citation ">")) + (move-to-column depth) (if (looking-at citation) (progn (while (looking-at citation) - (forward-line)) + (forward-line) + (move-to-column depth)) (let ((overlay (make-overlay beg-sub (point))) (invis-spec (make-symbol "notmuch-citation-region"))) (add-to-invisibility-spec invis-spec) (overlay-put overlay 'invisible invis-spec) - (let ((p (point)) + (let ((p (point-marker)) (cite-button-text (concat "[" (number-to-string (count-lines beg-sub (point))) - "-line citation.]"))) + "-line citation. Click/Enter to show.]"))) (goto-char (- beg-sub 1)) (insert (concat "\n" indent)) (insert-button cite-button-text 'invisibility-spec invis-spec :type 'notmuch-button-citation-toggle-type) - (insert "\n") - (goto-char (+ (length cite-button-text) p)) + (forward-line) )))) (move-to-column depth) (if (looking-at notmuch-show-signature-regexp) @@ -624,7 +634,7 @@ which this thread was originally shown." (goto-char (- beg-sub 1)) (insert (concat "\n" indent)) (let ((sig-button-text (concat "[" (number-to-string sig-lines) - "-line signature.]"))) + "-line signature. Click/Enter to show.]"))) (insert-button sig-button-text 'invisibility-spec invis-spec :type 'notmuch-button-signature-toggle-type) ) @@ -632,54 +642,44 @@ which this thread was originally shown." (goto-char end)))))) (forward-line)))) -(defun notmuch-show-markup-part (beg end depth mime-message) +(defun notmuch-show-markup-part (beg end depth) (if (re-search-forward notmuch-show-part-begin-regexp nil t) (progn - (if (eq mime-message nil) - (let ((filename (notmuch-show-get-filename))) - (with-temp-buffer - (insert-file-contents filename nil nil nil t) - (setq mime-message (mm-dissect-buffer))))) (forward-line) - (let ((part-beg (point-marker))) + (let ((beg (point-marker))) (re-search-forward notmuch-show-part-end-regexp) - - (let ((part-end (copy-marker (match-beginning 0)))) - (goto-char part-end) + (let ((end (copy-marker (match-beginning 0)))) + (goto-char end) (if (not (bolp)) (insert "\n")) - (indent-rigidly part-beg part-end depth) - (save-excursion - (goto-char part-beg) - (forward-line -1) - (beginning-of-line) - (let ((handle-type (mm-handle-type mime-message)) - mime-type) - (if (sequencep (car handle-type)) - (setq mime-type (car handle-type)) - (setq mime-type (car (car (cdr handle-type)))) - ) - (if (equal mime-type "text/html") - (mm-display-part mime-message)))) - - (notmuch-show-markup-citations-region part-beg part-end depth) + (indent-rigidly beg end depth) + (notmuch-show-markup-citations-region beg end depth) ; Advance to the next part (if any) (so the outer loop can ; determine whether we've left the current message. (if (re-search-forward notmuch-show-part-begin-regexp nil t) (beginning-of-line))))) - (goto-char end)) - mime-message) + (goto-char end))) (defun notmuch-show-markup-parts-region (beg end depth) (save-excursion (goto-char beg) - (let (mime-message) - (while (< (point) end) - (setq mime-message - (notmuch-show-markup-part - beg end depth mime-message)))))) - -(defun notmuch-show-markup-body (depth btn) + (while (< (point) end) + (notmuch-show-markup-part beg end depth)))) + +(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))) @@ -690,85 +690,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)) - (overlay-put (make-overlay beg summary-end) 'face 'notmuch-message-summary-face) - (setq btn (make-button beg summary-end :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 () @@ -921,12 +931,15 @@ All currently available key bindings: (lambda() (hl-line-mode 1) )) -(defun notmuch-show (thread-id &optional parent-buffer) +(defun notmuch-show (thread-id &optional parent-buffer query-context) "Run \"notmuch show\" with the given thread ID and display results. 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)." +thread from that buffer can be show when done with this one). + +The optional QUERY-CONTEXT is a notmuch search term. Only messages from the thread +matching this search term are shown if non-nil. " (interactive "sNotmuch show: ") (let ((buffer (get-buffer-create (concat "*notmuch-show-" thread-id "*")))) (switch-to-buffer buffer) @@ -940,30 +953,17 @@ 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" "--entire-thread" thread-id) + (let* ((basic-args (list notmuch-command nil t nil "show" "--entire-thread" thread-id)) + (args (if query-context (append basic-args (list "and (" query-context ")")) basic-args))) + (apply 'call-process args) + (when (and (eq (buffer-size) 0) query-context) + (apply 'call-process basic-args))) (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 @@ -1002,6 +1002,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) @@ -1119,12 +1121,20 @@ Complete list of currently available key bindings: "Return the thread for the current thread" (get-text-property (point) 'notmuch-search-thread-id)) +(defun notmuch-search-find-authors () + "Return the authors for the current thread" + (get-text-property (point) 'notmuch-search-authors)) + +(defun notmuch-search-find-subject () + "Return the subject for the current thread" + (get-text-property (point) 'notmuch-search-subject)) + (defun notmuch-search-show-thread () "Display the currently selected thread." (interactive) (let ((thread-id (notmuch-search-find-thread-id))) (if (> (length thread-id) 0) - (notmuch-show thread-id (current-buffer)) + (notmuch-show thread-id (current-buffer) notmuch-search-query-string) (error "End of search results")))) (defun notmuch-search-reply-to-thread () @@ -1179,7 +1189,7 @@ The tag is added to messages in the currently selected thread which match the current search terms." (interactive (list (notmuch-select-tag-with-completion "Tag to add: "))) - (notmuch-call-notmuch-process "tag" (concat "+" tag) (notmuch-search-find-thread-id) " and " notmuch-search-query-string) + (notmuch-call-notmuch-process "tag" (concat "+" tag) (notmuch-search-find-thread-id)) (notmuch-search-set-tags (delete-dups (sort (cons tag (notmuch-search-get-tags)) 'string<)))) (defun notmuch-search-remove-tag (tag) @@ -1189,7 +1199,7 @@ The tag is removed from messages in the currently selected thread which match the current search terms." (interactive (list (notmuch-select-tag-with-completion "Tag to remove: " (notmuch-search-find-thread-id)))) - (notmuch-call-notmuch-process "tag" (concat "-" tag) (notmuch-search-find-thread-id) " and " notmuch-search-query-string) + (notmuch-call-notmuch-process "tag" (concat "-" tag) (notmuch-search-find-thread-id)) (notmuch-search-set-tags (delete tag (notmuch-search-get-tags)))) (defun notmuch-search-archive-thread () @@ -1243,7 +1253,9 @@ This function advances the next thread when finished." (goto-char (point-max)) (let ((beg (point-marker))) (insert (format "%s %-7s %-40s %s (%s)\n" date count authors subject tags)) - (put-text-property beg (point-marker) 'notmuch-search-thread-id thread-id)) + (put-text-property beg (point-marker) 'notmuch-search-thread-id thread-id) + (put-text-property beg (point-marker) 'notmuch-search-authors authors) + (put-text-property beg (point-marker) 'notmuch-search-subject subject)) (set 'line (match-end 0))) (set 'more nil)))))) (delete-process proc)))) @@ -1340,7 +1352,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.