X-Git-Url: https://git.notmuchmail.org/git?p=notmuch;a=blobdiff_plain;f=notmuch.el;h=5b553bbf55050be5b11a61221a28fb7c855e6ed7;hp=e605d9d24227823f7c9511bcb910747c868bb175;hb=07876ac135bbb3da264264ee5f7b83d36aad4290;hpb=7a6394257722c81dd47adefb172c8df2ac898973 diff --git a/notmuch.el b/notmuch.el index e605d9d2..5b553bbf 100644 --- a/notmuch.el +++ b/notmuch.el @@ -51,11 +51,26 @@ (require 'mm-view) (require 'message) +(defvar notmuch-show-stash-map + (let ((map (make-sparse-keymap))) + (define-key map "c" 'notmuch-show-stash-cc) + (define-key map "d" 'notmuch-show-stash-date) + (define-key map "F" 'notmuch-show-stash-filename) + (define-key map "f" 'notmuch-show-stash-from) + (define-key map "i" 'notmuch-show-stash-message-id) + (define-key map "s" 'notmuch-show-stash-subject) + (define-key map "T" 'notmuch-show-stash-tags) + (define-key map "t" 'notmuch-show-stash-to) + map) + "Submap for stash commands" + ) + +(fset 'notmuch-show-stash-map notmuch-show-stash-map) + (defvar notmuch-show-mode-map (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) @@ -68,15 +83,19 @@ (define-key map "w" 'notmuch-show-save-attachments) (define-key map "V" 'notmuch-show-view-raw-message) (define-key map "v" 'notmuch-show-view-all-mime-parts) + (define-key map "c" 'notmuch-show-stash-map) + (define-key map "b" 'notmuch-show-toggle-current-body) + (define-key map "h" 'notmuch-show-toggle-current-header) (define-key map "-" 'notmuch-show-remove-tag) (define-key map "+" 'notmuch-show-add-tag) - (define-key map "A" 'notmuch-show-mark-read-then-archive-thread) + (define-key map "x" 'notmuch-show-archive-thread-then-exit) (define-key map "a" 'notmuch-show-archive-thread) - (define-key map "p" 'notmuch-show-previous-message) - (define-key map "N" 'notmuch-show-mark-read-then-next-open-message) - (define-key map "n" 'notmuch-show-next-message) + (define-key map "P" 'notmuch-show-previous-message) + (define-key map "N" 'notmuch-show-next-message) + (define-key map "p" 'notmuch-show-previous-open-message) + (define-key map "n" 'notmuch-show-next-open-message) (define-key map (kbd "DEL") 'notmuch-show-rewind) - (define-key map " " 'notmuch-show-advance-marking-read-and-archiving) + (define-key map " " 'notmuch-show-advance-and-archive) map) "Keymap for \"notmuch show\" buffers.") (fset 'notmuch-show-mode-map notmuch-show-mode-map) @@ -91,9 +110,27 @@ for indentation at the beginning of the line. But notmuch will move past the indentation when testing this pattern, (so that the pattern can still test against the entire line).") +(defvar notmuch-show-signature-button-format + "[ %d-line signature. Click/Enter to toggle visibility. ]" + "String used to construct button text for hidden signatures + +Can use up to one integer format parameter, i.e. %d") + +(defvar notmuch-show-citation-button-format + "[ %d more citation lines. Click/Enter to toggle visibility. ]" + "String used to construct button text for hidden citations. + +Can use up to one integer format parameter, i.e. %d") + (defvar notmuch-show-signature-lines-max 12 "Maximum length of signature that will be hidden by default.") +(defvar notmuch-show-citation-lines-prefix 4 + "Always show at least this many lines of a citation. + +If there is one more line, show that, otherwise collapse +remaining lines into a button.") + (defvar notmuch-command "notmuch" "Command to run the notmuch binary.") @@ -110,8 +147,10 @@ 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-contentype-regexp "Content-type: \\(.*\\)") + (defvar notmuch-show-tags-regexp "(\\([^)]*\\))$") (defvar notmuch-show-parent-buffer nil) @@ -202,6 +241,62 @@ Unlike builtin `previous-line' this version accepts no arguments." (re-search-forward notmuch-show-tags-regexp) (split-string (buffer-substring (match-beginning 1) (match-end 1))))) +(defun notmuch-show-get-bcc () + "Return BCC address(es) of current message" + (notmuch-show-get-header-field 'bcc)) + +(defun notmuch-show-get-cc () + "Return CC address(es) of current message" + (notmuch-show-get-header-field 'cc)) + +(defun notmuch-show-get-date () + "Return Date of current message" + (notmuch-show-get-header-field 'date)) + +(defun notmuch-show-get-from () + "Return From address of current message" + (notmuch-show-get-header-field 'from)) + +(defun notmuch-show-get-subject () + "Return Subject of current message" + (notmuch-show-get-header-field 'subject)) + +(defun notmuch-show-get-to () + "Return To address(es) of current message" + (notmuch-show-get-header-field 'to)) + +(defun notmuch-show-get-header-field (name) + "Retrieve the header field NAME from the current message. +NAME should be a symbol, in lower case, as returned by +mail-header-extract-no-properties" + (let* ((result (assoc name (notmuch-show-get-header))) + (val (and result (cdr result)))) + val)) + +(defun notmuch-show-get-header () + "Retrieve and parse the header from the current message. Returns an alist with of (header . value) +where header is a symbol and value is a string. The summary from notmuch-show is returned as the +pseudoheader summary" + (require 'mailheader) + (save-excursion + (beginning-of-line) + (if (not (looking-at notmuch-show-message-begin-regexp)) + (re-search-backward notmuch-show-message-begin-regexp)) + (re-search-forward (concat notmuch-show-header-begin-regexp "\n[[:space:]]*\\(.*\\)\n")) + (let* ((summary (buffer-substring-no-properties (match-beginning 1) (match-end 1))) + (beg (point))) + (re-search-forward notmuch-show-header-end-regexp) + (let ((text (buffer-substring beg (match-beginning 0)))) + (with-temp-buffer + (insert text) + (goto-char (point-min)) + (while (looking-at "\\([[:space:]]*\\)[A-Za-z][-A-Za-z0-9]*:") + (delete-region (match-beginning 1) (match-end 1)) + (forward-line) + ) + (goto-char (point-min)) + (cons (cons 'summary summary) (mail-header-extract-no-properties))))))) + (defun notmuch-show-add-tag (&rest toadd) "Add a tag to the current message." (interactive @@ -225,13 +320,22 @@ Unlike builtin `previous-line' this version accepts no arguments." (cons (notmuch-show-get-message-id) nil))) (notmuch-show-set-tags (sort (set-difference tags toremove :test 'string=) 'string<)))))) -(defun notmuch-show-archive-thread-maybe-mark-read (markread) +(defun notmuch-show-archive-thread () + "Archive each message in thread, then show next thread from search. + +Archive each message currently shown by removing the \"inbox\" +tag from each. Then kill this buffer and show the next thread +from the search from which this thread was originally shown. + +Note: This command is safe from any race condition of new messages +being delivered to the same thread. It does not archive the +entire thread, but only the messages shown in the current +buffer." + (interactive) (save-excursion (goto-char (point-min)) (while (not (eobp)) - (if markread - (notmuch-show-remove-tag "unread" "inbox") - (notmuch-show-remove-tag "inbox")) + (notmuch-show-remove-tag "inbox") (if (not (eobp)) (forward-char)) (if (not (re-search-forward notmuch-show-message-begin-regexp nil t)) @@ -244,34 +348,11 @@ Unlike builtin `previous-line' this version accepts no arguments." (forward-line) (notmuch-search-show-thread))))) -(defun notmuch-show-mark-read-then-archive-thread () - "Remove unread tags from thread, then archive and show next thread. - -Archive each message currently shown by removing the \"unread\" -and \"inbox\" tag from each. Then kill this buffer and show the -next thread from the search from which this thread was originally -shown. - -Note: This command is safe from any race condition of new messages -being delivered to the same thread. It does not archive the -entire thread, but only the messages shown in the current -buffer." +(defun notmuch-show-archive-thread-then-exit () + "Archive each message in thread, then exit back to search results." (interactive) - (notmuch-show-archive-thread-maybe-mark-read t)) - -(defun notmuch-show-archive-thread () - "Archive each message in thread, then show next thread from search. - -Archive each message currently shown by removing the \"inbox\" -tag from each. Then kill this buffer and show the next thread -from the search from which this thread was originally shown. - -Note: This command is safe from any race condition of new messages -being delivered to the same thread. It does not archive the -entire thread, but only the messages shown in the current -buffer." - (interactive) - (notmuch-show-archive-thread-maybe-mark-read nil)) + (notmuch-show-archive-thread) + (kill-this-buffer)) (defun notmuch-show-view-raw-message () "View the raw email of the current message." @@ -286,13 +367,28 @@ buffer." (with-current-buffer buf (insert-file-contents filename nil nil nil t) ,@body) - (kill-buffer buf))))) + (kill-buffer buf))))) (defun notmuch-show-view-all-mime-parts () "Use external viewers to view all attachments from the current message." (interactive) (with-current-notmuch-show-message - (mm-display-parts (mm-dissect-buffer)))) + ; We ovverride the mm-inline-media-tests to indicate which message + ; parts are already sufficiently handled by the original + ; presentation of the message in notmuch-show mode. These parts + ; will be inserted directly into the temporary buffer of + ; with-current-notmuch-show-message and silently discarded. + ; + ; Any MIME part not explicitly mentioned here will be handled by an + ; external viewer as configured in the various mailcap files. + (let ((mm-inline-media-tests '( + ("text/.*" ignore identity) + ("application/pgp-signature" ignore identity) + ("multipart/alternative" ignore identity) + ("multipart/mixed" ignore identity) + ("multipart/related" ignore identity) + ))) + (mm-display-parts (mm-dissect-buffer))))) (defun notmuch-foreach-mime-part (function mm-handle) (cond ((stringp (car mm-handle)) @@ -309,7 +405,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)) @@ -319,7 +417,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)) "' "))) @@ -378,7 +478,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 +495,46 @@ 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-next-message () +(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-without-marking-read () "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." - (interactive) +already on the last message in the buffer. + +Returns nil if already on the last message in the buffer." (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-next-message () + "Advance to the next message (whether open or closed) +and remove the unread tag from that message. + +Moves to the last visible character of the current message if +already on the last message in the buffer. + +Returns nil if already on the last message in the buffer." + (interactive) + (notmuch-show-next-message-without-marking-read) + (notmuch-show-mark-read)) (defun notmuch-show-find-next-message () "Returns the position of the next message in the buffer. @@ -422,37 +546,39 @@ message if already within the last message in the buffer." ; Looks like we have to use both. (save-excursion (save-window-excursion - (notmuch-show-next-message) + (notmuch-show-next-message-without-marking-read) (point)))) (defun notmuch-show-next-unread-message () - "Advance to the beginning of the next unread message in the buffer. + "Advance to the next unread message. Moves to the last visible character of the current message if there are no more unread messages past the current point." - (notmuch-show-next-message) + (notmuch-show-next-message-without-marking-read) (while (and (not (notmuch-show-last-message-p)) (not (notmuch-show-message-unread-p))) - (notmuch-show-next-message)) + (notmuch-show-next-message-without-marking-read)) (if (not (notmuch-show-message-unread-p)) - (notmuch-show-next-message))) + (notmuch-show-next-message-without-marking-read)) + (notmuch-show-mark-read)) (defun notmuch-show-next-open-message () - "Advance to the next message which is not hidden. + "Advance to the next open message (that is, body is visible). -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))) +Moves to the last visible character of the final message in the buffer +if there are no more open messages." + (interactive) + (while (and (notmuch-show-next-message-without-marking-read) + (not (notmuch-show-message-open-p)))) + (notmuch-show-mark-read)) -(defun notmuch-show-previous-message () +(defun notmuch-show-previous-message-without-marking-read () "Backup to the beginning of the previous message in the buffer. If within a message rather than at the beginning of it, then -simply move to the beginning of the current message." - (interactive) +simply move to the beginning of the current message. + +Returns nil if already on the first message in the buffer." (let ((start (point))) (notmuch-show-move-to-current-message-summary-line) (if (not (< (point) start)) @@ -461,8 +587,22 @@ simply move to the beginning of the current message." (re-search-backward notmuch-show-message-begin-regexp nil t) (re-search-backward notmuch-show-message-begin-regexp nil t) (notmuch-show-move-to-current-message-summary-line) - )) - (recenter 0))) + (recenter 0) + (if (= (point) start) + nil + t)) + (recenter 0) + nil))) + +(defun notmuch-show-previous-message () + "Backup to the previous message (whether open or closed) +and remove the unread tag from that message. + +If within a message rather than at the beginning of it, then +simply move to the beginning of the current message." + (interactive) + (notmuch-show-previous-message-without-marking-read) + (notmuch-show-mark-read)) (defun notmuch-show-find-previous-message () "Returns the position of the previous message in the buffer. @@ -475,17 +615,21 @@ it." ; Looks like we have to use both. (save-excursion (save-window-excursion - (notmuch-show-previous-message) + (notmuch-show-previous-message-without-marking-read) (point)))) -(defun notmuch-show-mark-read-then-next-open-message () - "Remove unread tag from this message, then advance to next open message." +(defun notmuch-show-previous-open-message () + "Backup to previous open message (that is, body is visible). + +Moves to the first message in the buffer if there are no previous +open messages." (interactive) - (notmuch-show-remove-tag "unread") - (notmuch-show-next-open-message)) + (while (and (notmuch-show-previous-message-without-marking-read) + (not (notmuch-show-message-open-p)))) + (notmuch-show-mark-read)) (defun notmuch-show-rewind () - "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-marking-read-and-archiving]). + "Backup through the thread, (reverse scrolling compared to \\[notmuch-show-advance-and-archive]). Specifically, if the beginning of the previous email is fewer than `window-height' lines from the current point, move to it @@ -495,7 +639,7 @@ Otherwise, just scroll down a screenful of the current message. This command does not modify any message tags, (it does not undo any effects from previous calls to -`notmuch-show-advance-marking-read-and-archiving'." +`notmuch-show-advance-and-archive'." (interactive) (let ((previous (notmuch-show-find-previous-message))) (if (> (count-lines previous (point)) (- (window-height) next-screen-context-lines)) @@ -503,11 +647,17 @@ any effects from previous calls to (condition-case nil (scroll-down nil) ((beginning-of-buffer) nil)) - (goto-char (window-start))) + (goto-char (window-start)) + ; Because count-lines counts invivisible lines, we may have + ; scrolled to far. If so., notice this and fix it up. + (if (< (point) previous) + (progn + (goto-char previous) + (recenter 0)))) (notmuch-show-previous-message)))) -(defun notmuch-show-advance-marking-read-and-archiving () - "Advance through thread, marking read and archiving. +(defun notmuch-show-advance-and-archive () + "Advance through thread and archive. This command is intended to be one of the simplest ways to process a thread of email. It does the following: @@ -516,8 +666,7 @@ If the current message in the thread is not yet fully visible, scroll by a near screenful to read more of the message. Otherwise, (the end of the current message is already within the -current window), remove the \"unread\" tag (if present) from the -current message and advance to the next open message. +current window), advance to the next open message. Finally, if there is no further message to advance to, and this last message is already read, then archive the entire current @@ -530,19 +679,19 @@ which this thread was originally shown." (if (> next (window-end)) (scroll-up nil) (let ((last (notmuch-show-last-message-p))) - (notmuch-show-mark-read-then-next-open-message) + (notmuch-show-next-open-message) (if last (notmuch-show-archive-thread)))))) (defun notmuch-show-next-button () "Advance point to the next button in the buffer." (interactive) - (goto-char (button-start (next-button (point))))) + (forward-button 1)) (defun notmuch-show-previous-button () "Move point back to the previous button in the buffer." (interactive) - (goto-char (button-start (previous-button (point))))) + (backward-button 1)) (defun notmuch-toggle-invisible-action (cite-button) (let ((invis-spec (button-get cite-button 'invisibility-spec))) @@ -553,112 +702,181 @@ 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) +(defun notmuch-show-toggle-current-body () + "Toggle the display of the current message body." + (interactive) + (save-excursion + (notmuch-show-move-to-current-message-summary-line) + (unless (button-at (point)) + (notmuch-show-next-button)) + (push-button)) + ) + +(defun notmuch-show-toggle-current-header () + "Toggle the display of the current message header." + (interactive) + (save-excursion + (notmuch-show-move-to-current-message-summary-line) + (forward-line) + (unless (button-at (point)) + (notmuch-show-next-button)) + (push-button)) + ) + +(define-button-type 'notmuch-button-invisibility-toggle-type + 'action 'notmuch-toggle-invisible-action + 'follow-link t + 'face 'font-lock-comment-face) (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-citation-regexp (depth) + "Build a regexp for matching citations at a given DEPTH (indent)" + (let ((line-regexp (format "[[:space:]]\\{%d\\}>.*\n" depth))) + (concat "\\(?:^" line-regexp + "\\(?:[[:space:]]*\n" line-regexp + "\\)?\\)+"))) + +(defun notmuch-show-region-to-button (beg end type prefix button-text) + "Auxilary function to do the actual making of overlays and buttons + +BEG and END are buffer locations. TYPE should a string, either +\"citation\" or \"signature\". PREFIX is some arbitrary text to +insert before the button, probably for indentation. BUTTON-TEXT +is what to put on the button." + +;; This uses some slightly tricky conversions between strings and +;; symbols because of the way the button code works. Note that +;; replacing intern-soft with make-symbol will cause this to fail, +;; since the newly created symbol has no plist. + + (let ((overlay (make-overlay beg end)) + (invis-spec (make-symbol (concat "notmuch-" type "-region"))) + (button-type (intern-soft (concat "notmuch-button-" + type "-toggle-type")))) + (add-to-invisibility-spec invis-spec) + (overlay-put overlay 'invisible invis-spec) + (goto-char (1+ end)) + (save-excursion + (goto-char (1- beg)) + (insert prefix) + (insert-button button-text + 'invisibility-spec invis-spec + :type button-type) + ))) + + (defun notmuch-show-markup-citations-region (beg end depth) - (goto-char beg) - (beginning-of-line) - (while (< (point) end) - (let ((beg-sub (point-marker)) - (indent (make-string depth ? )) - (citation "[[:space:]]*>")) - (if (looking-at citation) - (progn - (while (looking-at citation) - (forward-line)) - (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)) - (cite-button-text - (concat "[" (number-to-string (count-lines beg-sub (point))) - "-line citation.]"))) - (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)) - )))) - (move-to-column depth) - (if (looking-at notmuch-show-signature-regexp) - (let ((sig-lines (- (count-lines beg-sub end) 1))) - (if (<= sig-lines notmuch-show-signature-lines-max) - (progn - (let ((invis-spec (make-symbol "notmuch-signature-region"))) - (add-to-invisibility-spec invis-spec) - (overlay-put (make-overlay beg-sub end) - 'invisible invis-spec) - - (goto-char (- beg-sub 1)) - (insert (concat "\n" indent)) - (let ((sig-button-text (concat "[" (number-to-string sig-lines) - "-line signature.]"))) - (insert-button sig-button-text 'invisibility-spec invis-spec - :type 'notmuch-button-signature-toggle-type) - ) - (insert "\n") - (goto-char end)))))) - (forward-line)))) - -(defun notmuch-show-markup-part (beg end depth mime-message) + "Markup citations, and up to one signature in the given region" + ;; it would be nice if the untabify was not required, but + ;; that would require notmuch to indent with spaces. + (untabify beg end) + (let ((citation-regexp (notmuch-show-citation-regexp depth)) + (signature-regexp (concat (format "^[[:space:]]\\{%d\\}" depth) + notmuch-show-signature-regexp)) + (indent (concat "\n" (make-string depth ? )))) + (goto-char beg) + (beginning-of-line) + (while (and (< (point) end) + (re-search-forward citation-regexp end t)) + (let* ((cite-start (match-beginning 0)) + (cite-end (match-end 0)) + (cite-lines (count-lines cite-start cite-end))) + (when (> cite-lines (1+ notmuch-show-citation-lines-prefix)) + (goto-char cite-start) + (forward-line notmuch-show-citation-lines-prefix) + (notmuch-show-region-to-button + (point) cite-end + "citation" + indent + (format notmuch-show-citation-button-format + (- cite-lines notmuch-show-citation-lines-prefix)) + )))) + (if (and (< (point) end) + (re-search-forward signature-regexp end t)) + (let* ((sig-start (match-beginning 0)) + (sig-end (match-end 0)) + (sig-lines (1- (count-lines sig-start end)))) + (if (<= sig-lines notmuch-show-signature-lines-max) + (notmuch-show-region-to-button + sig-start + end + "signature" + indent + (format notmuch-show-signature-button-format sig-lines) + )))))) + +(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))) - (re-search-forward notmuch-show-part-end-regexp) - - (let ((part-end (copy-marker (match-beginning 0)))) - (goto-char part-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) - ; 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) + (let (mime-message mime-type) + (save-excursion + (re-search-forward notmuch-show-contentype-regexp end t) + (setq mime-type (car (split-string (buffer-substring + (match-beginning 1) (match-end 1)))))) + + (if (equal mime-type "text/html") + (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 ((beg (point-marker))) + (re-search-forward notmuch-show-part-end-regexp) + (let ((end (copy-marker (match-beginning 0)))) + (goto-char end) + (if (not (bolp)) + (insert "\n")) + (indent-rigidly beg end depth) + (if (not (eq mime-message nil)) + (save-excursion + (goto-char beg) + (forward-line -1) + (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 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)) + (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 hiding the body itself if the message 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 +887,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 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 () @@ -804,8 +1031,12 @@ For a mouse binding, return nil." (if (mouse-event-p key) nil (if (keymapp action) - (let ((substitute (apply-partially 'notmuch-substitute-one-command-key-with-prefix (notmuch-prefix-key-description key)))) - (mapconcat substitute (cdr action) "\n")) + (let ((substitute (apply-partially 'notmuch-substitute-one-command-key-with-prefix (notmuch-prefix-key-description key))) + (as-list)) + (map-keymap (lambda (a b) + (push (cons a b) as-list)) + action) + (mapconcat substitute as-list "\n")) (concat prefix (format-kbd-macro (vector key)) "\t" (notmuch-documentation-first-line action)))))) @@ -848,17 +1079,15 @@ pressing RET after positioning the cursor on a hidden part, (for which \\[notmuch-show-next-button] and \\[notmuch-show-previous-button] are helpful). Reading the thread sequentially is well-supported by pressing -\\[notmuch-show-advance-marking-read-and-archiving]. This will scroll the current message (if necessary), -advance to the next message, or advance to the next thread (if -already on the last message of a thread). As each message is -scrolled away its \"unread\" tag will be removed, and as each -thread is scrolled away the \"inbox\" tag will be removed from -each message in the thread. +\\[notmuch-show-advance-and-archive]. This will +scroll the current message (if necessary), advance to the next +message, or advance to the next thread (if already on the last +message of a thread). Other commands are available to read or manipulate the thread more selectively, (such as '\\[notmuch-show-next-message]' and '\\[notmuch-show-previous-message]' to advance to messages without removing any tags, and '\\[notmuch-show-archive-thread]' to archive an entire thread without -scrolling through with \\[notmuch-show-advance-marking-read-and-archiving]). +scrolling through with \\[notmuch-show-advance-and-archive]). You can add or remove arbitary tags from the current message with '\\[notmuch-show-add-tag]' or '\\[notmuch-show-remove-tag]'. @@ -890,23 +1119,74 @@ All currently available key bindings: :options '(hl-line-mode) :group 'notmuch) +(defun notmuch-show-do-stash (text) + (kill-new text) + (message (concat "Saved: " text))) + +(defun notmuch-show-stash-cc () + "Copy CC field of current message to kill-ring." + (interactive) + (notmuch-show-do-stash (notmuch-show-get-cc))) + +(defun notmuch-show-stash-date () + "Copy date of current message to kill-ring." + (interactive) + (notmuch-show-do-stash (notmuch-show-get-date))) + +(defun notmuch-show-stash-filename () + "Copy filename of current message to kill-ring." + (interactive) + (notmuch-show-do-stash (notmuch-show-get-filename))) + +(defun notmuch-show-stash-from () + "Copy From address of current message to kill-ring." + (interactive) + (notmuch-show-do-stash (notmuch-show-get-from))) + +(defun notmuch-show-stash-message-id () + "Copy message ID of current message to kill-ring." + (interactive) + (notmuch-show-do-stash (notmuch-show-get-message-id))) + +(defun notmuch-show-stash-subject () + "Copy Subject field of current message to kill-ring." + (interactive) + (notmuch-show-do-stash (notmuch-show-get-subject))) + +(defun notmuch-show-stash-tags () + "Copy tags of current message to kill-ring as a comma separated list." + (interactive) + (notmuch-show-do-stash (mapconcat 'identity (notmuch-show-get-tags) ","))) + +(defun notmuch-show-stash-to () + "Copy To address of current message to kill-ring." + (interactive) + (notmuch-show-do-stash (notmuch-show-get-to))) + ; Make show mode a bit prettier, highlighting URLs and using word wrap +(defun notmuch-show-mark-read () + (notmuch-show-remove-tag "unread")) + (defun notmuch-show-pretty-hook () (goto-address-mode 1) (visual-line-mode)) +(add-hook 'notmuch-show-hook 'notmuch-show-mark-read) (add-hook 'notmuch-show-hook 'notmuch-show-pretty-hook) (add-hook 'notmuch-search-hook (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) @@ -920,30 +1200,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" 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 @@ -979,9 +1246,13 @@ thread from that buffer can be show when done with this one)." (fset 'notmuch-search-mode-map notmuch-search-mode-map) (defvar notmuch-search-query-string) +(defvar notmuch-search-target-thread) +(defvar notmuch-search-target-position) (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) @@ -1025,6 +1296,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)) @@ -1071,6 +1348,8 @@ Complete list of currently available key bindings: (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) + (make-local-variable 'notmuch-search-target-position) (set (make-local-variable 'scroll-preserve-screen-position) t) (add-to-invisibility-spec 'notmuch-search) (use-local-map notmuch-search-mode-map) @@ -1093,12 +1372,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 () @@ -1153,17 +1440,16 @@ 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) "Remove a tag from the currently selected thread. -The tag is removed from messages in the currently selected thread -which match the current search terms." +The tag is removed from all messages in the currently selected thread." (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 () @@ -1178,12 +1464,14 @@ This function advances the next thread when finished." "Add a message to let user know when \"notmuch search\" exits" (let ((buffer (process-buffer proc)) (status (process-status proc)) - (exit-status (process-exit-status proc))) + (exit-status (process-exit-status proc)) + (never-found-target-thread nil)) (if (memq status '(exit signal)) (if (buffer-live-p buffer) (with-current-buffer buffer (save-excursion - (let ((inhibit-read-only t)) + (let ((inhibit-read-only t) + (atbob (bobp))) (goto-char (point-max)) (if (eq status 'signal) (insert "Incomplete search results (search process was killed).\n")) @@ -1192,11 +1480,17 @@ This function advances the next thread when finished." (insert "End of search results.") (if (not (= exit-status 0)) (insert (format " (process returned %d)" exit-status))) - (insert "\n")))))))))) + (insert "\n") + (if (and atbob + notmuch-search-target-thread) + (set 'never-found-target-thread t)))))) + (if never-found-target-thread + (goto-char notmuch-search-target-position))))))) (defun notmuch-search-process-filter (proc string) "Process and filter the output of \"notmuch search\"" - (let ((buffer (process-buffer proc))) + (let ((buffer (process-buffer proc)) + (found-target nil)) (if (buffer-live-p buffer) (with-current-buffer buffer (save-excursion @@ -1204,7 +1498,7 @@ This function advances the next thread when finished." (more t) (inhibit-read-only t)) (while more - (if (string-match "^\\(thread:[0-9A-Fa-f]*\\) \\(.*\\) \\(\\[[0-9/]*\\]\\) \\([^:]*\\); \\(.*\\) (\\([^()]*\\))$" string line) + (if (string-match "^\\(thread:[0-9A-Fa-f]*\\) \\(.*\\) \\(\\[[0-9/]*\\]\\) \\([^;]*\\); \\(.*\\) (\\([^()]*\\))$" string line) (let* ((thread-id (match-string 1 string)) (date (match-string 2 string)) (count (match-string 3 string)) @@ -1217,9 +1511,18 @@ 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) + (if (and notmuch-search-target-thread + (string= thread-id notmuch-search-target-thread)) + (progn + (set 'found-target beg) + (set 'notmuch-search-target-thread nil)))) (set 'line (match-end 0))) - (set 'more nil)))))) + (set 'more nil))))) + (if found-target + (goto-char found-target))) (delete-process proc)))) (defun notmuch-search-operate-all (action) @@ -1246,14 +1549,27 @@ characters as well as `_.+-'. (append action-split (list notmuch-search-query-string) nil)))) ;;;###autoload -(defun notmuch-search (query &optional oldest-first) - "Run \"notmuch search\" with the given query string and display results." +(defun notmuch-search (query &optional oldest-first target-thread target-position) + "Run \"notmuch search\" with the given query string and display results. + +The optional parameters are used as follows: + + oldest-first: A Boolean controlling the sort order of returned threads + target-thread: A thread ID (with the thread: prefix) that will be made + current if it appears in the search results. + saved-position: If the search results complete, and the target thread is + not found in the results, and the point is still at the + beginning of the buffer, then the point will be moved to + the saved position. +" (interactive "sNotmuch search: ") (let ((buffer (get-buffer-create (concat "*notmuch-search-" query "*")))) (switch-to-buffer buffer) (notmuch-search-mode) (set 'notmuch-search-query-string query) (set 'notmuch-search-oldest-first oldest-first) + (set 'notmuch-search-target-thread target-thread) + (set 'notmuch-search-target-position target-position) (let ((proc (get-buffer-process (current-buffer))) (inhibit-read-only t)) (if proc @@ -1284,11 +1600,9 @@ same relative position within the new buffer." (thread (notmuch-search-find-thread-id)) (query notmuch-search-query-string)) (kill-this-buffer) - (notmuch-search query oldest-first) + (notmuch-search query oldest-first thread here) (goto-char (point-min)) - (if (re-search-forward (concat "^" thread) nil t) - (beginning-of-line) - (goto-char here)))) + )) (defun notmuch-search-toggle-order () "Toggle the current search order. @@ -1314,7 +1628,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. @@ -1339,12 +1654,15 @@ current search results AND that are tagged with the given tag." (define-key map "?" 'notmuch-help) (define-key map "x" 'kill-this-buffer) (define-key map "q" 'kill-this-buffer) + (define-key map "m" 'message-mail) + (define-key map "e" 'notmuch-folder-show-empty-toggle) (define-key map ">" 'notmuch-folder-last) (define-key map "<" 'notmuch-folder-first) (define-key map "=" 'notmuch-folder) (define-key map "s" 'notmuch-search) (define-key map [mouse-1] 'notmuch-folder-show-search) (define-key map (kbd "RET") 'notmuch-folder-show-search) + (define-key map " " 'notmuch-folder-show-search) (define-key map "p" 'notmuch-folder-previous) (define-key map "n" 'notmuch-folder-next) map) @@ -1414,22 +1732,40 @@ Currently available key bindings: (goto-char (point-max)) (forward-line -1)) +(defun notmuch-folder-count (search) + (car (process-lines notmuch-command "count" search))) + +(setq notmuch-folder-show-empty t) + +(defun notmuch-folder-show-empty-toggle () + "Toggle the listing of empty folders" + (interactive) + (setq notmuch-folder-show-empty (not notmuch-folder-show-empty)) + (notmuch-folder)) + (defun notmuch-folder-add (folders) (if folders - (let ((name (car (car folders))) + (let* ((name (car (car folders))) (inhibit-read-only t) - (search (cdr (car folders)))) - (insert name) - (indent-to 16 1) - (call-process notmuch-command nil t nil "count" search) + (search (cdr (car folders))) + (count (notmuch-folder-count search))) + (if (or notmuch-folder-show-empty + (not (equal count "0"))) + (progn + (insert name) + (indent-to 16 1) + (insert count) + (insert "\n") + ) + ) (notmuch-folder-add (cdr folders))))) (defun notmuch-folder-find-name () (save-excursion (beginning-of-line) (let ((beg (point))) - (forward-word) - (filter-buffer-substring beg (point))))) + (re-search-forward "\\([ \t]*[^ \t]+\\)") + (filter-buffer-substring (match-beginning 1) (match-end 1))))) (defun notmuch-folder-show-search (&optional folder) "Show a search window for the search related to the specified folder."