(rmail-write-region-annotate): Prevent viewing different
[bpt/emacs.git] / lisp / mail / rmail.el
index 101283f..8f44cc6 100644 (file)
@@ -139,57 +139,28 @@ its character representation and its display representation.")
   :prefix "rmail-edit-"
   :group 'rmail)
 
-(defgroup rmail-obsolete nil
-  "Rmail obsolete customization variables."
-  :group 'rmail)
-
 (defcustom rmail-movemail-program nil
   "If non-nil, the file name of the `movemail' program."
   :group 'rmail-retrieve
   :type '(choice (const nil) string))
 
-(defcustom rmail-pop-password nil
-  "Password to use when reading mail from POP server.
-Please use `rmail-remote-password' instead."
-  :type '(choice (string :tag "Password")
-                (const :tag "Not Required" nil))
-  :group 'rmail-obsolete)
-
-(defcustom rmail-pop-password-required nil
-  "Non-nil if a password is required when reading mail from a POP server.
-Please use rmail-remote-password-required instead."
-  :type 'boolean
-  :group 'rmail-obsolete)
+(define-obsolete-variable-alias 'rmail-pop-password
+  'rmail-remote-password "22.1")
 
 (defcustom rmail-remote-password nil
   "Password to use when reading mail from a remote server.
 This setting is ignored for mailboxes whose URL already contains a password."
   :type '(choice (string :tag "Password")
                 (const :tag "Not Required" nil))
-  :set-after '(rmail-pop-password)
-  :set #'(lambda (symbol value)
-          (set-default symbol
-                       (if (and (not value)
-                                 (boundp 'rmail-pop-password)
-                                rmail-pop-password)
-                           rmail-pop-password
-                         value))
-          (setq rmail-pop-password nil))
   :group 'rmail-retrieve
   :version "22.1")
 
+(define-obsolete-variable-alias 'rmail-pop-password-required
+  'rmail-remote-password-required "22.1")
+
 (defcustom rmail-remote-password-required nil
   "Non-nil if a password is required when reading mail from a remote server."
   :type 'boolean
-  :set-after '(rmail-pop-password-required)
-  :set #'(lambda (symbol value)
-          (set-default symbol
-                       (if (and (not value)
-                                 (boundp 'rmail-pop-password-required)
-                                rmail-pop-password-required)
-                           rmail-pop-password-required
-                         value))
-          (setq rmail-pop-password-required nil))
   :group 'rmail-retrieve
   :version "22.1")
 
@@ -295,21 +266,45 @@ Currently known variants are 'emacs and 'mailutils."
 ;; If so, this can be moved there.
 (rmail-movemail-variant-p)
 
+;;;###autoload
+(defcustom rmail-user-mail-address-regexp nil
+  "Regexp matching user mail addresses.
+If non-nil, this variable is used to identify the correspondent
+when receiving new mail.  If it matches the address of the sender,
+the recipient is taken as correspondent of a mail.
+If nil \(default value\), your `user-login-name' and `user-mail-address'
+are used to exclude yourself as correspondent.
+
+Usually you don't have to set this variable, except if you collect mails
+sent by you under different user names.
+Then it should be a regexp matching your mail addresses.
+
+Setting this variable has an effect only before reading a mail."
+  :type '(choice (const :tag "None" nil) regexp)
+  :group 'rmail-retrieve
+  :version "21.1")
+
 ;;;###autoload
 (defcustom rmail-dont-reply-to-names nil
   "A regexp specifying addresses to prune from a reply message.
-A value of nil means exclude your own email address as an address
-plus whatever is specified by `rmail-default-dont-reply-to-names'."
+If this is nil, it is set the first time you compose a reply, to
+a value which excludes your own email address, plus whatever is
+specified by `rmail-default-dont-reply-to-names'.
+
+Matching addresses are excluded from the CC field in replies, and
+also the To field, unless this would leave an empty To field."
   :type '(choice regexp (const :tag "Your Name" nil))
   :group 'rmail-reply)
 
 ;;;###autoload
 (defvar rmail-default-dont-reply-to-names "\\`info-"
-  "A regular expression specifying part of the default value of the
-variable `rmail-dont-reply-to-names', for when the user does not set
-`rmail-dont-reply-to-names' explicitly.  (The other part of the default
-value is the user's email address and name.)
-It is useful to set this variable in the site customization file.")
+  "Regexp specifying part of the default value of `rmail-dont-reply-to-names'.
+This is used when the user does not set `rmail-dont-reply-to-names'
+explicitly.  (The other part of the default value is the user's
+email address and name.)  It is useful to set this variable in
+the site customization file.  The default value is conventionally
+used for large mailing lists to broadcast announcements.")
+;; Is it really useful to set this site-wide?
 
 ;;;###autoload
 (defcustom rmail-ignored-headers
@@ -365,27 +360,31 @@ If nil, display all header fields except those matched by
   :group 'rmail-headers)
 
 ;;;###autoload
-(defcustom rmail-retry-ignored-headers "^x-authentication-warning:"
+(defcustom rmail-retry-ignored-headers "^x-authentication-warning:\\|^x-detected-operating-system:\\|^x-spam[-a-z]*:\\|content-type:\\|content-transfer-encoding:\\|mime-version:"
   "Headers that should be stripped when retrying a failed message."
   :type '(choice regexp (const nil :tag "None"))
-  :group 'rmail-headers)
+  :group 'rmail-headers
+  :version "23.2")        ; added x-detected-operating-system, x-spam
 
 ;;;###autoload
 (defcustom rmail-highlighted-headers "^From:\\|^Subject:"
   "Regexp to match Header fields that Rmail should normally highlight.
-A value of nil means don't highlight."
+A value of nil means don't highlight.  Uses the face `rmail-highlight'."
   :type 'regexp
   :group 'rmail-headers)
 
 (defface rmail-highlight
   '((t (:inherit highlight)))
-  "Face to use for highlighting the most important header fields."
+  "Face to use for highlighting the most important header fields.
+The variable `rmail-highlighted-headers' specifies which headers."
   :group 'rmail-headers
   :version "22.1")
 
 (defface rmail-header-name
   '((t (:inherit font-lock-function-name-face)))
-  "Face to use for highlighting the header names."
+  "Face to use for highlighting the header names.
+The variable `rmail-font-lock-keywords' specifies which headers
+get highlighted."
   :group 'rmail-headers
   :version "23.1")
 
@@ -430,12 +429,15 @@ the frame where you have the RMAIL buffer displayed."
   :group 'rmail-files)
 
 (defcustom rmail-confirm-expunge 'y-or-n-p
-  "Whether and how to ask for confirmation before expunging deleted messages."
+  "Whether and how to ask for confirmation before expunging deleted messages.
+The value, if non-nil is a function to call with a question (string)
+as argument, to ask the user that question."
   :type '(choice (const :tag "No confirmation" nil)
                 (const :tag "Confirm with y-or-n-p" y-or-n-p)
                 (const :tag "Confirm with yes-or-no-p" yes-or-no-p))
   :version "21.1"
   :group 'rmail-files)
+(put 'rmail-confirm-expunge 'risky-local-variable t)
 
 ;;;###autoload
 (defvar rmail-mode-hook nil
@@ -468,6 +470,13 @@ still the current message in the Rmail buffer.")
 (defvar rmail-mmdf-delim2 "^\001\001\001\001\n"
   "Regexp marking the end of an mmdf message.")
 
+;; FIXME Post-mbox, this is now unused.
+;; In Emacs-22, this was called:
+;;  i) the very first time a message was shown.
+;; ii) when toggling the headers to the normal state, every time.
+;; It's not clear what it should do now, since there is nothing that
+;; records when a message is shown for the first time (unseen is not
+;; necessarily the same thing).
 (defcustom rmail-message-filter nil
   "If non-nil, a filter function for new messages in RMAIL.
 Called with region narrowed to the message, including headers,
@@ -527,6 +536,14 @@ In an RMAIL buffer, this holds the RMAIL buffer itself.
 In a summary buffer, this holds the RMAIL buffer it is a summary for.")
 (put 'rmail-buffer 'permanent-local t)
 
+(defvar rmail-was-converted nil
+  "Non-nil in an Rmail buffer that was just converted from Babyl format.")
+(put 'rmail-was-converted 'permanent-local t)
+
+(defvar rmail-seriously-modified nil
+  "Non-nil in an Rmail buffer that has been modified in a major way.")
+(put 'rmail-seriously-modified 'permanent-local t)
+
 ;; Message counters and markers.  Deleted flags.
 
 (defvar rmail-current-message nil
@@ -744,6 +761,9 @@ The first parenthesized expression should match the MIME-charset name.")
 this expression, you must change the code in `rmail-nuke-pinhead-header'
 that knows the exact ordering of the \\( \\) subexpressions.")
 
+;; FIXME the rmail-header-name headers ought to be customizable.
+;; It seems a bit arbitrary, for example, that all of the Date: line
+;; gets highlighted.
 (defvar rmail-font-lock-keywords
   ;; These are all matched case-insensitively.
   (eval-when-compile
@@ -752,7 +772,8 @@ that knows the exact ordering of the \\( \\) subexpressions.")
           (cite-suffix (concat cite-prefix "0-9_.@-`'\"")))
       (list '("^\\(From\\|Sender\\|Resent-From\\):"
              . 'rmail-header-name)
-           '("^Reply-To:.*$" . 'rmail-header-name)
+           '("^\\(Mail-\\)?Reply-To:.*$" . 'rmail-header-name)
+           ;; FIXME Mail-Followup-To should probably be here too.
            '("^Subject:" . 'rmail-header-name)
            '("^X-Spam-Status:" . 'rmail-header-name)
            '("^\\(To\\|Apparently-To\\|Cc\\|Newsgroups\\):"
@@ -918,9 +939,6 @@ MSGNUM, if present, indicates the malformed message."
 (defun rmail-convert-babyl-to-mbox ()
   "Convert the mail file from Babyl version 5 to mbox.
 This function also reinitializes local variables used by Rmail."
-  (unless (y-or-n-p "Babyl mail file detected.  Rmail now uses mbox format for mail files.
-Convert Babyl mail file to mbox format? ")
-    (error "Aborted"))
   (let ((old-file (make-temp-file "rmail"))
        (new-file (make-temp-file "rmail")))
     (unwind-protect
@@ -940,6 +958,8 @@ Convert Babyl mail file to mbox format? ")
            (rmail-mode-1)
            (rmail-perm-variables)
            (rmail-variables)
+           (setq rmail-was-converted t)
+           (rmail-dont-modify-format)
            (goto-char (point-max))
            (rmail-set-message-counters))
          (message "Replacing BABYL format with mbox format...done"))
@@ -1232,7 +1252,7 @@ Instead, these commands are available:
     (rmail-mode-2)
     (when (and finding-rmail-file
               (null coding-system-for-read)
-              default-enable-multibyte-characters)
+              (default-value 'enable-multibyte-characters))
       (let ((rmail-enable-multibyte t))
        (rmail-require-mime-maybe)
        (rmail-convert-file-maybe)
@@ -1274,11 +1294,15 @@ Instead, these commands are available:
 (defun rmail-generate-viewer-buffer ()
   "Return a reusable buffer suitable for viewing messages.
 Create the buffer if necessary."
-  (let* ((suffix (file-name-nondirectory (or buffer-file-name (buffer-name))))
-        (name (format " *message-viewer %s*" suffix))
-        (buf (get-buffer name)))
-    (or buf
-       (generate-new-buffer name))))
+  ;; We want to reuse any existing view buffer, so as not to create an
+  ;; endless number of them.  But we must avoid clashes if we visit
+  ;; two different rmail files with the same basename (Bug#4593).
+  (if (and (local-variable-p 'rmail-view-buffer)
+          (buffer-live-p rmail-view-buffer))
+      rmail-view-buffer
+    (generate-new-buffer
+     (format " *message-viewer %s*"
+            (file-name-nondirectory (or buffer-file-name (buffer-name)))))))
 
 (defun rmail-swap-buffers ()
   "Swap text between current buffer and `rmail-view-buffer'.
@@ -1313,6 +1337,24 @@ If so restore the actual mbox message collection."
       (rmail-swap-buffers)
       (setq rmail-buffer-swapped nil))))
 
+(defun rmail-modify-format ()
+  "Warn if important modifications would change Rmail file's format."
+  (with-current-buffer rmail-buffer
+    (and rmail-was-converted
+        ;; If it's already modified, don't warn again.
+        (not rmail-seriously-modified)
+        (not
+         (yes-or-no-p
+          (message "After this, %s would be saved in mbox format.  Proceed? "
+                   (buffer-name))))
+        (error "Aborted"))
+    (setq rmail-seriously-modified t)))
+
+(defun rmail-dont-modify-format ()
+  (when (and rmail-was-converted (not rmail-seriously-modified))
+    (set-buffer-modified-p nil)
+    (message "Marking buffer unmodified to avoid rewriting Babyl file as mbox file")))
+
 (defun rmail-mode-kill-buffer-hook ()
   (if (buffer-live-p rmail-view-buffer) (kill-buffer rmail-view-buffer)))
 
@@ -1321,10 +1363,18 @@ If so restore the actual mbox message collection."
   (make-local-variable 'rmail-last-regexp)
   (make-local-variable 'rmail-deleted-vector)
   (make-local-variable 'rmail-buffer)
+  (make-local-variable 'rmail-was-converted)
+  (setq rmail-was-converted nil)
+  (make-local-variable 'rmail-seriously-modified)
+  (setq rmail-seriously-modified nil)
   (setq rmail-buffer (current-buffer))
   (set-buffer-multibyte nil)
   (with-current-buffer (setq rmail-view-buffer (rmail-generate-viewer-buffer))
     (setq buffer-undo-list t)
+    ;; Note that this does not erase the buffer.  Should it?
+    ;; It depends on how this is called.  If somehow called with the
+    ;; rmail buffers swapped, it would erase the message collection.
+    (set (make-local-variable 'rmail-overlay-list) nil)
     (set-buffer-multibyte t)
     ;; Force C-x C-s write Unix EOLs.
     (set-buffer-file-coding-system 'undecided-unix))
@@ -1333,8 +1383,6 @@ If so restore the actual mbox message collection."
   (make-local-variable 'rmail-current-message)
   (make-local-variable 'rmail-total-messages)
   (setq rmail-total-messages 0)
-  (make-local-variable 'rmail-overlay-list)
-  (setq rmail-overlay-list nil)
   (make-local-variable 'rmail-message-vector)
   (make-local-variable 'rmail-msgref-vector)
   (make-local-variable 'rmail-inbox-list)
@@ -1358,6 +1406,10 @@ If so restore the actual mbox message collection."
   ;; Don't let a local variables list in a message cause confusion.
   (make-local-variable 'local-enable-local-variables)
   (setq local-enable-local-variables nil)
+  ;; Don't turn off auto-saving based on the size of the buffer
+  ;; because that code does not understand buffer-swapping.
+  (make-local-variable 'auto-save-include-big-deletions)
+  (setq auto-save-include-big-deletions t)
   (make-local-variable 'revert-buffer-function)
   (setq revert-buffer-function 'rmail-revert)
   (make-local-variable 'font-lock-defaults)
@@ -1410,13 +1462,12 @@ If so restore the actual mbox message collection."
   "Expunge and save RMAIL file."
   (interactive)
   (set-buffer rmail-buffer)
-  (rmail-expunge t)
+  (rmail-expunge)
   ;; No need to swap buffers: rmail-write-region-annotate takes care of it.
   ;; (rmail-swap-buffers-maybe)
   (save-buffer)
   (if (rmail-summary-exists)
-      (rmail-select-summary (set-buffer-modified-p nil)))
-  (rmail-show-message))
+      (rmail-select-summary (set-buffer-modified-p nil))))
 
 (defun rmail-quit ()
   "Quit out of RMAIL.
@@ -1429,6 +1480,8 @@ Hook `rmail-quit-hook' is run after expunging."
     (run-hooks 'rmail-quit-hook))
   ;; Don't switch to the summary buffer even if it was recently visible.
   (when rmail-summary-buffer
+    (with-current-buffer rmail-summary-buffer
+      (set-buffer-modified-p nil))
     (replace-buffer-in-windows rmail-summary-buffer)
     (bury-buffer rmail-summary-buffer))
   (if rmail-enable-mime
@@ -1462,6 +1515,7 @@ The duplicate copy goes into the Rmail file just after the original."
   ;; If we are in a summary buffer, switch to the Rmail buffer.
   ;; FIXME simpler to swap the contents, not the buffers?
   (set-buffer rmail-buffer)
+  (rmail-modify-format)
   (let ((buff (current-buffer))
         (n rmail-current-message)
         (beg (rmail-msgbeg rmail-current-message))
@@ -1608,6 +1662,7 @@ not be a new one).  It returns non-nil if it got any new messages."
   (or (verify-visited-file-modtime (current-buffer))
       (find-file (buffer-file-name)))
   (set-buffer rmail-buffer)
+  (rmail-modify-format)
   (rmail-swap-buffers-maybe)
   (rmail-maybe-set-message-counters)
   (widen)
@@ -1783,6 +1838,49 @@ is non-nil if the user has supplied the password interactively.
    (t
     (list file nil nil nil))))
 
+(defun rmail-unrmail-new-mail (from-file)
+  "Replace newly read mail in Babyl format with equivalent mbox format.
+
+FROM-FILE is the Babyl file from which the new mail should be read."
+  (let ((to-file (make-temp-file "rmail"))
+       size)
+    (unrmail from-file to-file)
+    (let ((inhibit-read-only t)
+         (coding-system-for-read 'raw-text)
+         (buffer-undo-list t))
+      (delete-region (point) (point-max))
+      (setq size (nth 1 (insert-file-contents to-file)))
+      (delete-file to-file)
+      size)))
+
+(defun rmail-unrmail-new-mail-maybe (file size)
+  "If newly read mail from FILE is in Babyl format, convert it to mbox format.
+
+SIZE is the original size of the newly read mail.
+Value is the size of the newly read mail after conversion."
+  ;; Detect previous Babyl format files.
+  (let ((case-fold-search nil)
+       (old-file file)
+       new-file)
+    (cond ((looking-at "BABYL OPTIONS:")
+          ;; The new mail is in Babyl version 5 format.  Use unrmail
+          ;; to convert it.
+          (setq size (rmail-unrmail-new-mail old-file)))
+         ((looking-at "Version: 5\n")
+          ;; New mail is in Babyl format made by old version of
+          ;; Rmail.  Fix the babyl file header and use unrmail to
+          ;; convert it.
+          (let ((buffer-read-only nil)
+                (write-region-annotate-functions nil)
+                (write-region-post-annotation-function nil)
+                (old-file  (make-temp-file "rmail")))
+            (insert "BABYL OPTIONS: -*- rmail -*-\n")
+            (forward-line -1)
+            (write-region (point) (point-max) old-file)
+            (setq size (rmail-unrmail-new-mail old-file))
+            (delete-file old-file))))
+    size))
+
 (defun rmail-insert-inbox-text (files renamep)
   ;; Detect a locked file now, so that we avoid moving mail
   ;; out of the real inbox file.  (That could scare people.)
@@ -1900,7 +1998,11 @@ is non-nil if the user has supplied the password interactively.
          (let ((coding-system-for-read 'no-conversion)
                size)
            (goto-char (point-max))
-           (setq size (nth 1 (insert-file-contents tofile)))
+           (setq size
+                 ;; If new mail is in Babyl format, convert it to mbox.
+                 (rmail-unrmail-new-mail-maybe
+                  tofile
+                  (nth 1 (insert-file-contents tofile))))
            ;; Determine if a pair of newline message separators need
            ;; to be added to the new collection of messages.  This is
            ;; the case for all new message collections added to a
@@ -1945,6 +2047,14 @@ is non-nil if the user has supplied the password interactively.
            (setq last-coding-system-used
                  (coding-system-change-eol-conversion coding 0)))))))
 
+(defun rmail-ensure-blank-line ()
+  "Ensure a message ends in a blank line.
+Call with point at the end of the message."
+  (unless (bolp)
+    (insert "\n"))
+  (unless (looking-back "\n\n")
+    (insert "\n")))
+
 (defun rmail-add-mbox-headers ()
   "Validate the RFC2822 format for the new messages.
 Point should be at the first new message.
@@ -1958,27 +2068,32 @@ new messages.  Return the number of new messages."
            (start (point))
            (value "------U-")
            (case-fold-search nil)
-           limit)
+           (delim (concat "\n\n" rmail-unix-mail-delimiter))
+           limit stop)
        ;; Detect an empty inbox file.
        (unless (= start (point-max))
          ;; Scan the new messages to establish a count and to ensure that
          ;; an attribute header is present.
-         (while (looking-at "From ")
-           ;; Determine if a new attribute header needs to be added to
-           ;; the message.
-           (if (search-forward "\n\n" nil t)
-               (progn
-                 (setq count (1+ count))
-                 (narrow-to-region start (point))
-                 (unless (mail-fetch-field rmail-attribute-header)
-                   (backward-char 1)
-                   (insert rmail-attribute-header ": " value "\n"))
-                 (widen))
-             (rmail-error-bad-format))
-           ;; Move to the next message.
-           (if (search-forward "\n\nFrom " nil 'move)
-               (forward-char -5))
-           (setq start (point))))
+         (if (looking-at rmail-unix-mail-delimiter)
+             (while (not stop)
+               ;; Determine if a new attribute header needs to be
+               ;; added to the message.
+               (if (search-forward "\n\n" nil t)
+                   (progn
+                     (setq count (1+ count))
+                     (narrow-to-region start (point))
+                     (unless (mail-fetch-field rmail-attribute-header)
+                       (backward-char 1)
+                       (insert rmail-attribute-header ": " value "\n"))
+                     (widen))
+                 (rmail-error-bad-format))
+               ;; Move to the next message.
+               (if (not (re-search-forward delim nil 'move))
+                   (setq stop t)
+                 (goto-char (match-beginning 0))
+                 (forward-char 2))
+               (setq start (point)))
+           (rmail-error-bad-format)))
        count))))
 \f
 (defun rmail-get-header-1 (name)
@@ -2020,10 +2135,13 @@ VALUE nil means to remove NAME altogether."
 If MSGNUM is nil, use the current message.  NAME and VALUE are strings.
 VALUE may also be nil, meaning to remove the header."
   (rmail-apply-in-message msgnum 'rmail-set-header-1 name value)
-  ;; Ensure header changes get saved.
-  ;; (Note replacing a header with an identical copy modifies.)
-  (with-current-buffer rmail-buffer (set-buffer-modified-p t)))
-
+  (with-current-buffer rmail-buffer
+    ;; Ensure header changes get saved.
+    ;; (Note replacing a header with an identical copy modifies.)
+    (set-buffer-modified-p t)
+    ;; However: don't save in mbox format over a Babyl file
+    ;; merely because of this.
+    (rmail-dont-modify-format)))
 \f
 ;;;; *** Rmail Attributes and Keywords ***
 
@@ -2035,9 +2153,9 @@ If MSG is nil, use the current message."
        (nmax (length rmail-attr-array))
        result temp)
     (when value
-      (if (/= (length value) nmax)
+      (if (> (length value) nmax)
           (message "Warning: corrupt attribute header in message")
-        (dotimes (index nmax)
+        (dotimes (index (length value))
           (setq temp (and (not (= ?- (aref value index)))
                           (nth 1 (aref rmail-attr-array index)))
                 result
@@ -2148,6 +2266,10 @@ change; nil means current message."
       (setq n (1+ n))))
   (if (stringp attr)
       (error "Unknown attribute `%s'" attr))
+  ;; Ask for confirmation before setting any attribute except `unseen'
+  ;; if it would force a format change.
+  (unless (= attr rmail-unseen-attr-index)
+    (rmail-modify-format))
   (with-current-buffer rmail-buffer
     (or msgnum (setq msgnum rmail-current-message))
     (when (> msgnum 0)
@@ -2159,10 +2281,13 @@ change; nil means current message."
               (rmail-apply-in-message msgnum 'rmail-set-attribute-1 attr state)
             (if (= msgnum rmail-current-message)
                 (rmail-display-labels)))
-          ;; If we made a significant change in an attribute, mark
-          ;; rmail-buffer modified, so it will be (1) saved and (2)
-          ;; displayed in the mode line.
-          (set-buffer-modified-p t)))))
+         ;; Don't save in mbox format over a Babyl file
+         ;; merely because of a change in `unseen' attribute.
+         (if (= attr rmail-unseen-attr-index)
+             (rmail-dont-modify-format)
+           ;; Otherwise, if we modified the file text via the view buffer,
+           ;; mark the main buffer modified too.
+           (set-buffer-modified-p t))))))
 
 (defun rmail-message-attr-p (msg attrs)
   "Return non-nil if message number MSG has attributes matching regexp ATTRS."
@@ -2173,6 +2298,10 @@ change; nil means current message."
   "Return non-nil if message number MSGNUM has the unseen attribute."
   (rmail-message-attr-p msgnum "......U"))
 
+;; FIXME rmail-get-labels does some formatting (eg leading space, `;'
+;; between attributes and labels), so this might not do what you want.
+;; Eg see rmail-sort-by-labels.  rmail-get-labels could have an
+;; optional `noformat' argument.
 (defun rmail-message-labels-p (msg labels)
   "Return non-nil if message number MSG has labels matching regexp LABELS."
   (string-match labels (rmail-get-labels msg)))
@@ -2236,12 +2365,11 @@ change the invisible header text."
 (defun rmail-forget-messages ()
   (unwind-protect
       (if (vectorp rmail-message-vector)
-         (let* ((i 0)
-                (v rmail-message-vector)
+         (let* ((v rmail-message-vector)
                 (n (length v)))
-           (while (< i n)
-             (move-marker (aref v i)  nil)
-             (setq i (1+ i)))))
+           (dotimes (i n)
+             (if (aref v i)
+                 (move-marker (aref v i)  nil)))))
     (setq rmail-message-vector nil)
     (setq rmail-msgref-vector nil)
     (setq rmail-deleted-vector nil)))
@@ -2307,20 +2435,25 @@ Output a helpful message unless NOMSG is non-nil."
        ;; the entry for message N+1, which marks
        ;; the end of message N.  (N = number of messages).
        (setq messages-head (list (point-marker)))
-       (rmail-set-message-counters-counter (min (point) point-save))
-       (setq messages-after-point total-messages)
+       (setq messages-after-point 
+             (or (rmail-set-message-counters-counter (min (point) point-save))
+                 0))
 
-       ;; Determine how many precede point.
-       (rmail-set-message-counters-counter)
        (setq rmail-total-messages total-messages)
        (setq rmail-current-message
              (min total-messages
                   (max 1 (- total-messages messages-after-point))))
-       (setq rmail-message-vector
-             (apply 'vector (cons (point-min-marker) messages-head))
-             rmail-deleted-vector (concat "0" deleted-head)
-             rmail-summary-vector (make-vector rmail-total-messages nil)
+
+       ;; Make an element 0 in rmail-message-vector and rmail-deleted-vector
+       ;; which will never be used.
+       (push nil messages-head)
+       (push ?0 deleted-head)
+       (setq rmail-message-vector (apply 'vector messages-head)
+             rmail-deleted-vector (concat deleted-head))
+
+       (setq rmail-summary-vector (make-vector rmail-total-messages nil)
              rmail-msgref-vector (make-vector (1+ rmail-total-messages) nil))
+
        (let ((i 0))
          (while (<= i rmail-total-messages)
            (aset rmail-msgref-vector i (list i))
@@ -2347,26 +2480,34 @@ the message.  Point is at the beginning of the message."
                    ?D
                  ?\s) deleted-head))))
 
-(defun rmail-set-message-counters-counter (&optional stop)
-  ;; Collect the start position for each message into 'messages-head.
-  (let ((start (point)))
-    (while (search-backward "\n\nFrom " stop t)
+(defun rmail-set-message-counters-counter (&optional spot-to-find)
+  "Collect the start positions of messages in list `messages-head'.
+Return the number of messages after the one containing SPOT-TO-FIND."
+  (let ((start (point))
+       messages-after-spot)
+    (while (search-backward "\n\nFrom " nil t)
       (forward-char 2)
-      (rmail-collect-deleted start)
-      (setq messages-head (cons (point-marker) messages-head)
-           total-messages (1+ total-messages)
-           start (point))
-      ;; Show progress after every 20 messages or so.
-      (if (zerop (% total-messages 20))
-         (message "Counting messages...%d" total-messages)))
+      (when (looking-at rmail-unix-mail-delimiter)
+       (if (and (<= (point) spot-to-find)
+                (null messages-after-spot))
+           (setq messages-after-spot total-messages))
+       (rmail-collect-deleted start)
+       (setq messages-head (cons (point-marker) messages-head)
+             total-messages (1+ total-messages)
+             start (point))
+       ;; Show progress after every 20 messages or so.
+       (if (zerop (% total-messages 20))
+           (message "Counting messages...%d" total-messages))))
     ;; Handle the first message, maybe.
-    (if stop
-       (goto-char stop)
-      (goto-char (point-min)))
-    (unless (not (looking-at "From "))
+    (goto-char (point-min))
+    (unless (not (looking-at rmail-unix-mail-delimiter))
+      (if (and (<= (point) spot-to-find)
+              (null messages-after-spot))
+         (setq messages-after-spot total-messages))
       (rmail-collect-deleted start)
       (setq messages-head (cons (point-marker) messages-head)
-           total-messages (1+ total-messages)))))
+           total-messages (1+ total-messages)))
+    messages-after-spot))
 \f
 ;; Display a message.
 
@@ -2505,7 +2646,8 @@ N defaults to the current message."
 (defcustom rmail-show-message-verbose-min 200000
   "Message size at which to show progress messages for displaying it."
   :type 'integer
-  :group 'rmail)
+  :group 'rmail
+  :version "23.1")
 
 (defun rmail-show-message-1 (&optional msg)
   "Show message MSG (default: current message) using `rmail-view-buffer'.
@@ -2530,9 +2672,10 @@ The current mail message becomes the message displayed."
            (t (setq rmail-current-message msg)))
       (with-current-buffer rmail-buffer
        (setq header-style rmail-header-style)
-       ;; Mark the message as seen, bracket the message in the mail
-       ;; buffer and determine the coding system the transfer encoding.
+       ;; Mark the message as seen
        (rmail-set-attribute rmail-unseen-attr-index nil)
+       ;; bracket the message in the mail
+       ;; buffer and determine the coding system the transfer encoding.
        (rmail-swap-buffers-maybe)
        (setq beg (rmail-msgbeg msg)
              end (rmail-msgend msg))
@@ -2722,22 +2865,16 @@ using the coding system CODING."
                      (symbol-name coding))))
          (rmail-show-message))))))
 
-;; Find all occurrences of certain fields, and highlight them.
 (defun rmail-highlight-headers ()
-  ;; Do this only if the system supports faces.
-  (if (and (fboundp 'internal-find-face)
-          rmail-highlighted-headers)
+  "Highlight the headers specified by `rmail-highlighted-headers'.
+Uses the face `rmail-highlight'."
+  (if rmail-highlighted-headers
       (save-excursion
        (search-forward "\n\n" nil 'move)
        (save-restriction
          (narrow-to-region (point-min) (point))
          (let ((case-fold-search t)
                (inhibit-read-only t)
-               ;; Highlight with boldface if that is available.
-               ;; Otherwise use the `highlight' face.
-               (face (or 'rmail-highlight
-                         (if (face-differs-from-default-p 'bold)
-                             'bold 'highlight)))
                ;; List of overlays to reuse.
                (overlays rmail-overlay-list))
            (goto-char (point-min))
@@ -2756,12 +2893,12 @@ using the coding system CODING."
                    (progn
                      (setq overlay (car overlays)
                            overlays (cdr overlays))
-                     (overlay-put overlay 'face face)
+                     (overlay-put overlay 'face 'rmail-highlight)
                      (move-overlay overlay beg (point)))
                  ;; Make a new overlay and add it to
                  ;; rmail-overlay-list.
                  (setq overlay (make-overlay beg (point)))
-                 (overlay-put overlay 'face face)
+                 (overlay-put overlay 'face 'rmail-highlight)
                  (setq rmail-overlay-list
                        (cons overlay rmail-overlay-list))))))))))
 
@@ -3018,6 +3155,10 @@ and typical reply prefixes such as Re:."
        (setq subject (substring subject (match-end 0))))
     (if (string-match "[ \t]+\\'" subject)
        (setq subject (substring subject 0 (match-beginning 0))))
+    ;; If Subject is long, mailers will break it into several lines at
+    ;; arbitrary places, so normalize whitespace by replacing every
+    ;; run of whitespace characters with a single space.
+    (setq subject (replace-regexp-in-string "[ \t\n]+" " " subject))
     subject))
 
 (defun rmail-simplified-subject-regexp ()
@@ -3140,14 +3281,15 @@ Deleted messages stay in the file until the \\[rmail-expunge] command is given."
       newnum)))
 
 (defun rmail-expunge-confirmed ()
-  "Return t if deleted message should be expunged. If necessary, ask the user.
-See also user-option `rmail-confirm-expunge'."
+  "Return t if expunge is needed and desirable.
+If `rmail-confirm-expunge' is non-nil, ask user to confirm."
   (set-buffer rmail-buffer)
-  (or (not (stringp rmail-deleted-vector))
-      (not (string-match "D" rmail-deleted-vector))
-      (null rmail-confirm-expunge)
-      (funcall rmail-confirm-expunge
-              "Erase deleted messages from Rmail file? ")))
+  (and (stringp rmail-deleted-vector)
+       (string-match "D" rmail-deleted-vector)
+       (if rmail-confirm-expunge
+          (funcall rmail-confirm-expunge
+                   "Erase deleted messages from Rmail file? ")
+        t)))
 
 (defun rmail-only-expunge (&optional dont-show)
   "Actually erase all deleted messages in the file."
@@ -3233,20 +3375,44 @@ See also user-option `rmail-confirm-expunge'."
          (goto-char (+ (point-min) opoint))
        (goto-char (+ (point) opoint))))))
 
+;; The DONT-SHOW argument is new in 23.  Does not seem very important.
 (defun rmail-expunge (&optional dont-show)
-  "Erase deleted messages from Rmail file and summary buffer."
+  "Erase deleted messages from Rmail file and summary buffer.
+This always shows a message (so as not to leave the Rmail buffer
+unswapped), and always updates any summary (so that it remains
+consistent with the Rmail buffer).  If DONT-SHOW is non-nil, it
+does not pop any summary buffer."
   (interactive)
   (when (rmail-expunge-confirmed)
-    (let ((old-total rmail-total-messages)
-         (opoint (with-current-buffer rmail-buffer
-                   (when (rmail-buffers-swapped-p)
-                     (point)))))
-      (rmail-only-expunge dont-show)
+    (rmail-modify-format)
+    (let ((was-deleted (rmail-message-deleted-p rmail-current-message))
+         (was-swapped (rmail-buffers-swapped-p)))
+      (rmail-only-expunge t)
+      ;; We always update the summary buffer, so that the contents
+      ;; remain consistent with the rmail buffer.
+      ;; The only difference is, in the dont-show case, we use a
+      ;; cut-down version of rmail-select-summary that does not pop
+      ;; the summary buffer.  It's only used by rmail-quit, which is
+      ;; just going to bury any summary immediately after.  If we made
+      ;; rmail-quit bury the summary first, dont-show could be removed.
+      ;; But the expunge might not be confirmed...
       (if (rmail-summary-exists)
-         (rmail-select-summary (rmail-update-summary))
-       (rmail-show-message-1 rmail-current-message)
-       (if (and (eq old-total rmail-total-messages) opoint)
-           (goto-char opoint))))))
+         (if dont-show
+             (let ((total rmail-total-messages))
+               (with-current-buffer rmail-summary-buffer
+                 (let ((rmail-total-messages total))
+                   (rmail-update-summary))))
+           (rmail-select-summary (rmail-update-summary))))
+      ;; We always show a message, because (rmail-only-expunge t)
+      ;; leaves the rmail buffer unswapped.
+      ;; If we expunged the current message, a new one is current now,
+      ;; so show it.  If we weren't showing a message, show it.
+      (if (or was-deleted (not was-swapped))
+         (rmail-show-message-1 rmail-current-message)
+       ;; We can just show the same message that was being shown before.
+       (rmail-display-labels)
+       (rmail-swap-buffers)
+       (setq rmail-buffer-swapped t)))))
 \f
 ;;;; *** Rmail Mailing Commands ***
 
@@ -3378,8 +3544,12 @@ use \\[mail-yank-original] to yank the original message into it."
                   (aref rmail-msgref-vector msgnum))
                 rmail-answered-attr-index))
      nil
-     (list (cons "References" (concat (mapconcat 'identity references " ")
-                                     " " message-id))))))
+     (if (or references message-id)
+        (list (cons "References" (if references
+                                     (concat
+                                      (mapconcat 'identity references " ")
+                                      " " message-id)
+                                   message-id)))))))
 \f
 (defun rmail-mark-message (buffer msgnum-list attribute)
   "Give BUFFER's message number in MSGNUM-LIST the attribute ATTRIBUTE.
@@ -3617,6 +3787,25 @@ typically for purposes of moderating a list."
 (defvar mail-mime-unsent-header "^Content-Type: message/rfc822 *$"
  "A regexp that matches the header of a MIME body part with a failed message.")
 
+;; This is a cut-down version of rmail-clear-headers from Emacs 22.
+;; It doesn't have the same functionality, hence the name change.
+(defun rmail-delete-headers (regexp)
+  "Delete any mail headers matching REGEXP.
+The message should be narrowed to just the headers."
+  (when regexp
+    (goto-char (point-min))
+    (while (re-search-forward regexp nil t)
+      (beginning-of-line)
+      ;; This code from Emacs 22 doesn't seem right, since r-n-h is
+      ;; just for display.
+;;;      (if (looking-at rmail-nonignored-headers)
+;;;      (forward-line 1)
+      (delete-region (point)
+                    (save-excursion
+                      (if (re-search-forward "\n[^ \t]" nil t)
+                          (1- (point))
+                        (point-max)))))))
+
 (defun rmail-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
@@ -3632,12 +3821,7 @@ specifying headers which should not be copied into the new message."
   (let ((rmail-this-buffer (current-buffer))
        (msgnum rmail-current-message)
        bounce-start bounce-end bounce-indent resending
-       ;; Fetch any content-type header in current message
-       ;; Must search thru the whole unpruned header.
-       (content-type
-        (save-excursion
-          (save-restriction
-            (mail-fetch-field "Content-Type") ))))
+       (content-type (rmail-get-header "Content-Type")))
     (save-excursion
       (goto-char (point-min))
       (let ((case-fold-search t))
@@ -3706,9 +3890,7 @@ specifying headers which should not be copied into the new message."
          ;; Insert original text as initial text of new draft message.
          ;; Bind inhibit-read-only since the header delimiter
          ;; of the previous message was probably read-only.
-         (let ((inhibit-read-only t)
-               rmail-displayed-headers
-               rmail-ignored-headers)
+         (let ((inhibit-read-only t))
            (erase-buffer)
            (insert-buffer-substring rmail-this-buffer
                                     bounce-start bounce-end)
@@ -3718,6 +3900,8 @@ specifying headers which should not be copied into the new message."
            (mail-sendmail-delimit-header)
            (save-restriction
              (narrow-to-region (point-min) (mail-header-end))
+             (rmail-delete-headers rmail-retry-ignored-headers)
+             (rmail-delete-headers "^\\(sender\\|return-path\\|received\\):")
              (setq resending (mail-fetch-field "resent-to"))
              (if mail-self-blind
                  (if resending
@@ -3992,9 +4176,231 @@ encoded string (and the same mask) will decode the string."
 (defun rmail-write-region-annotate (start end)
   (when (and (null start) (rmail-buffers-swapped-p))
     (set-buffer rmail-view-buffer)
+    ;; Prevent viewing different messages from messing up the coding. (Bug#4623)
+    ;; FIXME is there a better solution?
+    (set (make-local-variable 'coding-system-for-write) 'no-conversion)
     (widen)
     nil))
 
+\f
+;;; Start of automatically extracted autoloads.
+\f
+;;;### (autoloads (rmail-edit-current-message) "rmailedit" "rmailedit.el"
+;;;;;;  "31f0128d57ee5aefe13ec6060a5c63cc")
+;;; Generated autoloads from rmailedit.el
+
+(autoload 'rmail-edit-current-message "rmailedit" "\
+Edit the contents of this message.
+
+\(fn)" t nil)
+
+;;;***
+\f
+;;;### (autoloads (rmail-next-labeled-message rmail-previous-labeled-message
+;;;;;;  rmail-read-label rmail-kill-label rmail-add-label) "rmailkwd"
+;;;;;;  "rmailkwd.el" "2e986921026eea971b49e91f53967f77")
+;;; Generated autoloads from rmailkwd.el
+
+(autoload 'rmail-add-label "rmailkwd" "\
+Add LABEL to labels associated with current RMAIL message.
+Completes (see `rmail-read-label') over known labels when reading.
+LABEL may be a symbol or string.  Only one label is allowed.
+
+\(fn LABEL)" t nil)
+
+(autoload 'rmail-kill-label "rmailkwd" "\
+Remove LABEL from labels associated with current RMAIL message.
+Completes (see `rmail-read-label') over known labels when reading.
+LABEL may be a symbol or string.  Only one label is allowed.
+
+\(fn LABEL)" t nil)
+
+(autoload 'rmail-read-label "rmailkwd" "\
+Read a label with completion, prompting with PROMPT.
+Completions are chosen from `rmail-label-obarray'.  The default
+is `rmail-last-label', if that is non-nil.  Updates `rmail-last-label'
+according to the choice made, and returns a symbol.
+
+\(fn PROMPT)" nil nil)
+
+(autoload 'rmail-previous-labeled-message "rmailkwd" "\
+Show previous message with one of the labels LABELS.
+LABELS should be a comma-separated list of label names.
+If LABELS is empty, the last set of labels specified is used.
+With prefix argument N moves backward N messages with these labels.
+
+\(fn N LABELS)" t nil)
+
+(autoload 'rmail-next-labeled-message "rmailkwd" "\
+Show next message with one of the labels LABELS.
+LABELS should be a comma-separated list of label names.
+If LABELS is empty, the last set of labels specified is used.
+With prefix argument N moves forward N messages with these labels.
+
+\(fn N LABELS)" t nil)
+
+;;;***
+\f
+;;;### (autoloads (rmail-mime) "rmailmm" "rmailmm.el" "ab34439779d8036dbd5cdc80fb4cea64")
+;;; Generated autoloads from rmailmm.el
+
+(autoload 'rmail-mime "rmailmm" "\
+Process the current Rmail message as a MIME message.
+This creates a temporary \"*RMAIL*\" buffer holding a decoded
+copy of the message.  Inline content-types are handled according to
+`rmail-mime-media-type-handlers-alist'.  By default, this
+displays text and multipart messages, and offers to download
+attachments as specfied by `rmail-mime-attachment-dirs-alist'.
+
+\(fn)" t nil)
+
+;;;***
+\f
+;;;### (autoloads (set-rmail-inbox-list) "rmailmsc" "rmailmsc.el"
+;;;;;;  "de01c37c81339201034a01732b97f44e")
+;;; Generated autoloads from rmailmsc.el
+
+(autoload 'set-rmail-inbox-list "rmailmsc" "\
+Set the inbox list of the current RMAIL file to FILE-NAME.
+You can specify one file name, or several names separated by commas.
+If FILE-NAME is empty, remove any existing inbox list.
+
+This applies only to the current session.
+
+\(fn FILE-NAME)" t nil)
+
+;;;***
+\f
+;;;### (autoloads (rmail-sort-by-labels rmail-sort-by-lines rmail-sort-by-correspondent
+;;;;;;  rmail-sort-by-recipient rmail-sort-by-author rmail-sort-by-subject
+;;;;;;  rmail-sort-by-date) "rmailsort" "rmailsort.el" "3f2b10b0272ea56cb604f29330d95fc4")
+;;; Generated autoloads from rmailsort.el
+
+(autoload 'rmail-sort-by-date "rmailsort" "\
+Sort messages of current Rmail buffer by \"Date\" header.
+If prefix argument REVERSE is non-nil, sorts in reverse order.
+
+\(fn REVERSE)" t nil)
+
+(autoload 'rmail-sort-by-subject "rmailsort" "\
+Sort messages of current Rmail buffer by \"Subject\" header.
+Ignores any \"Re: \" prefix.  If prefix argument REVERSE is
+non-nil, sorts in reverse order.
+
+\(fn REVERSE)" t nil)
+
+(autoload 'rmail-sort-by-author "rmailsort" "\
+Sort messages of current Rmail buffer by author.
+This uses either the \"From\" or \"Sender\" header, downcased.
+If prefix argument REVERSE is non-nil, sorts in reverse order.
+
+\(fn REVERSE)" t nil)
+
+(autoload 'rmail-sort-by-recipient "rmailsort" "\
+Sort messages of current Rmail buffer by recipient.
+This uses either the \"To\" or \"Apparently-To\" header, downcased.
+If prefix argument REVERSE is non-nil, sorts in reverse order.
+
+\(fn REVERSE)" t nil)
+
+(autoload 'rmail-sort-by-correspondent "rmailsort" "\
+Sort messages of current Rmail buffer by other correspondent.
+This uses either the \"From\", \"Sender\", \"To\", or
+\"Apparently-To\" header, downcased.  Uses the first header not
+excluded by `rmail-dont-reply-to-names'.  If prefix argument
+REVERSE is non-nil, sorts in reverse order.
+
+\(fn REVERSE)" t nil)
+
+(autoload 'rmail-sort-by-lines "rmailsort" "\
+Sort messages of current Rmail buffer by the number of lines.
+If prefix argument REVERSE is non-nil, sorts in reverse order.
+
+\(fn REVERSE)" t nil)
+
+(autoload 'rmail-sort-by-labels "rmailsort" "\
+Sort messages of current Rmail buffer by labels.
+LABELS is a comma-separated list of labels.  The order of these
+labels specifies the order of messages: messages with the first
+label come first, messages with the second label come second, and
+so on.  Messages that have none of these labels come last.
+If prefix argument REVERSE is non-nil, sorts in reverse order.
+
+\(fn REVERSE LABELS)" t nil)
+
+;;;***
+\f
+;;;### (autoloads (rmail-summary-by-senders rmail-summary-by-topic
+;;;;;;  rmail-summary-by-regexp rmail-summary-by-recipients rmail-summary-by-labels
+;;;;;;  rmail-summary) "rmailsum" "rmailsum.el" "60bec0ae88b7ed18dd6845ddb9ccd904")
+;;; Generated autoloads from rmailsum.el
+
+(autoload 'rmail-summary "rmailsum" "\
+Display a summary of all messages, one line per message.
+
+\(fn)" t nil)
+
+(autoload 'rmail-summary-by-labels "rmailsum" "\
+Display a summary of all messages with one or more LABELS.
+LABELS should be a string containing the desired labels, separated by commas.
+
+\(fn LABELS)" t nil)
+
+(autoload 'rmail-summary-by-recipients "rmailsum" "\
+Display a summary of all messages with the given RECIPIENTS.
+Normally checks the To, From and Cc fields of headers;
+but if PRIMARY-ONLY is non-nil (prefix arg given),
+ only look in the To and From fields.
+RECIPIENTS is a string of regexps separated by commas.
+
+\(fn RECIPIENTS &optional PRIMARY-ONLY)" t nil)
+
+(autoload 'rmail-summary-by-regexp "rmailsum" "\
+Display a summary of all messages according to regexp REGEXP.
+If the regular expression is found in the header of the message
+\(including in the date and other lines, as well as the subject line),
+Emacs will list the message in the summary.
+
+\(fn REGEXP)" t nil)
+
+(autoload 'rmail-summary-by-topic "rmailsum" "\
+Display a summary of all messages with the given SUBJECT.
+Normally checks just the Subject field of headers; but with prefix
+argument WHOLE-MESSAGE is non-nil, looks in the whole message.
+SUBJECT is a string of regexps separated by commas.
+
+\(fn SUBJECT &optional WHOLE-MESSAGE)" t nil)
+
+(autoload 'rmail-summary-by-senders "rmailsum" "\
+Display a summary of all messages whose \"From\" field matches SENDERS.
+SENDERS is a string of regexps separated by commas.
+
+\(fn SENDERS)" t nil)
+
+;;;***
+\f
+;;;### (autoloads (unforward-rmail-message undigestify-rmail-message)
+;;;;;;  "undigest" "undigest.el" "b691540ddff5c394e9ebc3517051445f")
+;;; Generated autoloads from undigest.el
+
+(autoload 'undigestify-rmail-message "undigest" "\
+Break up a digest message into its constituent messages.
+Leaves original message, deleted, before the undigestified messages.
+
+\(fn)" t nil)
+
+(autoload 'unforward-rmail-message "undigest" "\
+Extract a forwarded message from the containing message.
+This puts the forwarded message into a separate rmail message
+following the containing message.
+
+\(fn)" t nil)
+
+;;;***
+\f
+;;; End of automatically extracted autoloads.
+
+
 (provide 'rmail)
 
 ;; arch-tag: 65d257d3-c281-4a65-9c38-e61af95af2f0