Synch with Oort Gnus.
[elisp/gnus.git-] / lisp / message.el
index b3ab83f..d6a920f 100644 (file)
     (require 'canlock)))
 (require 'mailheader)
 (require 'nnheader)
-;; This is apparently necessary even though things are autoloaded:
+;; This is apparently necessary even though things are autoloaded.
+;; Because we dynamically bind mail-abbrev-mode-regexp, we'd better
+;; require mailabbrev here.
 (if (featurep 'xemacs)
-    (require 'mail-abbrevs))
+    (require 'mail-abbrevs)
+  (require 'mailabbrev))
 (require 'mime-edit)
 (eval-when-compile (require 'static))
 
@@ -203,7 +206,14 @@ Otherwise, most addresses look like `angles', but they look like
                 (const default))
   :group 'message-headers)
 
-(defcustom message-syntax-checks nil
+(defcustom message-insert-canlock t
+  "Whether to insert a Cancel-Lock header in news postings."
+  :version "21.3"
+  :group 'message-headers
+  :type 'boolean)
+
+(defcustom message-syntax-checks 
+  (if message-insert-canlock '((sender . disabled)) nil)
   ;; Guess this one shouldn't be easy to customize...
   "*Controls what syntax checks should not be performed on outgoing posts.
 To disable checking of long signatures, for instance, add
@@ -275,7 +285,8 @@ any confusion."
   :group 'message-sending
   :type 'function)
 
-(defcustom message-subject-re-regexp "^[ \t]*\\([Rr][Ee]:[ \t]*\\)*[ \t]*"
+(defcustom message-subject-re-regexp
+  "^[ \t]*\\([Rr][Ee]\\(\\[[0-9]*\\]\\)*:[ \t]*\\)*[ \t]*"
   "*Regexp matching \"Re: \" in the subject line."
   :group 'message-various
   :type 'regexp)
@@ -474,9 +485,22 @@ The provided functions are:
 
 (defcustom message-cite-prefix-regexp
   (if (string-match "[[:digit:]]" "1") ;; support POSIX?
-      "\\([ \t]*[-_.[:word:]]+>+\\|[ \t]*[]>~|:}+]\\)+"
+      "\\([ \t]*[-_.[:word:]]+>+\\|[ \t]*[]>»|:}+]\\)+"
     ;; ?-, ?_ or ?. MUST NOT be in syntax entry w.
-    "\\([ \t]*\\(\\w\\|[-_.]\\)+>+\\|[ \t]*[]>~|:}+]\\)+")
+    (let ((old-table (syntax-table))
+         non-word-constituents)
+      (set-syntax-table text-mode-syntax-table)
+      (setq non-word-constituents
+           (concat
+            (if (string-match "\\w" "-")  "" "-")
+            (if (string-match "\\w" "_")  "" "_")
+            (if (string-match "\\w" ".")  "" ".")))
+      (set-syntax-table old-table)
+      (if (equal non-word-constituents "")
+         "\\([ \t]*\\(\\w\\)+>+\\|[ \t]*[]>»|:}+]\\)+"
+       (concat "\\([ \t]*\\(\\w\\|["
+               non-word-constituents
+               "]\\)+>+\\|[ \t]*[]>»|:}+]\\)+"))))
   "*Regexp matching the longest possible citation prefix on a line."
   :group 'message-insertion
   :type 'regexp)
@@ -644,13 +668,15 @@ Doing so would be even more evil than leaving it out."
 
 (defcustom message-qmail-inject-args nil
   "Arguments passed to qmail-inject programs.
-This should be a list of strings, one string for each argument.
+This should be a list of strings, one string for each argument.  It
+may also be a function.
 
 For e.g., if you wish to set the envelope sender address so that bounces
 go to the right place or to deal with listserv's usage of that address, you
 might set this variable to '(\"-f\" \"you@some.where\")."
   :group 'message-sending
-  :type '(repeat string))
+  :type '(choice (function)
+                (repeat string)))
 
 (defvar message-cater-to-broken-inn t
   "Non-nil means Gnus should not fold the `References' header.
@@ -822,8 +848,6 @@ point and mark around the citation text as modified."
   :type 'function
   :group 'message-insertion)
 
-(defvar message-abbrevs-loaded nil)
-
 ;;;###autoload
 (defcustom message-signature t
   "*String to be inserted at the end of the message buffer.
@@ -865,7 +889,10 @@ If stringp, use this; if non-nil, use no host name (user name only)."
                 (sexp :tag "none" :format "%t" t)))
 
 (defvar message-reply-buffer nil)
-(defvar message-reply-headers nil)
+(defvar message-reply-headers nil
+  "The headers of the current replied article.
+It is a vector of the following headers:
+\[number subject from date id references chars lines xref extra].")
 (defvar message-sent-message-via nil)
 (defvar message-checksum nil)
 (defvar message-send-actions nil
@@ -1009,8 +1036,9 @@ feet of Good Net-Keeping Seal of Approval. The following are foot
 candidates:
 `empty-article'     Allow you to post an empty article;
 `quoted-text-only'  Allow you to post quoted text only;
-`multiple-copies'   Allow you to post multiple copies.")
-;; `cancel-messages'   Allow you to cancel or supersede others' messages.
+`multiple-copies'   Allow you to post multiple copies;
+`cancel-messages'   Allow you to cancel or supersede messages from 
+                    your other email addresses.")
 
 (defsubst message-gnksa-enable-p (feature)
   (or (not (listp message-shoot-gnksa-feet))
@@ -1255,6 +1283,19 @@ The first matched address (not primary one) is used in the From field."
   :type '(choice (const :tag "Always use primary" nil)
                 regexp))
 
+(defcustom message-hierarchical-addresses nil
+  "A list of hierarchical mail address definitions.
+
+Inside each entry, the first address is the \"top\" address, and
+subsequent addresses are subaddresses; this is used to indicate that
+mail sent to the first address will automatically be delivered to the
+subaddresses.  So if the first address appears in the recipient list
+for a message, the subaddresses will be removed (if present) before
+the mail is sent.  All addresses in this structure should be
+downcased."
+  :group 'message-headers
+  :type '(repeat (repeat string)))
+
 (defcustom message-mail-user-agent nil
   "Like `mail-user-agent'.
 Except if it is nil, use Gnus native MUA; if it is t, use
@@ -1282,11 +1323,7 @@ If this variable is non-nil, pose the question \"Reply to all
 recipients?\" before a wide reply to multiple recipients.  If the user
 answers yes, reply to all recipients as usual.  If the user answers
 no, only reply back to the author."
-  :group 'message-headers
-  :type 'boolean)
-
-(defcustom message-insert-canlock t
-  "Whether to insert a Cancel-Lock header in news postings."
+  :version "21.3"
   :group 'message-headers
   :type 'boolean)
 
@@ -1406,7 +1443,6 @@ no, only reply back to the author."
   (autoload 'gnus-point-at-bol "gnus-util")
   (autoload 'gnus-output-to-rmail "gnus-util")
   (autoload 'gnus-output-to-mail "gnus-util")
-  (autoload 'mail-abbrev-in-expansion-header-p "mailabbrev")
   (autoload 'nndraft-request-associate-buffer "nndraft")
   (autoload 'nndraft-request-expire-articles "nndraft")
   (autoload 'gnus-open-server "gnus-int")
@@ -1773,6 +1809,7 @@ Point is left at the beginning of the narrowed-to region."
   (define-key message-mode-map "\C-c\C-f\C-k" 'message-goto-keywords)
   (define-key message-mode-map "\C-c\C-f\C-u" 'message-goto-summary)
   (define-key message-mode-map "\C-c\C-f\C-i" 'message-insert-or-toggle-importance)
+  (define-key message-mode-map "\C-c\C-f\C-a" 'message-gen-unsubscribed-mft)
   (define-key message-mode-map "\C-c\C-b" 'message-goto-body)
   (define-key message-mode-map "\C-c\C-i" 'message-goto-signature)
   (define-key message-mode-map "\C-c\C-fc" 'message-goto-mail-copies-to)
@@ -1953,6 +1990,7 @@ C-c C-f  move to a header field (and create it if there isn't):
         C-c C-f C-w  move to Fcc       C-c C-f C-r  move to Reply-To
         C-c C-f C-u  move to Summary   C-c C-f C-n  move to Newsgroups
         C-c C-f C-k  move to Keywords  C-c C-f C-d  move to Distribution
+         C-c C-f C-o  move to From (\"Originator\")
         C-c C-f C-f  move to Followup-To
         C-c C-f C-m  move to Mail-Followup-To
         C-c C-f C-i  cycle through Importance values
@@ -1971,6 +2009,7 @@ C-c C-r  `message-caesar-buffer-body' (rot13 the message body).
 C-c C-u  `message-insert-or-toggle-importance'  (insert or cycle importance).
 C-c M-n  `message-insert-disposition-notification-to'  (request receipt).
 M-RET    `message-newline-and-reformat' (break the line and reformat)."
+  (setq local-abbrev-table text-mode-abbrev-table)
   (set (make-local-variable 'message-reply-buffer) nil)
   (make-local-variable 'message-send-actions)
   (make-local-variable 'message-exit-actions)
@@ -2016,7 +2055,8 @@ M-RET    `message-newline-and-reformat' (break the line and reformat)."
     (if (fboundp 'mail-abbrevs-setup)
        (mail-abbrevs-setup)
       (mail-aliases-setup)))
-  (message-set-auto-save-file-name)
+  (unless buffer-file-name
+    (message-set-auto-save-file-name))
   (set (make-local-variable 'indent-tabs-mode) nil)) ;No tabs for indentation.
 
 (defun message-setup-fill-variables ()
@@ -2186,6 +2226,26 @@ return nil."
     (goto-char (point-max))
     nil))
 
+(defun message-gen-unsubscribed-mft (&optional include-cc)
+  "Insert a reasonable MFT header in a post to an unsubscribed list.
+When making original posts to a mailing list you are not subscribed to,
+you have to type in a MFT header by hand.  The contents, usually, are
+the addresses of the list and your own address.  This function inserts
+such a header automatically.  It fetches the contents of the To: header
+in the current mail buffer, and appends the current user-mail-address.
+
+If the optional argument `include-cc' is non-nil, the addresses in the
+Cc: header are also put into the MFT."
+
+  (interactive)
+  (message-remove-header "Mail-Followup-To")
+  (let* ((cc (and include-cc (message-fetch-field "Cc")))
+        (tos (if cc
+                 (concat (message-fetch-field "To") "," cc)
+               (message-fetch-field "To"))))
+    (message-goto-mail-followup-to)
+    (insert (concat tos ", " user-mail-address))))
+
 \f
 
 (defun message-insert-to (&optional force)
@@ -3013,7 +3073,7 @@ It should typically alter the sending method in some way or other."
        ;; ;; Avoid copying text props (except hard newlines).
        ;; T-gnus change: copy all text props from the editing buffer
        ;; into the encoding buffer.
-       (insert-buffer message-edit-buffer)
+       (insert-buffer-substring message-edit-buffer)
        (funcall message-encode-function)
        (while (and success
                    (setq elem (pop alist)))
@@ -3143,14 +3203,62 @@ used to distinguish whether the invisible text is a MIME part or not."
        (set-window-start (selected-window) (gnus-point-at-bol))
        (unless (yes-or-no-p
                 "Invisible text found and made visible; continue posting? ")
-         (error "Invisible text found and made visible"))))))
+         (error "Invisible text found and made visible")))))
+  (message-check 'illegible-text
+    (let ((mm-7bit-chars "\x20-\x7f\r\n\t\x7\x8\xb\xc\x1f\x1b")
+         found choice)
+      (message-goto-body)
+      (skip-chars-forward mm-7bit-chars)
+      (while (not (eobp))
+       (when (let ((char (char-after)))
+               (or (< (mm-char-int char) 128)
+                   (and (mm-multibyte-p)
+                        (memq (char-charset char)
+                              '(eight-bit-control eight-bit-graphic
+                                                  control-1)))))
+         (add-text-properties (point) (1+ (point)) '(highlight t))
+         (setq found t))
+       (forward-char)
+       (skip-chars-forward mm-7bit-chars))
+      (when found
+       (setq choice
+             (gnus-multiple-choice
+              "Illegible text found. Continue posting? "
+              '((?d "Remove and continue posting")
+                (?r "Replace with dots and continue posting")
+                (?i "Ignore and continue posting")
+                (?e "Continue editing"))))
+       (if (eq choice ?e)
+         (error "Illegible text found"))
+       (message-goto-body)
+       (skip-chars-forward mm-7bit-chars)
+       (while (not (eobp))
+         (when (let ((char (char-after)))
+                 (or (< (mm-char-int char) 128)
+                     (and (mm-multibyte-p)
+                          (memq (char-charset char)
+                                '(eight-bit-control eight-bit-graphic
+                                                    control-1)))))
+           (if (eq choice ?i)
+               (remove-text-properties (point) (1+ (point)) '(highlight t))
+             (delete-char 1)
+             (if (eq choice ?r)
+                 (insert "."))))
+         (forward-char)
+         (skip-chars-forward mm-7bit-chars))))))
 
 (defun message-add-action (action &rest types)
   "Add ACTION to be performed when doing an exit of type TYPES."
+  (while types
+    (add-to-list (intern (format "message-%s-actions" (pop types)))
+                action)))
+
+(defun message-delete-action (action &rest types)
+  "Delete ACTION from lists of actions performed when doing an exit of type TYPES."
   (let (var)
     (while types
       (set (setq var (intern (format "message-%s-actions" (pop types))))
-          (nconc (symbol-value var) (list action))))))
+          (delq action (symbol-value var))))))
 
 (defun message-do-actions (actions)
   "Perform all actions in ACTIONS."
@@ -3323,7 +3431,7 @@ This sub function is for exclusive use of `message-send-mail'."
            ;; ;; Avoid copying text props (except hard newlines).
            ;; T-gnus change: copy all text props from the editing buffer
            ;; into the encoding buffer.
-           (insert-buffer message-encoding-buffer)
+           (insert-buffer-substring message-encoding-buffer)
            ;; Remove some headers.
            (save-restriction
              (message-narrow-to-headers)
@@ -3455,7 +3563,9 @@ to find out how to use this."
        ;; free for -inject-arguments -- a big win for the user and for us
        ;; since we don't have to play that double-guessing game and the user
        ;; gets full control (no gestapo'ish -f's, for instance).  --sj
-       message-qmail-inject-args))
+       (if (functionp message-qmail-inject-args)
+           (funcall message-qmail-inject-args)
+         message-qmail-inject-args)))
     ;; qmail-inject doesn't say anything on it's stdout/stderr,
     ;; we have to look at the retval instead
     (0 nil)
@@ -3622,7 +3732,7 @@ Otherwise, generate and save a value for `canlock-password' first."
            (set-buffer tembuf)
            (buffer-disable-undo)
            (erase-buffer)
-           (insert-buffer message-encoding-buffer)
+           (insert-buffer-substring message-encoding-buffer)
            ;; Remove some headers.
            (save-restriction
              (message-narrow-to-headers)
@@ -3657,7 +3767,7 @@ Otherwise, generate and save a value for `canlock-password' first."
     (backward-char 1)
     (run-hooks 'message-send-news-hook)
     (gnus-open-server method)
-    (message "Sending news with %s..." (gnus-server-string method))
+    (message "Sending news via %s..." (gnus-server-string method))
     (gnus-request-post method)
     ))
 
@@ -4076,7 +4186,7 @@ Otherwise, generate and save a value for `canlock-password' first."
        (erase-buffer)
        (goto-char (point-min))
        (set-buffer-multibyte nil)
-       (insert-buffer message-encoding-buffer)
+       (insert-buffer-substring message-encoding-buffer)
        (goto-char (point-min))
        (if (re-search-forward "[^\x00-\x7f]" nil t)
            (y-or-n-p
@@ -4449,7 +4559,8 @@ give as trustworthy answer as possible."
 
 (defun message-make-mft ()
   "Return the Mail-Followup-To header."
-  (let* ((msg-recipients (message-options-get 'message-recipients))
+  (let* ((case-fold-search t)
+        (msg-recipients (message-options-get 'message-recipients))
         (recipients
          (mapcar 'mail-strip-quoted-names
                  (message-tokenize-header msg-recipients)))
@@ -4475,15 +4586,15 @@ give as trustworthy answer as possible."
                             (mapcar 'funcall
                                     message-subscribed-address-functions))))
     (save-match-data
-      (when (eval (apply 'append '(or)
+      (when (eval
+            (apply 'append '(or)
+                   (mapcar
+                    #'(lambda (regexp)
                         (mapcar
-                         (function (lambda (regexp)
-                                     (mapcar
-                                      (function (lambda (recipient)
-                                                  `(string-match ,regexp
-                                                                 ,recipient)))
-                                      recipients)))
-                         mft-regexps)))
+                         #'(lambda (recipient)
+                             `(string-match ,regexp ,recipient))
+                         recipients))
+                    mft-regexps)))
        msg-recipients))))
 
 ;; Dummy to avoid byte-compile warning.
@@ -5206,16 +5317,34 @@ responses here are directed to other addresses.")))
       ;; Perhaps "Mail-Copies-To: never" removed the only address?
       (if (string-equal recipients "")
          (setq recipients author))
-      ;; Convert string to a list of (("foo@bar" . "Name <foo@bar>") ...).
+      ;; Convert string to a list of (("foo@bar" . "Name <Foo@BAR>") ...).
       (setq recipients
            (mapcar
             (lambda (addr)
-              (cons (mail-strip-quoted-names addr) addr))
+              (cons (downcase (mail-strip-quoted-names addr)) addr))
             (message-tokenize-header recipients)))
       ;; Remove first duplicates.  (Why not all duplicates?  Is this a bug?)
       (let ((s recipients))
        (while s
          (setq recipients (delq (assoc (car (pop s)) s) recipients))))
+
+      ;; Remove hierarchical lists that are contained within each other,
+      ;; if message-hierarchical-addresses is defined.
+      (when message-hierarchical-addresses
+       (let ((plain-addrs (mapcar 'car recipients))
+             subaddrs recip)
+         (while plain-addrs
+           (setq subaddrs (assoc (car plain-addrs)
+                                 message-hierarchical-addresses)
+                 plain-addrs (cdr plain-addrs))
+           (when subaddrs
+             (setq subaddrs (cdr subaddrs))
+             (while subaddrs
+               (setq recip (assoc (car subaddrs) recipients)
+                     subaddrs (cdr subaddrs))
+               (if recip
+                   (setq recipients (delq recip recipients))))))))
+
       ;; Build the header alist.  Allow the user to be asked whether
       ;; or not to reply to all recipients in a wide reply.
       (setq follow-to (list (cons 'To (cdr (pop recipients)))))
@@ -5707,6 +5836,8 @@ Optional NEWS will use news to forward instead of mail."
 (defun message-forward-make-body (forward-buffer)
   ;; Put point where we want it before inserting the forwarded
   ;; message.
+  ;; Note that this function definition for T-gnus is totally different
+  ;; from the original Gnus."
   (if message-forward-before-signature
       (message-goto-body)
     (goto-char (point-max)))
@@ -5736,8 +5867,10 @@ Optional NEWS will use news to forward instead of mail."
 (defun message-forward-rmail-make-body (forward-buffer)
   (save-window-excursion
     (set-buffer forward-buffer)
-    (let (rmail-enable-mime)
-      (rmail-toggle-header 0)))
+    ;; Rmail doesn't have rmail-msg-restore-non-pruned-header in Emacs
+    ;; 20.  FIXIT, or we drop support for rmail in Emacs 20.
+    (if (rmail-msg-is-pruned)
+       (rmail-msg-restore-non-pruned-header)))
   (message-forward-make-body forward-buffer))
 
 ;;;###autoload
@@ -6308,10 +6441,12 @@ regexp varstr."
   (let ((reply-headers message-reply-headers)
        (buffer (current-buffer)))
     (with-temp-file buffer-file-name
-      (insert-buffer buffer)
+      (insert-buffer-substring buffer)
       (setq message-reply-headers reply-headers)
       (message-generate-headers '((optional . In-Reply-To)))
-      (mime-edit-translate-buffer))
+      (let ((mime-header-encode-method-alist
+            '((eword-encode-unstructured-field-body))))
+       (mime-edit-translate-buffer)))
     (set-buffer-modified-p nil))
   (message "Saving %s...done" buffer-file-name))