X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/92a3826784cea982fd40d08c88fc038b779bb3dd..f98b752f204fbcd2a3ae8e57678e026470b34b27:/lisp/mail/rmailsum.el diff --git a/lisp/mail/rmailsum.el b/lisp/mail/rmailsum.el index 5f02284619..916782cb4b 100644 --- a/lisp/mail/rmailsum.el +++ b/lisp/mail/rmailsum.el @@ -1,6 +1,6 @@ ;;; rmailsum.el --- make summary buffers for the mail reader -;; Copyright (C) 1985, 1993 Free Software Foundation, Inc. +;; Copyright (C) 1985, 1993, 1994, 1995, 1996 Free Software Foundation, Inc. ;; Maintainer: FSF ;; Keywords: mail @@ -18,8 +18,9 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with GNU Emacs; see the file COPYING. If not, write to -;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. ;;; Commentary: @@ -29,6 +30,29 @@ ;;; Code: +;; For rmail-select-summary +(require 'rmail) + +;;;###autoload +(defcustom rmail-summary-scroll-between-messages t + "*Non-nil means Rmail summary scroll commands move between messages." + :type 'boolean + :group 'rmail-summary) + +;;;###autoload +(defcustom rmail-summary-line-count-flag t + "*Non-nil if Rmail summary should show the number of lines in each message." + :type 'boolean + :group 'rmail-summary) + +(defvar rmail-summary-font-lock-keywords + '(("^....D.*" . font-lock-string-face) ; Deleted. + ("^....-.*" . font-lock-type-face) ; Unread. + ;; Neither of the below will be highlighted if either of the above are: + ("^....[^D-] \\(......\\)" 1 font-lock-keyword-face) ; Date. + ("{ \\([^}]+\\),}" 1 font-lock-comment-face)) ; Labels. + "Additional expressions to highlight in Rmail Summary mode.") + ;; Entry points for making a summary buffer. ;; Regenerate the contents of the summary @@ -37,11 +61,13 @@ (defun rmail-update-summary (&rest ignore) (apply (car rmail-summary-redo) (cdr rmail-summary-redo))) +;;;###autoload (defun rmail-summary () "Display a summary of all messages, one line per message." (interactive) (rmail-new-summary "All" '(rmail-summary) nil)) +;;;###autoload (defun rmail-summary-by-labels (labels) "Display a summary of all messages with one or more LABELS. LABELS should be a string containing the desired labels, separated by commas." @@ -55,6 +81,7 @@ LABELS should be a string containing the desired labels, separated by commas." 'rmail-message-labels-p (concat ", \\(" (mail-comma-list-regexp labels) "\\),"))) +;;;###autoload (defun rmail-summary-by-recipients (recipients &optional primary-only) "Display a summary of all messages with the given RECIPIENTS. Normally checks the To, From and Cc fields of headers; @@ -68,6 +95,7 @@ RECIPIENTS is a string of regexps separated by commas." 'rmail-message-recipients-p (mail-comma-list-regexp recipients) primary-only)) +;;;###autoload (defun rmail-summary-by-regexp (regexp) "Display a summary of all messages according to regexp REGEXP. If the regular expression is found in the header of the message @@ -86,6 +114,7 @@ Emacs will list the header line in the RMAIL-summary." ;; rmail-summary-by-topic ;; 1989 R.A. Schnitzler +;;;###autoload (defun rmail-summary-by-topic (subject &optional whole-message) "Display a summary of all messages with the given SUBJECT. Normally checks the Subject field of headers; @@ -110,12 +139,14 @@ SUBJECT is a string of regexps separated by commas." (if whole-message (re-search-forward subject nil t) (string-match subject (or (mail-fetch-field "Subject") "")) ))) +;;;###autoload (defun rmail-summary-by-senders (senders) "Display a summary of all messages with the given SENDERS. SENDERS is a string of names separated by commas." (interactive "sSenders to summarize by: ") (rmail-new-summary (concat "senders " senders) + (list 'rmail-summary-by-senders senders) 'rmail-message-senders-p (mail-comma-list-regexp senders))) @@ -155,24 +186,29 @@ nil for FUNCTION means all messages." (let ((summary-msgs ()) (new-summary-line-count 0)) (let ((msgnum 1) - (buffer-read-only nil)) - (save-restriction - (save-excursion - (widen) - (goto-char (point-min)) - (while (>= rmail-total-messages msgnum) - (if (or (null function) - (apply function (cons msgnum args))) - (setq summary-msgs - (cons (cons msgnum (rmail-make-summary-line msgnum)) - summary-msgs))) - (setq msgnum (1+ msgnum))) - (setq summary-msgs (nreverse summary-msgs))))) + (buffer-read-only nil) + (old-min (point-min-marker)) + (old-max (point-max-marker))) + ;; Can't use save-restriction here; that doesn't work if we + ;; plan to modify text outside the original restriction. + (save-excursion + (widen) + (goto-char (point-min)) + (while (>= rmail-total-messages msgnum) + (if (or (null function) + (apply function (cons msgnum args))) + (setq summary-msgs + (cons (cons msgnum (rmail-make-summary-line msgnum)) + summary-msgs))) + (setq msgnum (1+ msgnum))) + (setq summary-msgs (nreverse summary-msgs))) + (narrow-to-region old-min old-max)) ;; Temporarily, while summary buffer is unfinished, ;; we "don't have" a summary. (setq rmail-summary-buffer nil) (save-excursion (let ((rbuf (current-buffer)) + (vbuf rmail-view-buffer) (total rmail-total-messages)) (set-buffer sumbuf) ;; Set up the summary buffer's contents. @@ -186,15 +222,34 @@ nil for FUNCTION means all messages." (setq buffer-read-only t) (rmail-summary-mode) (make-local-variable 'minor-mode-alist) - (setq minor-mode-alist (list '(t (concat ": " description)))) + (setq minor-mode-alist (list (list t (concat ": " description)))) (setq rmail-buffer rbuf + rmail-view-buffer vbuf rmail-summary-redo redo-form rmail-total-messages total)))) (setq rmail-summary-buffer sumbuf)) ;; Now display the summary buffer and go to the right place in it. (or was-in-summary - (pop-to-buffer sumbuf)) + (progn + (if (and (one-window-p) + pop-up-windows (not pop-up-frames)) + ;; If there is just one window, put the summary on the top. + (progn + (split-window (selected-window) rmail-summary-window-size) + (select-window (next-window (frame-first-window))) + (pop-to-buffer sumbuf) + ;; If pop-to-buffer did not use that window, delete that + ;; window. (This can happen if it uses another frame.) + (if (not (eq sumbuf (window-buffer (frame-first-window)))) + (delete-other-windows))) + (pop-to-buffer sumbuf)) + (set-buffer rmail-buffer) + ;; This is how rmail makes the summary buffer reappear. + ;; We do this here to make the window the proper size. + (rmail-select-summary nil) + (set-buffer rmail-summary-buffer))) (rmail-summary-goto-msg mesg t t) + (rmail-summary-construct-io-menu) (message "Computing summary lines...done"))) ;; Low levels of generating a summary. @@ -215,6 +270,14 @@ nil for FUNCTION means all messages." ?\- ?\ ))) line)) +;;;###autoload +(defcustom rmail-summary-line-decoder (function identity) + "*Function to decode summary-line. + +By default, `identity' is set." + :type 'function + :group 'rmail-summary) + (defun rmail-make-summary-line-1 (msg) (goto-char (rmail-msgbeg msg)) (let* ((lim (save-excursion (forward-line 2) (point))) @@ -269,10 +332,12 @@ nil for FUNCTION means all messages." (insert "Summary-line: " line))) (setq pos (string-match "#" line)) (aset rmail-summary-vector (1- msg) - (concat (format "%4d " msg) - (substring line 0 pos) - labels - (substring line (1+ pos)))))) + (funcall rmail-summary-line-decoder + (concat (format "%4d " msg) + (substring line 0 pos) + labels + (substring line (1+ pos))))) + )) (defun rmail-make-basic-summary-line () (goto-char (point-min)) @@ -295,6 +360,15 @@ nil for FUNCTION means all messages." (match-end 4))) (buffer-substring (match-beginning 2) (match-end 2)))) + ((re-search-forward "\\(19\\|20\\)\\([0-9][0-9]\\)-\\([01][0-9]\\)-\\([0-3][0-9]\\)" + (save-excursion (end-of-line) (point)) t) + (format "%2s%2s%2s" + (buffer-substring + (match-beginning 2) (match-end 2)) + (buffer-substring + (match-beginning 3) (match-end 3)) + (buffer-substring + (match-beginning 4) (match-end 4)))) (t "??????")))) " " (save-excursion @@ -303,13 +377,29 @@ nil for FUNCTION means all messages." (let* ((from (mail-strip-quoted-names (buffer-substring (1- (point)) - (progn (end-of-line) - (skip-chars-backward " \t") - (point))))) - len mch lo) - (if (string-match (concat "^" + ;; Get all the lines of the From field + ;; so that we get a whole comment if there is one, + ;; so that mail-strip-quoted-names can discard it. + (let ((opoint (point))) + (while (progn (forward-line 1) + (looking-at "[ \t]"))) + ;; Back up over newline, then trailing spaces or tabs + (forward-char -1) + (skip-chars-backward " \t") + (point))))) + len mch lo) + (if (string-match (concat "^\\(" (regexp-quote (user-login-name)) - "\\($\\|@\\)") + "\\($\\|@\\)\\|" + (regexp-quote + ;; Don't lose if run from init file + ;; where user-mail-address is not + ;; set yet. + (or user-mail-address + (concat (user-login-name) "@" + (or mail-host-address + (system-name))))) + "\\>\\)") from) (save-excursion (goto-char (point-min)) @@ -329,12 +419,32 @@ nil for FUNCTION means all messages." (if (or (not mch) (<= len 25)) (substring from (max 0 (- len 25))) (substring from - (setq lo (cond ((< (- mch 9) 0) 0) - ((< len (+ mch 16)) + (setq lo (cond ((< (- mch 14) 0) 0) + ((< len (+ mch 11)) (- len 25)) - (t (- mch 9)))) + (t (- mch 14)))) (min len (+ lo 25)))))))) - " #" + (if rmail-summary-line-count-flag + (save-excursion + (save-restriction + (widen) + (let ((beg (rmail-msgbeg msgnum)) + (end (rmail-msgend msgnum)) + lines) + (save-excursion + (goto-char beg) + ;; Count only lines in the reformatted header, + ;; if we have reformatted it. + (search-forward "\n*** EOOH ***\n" end t) + (setq lines (count-lines (point) end))) + (format (cond + ((<= lines 9) " [%d]") + ((<= lines 99) " [%d]") + ((<= lines 999) " [%3d]") + (t "[%d]")) + lines)))) + " ") + " #" ;The # is part of the format. (if (re-search-forward "^Subject:" nil t) (progn (skip-chars-forward " \t") (buffer-substring (point) @@ -349,11 +459,17 @@ nil for FUNCTION means all messages." (defun rmail-summary-next-all (&optional number) (interactive "p") (forward-line (if number number 1)) + ;; It doesn't look nice to move forward past the last message line. + (and (eobp) (> number 0) + (forward-line -1)) (display-buffer rmail-buffer)) (defun rmail-summary-previous-all (&optional number) (interactive "p") (forward-line (- (if number number 1))) + ;; It doesn't look nice to move forward past the last message line. + (and (eobp) (< number 0) + (forward-line -1)) (display-buffer rmail-buffer)) (defun rmail-summary-next-msg (&optional number) @@ -371,7 +487,8 @@ messages, or backward if NUMBER is negative." non-del-msg-found))) (setq count (1- count)))) (beginning-of-line) - (display-buffer rmail-buffer)) + (display-buffer rmail-view-buffer) + ) (defun rmail-summary-previous-msg (&optional number) (interactive "p") @@ -381,45 +498,122 @@ messages, or backward if NUMBER is negative." "Show next message with LABEL. Defaults to last labels used. With prefix argument N moves forward N messages with these labels." (interactive "p\nsMove to next msg with labels: ") - (save-excursion - (set-buffer rmail-buffer) - (rmail-next-labeled-message n labels))) + (let (msg) + (save-excursion + (set-buffer rmail-buffer) + (rmail-next-labeled-message n labels) + (setq msg rmail-current-message)) + (rmail-summary-goto-msg msg))) (defun rmail-summary-previous-labeled-message (n labels) "Show previous message with LABEL. Defaults to last labels used. With prefix argument N moves backward N messages with these labels." (interactive "p\nsMove to previous msg with labels: ") - (save-excursion - (set-buffer rmail-buffer) - (rmail-previous-labeled-message n labels))) + (let (msg) + (save-excursion + (set-buffer rmail-buffer) + (rmail-previous-labeled-message n labels) + (setq msg rmail-current-message)) + (rmail-summary-goto-msg msg))) + +(defun rmail-summary-next-same-subject (n) + "Go to the next message in the summary having the same subject. +With prefix argument N, do this N times. +If N is negative, go backwards." + (interactive "p") + (let (subject search-regexp i found + (forward (> n 0))) + (save-excursion + (set-buffer rmail-buffer) + (setq subject (mail-fetch-field "Subject")) + (setq i rmail-current-message)) + (if (string-match "Re:[ \t]*" subject) + (setq subject (substring subject (match-end 0)))) + (setq search-regexp (concat "^Subject: *\\(Re: *\\)?" + (regexp-quote subject) + "\n")) + (save-excursion + (while (and (/= n 0) + (if forward + (not (eobp)) + (not (bobp)))) + (let (done) + (while (and (not done) + (if forward + (not (eobp)) + (not (bobp)))) + ;; Advance thru summary. + (forward-line (if forward 1 -1)) + ;; Get msg number of this line. + (setq i (string-to-int + (buffer-substring (point) + (min (point-max) (+ 5 (point)))))) + ;; See if that msg has desired subject. + (save-excursion + (set-buffer rmail-buffer) + (save-restriction + (widen) + (goto-char (rmail-msgbeg i)) + (search-forward "\n*** EOOH ***\n") + (let ((beg (point)) end) + (search-forward "\n\n") + (setq end (point)) + (goto-char beg) + (setq done (re-search-forward search-regexp end t)))))) + (if done (setq found i))) + (setq n (if forward (1- n) (1+ n))))) + (if found + (rmail-summary-goto-msg found) + (error "No %s message with same subject" + (if forward "following" "previous"))))) + +(defun rmail-summary-previous-same-subject (n) + "Go to the previous message in the summary having the same subject. +With prefix argument N, do this N times. +If N is negative, go forwards instead." + (interactive "p") + (rmail-summary-next-same-subject (- n))) ;; Delete and undelete summary commands. -(defun rmail-summary-delete-forward (&optional backward) +(defun rmail-summary-delete-forward (&optional count) "Delete this message and move to next nondeleted one. Deleted messages stay in the file until the \\[rmail-expunge] command is given. -With prefix argument, delete and move backward." - (interactive "P") - (let (end) - (rmail-summary-goto-msg) - (pop-to-buffer rmail-buffer) - (rmail-delete-message) - (let ((del-msg rmail-current-message)) - (pop-to-buffer rmail-summary-buffer) +A prefix argument serves as a repeat count; +a negative argument means to delete and move backward." + (interactive "p") + (unless (numberp count) (setq count 1)) + (let (end del-msg + (backward (< count 0))) + (while (/= count 0) + (rmail-summary-goto-msg) + (with-current-buffer rmail-buffer + (rmail-delete-message) + (setq del-msg rmail-current-message)) (rmail-summary-mark-deleted del-msg) (while (and (not (if backward (bobp) (eobp))) - (save-excursion (beginning-of-line) (looking-at " [0-9]+D"))) - (forward-line (if backward -1 1)))))) - -(defun rmail-summary-delete-backward () + (save-excursion (beginning-of-line) + (looking-at " *[0-9]+D"))) + (forward-line (if backward -1 1))) + ;; It looks ugly to move to the empty line at end of buffer. + (and (eobp) (not backward) + (forward-line -1)) + (setq count + (if (> count 0) (1- count) (1+ count)))))) + +(defun rmail-summary-delete-backward (&optional count) "Delete this message and move to previous nondeleted one. -Deleted messages stay in the file until the \\[rmail-expunge] command is given." - (interactive) - (rmail-summary-delete-forward t)) +Deleted messages stay in the file until the \\[rmail-expunge] command is given. +A prefix argument serves as a repeat count; +a negative argument means to delete and move forward." + (interactive "p") + (rmail-summary-delete-forward (- count))) (defun rmail-summary-mark-deleted (&optional n undel) + ;; Since third arg is t, this only alters the summary, not the Rmail buf. (and n (rmail-summary-goto-msg n t t)) (or (eobp) + (not (overlay-get rmail-summary-overlay 'face)) (let ((buffer-read-only nil)) (skip-chars-forward " ") (skip-chars-forward "[0-9]") @@ -446,7 +640,8 @@ Optional prefix ARG means undelete ARG previous messages." (interactive "p") (if (/= arg 1) (rmail-summary-undelete-many arg) - (let ((buffer-read-only nil)) + (let ((buffer-read-only nil) + (opoint (point))) (end-of-line) (cond ((re-search-backward "\\(^ *[0-9]*\\)\\(D\\)" nil t) (replace-match "\\1 ") @@ -454,7 +649,8 @@ Optional prefix ARG means undelete ARG previous messages." (pop-to-buffer rmail-buffer) (and (rmail-message-deleted-p rmail-current-message) (rmail-undelete-previous-message)) - (pop-to-buffer rmail-summary-buffer)))))) + (pop-to-buffer rmail-summary-buffer)) + (t (goto-char opoint)))))) (defun rmail-summary-undelete-many (&optional n) "Undelete all deleted msgs, optional prefix arg N means undelete N prev msgs." @@ -505,59 +701,107 @@ Commands for sorting the summary: \\[rmail-summary-sort-by-author] Sort by author. \\[rmail-summary-sort-by-recipient] Sort by recipient. \\[rmail-summary-sort-by-correspondent] Sort by correspondent. -\\[rmail-summary-sort-by-lines] Sort by lines." +\\[rmail-summary-sort-by-lines] Sort by lines. +\\[rmail-summary-sort-by-keywords] Sort by keywords." (interactive) (kill-all-local-variables) (setq major-mode 'rmail-summary-mode) (setq mode-name "RMAIL Summary") - (use-local-map rmail-summary-mode-map) (setq truncate-lines t) (setq buffer-read-only t) (set-syntax-table text-mode-syntax-table) (make-local-variable 'rmail-buffer) + (make-local-variable 'rmail-view-buffer) (make-local-variable 'rmail-total-messages) (make-local-variable 'rmail-current-message) (setq rmail-current-message nil) (make-local-variable 'rmail-summary-redo) (setq rmail-summary-redo nil) (make-local-variable 'revert-buffer-function) - (setq revert-buffer-function 'rmail-update-summary) - (make-local-variable 'post-command-hook) - (add-hook 'post-command-hook 'rmail-summary-rmail-update) + (make-local-hook 'post-command-hook) + (make-local-variable 'font-lock-defaults) + (setq font-lock-defaults '(rmail-summary-font-lock-keywords t)) + (rmail-summary-enable) (run-hooks 'rmail-summary-mode-hook)) +;; Summary features need to be disabled during edit mode. +(defun rmail-summary-disable () + (use-local-map text-mode-map) + (remove-hook 'post-command-hook 'rmail-summary-rmail-update t) + (setq revert-buffer-function nil)) + +(defun rmail-summary-enable () + (use-local-map rmail-summary-mode-map) + (add-hook 'post-command-hook 'rmail-summary-rmail-update nil t) + (setq revert-buffer-function 'rmail-update-summary)) + +(defvar rmail-summary-put-back-unseen nil + "Used for communicating between calls to `rmail-summary-rmail-update'. +If it moves to a message within an Incremental Search, and removes +the `unseen' attribute from that message, it sets this flag +so that if the next motion between messages is in the same Incremental +Search, the `unseen' attribute is restored.") + ;; Show in Rmail the message described by the summary line that point is on, ;; but only if the Rmail buffer is already visible. ;; This is a post-command-hook in summary buffers. (defun rmail-summary-rmail-update () - (if (get-buffer-window rmail-buffer) - (let (buffer-read-only) - (save-excursion - ;; If at end of buffer, pretend we are on the last text line. - (if (eobp) - (forward-line -1)) - (beginning-of-line) - (skip-chars-forward " ") - (let ((beg (point)) - msg-num - (buf rmail-buffer)) - (skip-chars-forward "0-9") - (setq msg-num (string-to-int (buffer-substring beg (point)))) - (or (eq rmail-current-message msg-num) - (let (go-where window (owin (selected-window))) - (setq rmail-current-message msg-num) - (if (= (following-char) ?-) - (progn - (delete-char 1) - (insert " "))) - (setq window (display-buffer rmail-buffer)) - ;; Using save-window-excursion caused the new value + (let (buffer-read-only) + (save-excursion + ;; If at end of buffer, pretend we are on the last text line. + (if (eobp) + (forward-line -1)) + (beginning-of-line) + (skip-chars-forward " ") + (let ((msg-num (string-to-int (buffer-substring + (point) + (progn (skip-chars-forward "0-9") + (point)))))) + ;; Always leave `unseen' removed + ;; if we get out of isearch mode. + ;; Don't let a subsequent isearch restore that `unseen'. + (if (not isearch-mode) + (setq rmail-summary-put-back-unseen nil)) + + (or (eq rmail-current-message msg-num) + (let ((window (get-buffer-window rmail-view-buffer t)) + (owin (selected-window))) + (if isearch-mode + (save-excursion + (set-buffer rmail-buffer) + ;; If we first saw the previous message in this search, + ;; and we have gone to a different message while searching, + ;; put back `unseen' on the former one. + (rmail-set-attribute "unseen" t + rmail-current-message) + ;; Arrange to do that later, for the new current message, + ;; if it still has `unseen'. + (setq rmail-summary-put-back-unseen + (rmail-message-labels-p msg-num ", ?\\(unseen\\),"))) + (setq rmail-summary-put-back-unseen nil)) + + ;; Go to the desired message. + (setq rmail-current-message msg-num) + + ;; Update the summary to show the message has been seen. + (if (= (following-char) ?-) + (progn + (delete-char 1) + (insert " "))) + + (if window + ;; Using save-window-excursion would cause the new value ;; of point to get lost. (unwind-protect (progn (select-window window) - (rmail-show-message msg-num)) - (select-window owin))))))))) + (rmail-show-message msg-num t)) + (select-window owin)) + (if (buffer-name rmail-buffer) + (save-excursion + (set-buffer rmail-buffer) + (rmail-show-message msg-num t)))))) + (rmail-summary-update-highlight nil))))) (defvar rmail-summary-mode-map nil) @@ -565,7 +809,10 @@ Commands for sorting the summary: nil (setq rmail-summary-mode-map (make-keymap)) (suppress-keymap rmail-summary-mode-map) + + (define-key rmail-summary-mode-map [mouse-2] 'rmail-summary-mouse-goto-message) (define-key rmail-summary-mode-map "a" 'rmail-summary-add-label) + (define-key rmail-summary-mode-map "b" 'rmail-summary-bury) (define-key rmail-summary-mode-map "c" 'rmail-summary-continue) (define-key rmail-summary-mode-map "d" 'rmail-summary-delete-forward) (define-key rmail-summary-mode-map "\C-d" 'rmail-summary-delete-backward) @@ -607,6 +854,8 @@ Commands for sorting the summary: (define-key rmail-summary-mode-map " " 'rmail-summary-scroll-msg-up) (define-key rmail-summary-mode-map "\177" 'rmail-summary-scroll-msg-down) (define-key rmail-summary-mode-map "?" 'describe-mode) + (define-key rmail-summary-mode-map "\C-c\C-n" 'rmail-summary-next-same-subject) + (define-key rmail-summary-mode-map "\C-c\C-p" 'rmail-summary-previous-same-subject) (define-key rmail-summary-mode-map "\C-c\C-s\C-d" 'rmail-summary-sort-by-date) (define-key rmail-summary-mode-map "\C-c\C-s\C-s" @@ -619,6 +868,8 @@ Commands for sorting the summary: 'rmail-summary-sort-by-correspondent) (define-key rmail-summary-mode-map "\C-c\C-s\C-l" 'rmail-summary-sort-by-lines) + (define-key rmail-summary-mode-map "\C-c\C-s\C-k" + 'rmail-summary-sort-by-keywords) ) ;;; Menu bar bindings. @@ -628,32 +879,47 @@ Commands for sorting the summary: (define-key rmail-summary-mode-map [menu-bar classify] (cons "Classify" (make-sparse-keymap "Classify"))) +(define-key rmail-summary-mode-map [menu-bar classify output-menu] + '("Output (Rmail Menu)..." . rmail-summary-output-menu)) + +(define-key rmail-summary-mode-map [menu-bar classify input-menu] + '("Input Rmail File (menu)..." . rmail-input-menu)) + +(define-key rmail-summary-mode-map [menu-bar classify input-menu] + '(nil)) + +(define-key rmail-summary-mode-map [menu-bar classify output-menu] + '(nil)) + (define-key rmail-summary-mode-map [menu-bar classify output-inbox] - '("Output (inbox)" . rmail-summary-output)) + '("Output (inbox)..." . rmail-summary-output)) (define-key rmail-summary-mode-map [menu-bar classify output] - '("Output (Rmail)" . rmail-summary-output-to-rmail-file)) + '("Output (Rmail)..." . rmail-summary-output-to-rmail-file)) (define-key rmail-summary-mode-map [menu-bar classify kill-label] - '("Kill Label" . rmail-summary-kill-label)) + '("Kill Label..." . rmail-summary-kill-label)) (define-key rmail-summary-mode-map [menu-bar classify add-label] - '("Add Label" . rmail-summary-add-label)) + '("Add Label..." . rmail-summary-add-label)) (define-key rmail-summary-mode-map [menu-bar summary] (cons "Summary" (make-sparse-keymap "Summary"))) +(define-key rmail-summary-mode-map [menu-bar summary senders] + '("By Senders..." . rmail-summary-by-senders)) + (define-key rmail-summary-mode-map [menu-bar summary labels] - '("By Labels" . rmail-summary-by-labels)) + '("By Labels..." . rmail-summary-by-labels)) (define-key rmail-summary-mode-map [menu-bar summary recipients] - '("By Recipients" . rmail-summary-by-recipients)) + '("By Recipients..." . rmail-summary-by-recipients)) (define-key rmail-summary-mode-map [menu-bar summary topic] - '("By Topic" . rmail-summary-by-topic)) + '("By Topic..." . rmail-summary-by-topic)) (define-key rmail-summary-mode-map [menu-bar summary regexp] - '("By Regexp" . rmail-summary-by-regexp)) + '("By Regexp..." . rmail-summary-by-regexp)) (define-key rmail-summary-mode-map [menu-bar summary all] '("All" . rmail-summary)) @@ -661,9 +927,18 @@ Commands for sorting the summary: (define-key rmail-summary-mode-map [menu-bar mail] (cons "Mail" (make-sparse-keymap "Mail"))) +(define-key rmail-summary-mode-map [menu-bar mail rmail-summary-get-new-mail] + '("Get New Mail" . rmail-summary-get-new-mail)) + +(define-key rmail-summary-mode-map [menu-bar mail lambda] + '("----")) + (define-key rmail-summary-mode-map [menu-bar mail continue] '("Continue" . rmail-summary-continue)) +(define-key rmail-summary-mode-map [menu-bar mail resend] + '("Re-send..." . rmail-summary-resend)) + (define-key rmail-summary-mode-map [menu-bar mail forward] '("Forward" . rmail-summary-forward)) @@ -695,10 +970,10 @@ Commands for sorting the summary: (cons "Move" (make-sparse-keymap "Move"))) (define-key rmail-summary-mode-map [menu-bar move search-back] - '("Search Back" . rmail-summary-search-backward)) + '("Search Back..." . rmail-summary-search-backward)) (define-key rmail-summary-mode-map [menu-bar move search] - '("Search" . rmail-summary-search)) + '("Search..." . rmail-summary-search)) (define-key rmail-summary-mode-map [menu-bar move previous] '("Previous Nondeleted" . rmail-summary-previous-msg)) @@ -718,29 +993,52 @@ Commands for sorting the summary: (define-key rmail-summary-mode-map [menu-bar move next] '("Next" . rmail-summary-next-all)) +(defvar rmail-summary-overlay nil) +(put 'rmail-summary-overlay 'permanent-local t) + +(defun rmail-summary-mouse-goto-message (event) + "Select the message whose summary line you click on." + (interactive "@e") + (goto-char (posn-point (event-end event))) + (rmail-summary-goto-msg)) + (defun rmail-summary-goto-msg (&optional n nowarn skip-rmail) + "Go to message N in the summary buffer and the Rmail buffer. +If N is nil, use the message corresponding to point in the summary +and move to that message in the Rmail buffer. + +If NOWARN, don't say anything if N is out of range. +If SKIP-RMAIL, don't do anything to the Rmail buffer." (interactive "P") (if (consp n) (setq n (prefix-numeric-value n))) (if (eobp) (forward-line -1)) (beginning-of-line) - (let ((buf rmail-buffer) - (cur (point)) - (curmsg (string-to-int - (buffer-substring (point) - (min (point-max) (+ 5 (point))))))) + (let* ((obuf (current-buffer)) + (buf rmail-buffer) + (cur (point)) + message-not-found + (curmsg (string-to-int + (buffer-substring (point) + (min (point-max) (+ 5 (point)))))) + (total (save-excursion (set-buffer buf) rmail-total-messages))) + ;; If message number N was specified, find that message's line + ;; or set message-not-found. + ;; If N wasn't specified or that message can't be found. + ;; set N by default. (if (not n) (setq n curmsg) (if (< n 1) (progn (message "No preceding message") (setq n 1))) - (if (> n rmail-total-messages) + (if (> n total) (progn (message "No following message") (goto-char (point-max)) - (rmail-summary-goto-msg))) + (rmail-summary-goto-msg nil nowarn skip-rmail))) (goto-char (point-min)) - (if (not (re-search-forward (concat "^ *" (int-to-string n)) nil t)) + (if (not (re-search-forward (format "^%4d[^0-9]" n) nil t)) (progn (or nowarn (message "Message %d not found" n)) (setq n curmsg) + (setq message-not-found t) (goto-char cur)))) (beginning-of-line) (skip-chars-forward " ") @@ -749,6 +1047,7 @@ Commands for sorting the summary: (let ((buffer-read-only nil)) (delete-char 1) (insert " ")))) + (rmail-summary-update-highlight message-not-found) (beginning-of-line) (if skip-rmail nil @@ -756,28 +1055,111 @@ Commands for sorting the summary: (unwind-protect (progn (pop-to-buffer buf) (rmail-show-message n)) - (select-window selwin)))))) + (select-window selwin) + ;; The actions above can alter the current buffer. Preserve it. + (set-buffer obuf)))))) + +;; Update the highlighted line in an rmail summary buffer. +;; That should be current. We highlight the line point is on. +;; If NOT-FOUND is non-nil, we turn off highlighting. +(defun rmail-summary-update-highlight (not-found) + ;; Make sure we have an overlay to use. + (or rmail-summary-overlay + (progn + (make-local-variable 'rmail-summary-overlay) + (setq rmail-summary-overlay (make-overlay (point) (point))))) + ;; If this message is in the summary, use the overlay to highlight it. + ;; Otherwise, don't highlight anything. + (if not-found + (overlay-put rmail-summary-overlay 'face nil) + (move-overlay rmail-summary-overlay + (save-excursion (beginning-of-line) + (skip-chars-forward " ") + (point)) + (save-excursion (end-of-line) (point))) + (overlay-put rmail-summary-overlay 'face 'highlight))) (defun rmail-summary-scroll-msg-up (&optional dist) - "Scroll other window forward." + "Scroll the Rmail window forward. +If the Rmail window is displaying the end of a message, +advance to the next message." (interactive "P") - (scroll-other-window dist)) + (if (eq dist '-) + (rmail-summary-scroll-msg-down nil) + (let ((rmail-buffer-window (get-buffer-window rmail-view-buffer))) + (if rmail-buffer-window + (if (let ((rmail-summary-window (selected-window))) + (select-window rmail-buffer-window) + (prog1 + ;; Is EOB visible in the buffer? + (save-excursion + (let ((ht (window-height (selected-window)))) + (move-to-window-line (- ht 2)) + (end-of-line) + (eobp))) + (select-window rmail-summary-window))) + (if (not rmail-summary-scroll-between-messages) + (error "End of buffer") + (rmail-summary-next-msg (or dist 1))) + (let ((other-window-scroll-buffer rmail-view-buffer)) + (scroll-other-window dist))) + ;; If it isn't visible at all, show the beginning. + (rmail-summary-beginning-of-message))))) (defun rmail-summary-scroll-msg-down (&optional dist) - "Scroll other window backward." + "Scroll the Rmail window backward. +If the Rmail window is now displaying the beginning of a message, +move to the previous message." (interactive "P") - (scroll-other-window - (cond ((eq dist '-) nil) - ((null dist) '-) - (t (- (prefix-numeric-value dist)))))) + (if (eq dist '-) + (rmail-summary-scroll-msg-up nil) + (let ((rmail-buffer-window (get-buffer-window rmail-buffer))) + (if rmail-buffer-window + (if (let ((rmail-summary-window (selected-window))) + (select-window rmail-buffer-window) + (prog1 + ;; Is BOB visible in the buffer? + (save-excursion + (move-to-window-line 0) + (beginning-of-line) + (bobp)) + (select-window rmail-summary-window))) + (if (not rmail-summary-scroll-between-messages) + (error "Beginning of buffer") + (rmail-summary-previous-msg (or dist 1))) + (let ((other-window-scroll-buffer rmail-buffer)) + (scroll-other-window-down dist))) + ;; If it isn't visible at all, show the beginning. + (rmail-summary-beginning-of-message))))) (defun rmail-summary-beginning-of-message () "Show current message from the beginning." (interactive) - (pop-to-buffer rmail-buffer) + (if (and (one-window-p) (not pop-up-frames)) + ;; If there is just one window, put the summary on the top. + (let ((buffer rmail-buffer)) + (split-window (selected-window) rmail-summary-window-size) + (select-window (frame-first-window)) + (pop-to-buffer rmail-buffer) + ;; If pop-to-buffer did not use that window, delete that + ;; window. (This can happen if it uses another frame.) + (or (eq buffer (window-buffer (next-window (frame-first-window)))) + (delete-other-windows))) + (pop-to-buffer rmail-buffer)) (beginning-of-buffer) (pop-to-buffer rmail-summary-buffer)) +(defun rmail-summary-bury () + "Bury the Rmail buffer and the Rmail summary buffer." + (interactive) + (let ((buffer-to-bury (current-buffer))) + (let (window) + (while (setq window (get-buffer-window rmail-buffer)) + (set-window-buffer window (other-buffer rmail-buffer))) + (bury-buffer rmail-buffer)) + (switch-to-buffer (other-buffer buffer-to-bury)) + (bury-buffer buffer-to-bury))) + (defun rmail-summary-quit () "Quit out of Rmail and Rmail summary." (interactive) @@ -813,19 +1195,28 @@ Commands for sorting the summary: (rmail-update-summary) (save-excursion (set-buffer rmail-buffer) - (save-buffer))) + (save-buffer)) + (set-buffer-modified-p nil)) -(defun rmail-summary-get-new-mail () - "Get new mail and recompute summary headers." - (interactive) +(defun rmail-summary-get-new-mail (&optional file-name) + "Get new mail and recompute summary headers. + +Optionally you can specify the file to get new mail from. In this case, +the file of new mail is not changed or deleted. Noninteractively, you can +pass the inbox file name as an argument. Interactively, a prefix +argument says to read a file name and use that file as the inbox." + (interactive + (list (if current-prefix-arg + (read-file-name "Get new mail from file: ")))) (let (msg) (save-excursion (set-buffer rmail-buffer) - (rmail-get-new-mail) + (rmail-get-new-mail file-name) ;; Get the proper new message number. (setq msg rmail-current-message)) ;; Make sure that message is displayed. - (rmail-summary-goto-msg msg))) + (or (zerop msg) + (rmail-summary-goto-msg msg)))) (defun rmail-summary-input (filename) "Run Rmail on file FILENAME." @@ -940,7 +1331,19 @@ Interactively, empty argument means use same regexp used last time." (interactive) (save-excursion (set-buffer rmail-buffer) - (rmail-toggle-header))) + (rmail-toggle-header)) + ;; Inside save-excursion, some changes to point in the RMAIL buffer are lost. + ;; Set point to point-min in the RMAIL buffer, if it is visible. + (let ((window (get-buffer-window rmail-buffer))) + (if window + ;; Using save-window-excursion would lose the new value of point. + (let ((owin (selected-window))) + (unwind-protect + (progn + (select-window window) + (goto-char (point-min))) + (select-window owin)))))) + (defun rmail-summary-add-label (label) "Add LABEL to labels associated with current Rmail message. @@ -969,7 +1372,11 @@ Completion is performed over known labels when reading." While composing the message, use \\[mail-yank-original] to yank the original message into it." (interactive) - (mail-other-window nil nil nil nil nil rmail-buffer) + (let ((window (get-buffer-window rmail-buffer))) + (if window + (select-window window) + (set-buffer rmail-buffer))) + (rmail-start-mail nil nil nil nil nil (current-buffer)) (use-local-map (copy-keymap (current-local-map))) (define-key (current-local-map) "\C-c\C-c" 'rmail-summary-send-and-exit)) @@ -977,39 +1384,40 @@ original message into it." (defun rmail-summary-continue () "Continue composing outgoing message previously being composed." (interactive) - (mail-other-window t)) + (let ((window (get-buffer-window rmail-buffer))) + (if window + (select-window window) + (set-buffer rmail-buffer))) + (rmail-start-mail t)) (defun rmail-summary-reply (just-sender) "Reply to the current message. Normally include CC: to all other recipients of original message; -prefix argument means ignore them. -While composing the reply, use \\[mail-yank-original] to yank the -original message into it." +prefix argument means ignore them. While composing the reply, +use \\[mail-yank-original] to yank the original message into it." (interactive "P") - (let (mailbuf) - (save-window-excursion - (set-buffer rmail-buffer) - (rmail-reply just-sender) - (setq mailbuf (current-buffer))) - (pop-to-buffer mailbuf) - (use-local-map (copy-keymap (current-local-map))) - (define-key (current-local-map) - "\C-c\C-c" 'rmail-summary-send-and-exit))) + (let ((window (get-buffer-window rmail-buffer))) + (if window + (select-window window) + (set-buffer rmail-buffer))) + (rmail-reply just-sender) + (use-local-map (copy-keymap (current-local-map))) + (define-key (current-local-map) + "\C-c\C-c" 'rmail-summary-send-and-exit)) (defun rmail-summary-retry-failure () "Edit a mail message which is based on the contents of the current message. For a message rejected by the mail system, extract the interesting headers and the body of the original message; otherwise copy the current message." (interactive) - (let (mailbuf) - (save-window-excursion - (set-buffer rmail-buffer) - (rmail-retry-failure) - (setq mailbuf (current-buffer))) - (pop-to-buffer mailbuf) - (use-local-map (copy-keymap (current-local-map))) - (define-key (current-local-map) - "\C-c\C-c" 'rmail-summary-send-and-exit))) + (let ((window (get-buffer-window rmail-buffer))) + (if window + (select-window window) + (set-buffer rmail-buffer))) + (rmail-retry-failure) + (use-local-map (copy-keymap (current-local-map))) + (define-key (current-local-map) + "\C-c\C-c" 'rmail-summary-send-and-exit)) (defun rmail-summary-send-and-exit () "Send mail reply and return to summary buffer." @@ -1022,15 +1430,28 @@ With prefix argument, \"resend\" the message instead of forwarding it; see the documentation of `rmail-resend'." (interactive "P") (save-excursion - (set-buffer rmail-buffer) + (let ((window (get-buffer-window rmail-buffer))) + (if window + (select-window window) + (set-buffer rmail-buffer))) (rmail-forward resend) (use-local-map (copy-keymap (current-local-map))) (define-key (current-local-map) "\C-c\C-c" 'rmail-summary-send-and-exit))) + +(defun rmail-summary-resend () + "Resend current message using 'rmail-resend'." + (interactive) + (save-excursion + (let ((window (get-buffer-window rmail-buffer))) + (if window + (select-window window) + (set-buffer rmail-buffer))) + (call-interactively 'rmail-resend))) ;; Summary output commands. -(defun rmail-summary-output-to-rmail-file () +(defun rmail-summary-output-to-rmail-file (&optional file-name) "Append the current message to an Rmail file named FILE-NAME. If the file does not exist, ask if it should be created. If file is being visited, the message is appended to the Emacs @@ -1038,14 +1459,55 @@ buffer visiting that file." (interactive) (save-excursion (set-buffer rmail-buffer) - (call-interactively 'rmail-output-to-rmail-file))) + (let ((rmail-delete-after-output nil)) + (if file-name + (rmail-output-to-rmail-file file-name) + (call-interactively 'rmail-output-to-rmail-file)))) + (if rmail-delete-after-output + (rmail-summary-delete-forward nil))) + +(defun rmail-summary-output-menu () + "Output current message to another Rmail file, chosen with a menu. +Also set the default for subsequent \\[rmail-output-to-rmail-file] commands. +The variables `rmail-secondary-file-directory' and +`rmail-secondary-file-regexp' control which files are offered in the menu." + (interactive) + (save-excursion + (set-buffer rmail-buffer) + (let ((rmail-delete-after-output nil)) + (call-interactively 'rmail-output-menu))) + (if rmail-delete-after-output + (rmail-summary-delete-forward nil))) (defun rmail-summary-output () "Append this message to Unix mail file named FILE-NAME." (interactive) (save-excursion (set-buffer rmail-buffer) - (call-interactively 'rmail-output))) + (let ((rmail-delete-after-output nil)) + (call-interactively 'rmail-output))) + (if rmail-delete-after-output + (rmail-summary-delete-forward nil))) + +(defun rmail-summary-construct-io-menu () + (let ((files (rmail-find-all-files rmail-secondary-file-directory))) + (if files + (progn + (define-key rmail-summary-mode-map [menu-bar classify input-menu] + (cons "Input Rmail File" + (rmail-list-to-menu "Input Rmail File" + files + 'rmail-summary-input))) + (define-key rmail-summary-mode-map [menu-bar classify output-menu] + (cons "Output Rmail File" + (rmail-list-to-menu "Output Rmail File" + files + 'rmail-summary-output-to-rmail-file)))) + (define-key rmail-summary-mode-map [menu-bar classify input-menu] + '("Input Rmail File" . rmail-disable-menu)) + (define-key rmail-summary-mode-map [menu-bar classify output-menu] + '("Output Rmail File" . rmail-disable-menu))))) + ;; Sorting messages in Rmail Summary buffer. @@ -1085,11 +1547,23 @@ If prefix argument REVERSE is non-nil, sort them in reverse order." (interactive "P") (rmail-sort-from-summary (function rmail-sort-by-lines) reverse)) +(defun rmail-summary-sort-by-keywords (reverse labels) + "Sort messages of current Rmail summary by keywords. +If prefix argument REVERSE is non-nil, sort them in reverse order. +KEYWORDS is a comma-separated list of labels." + (interactive "P\nsSort by labels: ") + (rmail-sort-from-summary + (function (lambda (reverse) + (rmail-sort-by-keywords reverse labels))) + reverse)) + (defun rmail-sort-from-summary (sortfun reverse) "Sort Rmail messages from Summary buffer and update it after sorting." (require 'rmailsort) - (pop-to-buffer rmail-buffer) - (funcall sortfun reverse) - (rmail-summary)) + (let ((selwin (selected-window))) + (unwind-protect + (progn (pop-to-buffer rmail-buffer) + (funcall sortfun reverse)) + (select-window selwin)))) ;;; rmailsum.el ends here