;;; message.el --- composing mail and news messages
;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-;; 2005, 2006 Free Software Foundation, Inc.
+;; 2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; Keywords: mail, news
;; This file is part of GNU Emacs.
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
-;; any later version.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; 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, Inc., 51 Franklin Street, Fifth Floor,
-;; Boston, MA 02110-1301, USA.
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
+;; For Emacs <22.2 and XEmacs.
+(eval-and-compile
+ (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
(eval-when-compile
- (require 'cl)
- (defvar gnus-message-group-art)
- (defvar gnus-list-identifiers) ; gnus-sum is required where necessary
- (require 'hashcash))
-(require 'canlock)
+ (require 'cl))
+
(require 'mailheader)
-(require 'nnheader)
(require 'gmm-utils)
+(require 'mail-utils)
+;; Only for the trivial macros mail-header-from, mail-header-date
+;; mail-header-references, mail-header-subject, mail-header-id
+(eval-when-compile (require 'nnheader))
;; This is apparently necessary even though things are autoloaded.
;; Because we dynamically bind mail-abbrev-mode-regexp, we'd better
;; require mailabbrev here.
(require 'mail-parse)
(require 'mml)
(require 'rfc822)
-(require 'ecomplete)
+
+(autoload 'mailclient-send-it "mailclient") ;; Emacs 22 or contrib/
+
+(defvar gnus-message-group-art)
+(defvar gnus-list-identifiers) ; gnus-sum is required where necessary
+(defvar rmail-enable-mime-composing)
(defgroup message '((user-mail-address custom-variable)
(user-full-name custom-variable))
:group 'message-interface
:type 'regexp)
-;;;###autoload
(defcustom message-from-style 'default
+ ;; In Emacs 24.1 this defaults to the value of `mail-from-style'
+ ;; that defaults to:
+ ;; `angles' in Emacs 22.1~23.1, XEmacs 21.4, 21.5, and SXEmacs 22.1;
+ ;; `default' in Emacs 23.2, and 24.1
"*Specifies how \"From\" headers look.
If nil, they contain just the return address like:
Otherwise, most addresses look like `angles', but they look like
`parens' if `angles' would need quoting and `parens' would not."
+ :version "23.2"
:type '(choice (const :tag "simple" nil)
(const parens)
(const angles)
Don't touch this variable unless you really know what you're doing.
-Checks include `subject-cmsg', `multiple-headers', `sendsys',
-`message-id', `from', `long-lines', `control-chars', `size',
-`new-text', `quoting-style', `redirected-followup', `signature',
-`approved', `sender', `empty', `empty-headers', `message-id', `from',
-`subject', `shorten-followup-to', `existing-newsgroups',
-`buffer-file-name', `unchanged', `newsgroups', `reply-to',
-`continuation-headers', `long-header-lines', `invisible-text' and
-`illegible-text'."
+Checks include `approved', `bogus-recipient', `continuation-headers',
+`control-chars', `empty', `existing-newsgroups', `from', `illegible-text',
+`invisible-text', `long-header-lines', `long-lines', `message-id',
+`multiple-headers', `new-text', `newsgroups', `quoting-style',
+`repeated-newsgroups', `reply-to', `sender', `sendsys', `shoot',
+`shorten-followup-to', `signature', `size', `subject', `subject-cmsg'
+and `valid-newsgroups'."
:group 'message-news
:type '(repeat sexp)) ; Fixme: improve this
:link '(custom-manual "(message)Message Headers")
:type '(repeat sexp))
-(defcustom message-draft-headers '(References From)
+(defcustom message-draft-headers '(References From Date)
"*Headers to be generated when saving a draft message."
:version "22.1"
:group 'message-news
"*Headers to be generated or prompted for when posting an article.
RFC977 and RFC1036 require From, Date, Newsgroups, Subject,
Message-ID. Organization, Lines, In-Reply-To, Expires, and
-User-Agent are optional. If don't you want message to insert some
+User-Agent are optional. If you don't want message to insert some
header, remove it from this list."
:group 'message-news
:group 'message-headers
:link '(custom-manual "(message)Message Headers")
:type '(repeat sexp))
+(defcustom message-prune-recipient-rules nil
+ "Rules for how to prune the list of recipients when doing wide replies.
+This is a list of regexps and regexp matches."
+ :version "24.1"
+ :group 'message-mail
+ :group 'message-headers
+ :link '(custom-manual "(message)Wide Reply")
+ :type '(repeat regexp))
+
(defcustom message-deletable-headers '(Message-ID Date Lines)
"Headers to be deleted if they already exist and were generated by message previously."
:group 'message-headers
:link '(custom-manual "(message)Mail Headers")
:type 'regexp)
-(defcustom message-ignored-supersedes-headers "^Path:\\|^Date\\|^NNTP-Posting-Host:\\|^Xref:\\|^Lines:\\|^Received:\\|^X-From-Line:\\|^X-Trace:\\|^X-Complaints-To:\\|Return-Path:\\|^Supersedes:\\|^NNTP-Posting-Date:\\|^X-Trace:\\|^X-Complaints-To:\\|^Cancel-Lock:\\|^Cancel-Key:\\|^X-Hashcash:\\|^X-Payment:\\|^Approved:"
+(defcustom message-ignored-supersedes-headers "^Path:\\|^Date\\|^NNTP-Posting-Host:\\|^Xref:\\|^Lines:\\|^Received:\\|^X-From-Line:\\|^X-Trace:\\|^X-ID:\\|^X-Complaints-To:\\|Return-Path:\\|^Supersedes:\\|^NNTP-Posting-Date:\\|^X-Trace:\\|^X-Complaints-To:\\|^Cancel-Lock:\\|^Cancel-Key:\\|^X-Hashcash:\\|^X-Payment:\\|^Approved:\\|^Injection-Date:\\|^Injection-Info:"
"*Header lines matching this regexp will be deleted before posting.
It's best to delete old Path and Date headers before posting to avoid
any confusion."
;;; Start of variables adopted from `message-utils.el'.
-(defcustom message-subject-trailing-was-query 'ask
+(defcustom message-subject-trailing-was-query t
"*What to do with trailing \"(was: <old subject>)\" in subject lines.
If nil, leave the subject unchanged. If it is the symbol `ask', query
the user what do do. In this case, the subject is matched against
`message-subject-trailing-was-query' is t, always strip the trailing
old subject. In this case, `message-subject-trailing-was-regexp' is
used."
- :version "22.1"
+ :version "24.1"
:type '(choice (const :tag "never" nil)
(const :tag "always strip" t)
- (const ask))
+ (const ask))
:link '(custom-manual "(message)Message Headers")
:group 'message-various)
;;; End of variables adopted from `message-utils.el'.
-;;;###autoload
-(defcustom message-signature-separator "^-- *$"
- "Regexp matching the signature separator."
- :type 'regexp
+(defcustom message-signature-separator "^-- $"
+ "Regexp matching the signature separator.
+This variable is used to strip off the signature from quoted text
+when `message-cite-function' is
+`message-cite-original-without-signature'. Most useful values
+are \"^-- $\" (strict) and \"^-- *$\" (loose; allow missing
+whitespace)."
+ :type '(choice (const :tag "strict" "^-- $")
+ (const :tag "loose" "^-- *$")
+ regexp)
+ :version "22.3" ;; Gnus 5.10.12 (changed default)
:link '(custom-manual "(message)Various Message Variables")
:group 'message-various)
:group 'message-various)
(defcustom message-interactive t
+ ;; In Emacs 24.1 this defaults to the value of `mail-interactive'
+ ;; that defaults to:
+ ;; `nil' in Emacs 22.1~22.3, XEmacs 21.4, 21.5, and SXEmacs 22.1;
+ ;; `t' in Emacs 23.1~24.1
"Non-nil means when sending a message wait for and display errors.
-nil means let mailer mail back a message to report errors."
+A value of nil means let mailer mail back a message to report errors."
+ :version "23.2"
:group 'message-sending
:group 'message-mail
:link '(custom-manual "(message)Sending Variables")
:type 'boolean)
-(defcustom message-generate-new-buffers 'unique
- "*Non-nil means create a new message buffer whenever `message-setup' is called.
-If this is a function, call that function with three parameters: The type,
-the to address and the group name. (Any of these may be nil.) The function
-should return the new buffer name."
+(defcustom message-confirm-send nil
+ "When non-nil, ask for confirmation when sending a message."
+ :group 'message-sending
+ :group 'message-mail
+ :version "23.1" ;; No Gnus
+ :link '(custom-manual "(message)Sending Variables")
+ :type 'boolean)
+
+(defcustom message-generate-new-buffers 'unsent
+ "*Say whether to create a new message buffer to compose a message.
+Valid values include:
+
+nil
+ Generate the buffer name in the Message way (e.g., *mail*, *news*,
+ *mail to whom*, *news on group*, etc.) and continue editing in the
+ existing buffer of that name. If there is no such buffer, it will
+ be newly created.
+
+`unique' or t
+ Create the new buffer with the name generated in the Message way.
+
+`unsent'
+ Similar to `unique' but the buffer name begins with \"*unsent \".
+
+`standard'
+ Similar to nil but the buffer name is simpler like *mail message*.
+
+function
+ If this is a function, call that function with three parameters:
+ The type, the To address and the group name (any of these may be nil).
+ The function should return the new buffer name."
+ :version "24.1"
:group 'message-buffers
:link '(custom-manual "(message)Message Buffers")
- :type '(choice (const :tag "off" nil)
- (const :tag "unique" unique)
- (const :tag "unsent" unsent)
- (function fun)))
+ :type '(choice (const nil)
+ (sexp :tag "unique" :format "unique\n" :value unique
+ :match (lambda (widget value) (memq value '(unique t))))
+ (const unsent)
+ (const standard)
+ (function :format "\n %{%t%}: %v")))
(defcustom message-kill-buffer-on-exit nil
"*Non-nil means that the message buffer will be killed after sending a message."
(defcustom message-kill-buffer-query t
"*Non-nil means that killing a modified message buffer has to be confirmed.
This is used by `message-kill-buffer'."
- :version "23.0" ;; No Gnus
+ :version "23.1" ;; No Gnus
:group 'message-buffers
:type 'boolean)
-(eval-when-compile
- (defvar gnus-local-organization))
+(defvar gnus-local-organization)
(defcustom message-user-organization
(or (and (boundp 'gnus-local-organization)
(stringp gnus-local-organization)
:type '(choice string
(const :tag "consult file" t)))
-;;;###autoload
-(defcustom message-user-organization-file "/usr/lib/news/organization"
+(defcustom message-user-organization-file
+ (let (orgfile)
+ (dolist (f (list "/etc/organization"
+ "/etc/news/organization"
+ "/usr/lib/news/organization"))
+ (when (file-readable-p f)
+ (setq orgfile f)))
+ orgfile)
"*Local news organization file."
:type 'file
:link '(custom-manual "(message)News Headers")
:link '(custom-manual "(message)Forwarding")
:type 'boolean)
-(defcustom message-ignored-resent-headers "^Return-receipt\\|^X-Gnus\\|^Gnus-Warning:\\|^>?From "
+(defcustom message-ignored-resent-headers
+ ;; `Delivered-To' needs to be removed because some mailers use it to
+ ;; detect loops, so if you resend a message to an address that ultimately
+ ;; comes back to you (e.g. a mailing-list to which you subscribe, in which
+ ;; case you may be removed from the list on the grounds that mail to you
+ ;; bounced with a "mailing loop" error).
+ "^Return-receipt\\|^X-Gnus\\|^Gnus-Warning:\\|^>?From \\|^Delivered-To:"
"*All headers that match this regexp will be deleted when resending a message."
:group 'message-interface
:link '(custom-manual "(message)Resending")
:type 'regexp)
(defcustom message-cite-prefix-regexp
- (if (string-match "[[:digit:]]" "1") ;; support POSIX?
- "\\([ \t]*[-_.[:word:]]+>+\\|[ \t]*[]>|}+]\\)+"
+ ;; In Emacs 24.1 this defaults to the value of
+ ;; `mail-citation-prefix-regexp'; the default value varies according
+ ;; to the Emacs version. In XEmacs 21.4 and 21.5, sendmail.el
+ ;; provides it.
+ (if (string-match "[[:digit:]]" "1")
+ ;; Support POSIX? XEmacs 21.5.27 doesn't.
+ "\\([ \t]*[_.[:word:]]+>+\\|[ \t]*[]>|]\\)+"
;; ?-, ?_ or ?. MUST NOT be in syntax entry w.
(let (non-word-constituents)
(with-syntax-table text-mode-syntax-table
(setq non-word-constituents
(concat
- (if (string-match "\\w" "-") "" "-")
(if (string-match "\\w" "_") "" "_")
(if (string-match "\\w" ".") "" "."))))
(if (equal non-word-constituents "")
- "\\([ \t]*\\(\\w\\)+>+\\|[ \t]*[]>|}+]\\)+"
+ "\\([ \t]*\\(\\w\\)+>+\\|[ \t]*[]>|]\\)+"
(concat "\\([ \t]*\\(\\w\\|["
non-word-constituents
- "]\\)+>+\\|[ \t]*[]>|}+]\\)+"))))
+ "]\\)+>+\\|[ \t]*[]>|]\\)+"))))
"*Regexp matching the longest possible citation prefix on a line."
- :version "22.1"
+ :version "24.1"
:group 'message-insertion
:link '(custom-manual "(message)Insertion Variables")
- :type 'regexp)
+ :type 'regexp
+ :set (lambda (symbol value)
+ (prog1
+ (custom-set-default symbol value)
+ (if (boundp 'gnus-message-cite-prefix-regexp)
+ (setq gnus-message-cite-prefix-regexp
+ (concat "^\\(?:" value "\\)"))))))
(defcustom message-cancel-message "I am canceling my own article.\n"
"Message to be inserted in the cancel message."
:link '(custom-manual "(message)Canceling News")
:type 'string)
+(defun message-send-mail-function ()
+ "Return suitable value for the variable `message-send-mail-function'."
+ (cond ((and (require 'sendmail)
+ (boundp 'sendmail-program)
+ sendmail-program
+ (executable-find sendmail-program))
+ 'message-send-mail-with-sendmail)
+ ((and (locate-library "smtpmail")
+ (boundp 'smtpmail-default-smtp-server)
+ smtpmail-default-smtp-server)
+ 'message-smtpmail-send-it)
+ ((locate-library "mailclient")
+ 'message-send-mail-with-mailclient)
+ (t
+ (error "Don't know how to send mail. Please customize `message-send-mail-function'"))))
+
;; Useful to set in site-init.el
-;;;###autoload
(defcustom message-send-mail-function
- (let ((program (if (boundp 'sendmail-program)
- ;; see paths.el
- sendmail-program)))
- (cond
- ((and program
- (string-match "/" program) ;; Skip path
- (file-executable-p program))
- 'message-send-mail-with-sendmail)
- ((and program
- (executable-find program))
- 'message-send-mail-with-sendmail)
- (t
- 'smtpmail-send-it)))
+ (cond ((eq send-mail-function 'smtpmail-send-it) 'message-smtpmail-send-it)
+ ((eq send-mail-function 'feedmail-send-it) 'feedmail-send-it)
+ ((eq send-mail-function 'mailclient-send-it)
+ 'message-send-mail-with-mailclient)
+ (t (message-send-mail-function)))
"Function to call to send the current buffer as mail.
The headers should be delimited by a line whose contents match the
variable `mail-header-separator'.
-Valid values include `message-send-mail-with-sendmail' (the default),
+Valid values include `message-send-mail-with-sendmail'
`message-send-mail-with-mh', `message-send-mail-with-qmail',
-`message-smtpmail-send-it', `smtpmail-send-it' and `feedmail-send-it'.
+`message-smtpmail-send-it', `smtpmail-send-it',
+`feedmail-send-it' and `message-send-mail-with-mailclient'. The
+default is system dependent and determined by the function
+`message-send-mail-function'.
See also `send-mail-function'."
:type '(radio (function-item message-send-mail-with-sendmail)
(function-item message-smtpmail-send-it)
(function-item smtpmail-send-it)
(function-item feedmail-send-it)
- (function :tag "Other"))
+ (function-item message-send-mail-with-mailclient
+ :tag "Use Mailclient package")
+ (function :tag "Other"))
:group 'message-sending
+ :version "23.2"
+ :initialize 'custom-initialize-default
:link '(custom-manual "(message)Mail Variables")
:group 'message-mail)
:type 'boolean)
(defcustom message-sendmail-envelope-from nil
+ ;; In Emacs 24.1 this defaults to the value of `mail-envelope-from'
+ ;; if it is available, or defaults to nil. sendmail.el provides it;
+ ;; the default value is nil in all (X)Emacsen that Gnus supports.
"*Envelope-from when sending mail with sendmail.
If this is nil, use `user-mail-address'. If it is the symbol
`header', use the From: header of the message."
- :version "22.1"
+ :version "23.2"
:type '(choice (string :tag "From name")
(const :tag "Use From: header from message" header)
(const :tag "Use `user-mail-address'" nil))
:link '(custom-manual "(message)Mail Variables")
:group 'message-sending)
+(defcustom message-sendmail-extra-arguments nil
+ "Additional arguments to `sendmail-program'."
+ ;; E.g. '("-a" "account") for msmtp
+ :version "23.1" ;; No Gnus
+ :type '(repeat string)
+ ;; :link '(custom-manual "(message)Mail Variables")
+ :group 'message-sending)
+
;; qmail-related stuff
(defcustom message-qmail-inject-program "/var/qmail/bin/qmail-inject"
"Location of the qmail-inject program."
(defcustom message-qmail-inject-args nil
"Arguments passed to qmail-inject programs.
-This should be a list of strings, one string for each argument. It
-may also be a function.
+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
:type '(choice (function)
(repeat string)))
-(eval-when-compile
- (defvar gnus-post-method)
- (defvar gnus-select-method))
+(defvar gnus-post-method)
+(defvar gnus-select-method)
(defcustom message-post-method
(cond ((and (boundp 'gnus-post-method)
(listp gnus-post-method)
:group 'message-headers
:link '(custom-manual "(message)Message Headers")
:type '(choice (const :tag "None" nil)
- (const :tag "References" '(references))
- (const :tag "All" t)
- (repeat (sexp :tag "Header"))))
+ (const :tag "References" '(references))
+ (const :tag "All" t)
+ (repeat (sexp :tag "Header"))))
+
+(defcustom message-fill-column 72
+ "Column beyond which automatic line-wrapping should happen.
+Local value for message buffers. If non-nil, also turn on
+auto-fill in message buffers."
+ :group 'message-various
+ ;; :link '(custom-manual "(message)Message Headers")
+ :type '(choice (const :tag "Don't turn on auto fill" nil)
+ (integer)))
(defcustom message-setup-hook nil
"Normal hook, run each time a new outgoing message is initialized.
:version "22.1"
:group 'message-various)
-;;;###autoload
(defcustom message-citation-line-function 'message-insert-citation-line
"*Function called to insert the \"Whomever writes:\" line.
+Predefined functions include `message-insert-citation-line' and
+`message-insert-formatted-citation-line' (see the variable
+`message-citation-line-format').
+
Note that Gnus provides a feature where the reader can click on
`writes:' to hide the cited text. If you change this line too much,
people who read your message will have to change their Gnus
configuration. See the variable `gnus-cite-attribution-suffix'."
- :type 'function
+ :type '(choice
+ (function-item :tag "plain" message-insert-citation-line)
+ (function-item :tag "formatted" message-insert-formatted-citation-line)
+ (function :tag "Other"))
:link '(custom-manual "(message)Insertion Variables")
:group 'message-insertion)
-;;;###autoload
+(defcustom message-citation-line-format "On %a, %b %d %Y, %N wrote:\n"
+ "Format of the \"Whomever writes:\" line.
+
+The string is formatted using `format-spec'. The following
+constructs are replaced:
+
+ %f The full From, e.g. \"John Doe <john.doe@example.invalid>\".
+ %n The mail address, e.g. \"john.doe@example.invalid\".
+ %N The real name if present, e.g.: \"John Doe\", else fall
+ back to the mail address.
+ %F The first name if present, e.g.: \"John\".
+ %L The last name if present, e.g.: \"Doe\".
+
+All other format specifiers are passed to `format-time-string'
+which is called using the date from the article your replying to.
+Extracting the first (%F) and last name (%L) is done
+heuristically, so you should always check it yourself.
+
+Please also read the note in the documentation of
+`message-citation-line-function'."
+ :type '(choice (const :tag "Plain" "%f writes:")
+ (const :tag "Include date" "On %a, %b %d %Y, %n wrote:")
+ string)
+ :link '(custom-manual "(message)Insertion Variables")
+ :version "23.1" ;; No Gnus
+ :group 'message-insertion)
+
(defcustom message-yank-prefix "> "
+ ;; In Emacs 24.1 this defaults to the value of `mail-yank-prefix'
+ ;; that defaults to:
+ ;; `nil' in Emacs 22.1~23.1;
+ ;; "> " in Emacs 23.2, 24.1, XEmacs 21.4, 21.5, and SXEmacs 22.1
"*Prefix inserted on the lines of yanked messages.
Fix `message-cite-prefix-regexp' if it is set to an abnormal value.
See also `message-yank-cited-prefix' and `message-yank-empty-prefix'."
+ :version "23.2"
:type 'string
:link '(custom-manual "(message)Insertion Variables")
:group 'message-insertion)
:group 'message-insertion)
(defcustom message-indentation-spaces 3
+ ;; In Emacs 24.1 this defaults to the value of
+ ;; `mail-indentation-spaces' that defaults to `3' in Emacs 22.1~24.1,
+ ;; and SXEmacs 22.1. In XEmacs 21.4 and 21.5, sendmail.el provides
+ ;; it; the defalut value is `3'.
"*Number of spaces to insert at the beginning of each cited line.
Used by `message-yank-original' via `message-yank-cite'."
+ :version "23.2"
:group 'message-insertion
:link '(custom-manual "(message)Insertion Variables")
:type 'integer)
-;;;###autoload
-(defcustom message-cite-function 'message-cite-original
+(defcustom message-cite-function 'message-cite-original-without-signature
"*Function for citing an original message.
Predefined functions include `message-cite-original' and
`message-cite-original-without-signature'.
(function-item sc-cite-original)
(function :tag "Other"))
:link '(custom-manual "(message)Insertion Variables")
+ :version "22.3" ;; Gnus 5.10.12 (changed default)
:group 'message-insertion)
-;;;###autoload
(defcustom message-indent-citation-function 'message-indent-citation
"*Function for modifying a citation just inserted in the mail buffer.
This can also be a list of functions. Each function can find the
:link '(custom-manual "(message)Insertion Variables")
:group 'message-insertion)
-;;;###autoload
(defcustom message-signature t
+ ;; In Emacs 24.1 this defaults to the value of `mail-signature' that
+ ;; defaults to:
+ ;; `nil' in Emacs 22.1~23.1, XEmacs 21.4, 21.5, and SXEmacs 22.1;
+ ;; `t' in Emacs 23.2, and 24.1
"*String to be inserted at the end of the message buffer.
If t, the `message-signature-file' file will be inserted instead.
If a function, the result from the function will be used instead.
If a form, the result from the form will be used instead."
+ :version "23.2"
:type 'sexp
:link '(custom-manual "(message)Insertion Variables")
:group 'message-insertion)
-;;;###autoload
(defcustom message-signature-file "~/.signature"
+ ;; In Emacs 24.1 this defaults to the value of `mail-signature-file'
+ ;; that defaults to "~/.signature" in Emacs 22.1~24.1, and SXEmacs
+ ;; 22.1. In XEmacs 21.4 and 21.5, sendmail.el provides it;
+ ;; the defalut value is "~/.signature".
"*Name of file containing the text inserted at end of message buffer.
Ignored if the named file doesn't exist.
-If nil, don't insert a signature."
+If nil, don't insert a signature.
+If a path is specified, the value of `message-signature-directory' is ignored,
+even if set."
+ :version "23.2"
:type '(choice file (const :tags "None" nil))
:link '(custom-manual "(message)Insertion Variables")
:group 'message-insertion)
-;;;###autoload
+(defcustom message-signature-directory nil
+ "*Name of directory containing signature files.
+Comes in handy if you have many such files, handled via posting styles for
+instance.
+If nil, `message-signature-file' is expected to specify the directory if
+needed."
+ :type '(choice string (const :tags "None" nil))
+ :link '(custom-manual "(message)Insertion Variables")
+ :group 'message-insertion)
+
(defcustom message-signature-insert-empty-line t
"*If non-nil, insert an empty line before the signature separator."
:version "22.1"
(string :tag "name")
(sexp :tag "none" :format "%t" t)))
+;; This can be the name of a buffer, or a cons cell (FUNCTION . ARGS)
+;; for yanking the original buffer.
(defvar message-reply-buffer nil)
(defvar message-reply-headers nil
"The headers of the current replied article.
:error "All header lines must be newline terminated")
(defcustom message-default-headers ""
- "*A string containing header lines to be inserted in outgoing messages.
-It is inserted before you edit the message, so you can edit or delete
-these lines."
+ "Header lines to be inserted in outgoing messages.
+This can be set to a string containing or a function returning
+header lines to be inserted before you edit the message, so you
+can edit or delete these lines. If set to a function, it is
+called and its result is inserted."
+ :version "23.2"
:group 'message-headers
:link '(custom-manual "(message)Message Headers")
- :type 'message-header-lines)
-
-(defcustom message-default-mail-headers ""
+ :type '(choice
+ (message-header-lines :tag "String")
+ (function :tag "Function")))
+
+(defcustom message-default-mail-headers
+ ;; Ease the transition from mail-mode to message-mode. See bugs#4431, 5555.
+ (concat (if (and (boundp 'mail-default-reply-to)
+ (stringp mail-default-reply-to))
+ (format "Reply-to: %s\n" mail-default-reply-to))
+ (if (and (boundp 'mail-self-blind)
+ mail-self-blind)
+ (format "BCC: %s\n" user-mail-address))
+ (if (and (boundp 'mail-archive-file-name)
+ (stringp mail-archive-file-name))
+ (format "FCC: %s\n" mail-archive-file-name))
+ ;; Use the value of `mail-default-headers' if available.
+ ;; Note: as for XEmacs 21.4 and 21.5, it is unavailable
+ ;; unless sendmail.el is loaded.
+ (if (boundp 'mail-default-headers)
+ mail-default-headers))
"*A string of header lines to be inserted in outgoing mails."
+ :version "23.2"
:group 'message-headers
:group 'message-mail
:link '(custom-manual "(message)Mail Headers")
(file-readable-p "/etc/sendmail.cf")
(let ((buffer (get-buffer-create " *temp*")))
(unwind-protect
- (save-excursion
- (set-buffer buffer)
+ (with-current-buffer buffer
(insert-file-contents "/etc/sendmail.cf")
(goto-char (point-min))
(let ((case-fold-search nil))
(defcustom message-mail-alias-type 'abbrev
"*What alias expansion type to use in Message buffers.
-The default is `abbrev', which uses mailabbrev. nil switches
-mail aliases off."
+The default is `abbrev', which uses mailabbrev. `ecomplete' uses
+an electric completion mode. nil switches mail aliases off.
+This can also be a list of values."
:group 'message
:link '(custom-manual "(message)Mail Aliases")
:type '(choice (const :tag "Use Mailabbrev" abbrev)
(const :tag "Use ecomplete" ecomplete)
(const :tag "No expansion" nil)))
+(defcustom message-self-insert-commands '(self-insert-command)
+ "List of `self-insert-command's used to trigger ecomplete.
+When one of those commands is invoked to enter a character in To or Cc
+header, ecomplete will suggest the candidates of recipients (see also
+`message-mail-alias-type'). If you use some tool to enter non-ASCII
+text and it replaces `self-insert-command' with the other command, e.g.
+`egg-self-insert-command', you may want to add it to this list."
+ :group 'message-various
+ :type '(repeat function))
+
(defcustom message-auto-save-directory
- (file-name-as-directory (nnheader-concat message-directory "drafts"))
+ (file-name-as-directory (expand-file-name "drafts" message-directory))
"*Directory where Message auto-saves buffers if Gnus isn't running.
If nil, Message won't auto-save."
:group 'message-buffers
(defcustom message-dont-reply-to-names
(and (boundp 'rmail-dont-reply-to-names) rmail-dont-reply-to-names)
- "*A regexp specifying addresses to prune when doing wide replies.
-A value of nil means exclude your own user name only."
+ "*Addresses to prune when doing wide replies.
+This can be a regexp or a list of regexps. Also, a value of nil means
+exclude your own user name only."
:version "21.1"
:group 'message
:link '(custom-manual "(message)Wide Reply")
:type '(choice (const :tag "Yourself" nil)
- regexp))
+ regexp
+ (repeat :tag "Regexp List" regexp)))
+
+(defsubst message-dont-reply-to-names ()
+ (gmm-regexp-concat message-dont-reply-to-names))
(defvar message-shoot-gnksa-feet nil
"*A list of GNKSA feet you are allowed to shoot.
`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 messages from
- your other email addresses.")
+ your other email addresses.")
(defsubst message-gnksa-enable-p (feature)
(or (not (listp message-shoot-gnksa-feet))
(memq feature message-shoot-gnksa-feet)))
-(defcustom message-hidden-headers "^References:"
+(defcustom message-hidden-headers '("^References:" "^Face:" "^X-Face:"
+ "^X-Draft-From:")
"Regexp of headers to be hidden when composing new messages.
This can also be a list of regexps to match headers. Or a list
starting with `not' and followed by regexps."
(defface message-header-to
'((((class color)
(background dark))
- (:foreground "green2" :bold t))
+ (:foreground "DarkOliveGreen1" :bold t))
(((class color)
(background light))
(:foreground "MidnightBlue" :bold t))
:group 'message-faces)
;; backward-compatibility alias
(put 'message-header-to-face 'face-alias 'message-header-to)
+(put 'message-header-to-face 'obsolete-face "22.1")
(defface message-header-cc
'((((class color)
(background dark))
- (:foreground "green4" :bold t))
+ (:foreground "chartreuse1" :bold t))
(((class color)
(background light))
(:foreground "MidnightBlue"))
:group 'message-faces)
;; backward-compatibility alias
(put 'message-header-cc-face 'face-alias 'message-header-cc)
+(put 'message-header-cc-face 'obsolete-face "22.1")
(defface message-header-subject
'((((class color)
(background dark))
- (:foreground "green3"))
+ (:foreground "OliveDrab1"))
(((class color)
(background light))
(:foreground "navy blue" :bold t))
:group 'message-faces)
;; backward-compatibility alias
(put 'message-header-subject-face 'face-alias 'message-header-subject)
+(put 'message-header-subject-face 'obsolete-face "22.1")
(defface message-header-newsgroups
'((((class color)
:group 'message-faces)
;; backward-compatibility alias
(put 'message-header-newsgroups-face 'face-alias 'message-header-newsgroups)
+(put 'message-header-newsgroups-face 'obsolete-face "22.1")
(defface message-header-other
'((((class color)
(background dark))
- (:foreground "#b00000"))
+ (:foreground "VioletRed1"))
(((class color)
(background light))
(:foreground "steel blue"))
:group 'message-faces)
;; backward-compatibility alias
(put 'message-header-other-face 'face-alias 'message-header-other)
+(put 'message-header-other-face 'obsolete-face "22.1")
(defface message-header-name
'((((class color)
(background dark))
- (:foreground "DarkGreen"))
+ (:foreground "green"))
(((class color)
(background light))
(:foreground "cornflower blue"))
:group 'message-faces)
;; backward-compatibility alias
(put 'message-header-name-face 'face-alias 'message-header-name)
+(put 'message-header-name-face 'obsolete-face "22.1")
(defface message-header-xheader
'((((class color)
(background dark))
- (:foreground "blue"))
+ (:foreground "DeepSkyBlue1"))
(((class color)
(background light))
(:foreground "blue"))
:group 'message-faces)
;; backward-compatibility alias
(put 'message-header-xheader-face 'face-alias 'message-header-xheader)
+(put 'message-header-xheader-face 'obsolete-face "22.1")
(defface message-separator
'((((class color)
(background dark))
- (:foreground "blue3"))
+ (:foreground "LightSkyBlue1"))
(((class color)
(background light))
(:foreground "brown"))
:group 'message-faces)
;; backward-compatibility alias
(put 'message-separator-face 'face-alias 'message-separator)
+(put 'message-separator-face 'obsolete-face "22.1")
(defface message-cited-text
'((((class color)
(background dark))
- (:foreground "red"))
+ (:foreground "LightPink1"))
(((class color)
(background light))
(:foreground "red"))
:group 'message-faces)
;; backward-compatibility alias
(put 'message-cited-text-face 'face-alias 'message-cited-text)
+(put 'message-cited-text-face 'obsolete-face "22.1")
(defface message-mml
'((((class color)
(background dark))
- (:foreground "ForestGreen"))
+ (:foreground "MediumSpringGreen"))
(((class color)
(background light))
(:foreground "ForestGreen"))
:group 'message-faces)
;; backward-compatibility alias
(put 'message-mml-face 'face-alias 'message-mml)
+(put 'message-mml-face 'obsolete-face "22.1")
(defun message-font-lock-make-header-matcher (regexp)
(let ((form
(1 'message-header-name)
(2 'message-header-newsgroups nil t))
(,(message-font-lock-make-header-matcher
- (concat "^\\([A-Z][^: \n\t]+:\\)" content))
+ (concat "^\\(X-[A-Za-z0-9-]+:\\|In-Reply-To:\\)" content))
(1 'message-header-name)
- (2 'message-header-other nil t))
+ (2 'message-header-xheader))
(,(message-font-lock-make-header-matcher
- (concat "^\\(X-[A-Za-z0-9-]+:\\|In-Reply-To:\\)" content))
+ (concat "^\\([A-Z][^: \n\t]+:\\)" content))
(1 'message-header-name)
- (2 'message-header-name))
+ (2 'message-header-other nil t))
,@(if (and mail-header-separator
(not (equal mail-header-separator "")))
`((,(concat "^\\(" (regexp-quote mail-header-separator) "\\)$")
and Emacs, you may use `iso-2022-7bit' for this value at your own risk.
Note that the coding-system `iso-2022-7bit' isn't suitable to all data.")
-(defcustom message-send-mail-partially-limit 1000000
+(defcustom message-send-mail-partially-limit nil
"The limitation of messages sent as message/partial.
The lower bound of message size in characters, beyond which the message
should be sent in several parts. If it is nil, the size is unlimited."
- :version "21.1"
+ :version "24.1"
:group 'message-buffers
:link '(custom-manual "(message)Mail Variables")
:type '(choice (const :tag "unlimited" nil)
(const :tag "Never" nil)
(const :tag "Always" t)))
-(defcustom message-generate-hashcash nil
+(defcustom message-generate-hashcash (if (executable-find "hashcash") 'opportunistic)
"*Whether to generate X-Hashcash: headers.
+If t, always generate hashcash headers. If `opportunistic',
+only generate hashcash headers if it can be done without the user
+waiting (i.e., only asynchronously).
+
You must have the \"hashcash\" binary installed, see `hashcash-path'."
+ :version "24.1"
:group 'message-headers
:link '(custom-manual "(message)Mail Headers")
- :type 'boolean)
+ :type '(choice (const :tag "Always" t)
+ (const :tag "Never" nil)
+ (const :tag "Opportunistic" opportunistic)))
;;; Internal variables.
(defvar message-mime-part nil)
(defvar message-posting-charset nil)
(defvar message-inserted-headers nil)
+(defvar message-inhibit-ecomplete nil)
;; Byte-compiler warning
-(eval-when-compile
- (defvar gnus-active-hashtb)
- (defvar gnus-read-active-file))
+(defvar gnus-active-hashtb)
+(defvar gnus-read-active-file)
;;; Regexp matching the delimiter of messages in UNIX mail format
;;; (UNIX From lines), minus the initial ^. It should be a copy
"^ *--+ +begin message +--+ *$\\|"
"^ *---+ +Original message follows +---+ *$\\|"
"^ *---+ +Undelivered message follows +---+ *$\\|"
+ "^------ This is a copy of the message, including all the headers. ------ *$\\|"
"^|? *---+ +Message text follows: +---+ *|?$")
"A regexp that matches the separator before the text of a failed message.")
(defvar message-send-mail-real-function nil
"Internal send mail function.")
-(defvar message-bogus-system-names "^localhost\\."
+(defvar message-bogus-system-names "^localhost\\.\\|\\.local$"
"The regexp of bogus system names.")
(defcustom message-valid-fqdn-regexp
(concat "[a-z0-9][-.a-z0-9]+\\." ;; [hostname.subdomain.]domain.
;; valid TLDs:
- "\\([a-z][a-z]" ;; two letter country TDLs
- "\\|biz\\|com\\|edu\\|gov\\|int\\|mil\\|net\\|org"
- "\\|aero\\|coop\\|info\\|name\\|museum"
- "\\|arpa\\|pro\\|uucp\\|bitnet\\|bofh" ;; old style?
- "\\)")
+ "\\([a-z][a-z]\\|" ;; two letter country TDLs
+ "aero\\|arpa\\|bitnet\\|biz\\|bofh\\|"
+ "cat\\|com\\|coop\\|edu\\|gov\\|"
+ "info\\|int\\|jobs\\|"
+ "mil\\|mobi\\|museum\\|name\\|net\\|"
+ "org\\|pro\\|travel\\|uucp\\)")
+ ;; http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains
+ ;; http://en.wikipedia.org/wiki/GTLD
+ ;; `in the process of being approved': .asia .post .tel .sex
+ ;; "dead" nato bitnet uucp
"Regular expression that matches a valid FQDN."
;; see also: gnus-button-valid-fqdn-regexp
:version "22.1"
:group 'message-headers
:type 'regexp)
-(eval-and-compile
- (autoload 'gnus-alive-p "gnus-util")
- (autoload 'gnus-delay-article "gnus-delay")
- (autoload 'gnus-extract-address-components "gnus-util")
- (autoload 'gnus-find-method-for-group "gnus")
- (autoload 'gnus-group-decoded-name "gnus-group")
- (autoload 'gnus-group-name-charset "gnus-group")
- (autoload 'gnus-group-name-decode "gnus-group")
- (autoload 'gnus-groups-from-server "gnus")
- (autoload 'gnus-make-local-hook "gnus-util")
- (autoload 'gnus-open-server "gnus-int")
- (autoload 'gnus-output-to-mail "gnus-util")
- (autoload 'gnus-output-to-rmail "gnus-util")
- (autoload 'gnus-request-post "gnus-int")
- (autoload 'gnus-server-string "gnus")
- (autoload 'idna-to-ascii "idna")
- (autoload 'message-setup-toolbar "messagexmas")
- (autoload 'mh-new-draft-name "mh-comp")
- (autoload 'mh-send-letter "mh-comp")
- (autoload 'nndraft-request-associate-buffer "nndraft")
- (autoload 'nndraft-request-expire-articles "nndraft")
- (autoload 'nnvirtual-find-group-art "nnvirtual")
- (autoload 'rmail-dont-reply-to "mail-utils")
- (autoload 'rmail-msg-is-pruned "rmail")
- (autoload 'rmail-msg-restore-non-pruned-header "rmail")
- (autoload 'rmail-output "rmailout"))
+(autoload 'gnus-alive-p "gnus-util")
+(autoload 'gnus-delay-article "gnus-delay")
+(autoload 'gnus-extract-address-components "gnus-util")
+(autoload 'gnus-find-method-for-group "gnus")
+(autoload 'gnus-group-decoded-name "gnus-group")
+(autoload 'gnus-group-name-charset "gnus-group")
+(autoload 'gnus-group-name-decode "gnus-group")
+(autoload 'gnus-groups-from-server "gnus")
+(autoload 'gnus-make-local-hook "gnus-util")
+(autoload 'gnus-open-server "gnus-int")
+(autoload 'gnus-output-to-mail "gnus-util")
+(autoload 'gnus-output-to-rmail "gnus-util")
+(autoload 'gnus-request-post "gnus-int")
+(autoload 'gnus-select-frame-set-input-focus "gnus-util")
+(autoload 'gnus-server-string "gnus")
+(autoload 'idna-to-ascii "idna")
+(autoload 'message-setup-toolbar "messagexmas")
+(autoload 'mh-new-draft-name "mh-comp")
+(autoload 'mh-send-letter "mh-comp")
+(autoload 'nndraft-request-associate-buffer "nndraft")
+(autoload 'nndraft-request-expire-articles "nndraft")
+(autoload 'nnvirtual-find-group-art "nnvirtual")
+(autoload 'rmail-dont-reply-to "mail-utils")
+(autoload 'rmail-msg-is-pruned "rmail")
+(autoload 'rmail-output "rmailout")
\f
(setq paren nil))))
(nreverse elems)))))
+(autoload 'nnheader-insert-file-contents "nnheader")
+
(defun message-mail-file-mbox-p (file)
"Say whether FILE looks like a Unix mbox file."
(when (and (file-exists-p file)
(defmacro message-with-reply-buffer (&rest forms)
"Evaluate FORMS in the reply buffer, if it exists."
- `(when (and message-reply-buffer
+ `(when (and (bufferp message-reply-buffer)
(buffer-name message-reply-buffer))
- (save-excursion
- (set-buffer message-reply-buffer)
+ (with-current-buffer message-reply-buffer
,@forms)))
(put 'message-with-reply-buffer 'lisp-indent-function 0)
(substring subject (match-end 0))
subject))
+(defcustom message-replacement-char "."
+ "Replacement character used instead of unprintable or not decodable chars."
+ :group 'message-various
+ :version "22.1" ;; Gnus 5.10.9
+ :type '(choice string
+ (const ".")
+ (const "?")))
+
+;; FIXME: We also should call `message-strip-subject-encoded-words'
+;; when forwarding. Probably in `message-make-forward-subject' and
+;; `message-forward-make-body'.
+
+(defun message-strip-subject-encoded-words (subject)
+ "Fix non-decodable words in SUBJECT."
+ ;; Cf. `gnus-simplify-subject-fully'.
+ (let* ((case-fold-search t)
+ (replacement-chars (format "[%s%s%s]"
+ message-replacement-char
+ message-replacement-char
+ message-replacement-char))
+ (enc-word-re "=\\?\\([^?]+\\)\\?\\([QB]\\)\\?\\([^?]+\\)\\(\\?=\\)")
+ cs-string
+ (have-marker
+ (with-temp-buffer
+ (insert subject)
+ (goto-char (point-min))
+ (when (re-search-forward enc-word-re nil t)
+ (setq cs-string (match-string 1)))))
+ cs-coding q-or-b word-beg word-end)
+ (if (or (not have-marker) ;; No encoded word found...
+ ;; ... or double encoding was correct:
+ (and (stringp cs-string)
+ (setq cs-string (downcase cs-string))
+ (mm-coding-system-p (intern cs-string))
+ (not (prog1
+ (y-or-n-p
+ (format "\
+Decoded Subject \"%s\"
+contains a valid encoded word. Decode again? "
+ subject))
+ (setq cs-coding (intern cs-string))))))
+ subject
+ (with-temp-buffer
+ (insert subject)
+ (goto-char (point-min))
+ (while (re-search-forward enc-word-re nil t)
+ (setq cs-string (downcase (match-string 1))
+ q-or-b (match-string 2)
+ word-beg (match-beginning 0)
+ word-end (match-end 0))
+ (setq cs-coding
+ (if (mm-coding-system-p (intern cs-string))
+ (setq cs-coding (intern cs-string))
+ nil))
+ ;; No double encoded subject? => bogus charset.
+ (unless cs-coding
+ (setq cs-coding
+ (mm-read-coding-system
+ (format "\
+Decoded Subject \"%s\"
+contains an encoded word. The charset `%s' is unknown or invalid.
+Hit RET to replace non-decodable characters with \"%s\" or enter replacement
+charset: "
+ subject cs-string message-replacement-char)))
+ (if cs-coding
+ (replace-match (concat "=?" (symbol-name cs-coding)
+ "?\\2?\\3\\4\\5"))
+ (save-excursion
+ (goto-char word-beg)
+ (re-search-forward "=\\?\\([^?]+\\)\\?\\([QB]\\)\\?" word-end t)
+ (replace-match "")
+ ;; QP or base64
+ (if (string-match "\\`Q\\'" q-or-b)
+ ;; QP
+ (progn
+ (message "Replacing non-decodable characters with \"%s\"."
+ message-replacement-char)
+ (while (re-search-forward "\\(=[A-F0-9][A-F0-9]\\)+"
+ word-end t)
+ (replace-match message-replacement-char)))
+ ;; base64
+ (message "Replacing non-decodable characters with \"%s\"."
+ replacement-chars)
+ (re-search-forward "[^?]+" word-end t)
+ (replace-match replacement-chars))
+ (re-search-forward "\\?=")
+ (replace-match "")))))
+ (rfc2047-decode-region (point-min) (point-max))
+ (buffer-string)))))
+
;;; Start of functions adopted from `message-utils.el'.
(defun message-strip-subject-trailing-was (subject)
(defun message-change-subject (new-subject)
"Ask for NEW-SUBJECT header, append (was: <Old Subject>)."
- ;; <URL:http://www.landfield.com/usefor/drafts/draft-ietf-usefor-useage--1.02.unpaged>
(interactive
(list
(read-from-minibuffer "New subject: ")))
(point-max)))
(goto-char (point-min)))
+;; FIXME: clarify diffference: message-narrow-to-head,
+;; message-narrow-to-headers-or-head, message-narrow-to-headers
(defun message-narrow-to-head ()
"Narrow the buffer to the head of the message.
Point is left at the beginning of the narrowed-to region."
(widen)
(narrow-to-region
(goto-char (point-min))
- (cond
- ((re-search-forward
- (concat "^" (regexp-quote mail-header-separator) "\n") nil t)
- (match-beginning 0))
- ((search-forward "\n\n" nil t)
- (1- (point)))
- (t
- (point-max))))
+ (if (re-search-forward (concat "\\(\n\\)\n\\|^\\("
+ (regexp-quote mail-header-separator)
+ "\n\\)")
+ nil t)
+ (or (match-end 1) (match-beginning 2))
+ (point-max)))
(goto-char (point-min)))
(defun message-news-p ()
(kill-region start (point))))
+(autoload 'Info-goto-node "info")
+(defvar mml2015-use)
+
(defun message-info (&optional arg)
"Display the Message manual.
-Prefixed with one \\[universal-argument], display the Emacs MIME manual.
-Prefixed with two \\[universal-argument]'s, display the PGG manual."
+Prefixed with one \\[universal-argument], display the Emacs MIME
+manual. With two \\[universal-argument]'s, display the EasyPG or
+PGG manual, depending on the value of `mml2015-use'."
(interactive "p")
- (cond ((eq arg 16) (Info-goto-node "(pgg)Top"))
- ((eq arg 4) (Info-goto-node "(emacs-mime)Top"))
- (t (Info-goto-node "(message)Top"))))
+ ;; Don't use `info' because support for `(filename)nodename' is not
+ ;; available in XEmacs < 21.5.12.
+ (Info-goto-node (format "(%s)Top"
+ (cond ((eq arg 16)
+ (require 'mml2015)
+ mml2015-use)
+ ((eq arg 4) 'emacs-mime)
+ ;; `booleanp' only available in Emacs 22+
+ ((and (not (memq arg '(nil t)))
+ (symbolp arg))
+ arg)
+ (t
+ 'message)))))
\f
(define-key message-mode-map "\C-a" 'message-beginning-of-line)
(define-key message-mode-map "\t" 'message-tab)
- (define-key message-mode-map "\M-;" 'comment-region)
(define-key message-mode-map "\M-n" 'message-display-abbrev))
(defvar message-tool-bar-map nil)
-(eval-when-compile
- (defvar facemenu-add-face-function)
- (defvar facemenu-remove-face-function))
+(defvar facemenu-add-face-function)
+(defvar facemenu-remove-face-function)
;;; Forbidden properties
;;
;; We use `after-change-functions' to keep special text properties
-;; that interfer with the normal function of message mode out of the
+;; that interfere with the normal function of message mode out of the
;; buffer.
(defcustom message-strip-special-text-properties t
(get-text-property pos 'egg-lang)
(get-text-property pos 'egg-start)))))
+(defsubst message-mail-alias-type-p (type)
+ (if (atom message-mail-alias-type)
+ (eq message-mail-alias-type type)
+ (memq type message-mail-alias-type)))
+
(defun message-strip-forbidden-properties (begin end &optional old-length)
"Strip forbidden properties between BEGIN and END, ignoring the third arg.
This function is intended to be called from `after-change-functions'.
See also `message-forbidden-properties'."
+ (when (and (message-mail-alias-type-p 'ecomplete)
+ (memq this-command message-self-insert-commands))
+ (message-display-abbrev))
(when (and message-strip-special-text-properties
(message-tamago-not-in-use-p begin))
(let ((buffer-read-only nil)
(inhibit-read-only t))
(remove-text-properties begin end message-forbidden-properties))))
+(autoload 'ecomplete-setup "ecomplete") ;; for Emacs <23.
+
;;;###autoload
(define-derived-mode message-mode text-mode "Message"
"Major mode for editing mail and news to be sent.
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-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-e move to Expires
(set (make-local-variable 'message-checksum) nil)
(set (make-local-variable 'message-mime-part) 0)
(message-setup-fill-variables)
+ (when message-fill-column
+ (setq fill-column message-fill-column)
+ (turn-on-auto-fill))
;; Allow using comment commands to add/remove quoting.
;; (set (make-local-variable 'comment-start) message-yank-prefix)
(when message-yank-prefix
nil 'local)
;; Allow mail alias things.
(cond
- ((eq message-mail-alias-type 'abbrev)
+ ((message-mail-alias-type-p 'abbrev)
(if (fboundp 'mail-abbrevs-setup)
(mail-abbrevs-setup)
(if (fboundp 'mail-aliases-setup) ; warning avoidance
(mail-aliases-setup))))
- ((eq message-mail-alias-type 'ecomplete)
+ ((message-mail-alias-type-p 'ecomplete)
(ecomplete-setup)))
(unless buffer-file-name
(message-set-auto-save-file-name))
;; solution would be not to use `define-derived-mode', and run
;; `text-mode-hook' ourself at the end of the mode.
;; -- Per Abrahamsen <abraham@dina.kvl.dk> Date: 2001-10-19.
+ ;; This kludge is unneeded in Emacs>=21 since define-derived-mode is
+ ;; now careful to run parent hooks after the body. --Stef
(when auto-fill-function
(setq auto-fill-function normal-auto-fill-function)))
(or (search-forward (concat "\n" mail-header-separator "\n") nil t)
(search-forward-regexp "[^:]+:\\([^\n]\\|\n[ \t]\\)+\n\n" nil t)))
+(defun message-in-body-p ()
+ "Return t if point is in the message body."
+ (let ((body (save-excursion (message-goto-body) (point))))
+ (>= (point) body)))
+
(defun message-goto-eoh ()
"Move point to the end of the headers."
(interactive)
(message-goto-body)
(forward-line -1))
-(defun message-in-body-p ()
- "Return t if point is in the message body."
- (let ((body (save-excursion (message-goto-body) (point))))
- (>= (point) body)))
-
(defun message-goto-signature ()
"Move point to the beginning of the message signature.
If there is no signature in the article, go to the end and
prefix FORCE is given."
(interactive "P")
(let* ((mct (message-fetch-reply-field "mail-copies-to"))
- (dont (and mct (or (equal (downcase mct) "never")
+ (dont (and mct (or (equal (downcase mct) "never")
(equal (downcase mct) "nobody"))))
- (to (or (message-fetch-reply-field "mail-reply-to")
- (message-fetch-reply-field "reply-to")
- (message-fetch-reply-field "from"))))
+ (to (or (message-fetch-reply-field "mail-reply-to")
+ (message-fetch-reply-field "reply-to")
+ (message-fetch-reply-field "from"))))
(when (and dont to)
(message
(if force
;; (mail-strip-quoted-names "Foo Bar <foo@bar>, bla@fasel (Bla Fasel)")
(dolist (header headers)
(let* ((header-name (symbol-name (car header)))
- (new-header (cdr header))
- (synonyms (loop for synonym in message-header-synonyms
+ (new-header (cdr header))
+ (synonyms (loop for synonym in message-header-synonyms
when (memq (car header) synonym) return synonym))
- (old-header
- (loop for synonym in synonyms
+ (old-header
+ (loop for synonym in synonyms
for old-header = (mail-fetch-field (symbol-name synonym))
when (and old-header (string-match new-header old-header))
return synonym)))
(if old-header
- (message "already have `%s' in `%s'" new-header old-header)
+ (message "already have `%s' in `%s'" new-header old-header)
(when (and (message-position-on-field header-name)
- (setq old-header (mail-fetch-field header-name))
- (not (string-match "\\` *\\'" old-header)))
+ (setq old-header (mail-fetch-field header-name))
+ (not (string-match "\\` *\\'" old-header)))
(insert ", "))
- (insert new-header)))))
+ (insert new-header)))))
(defun message-widen-reply ()
"Widen the reply to include maximum recipients."
(interactive)
(let ((follow-to
- (and message-reply-buffer
+ (and (bufferp message-reply-buffer)
(buffer-name message-reply-buffer)
- (save-excursion
- (set-buffer message-reply-buffer)
+ (with-current-buffer message-reply-buffer
(message-get-reply-headers t)))))
(save-excursion
(save-restriction
(defun message-kill-to-signature (&optional arg)
"Kill all text up to the signature.
-If a numberic argument or prefix arg is given, leave that number
+If a numeric argument or prefix arg is given, leave that number
of lines before the signature intact."
(interactive "P")
(save-excursion
(message-newline-and-reformat arg t))
t))
-;; Is it better to use `mail-header-end'?
(defun message-point-in-header-p ()
"Return t if point is in the header."
(save-excursion
- (let ((p (point)))
- (goto-char (point-min))
- (not (re-search-forward
- (concat "^" (regexp-quote mail-header-separator) "\n")
- p t)))))
+ (not (re-search-backward
+ (concat "^" (regexp-quote mail-header-separator) "\n") nil t))))
(defun message-do-auto-fill ()
"Like `do-auto-fill', but don't fill in message header."
((listp message-signature)
(eval message-signature))
(t message-signature)))
- (signature
+ signature-file)
+ (setq signature
(cond ((stringp signature)
signature)
- ((and (eq t signature)
- message-signature-file
- (file-exists-p message-signature-file))
- signature))))
+ ((and (eq t signature) message-signature-file)
+ (setq signature-file
+ (if (and message-signature-directory
+ ;; don't actually use the signature directory
+ ;; if message-signature-file contains a path.
+ (not (file-name-directory
+ message-signature-file)))
+ (expand-file-name message-signature-file
+ message-signature-directory)
+ message-signature-file))
+ (file-exists-p signature-file))))
(when signature
(goto-char (point-max))
;; Insert the signature.
(insert "\n"))
(insert "-- \n")
(if (eq signature t)
- (insert-file-contents message-signature-file)
+ (insert-file-contents signature-file)
(insert signature))
(goto-char (point-max))
(or (bolp) (insert "\n")))))
and `low'."
(interactive)
(save-excursion
- (let ((valid '("high" "normal" "low"))
- (new "high")
+ (let ((new "high")
cur)
(save-restriction
(message-narrow-to-headers)
(substring table ?a (+ ?a n))
(substring table (+ ?a 26) 255))))
-(defun message-caesar-buffer-body (&optional rotnum)
+(defun message-caesar-buffer-body (&optional rotnum wide)
"Caesar rotate all letters in the current buffer by 13 places.
Used to encode/decode possibly offensive messages (commonly in rec.humor).
With prefix arg, specifies the number of places to rotate each letter forward.
-Mail and USENET news headers are not rotated."
+Mail and USENET news headers are not rotated unless WIDE is non-nil."
(interactive (if current-prefix-arg
(list (prefix-numeric-value current-prefix-arg))
(list nil)))
(save-excursion
(save-restriction
- (when (message-goto-body)
+ (when (and (not wide) (message-goto-body))
(narrow-to-region (point) (point-max)))
(message-caesar-region (point-min) (point-max) rotnum))))
(let ((fill-prefix message-yank-prefix))
(fill-individual-paragraphs (point) (point-max) justifyp))))
-(defun message-indent-citation ()
+(defun message-indent-citation (&optional start end yank-only)
"Modify text just inserted from a message to be cited.
The inserted text should be the region.
When this function returns, the region is again around the modified text.
Normally, indent each nonblank line `message-indentation-spaces' spaces.
However, if `message-yank-prefix' is non-nil, insert that prefix on each line."
- (let ((start (point)))
+ (unless start (setq start (point)))
+ (unless yank-only
;; Remove unwanted headers.
(when message-ignored-cited-headers
(let (all-removed)
(insert "\n"))
(while (and (zerop (forward-line -1))
(looking-at "$"))
- (message-delete-line))
- ;; Do the indentation.
- (if (null message-yank-prefix)
- (indent-rigidly start (mark t) message-indentation-spaces)
- (save-excursion
- (goto-char start)
- (while (< (point) (mark t))
- (cond ((looking-at ">")
- (insert message-yank-cited-prefix))
- ((looking-at "^$")
- (insert message-yank-empty-prefix))
- (t
- (insert message-yank-prefix)))
- (forward-line 1))))
- (goto-char start)))
+ (message-delete-line)))
+ ;; Do the indentation.
+ (if (null message-yank-prefix)
+ (indent-rigidly start (or end (mark t)) message-indentation-spaces)
+ (save-excursion
+ (goto-char start)
+ (while (< (point) (or end (mark t)))
+ (cond ((looking-at ">")
+ (insert message-yank-cited-prefix))
+ ((looking-at "^$")
+ (insert message-yank-empty-prefix))
+ (t
+ (insert message-yank-prefix)))
+ (forward-line 1))))
+ (goto-char start))
+
+(defun message-remove-blank-cited-lines (&optional remove)
+ "Remove cited lines containing only blanks.
+If REMOVE is non-nil, remove newlines, too.
+
+To use this automatically, you may add this function to
+`gnus-message-setup-hook'."
+ (interactive "P")
+ (let ((citexp
+ (concat
+ "^\\("
+ (when (boundp 'message-yank-cited-prefix)
+ (concat message-yank-cited-prefix "\\|"))
+ message-yank-prefix
+ "\\)+ *\n"
+ )))
+ (gnus-message 8 "removing `%s'" citexp)
+ (save-excursion
+ (message-goto-body)
+ (while (re-search-forward citexp nil t)
+ (replace-match (if remove "" "\n"))))))
+
+(defvar message-cite-reply-above nil
+ "If non-nil, start own text above the quote.
+
+Note: Top posting is bad netiquette. Don't use it unless you
+really must. You probably want to set variable only for specific
+groups, e.g. using `gnus-posting-styles':
+
+ (eval (set (make-local-variable 'message-cite-reply-above) t))
+
+This variable has no effect in news postings.")
(defun message-yank-original (&optional arg)
"Insert the message being replied to, if any.
Just \\[universal-argument] as argument means don't indent, insert no
prefix, and don't delete any headers."
(interactive "P")
- (let ((modified (buffer-modified-p)))
+ (let ((modified (buffer-modified-p))
+ body-text)
(when (and message-reply-buffer
message-cite-function)
- (delete-windows-on message-reply-buffer t)
+ (when message-cite-reply-above
+ (if (and (not (message-news-p))
+ (or (eq message-cite-reply-above 'is-evil)
+ (y-or-n-p "\
+Top posting is bad netiquette. Please don't top post unless you really must.
+Really top post? ")))
+ (save-excursion
+ (setq body-text
+ (buffer-substring (message-goto-body)
+ (point-max)))
+ (delete-region (message-goto-body) (point-max)))
+ (set (make-local-variable 'message-cite-reply-above) nil)))
+ (if (bufferp message-reply-buffer)
+ (delete-windows-on message-reply-buffer t))
(push-mark (save-excursion
- (insert-buffer-substring message-reply-buffer)
+ (cond
+ ((bufferp message-reply-buffer)
+ (insert-buffer-substring message-reply-buffer))
+ ((and (consp message-reply-buffer)
+ (functionp (car message-reply-buffer)))
+ (apply (car message-reply-buffer)
+ (cdr message-reply-buffer))))
+ (unless (bolp)
+ (insert ?\n))
(point)))
(unless arg
- (funcall message-cite-function))
- (message-exchange-point-and-mark)
- (unless (bolp)
- (insert ?\n))
+ (funcall message-cite-function)
+ (unless (eq (char-before (mark t)) ?\n)
+ (let ((pt (point)))
+ (goto-char (mark t))
+ (insert-before-markers ?\n)
+ (goto-char pt))))
+ (when message-cite-reply-above
+ (message-goto-body)
+ (insert body-text)
+ (insert (if (bolp) "\n" "\n\n"))
+ (message-goto-body))
+ ;; Add a `message-setup-very-last-hook' here?
+ ;; Add `gnus-article-highlight-citation' here?
(unless modified
(setq message-checksum (message-checksum))))))
(defun message-buffers ()
"Return a list of active message buffers."
(let (buffers)
- (save-excursion
+ (save-current-buffer
(dolist (buffer (buffer-list t))
(set-buffer buffer)
(when (and (eq major-mode 'message-mode)
(push (buffer-name buffer) buffers))))
(nreverse buffers)))
-(eval-when-compile (defvar mail-citation-hook)) ; Compiler directive
-
(defun message-cite-original-1 (strip-signature)
"Cite an original message.
If STRIP-SIGNATURE is non-nil, strips off the signature from the
(setq x-no-archive (message-fetch-field "x-no-archive"))
(vector 0
(or (message-fetch-field "subject") "none")
- (message-fetch-field "from")
+ (or (message-fetch-field "from") "nobody")
(message-fetch-field "date")
(message-fetch-field "message-id" t)
(message-fetch-field "references")
(undo-boundary)
(delete-region (point) (mark t))
(insert "> [Quoted text removed due to X-No-Archive]\n")
+ (push-mark)
(forward-line -1)))))
(defun message-cite-original ()
"Cite function in the standard Message manner."
(message-cite-original-1 nil))
+(defvar gnus-extract-address-components)
+
+(autoload 'format-spec "format-spec")
+
+(defun message-insert-formatted-citation-line (&optional from date)
+ "Function that inserts a formatted citation line.
+
+See `message-citation-line-format'."
+ ;; The optional args are for testing/debugging. They will disappear later.
+ ;; Example:
+ ;; (with-temp-buffer
+ ;; (message-insert-formatted-citation-line
+ ;; "John Doe <john.doe@example.invalid>"
+ ;; (current-time))
+ ;; (buffer-string))
+ (when (or message-reply-headers (and from date))
+ (unless from
+ (setq from (mail-header-from message-reply-headers)))
+ (let* ((data (condition-case ()
+ (funcall (if (boundp gnus-extract-address-components)
+ gnus-extract-address-components
+ 'mail-extract-address-components)
+ from)
+ (error nil)))
+ (name (car data))
+ (fname name)
+ (lname name)
+ (net (car (cdr data)))
+ (name-or-net (or (car data)
+ (car (cdr data)) from))
+ (replydate
+ (or
+ date
+ ;; We need Gnus functionality if the user wants date or time from
+ ;; the original article:
+ (when (string-match "%[^fnNFL]" message-citation-line-format)
+ (autoload 'gnus-date-get-time "gnus-util")
+ (gnus-date-get-time (mail-header-date message-reply-headers)))))
+ (flist
+ (let ((i ?A) lst)
+ (when (stringp name)
+ ;; Guess first name and last name:
+ (cond ((string-match
+ "\\`\\(\\w\\|[-.]\\)+ \\(\\w\\|[-.]\\)+\\'" name)
+ (setq fname (nth 0 (split-string name "[ \t]+"))
+ lname (nth 1 (split-string name "[ \t]+"))))
+ ((string-match
+ "\\`\\(\\w\\|[-.]\\)+, \\(\\w\\|[-.]\\)+\\'" name)
+ (setq fname (nth 1 (split-string name "[ \t,]+"))
+ lname (nth 0 (split-string name "[ \t,]+"))))
+ ((string-match
+ "\\`\\(\\w\\|[-.]\\)+\\'" name)
+ (setq fname name
+ lname ""))))
+ ;; The following letters are not used in `format-time-string':
+ (push ?E lst) (push "<E>" lst)
+ (push ?F lst) (push fname lst)
+ ;; We might want to use "" instead of "<X>" later.
+ (push ?J lst) (push "<J>" lst)
+ (push ?K lst) (push "<K>" lst)
+ (push ?L lst) (push lname lst)
+ (push ?N lst) (push name-or-net lst)
+ (push ?O lst) (push "<O>" lst)
+ (push ?P lst) (push "<P>" lst)
+ (push ?Q lst) (push "<Q>" lst)
+ (push ?f lst) (push from lst)
+ (push ?i lst) (push "<i>" lst)
+ (push ?n lst) (push net lst)
+ (push ?o lst) (push "<o>" lst)
+ (push ?q lst) (push "<q>" lst)
+ (push ?t lst) (push "<t>" lst)
+ (push ?v lst) (push "<v>" lst)
+ ;; Delegate the rest to `format-time-string':
+ (while (<= i ?z)
+ (when (and (not (memq i lst))
+ ;; Skip (Z,a)
+ (or (<= i ?Z)
+ (>= i ?a)))
+ (push i lst)
+ (push (condition-case nil
+ (format-time-string (format "%%%c" i) replydate)
+ (error (format ">%c<" i)))
+ lst))
+ (setq i (1+ i)))
+ (reverse lst)))
+ (spec (apply 'format-spec-make flist)))
+ (insert (format-spec message-citation-line-format spec)))
+ (newline)))
+
(defun message-cite-original-without-signature ()
"Cite function in the standard Message manner.
This function strips off the signature from the original message."
(put-text-property (point-min) (point-max) 'read-only nil))
(message-fix-before-sending)
(run-hooks 'message-send-hook)
+ (when message-confirm-send
+ (or (y-or-n-p "Send message? ")
+ (keyboard-quit)))
(message message-sending-message)
(let ((alist message-send-method-alist)
(success t)
(run-hooks 'message-sent-hook))
(message "Sending...done")
;; Do ecomplete address snarfing.
- (when (eq message-mail-alias-type 'ecomplete)
+ (when (and (message-mail-alias-type-p 'ecomplete)
+ (not message-inhibit-ecomplete))
(message-put-addresses-in-ecomplete))
;; Mark the buffer as unmodified and delete auto-save.
(set-buffer-modified-p nil)
(setq start next)))
(nreverse regions)))
+(defcustom message-bogus-addresses
+ ;; '("noreply" "nospam" "invalid")
+ '("noreply" "nospam" "invalid" "@@" "[^[:ascii:]].*@" "[ \t]")
+ "List of regexps of potentially bogus mail addresses.
+See `message-check-recipients' how to setup checking.
+
+This list should make it possible to catch typos or warn about
+spam-trap addresses. It doesn't aim to verify strict RFC
+conformance."
+ :version "23.1" ;; No Gnus
+ :group 'message-headers
+ :type '(choice
+ (const :tag "None" nil)
+ (list
+ (set :inline t
+ (const "noreply")
+ (const "nospam")
+ (const "invalid")
+ (const :tag "duplicate @" "@@")
+ (const :tag "non-ascii local part" "[^[:ascii:]].*@")
+ ;; Already caught by `message-valid-fqdn-regexp'
+ ;; (const :tag "`_' in domain part" "@.*_")
+ (const :tag "whitespace" "[ \t]"))
+ (repeat :inline t
+ :tag "Other"
+ (regexp)))))
+
(defun message-fix-before-sending ()
"Do various things to make the message nice before sending it."
;; Make sure there's a newline at the end of the message.
"Invisible text found and made visible; continue sending? ")
(error "Invisible text found and made visible")))))
(message-check 'illegible-text
- (let (found choice)
+ (let (char 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))
- (not (get-text-property
- (point) 'untranslated-utf-8)))))
+ (while (progn
+ (skip-chars-forward mm-7bit-chars)
+ (when (get-text-property (point) 'no-illegible-text)
+ ;; There is a signed or encrypted raw message part
+ ;; that is considered to be safe.
+ (goto-char (or (next-single-property-change
+ (point) 'no-illegible-text)
+ (point-max))))
+ (setq char (char-after)))
+ (when (or (< (mm-char-int char) 128)
+ (and (mm-multibyte-p)
+ (memq (char-charset char)
+ '(eight-bit-control eight-bit-graphic
+ ;; Emacs 23, Bug#1770:
+ eight-bit
+ control-1))
+ (not (get-text-property
+ (point) 'untranslated-utf-8))))
(message-overlay-put (message-make-overlay (point) (1+ (point)))
'face 'highlight)
(setq found t))
- (forward-char)
- (skip-chars-forward mm-7bit-chars))
+ (forward-char))
(when found
(setq choice
(gnus-multiple-choice
"Non-printable characters found. Continue sending?"
- '((?d "Remove non-printable characters and send")
- (?r "Replace non-printable characters with dots and send")
- (?i "Ignore non-printable characters and send")
+ `((?d "Remove non-printable characters and send")
+ (?r ,(format
+ "Replace non-printable characters with \"%s\" and send"
+ message-replacement-char))
+ (?s "Send as is without removing anything")
(?e "Continue editing"))))
(if (eq choice ?e)
(error "Non-printable characters"))
(or (< (mm-char-int char) 128)
(and (mm-multibyte-p)
;; FIXME: Wrong for Emacs 23 (unicode) and for
- ;; things like undecable utf-8. Should at least
- ;; use find-coding-systems-region.
+ ;; things like undecodable utf-8 (in Emacs 21?).
+ ;; Should at least use find-coding-systems-region.
+ ;; -- fx
(memq (char-charset char)
'(eight-bit-control eight-bit-graphic
+ ;; Emacs 23, Bug#1770:
+ eight-bit
control-1))
(not (get-text-property
(point) 'untranslated-utf-8)))))
(message-kill-all-overlays)
(delete-char 1)
(when (eq choice ?r)
- (insert "."))))
+ (insert message-replacement-char))))
(forward-char)
- (skip-chars-forward mm-7bit-chars))))))
+ (skip-chars-forward mm-7bit-chars)))))
+ (message-check 'bogus-recipient
+ ;; Warn before sending a mail to an invalid address.
+ (message-check-recipients)))
+
+(defun message-bogus-recipient-p (recipients)
+ "Check if a mail address in RECIPIENTS looks bogus.
+
+RECIPIENTS is a mail header. Return a list of potentially bogus
+addresses. If none is found, return nil.
+
+An address might be bogus if the domain part is not fully
+qualified, see `message-valid-fqdn-regexp', or if there's a
+matching entry in `message-bogus-addresses'."
+ ;; FIXME: How about "foo@subdomain", when the MTA adds ".domain.tld"?
+ (let (found)
+ (mapc (lambda (address)
+ (setq address (cadr address))
+ (when
+ (or (not
+ (or
+ (not (string-match "@" address))
+ (string-match
+ (concat ".@.*\\("
+ message-valid-fqdn-regexp "\\)\\'") address)))
+ (and message-bogus-addresses
+ (let ((re
+ (if (listp message-bogus-addresses)
+ (mapconcat 'identity
+ message-bogus-addresses
+ "\\|")
+ message-bogus-addresses)))
+ (string-match re address))))
+ (push address found)))
+ ;;
+ (mail-extract-address-components recipients t))
+ found))
+
+(defun message-check-recipients ()
+ "Warn before composing or sending a mail to an invalid address.
+
+This function could be useful in `message-setup-hook'."
+ (interactive)
+ (save-restriction
+ (message-narrow-to-headers)
+ (dolist (hdr '("To" "Cc" "Bcc"))
+ (let ((addr (message-fetch-field hdr)))
+ (when (stringp addr)
+ (dolist (bog (message-bogus-recipient-p addr))
+ (and bog
+ (not (y-or-n-p
+ (format
+ "Address `%s' might be bogus. Continue? " bog)))
+ (error "Bogus address"))))))))
+
+(custom-add-option 'message-setup-hook 'message-check-recipients)
(defun message-add-action (action &rest types)
"Add ACTION to be performed when doing an exit of type TYPES."
(end-of-line)
(insert (format " (%d/%d)" n total))
(widen)
- (mm-with-unibyte-current-buffer
- (funcall (or message-send-mail-real-function
- message-send-mail-function))))
+ (funcall (or message-send-mail-real-function
+ message-send-mail-function)))
(setq n (+ n 1))
(setq p (pop plist))
(erase-buffer)))
(kill-buffer tembuf))))
+(declare-function hashcash-wait-async "hashcash" (&optional buffer))
+
(defun message-send-mail (&optional arg)
(require 'mail-utils)
(let* ((tembuf (message-generate-new-buffer-clone-locals " message temp"))
(news (message-news-p))
(mailbuf (current-buffer))
(message-this-is-mail t)
+ ;; gnus-setup-posting-charset is autoloaded in mml.el (FIXME
+ ;; maybe it should not be), which this file requires. Hence
+ ;; the fboundp test is always true. Loading it from gnus-msg
+ ;; loads many Gnus files (Bug#5642). If
+ ;; gnus-group-posting-charset-alist hasn't been customized,
+ ;; this is just going to return nil anyway. FIXME it would
+ ;; be good to improve this further, because even if g-g-p-c-a
+ ;; has been customized, that is likely to just be for news.
+ ;; Eg either move the definition from gnus-msg, or separate out
+ ;; the mail and news parts.
(message-posting-charset
- (if (fboundp 'gnus-setup-posting-charset)
+ (if (and (fboundp 'gnus-setup-posting-charset)
+ (boundp 'gnus-group-posting-charset-alist))
(gnus-setup-posting-charset nil)
message-posting-charset))
(headers message-required-mail-headers))
- (when message-generate-hashcash
+ (when (and message-generate-hashcash
+ (not (eq message-generate-hashcash 'opportunistic)))
(message "Generating hashcash...")
+ (require 'hashcash)
;; Wait for calculations already started to finish...
(hashcash-wait-async)
;; ...and do calculations not already done. mail-add-payment
(let ((message-deletable-headers
(if news nil message-deletable-headers)))
(message-generate-headers headers))
+ ;; Check continuation headers.
+ (message-check 'continuation-headers
+ (goto-char (point-min))
+ (while (re-search-forward "^[^ \t\n][^ \t\n:]*[ \t\n]" nil t)
+ (goto-char (match-beginning 0))
+ (if (y-or-n-p "Fix continuation lines? ")
+ (insert " ")
+ (forward-line 1)
+ (unless (y-or-n-p "Send anyway? ")
+ (error "Failed to send the message")))))
;; Let the user do all of the above.
(run-hooks 'message-header-hook))
(unwind-protect
- (save-excursion
- (set-buffer tembuf)
+ (with-current-buffer tembuf
(erase-buffer)
;; Avoid copying text props (except hard newlines).
(insert (with-current-buffer mailbuf
(message-fetch-field
"content-transfer-encoding")))))))
(message-insert-courtesy-copy))
+ ;; Let's make sure we encoded all the body.
+ (assert (save-excursion
+ (goto-char (point-min))
+ (not (re-search-forward "[^\000-\377]" nil t))))
+ (mm-disable-multibyte)
(if (or (not message-send-mail-partially-limit)
(< (buffer-size) message-send-mail-partially-limit)
(not (message-y-or-n-p
If you always want Gnus to send messages in one piece, set
`message-send-mail-partially-limit' to nil.
")))
- (mm-with-unibyte-current-buffer
+ (progn
(message "Sending via mail...")
(funcall (or message-send-mail-real-function
message-send-mail-function)))
(defun message-send-mail-with-sendmail ()
"Send off the prepared buffer with sendmail."
+ (require 'sendmail)
(let ((errbuf (if message-interactive
(message-generate-new-buffer-clone-locals
" sendmail errors")
'call-process-region
(append
(list (point-min) (point-max)
- (if (boundp 'sendmail-program)
- sendmail-program
- "/usr/lib/sendmail")
+ (cond ((boundp 'sendmail-program)
+ sendmail-program)
+ ((file-exists-p "/usr/sbin/sendmail")
+ "/usr/sbin/sendmail")
+ ((file-exists-p "/usr/lib/sendmail")
+ "/usr/lib/sendmail")
+ ((file-exists-p "/usr/ucblib/sendmail")
+ "/usr/ucblib/sendmail")
+ (t "fakemail"))
nil errbuf nil "-oi")
+ message-sendmail-extra-arguments
;; Always specify who from,
;; since some systems have broken sendmails.
;; But some systems are more broken with -f, so
;; we'll let users override this.
- (if (null message-sendmail-f-is-evil)
- (list "-f" (message-sendmail-envelope-from)))
+ (and (null message-sendmail-f-is-evil)
+ (list "-f" (message-sendmail-envelope-from)))
;; These mean "report errors by mail"
;; and "deliver in background".
(if (null message-interactive) '("-oem" "-odb"))
(list resend-to-addresses)
'("-t"))))))
(unless (or (null cpr) (and (numberp cpr) (zerop cpr)))
+ (if errbuf (pop-to-buffer errbuf))
(error "Sending...failed with exit value %d" cpr)))
(when message-interactive
- (save-excursion
- (set-buffer errbuf)
+ (with-current-buffer errbuf
(goto-char (point-min))
(while (re-search-forward "\n+ *" nil t)
(replace-match "; "))
(apply
'call-process-region (point-min) (point-max)
message-qmail-inject-program nil nil nil
- ;; qmail-inject's default behaviour is to look for addresses on the
+ ;; qmail-inject's default behavior is to look for addresses on the
;; command line; if there're none, it scans the headers.
;; yes, it does The Right Thing w.r.t. Resent-To and it's kin.
;;
;; 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
- (if (functionp message-qmail-inject-args)
- (funcall message-qmail-inject-args)
- 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)
(defun message-smtpmail-send-it ()
"Send the prepared message buffer with `smtpmail-send-it'.
-This only differs from `smtpmail-send-it' that this command evaluates
-`message-send-mail-hook' just before sending a message. It is useful
-if your ISP requires the POP-before-SMTP authentication. See the Gnus
-manual for details."
+The only difference from `smtpmail-send-it' is that this command
+evaluates `message-send-mail-hook' just before sending a message.
+It is useful if your ISP requires the POP-before-SMTP
+authentication. See the Gnus manual for details."
(run-hooks 'message-send-mail-hook)
(smtpmail-send-it))
+(defun message-send-mail-with-mailclient ()
+ "Send the prepared message buffer with `mailclient-send-it'.
+The only difference from `mailclient-send-it' is that this
+command evaluates `message-send-mail-hook' just before sending a message."
+ (run-hooks 'message-send-mail-hook)
+ (mailclient-send-it))
+
(defun message-canlock-generate ()
"Return a string that is non-trivial to guess.
Do not use this for anything important, it is cryptographically weak."
(prin1-to-string (recent-keys))
(prin1-to-string (garbage-collect))))))
+(defvar canlock-password)
+(defvar canlock-password-for-verify)
+
(defun message-canlock-password ()
"The password used by message for cancel locks.
This is the value of `canlock-password', if that option is non-nil.
Otherwise, generate and save a value for `canlock-password' first."
+ (require 'canlock)
(unless canlock-password
(customize-save-variable 'canlock-password (message-canlock-generate))
(setq canlock-password-for-verify canlock-password))
(message-canlock-password)
(canlock-insert-header)))
+(autoload 'nnheader-get-report "nnheader")
+
+(declare-function gnus-setup-posting-charset "gnus-msg" (group))
+
(defun message-send-news (&optional arg)
+ (require 'gnus-msg)
(let* ((tembuf (message-generate-new-buffer-clone-locals " *message temp*"))
(case-fold-search nil)
(method (if (functionp message-post-method)
(message-check-news-syntax)))
nil
(unwind-protect
- (save-excursion
- (set-buffer tembuf)
+ (with-current-buffer tembuf
(buffer-disable-undo)
(erase-buffer)
;; Avoid copying text props (except hard newlines).
(message-check 'continuation-headers
(goto-char (point-min))
(let ((do-posting t))
- (while (re-search-forward "^[^ \t\n][^:\n]*$" nil t)
+ (while (re-search-forward "^[^ \t\n][^ \t\n:]*[ \t\n]" nil t)
+ (goto-char (match-beginning 0))
(if (y-or-n-p "Fix continuation lines? ")
- (progn
- (goto-char (match-beginning 0))
- (insert " "))
+ (insert " ")
+ (forward-line 1)
(unless (y-or-n-p "Send anyway? ")
(setq do-posting nil))))
do-posting))
"Denied posting -- the From looks strange: \"%s\"." from)
nil)
((let ((addresses (rfc822-addresses from)))
- (while (and addresses
+ ;; `rfc822-addresses' returns a string if parsing fails.
+ (while (and (consp addresses)
(not (eq (string-to-char (car addresses)) ?\()))
(setq addresses (cdr addresses)))
addresses)
;; Check for control characters.
(message-check 'control-chars
(if (re-search-forward
- (mm-string-as-multibyte "[\000-\007\013\015-\032\034-\037\200-\237]")
+ (mm-string-to-multibyte "[\000-\007\013\015-\032\034-\037\200-\237]")
nil t)
(y-or-n-p
"The article contains control characters. Really post? ")
nil)))
;; Check the length of the signature.
(message-check 'signature
- (goto-char (point-max))
- (if (> (count-lines (point) (point-max)) 5)
- (y-or-n-p
- (format
- "Your .sig is %d lines; it should be max 4. Really post? "
- (1- (count-lines (point) (point-max)))))
- t))
+ (let (sig-start sig-end)
+ (goto-char (point-max))
+ (if (not (re-search-backward message-signature-separator nil t))
+ t
+ (setq sig-start (1+ (point-at-eol)))
+ (setq sig-end
+ (if (re-search-forward
+ "<#/?\\(multipart\\|part\\|external\\|mml\\)" nil t)
+ (- (point-at-bol) 1)
+ (point-max)))
+ (if (>= (count-lines sig-start sig-end) 5)
+ (if (message-gnksa-enable-p 'signature)
+ (y-or-n-p
+ (format "Signature is excessively long (%d lines). Really post? "
+ (count-lines sig-start sig-end)))
+ (message "Denied posting -- Excessive signature.")
+ nil)
+ t))))
;; Ensure that text follows last quoted portion.
(message-check 'quoting-style
(goto-char (point-max))
(if (and message-fcc-handler-function
(not (eq message-fcc-handler-function 'rmail-output)))
(funcall message-fcc-handler-function file)
+ ;; FIXME this option, rmail-output (also used if
+ ;; message-fcc-handler-function is nil) is not
+ ;; documented anywhere AFAICS. It should work in Emacs
+ ;; 23; I suspect it does not work in Emacs 22.
+ ;; FIXME I don't see the need for the two different cases here.
+ ;; mail-use-rfc822 makes no difference (in Emacs 23),and
+ ;; the third argument just controls \"Wrote file\" message.
(if (and (file-readable-p file) (mail-file-babyl-p file))
(rmail-output file 1 nil t)
(let ((mail-use-rfc822 t))
(defun message-output (filename)
"Append this article to Unix/babyl mail file FILENAME."
- (if (and (file-readable-p filename)
- (mail-file-babyl-p filename))
+ (if (or (and (file-readable-p filename)
+ (mail-file-babyl-p filename))
+ ;; gnus-output-to-mail does the wrong thing with live, mbox
+ ;; Rmail buffers in Emacs 23.
+ ;; http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=597255
+ (let ((buff (find-buffer-visiting filename)))
+ (and buff (with-current-buffer buff
+ (eq major-mode 'rmail-mode)))))
(gnus-output-to-rmail filename t)
(gnus-output-to-mail filename t)))
(* 25 25)))
(let ((tm (current-time)))
(concat
- (if (or (memq system-type '(ms-dos emx vax-vms))
+ (if (or (eq system-type 'ms-dos)
;; message-number-base36 doesn't handle bigints.
(floatp (user-uid)))
(let ((user (downcase (user-login-name))))
(defun message-make-references ()
"Return the References header for this message."
(when message-reply-headers
- (let ((message-id (mail-header-message-id message-reply-headers))
- (references (mail-header-references message-reply-headers))
- new-references)
+ (let ((message-id (mail-header-id message-reply-headers))
+ (references (mail-header-references message-reply-headers)))
(if (or references message-id)
(concat (or references "") (and references " ")
(or message-id ""))
(when message-reply-headers
(let ((from (mail-header-from message-reply-headers))
(date (mail-header-date message-reply-headers))
- (msg-id (mail-header-message-id message-reply-headers)))
+ (msg-id (mail-header-id message-reply-headers)))
(when from
(let ((name (mail-extract-address-components from)))
- (concat msg-id (if msg-id " (")
- (or (car name)
- (nth 1 name))
- "'s message of \""
- (if (or (not date) (string= date ""))
- "(unknown date)" date)
- "\"" (if msg-id ")")))))))
+ (concat
+ msg-id (if msg-id " (")
+ (if (car name)
+ (if (string-match "[^\000-\177]" (car name))
+ ;; Quote a string containing non-ASCII characters.
+ ;; It will make the RFC2047 encoder cause an error
+ ;; if there are special characters.
+ (mm-with-multibyte-buffer
+ (insert (car name))
+ (goto-char (point-min))
+ (while (search-forward "\"" nil t)
+ (when (prog2
+ (backward-char)
+ (zerop (% (skip-chars-backward "\\\\") 2))
+ (goto-char (match-beginning 0)))
+ (insert "\\"))
+ (forward-char))
+ ;; Those quotes will be removed by the RFC2047 encoder.
+ (concat "\"" (buffer-string) "\""))
+ (car name))
+ (nth 1 name))
+ "'s message of \""
+ (if (or (not date) (string= date ""))
+ "(unknown date)" date)
+ "\"" (if msg-id ")")))))))
(defun message-make-distribution ()
"Make a Distribution header."
(concat message-user-path "!" login-name))
(t login-name))))
-(defun message-make-from ()
+(defun message-make-from (&optional name address)
"Make a From header."
(let* ((style message-from-style)
- (login (message-make-address))
- (fullname
- (or (and (boundp 'user-full-name)
- user-full-name)
- (user-full-name))))
+ (login (or address (message-make-address)))
+ (fullname (or name
+ (and (boundp 'user-full-name)
+ user-full-name)
+ (user-full-name))))
(when (string= fullname "&")
(setq fullname (user-login-name)))
(with-temp-buffer
(string-match "[\\()]" tmp)))))
(insert fullname)
(goto-char (point-min))
- ;; Look for a character that cannot appear unquoted
- ;; according to RFC 822.
- (when (re-search-forward "[^- !#-'*+/-9=?A-Z^-~]" nil 1)
- ;; Quote fullname, escaping specials.
- (goto-char (point-min))
- (insert "\"")
- (while (re-search-forward "[\"\\]" nil 1)
- (replace-match "\\\\\\&" t))
- (insert "\""))
+ ;; Look for a character that cannot appear unquoted
+ ;; according to RFC 822.
+ (when (re-search-forward "[^- !#-'*+/-9=?A-Z^-~]" nil 1)
+ ;; Quote fullname, escaping specials.
+ (goto-char (point-min))
+ (insert "\"")
+ (while (re-search-forward "[\"\\]" nil 1)
+ (replace-match "\\\\\\&" t))
+ (insert "\""))
(insert " <" login ">"))
(t ; 'parens or default
(insert login " (")
(stringp message-user-fqdn)
(string-match message-valid-fqdn-regexp message-user-fqdn)
(not (string-match message-bogus-system-names message-user-fqdn)))
+ ;; `message-user-fqdn' seems to be valid
message-user-fqdn)
- ;; `message-user-fqdn' seems to be valid
((and (string-match message-valid-fqdn-regexp system-name)
(not (string-match message-bogus-system-names system-name)))
;; `system-name' returned the right result.
(mapcar 'funcall
message-subscribed-address-functions))))
(save-match-data
- (let ((subscribed-lists nil)
- (list
+ (let ((list
(loop for recipient in recipients
when (loop for regexp in mft-regexps
when (string-match regexp recipient) return t)
(mapcar (lambda (rhs) (or (cadr (split-string rhs "@")) ""))
(mapcar 'downcase
(mapcar
- 'car (mail-header-parse-addresses field))))))
- (setq ace (downcase (idna-to-ascii rhs)))
+ (lambda (elem)
+ (or (cadr elem)
+ ""))
+ (mail-extract-address-components field t))))))
+ ;; Note that `rhs' will be "" if the address does not have
+ ;; the domain part, i.e., if it is a local user's address.
+ (setq ace (if (string-match "\\`[[:ascii:]]*\\'" rhs)
+ rhs
+ (downcase (idna-to-ascii rhs))))
(when (and (not (equal rhs ace))
(or (not (eq message-use-idna 'ask))
(y-or-n-p (format "Replace %s with %s in %s:? "
(when message-use-idna
(save-excursion
(save-restriction
- (message-narrow-to-head)
+ ;; `message-narrow-to-head' that recognizes only the first empty
+ ;; line as the message header separator used to be used here.
+ ;; However, since there is the "--text follows this line--" line
+ ;; normally, it failed in narrowing to the headers and potentially
+ ;; caused the IDNA encoding on lines that look like headers in
+ ;; the message body.
+ (message-narrow-to-headers-or-head)
(message-idna-to-ascii-rhs-1 "From")
(message-idna-to-ascii-rhs-1 "To")
(message-idna-to-ascii-rhs-1 "Reply-To")
(if formatter
(funcall formatter header value)
(insert header-string ": " value))
+ (push header-string message-inserted-headers)
(goto-char (message-fill-field))
;; We check whether the value was ended by a
;; newline. If not, we insert one.
When sending via news, also check that the REFERENCES are less
than 988 characters long, and if they are not, trim them until
they are."
+ ;; 21 is the number suggested by USEAGE.
(let ((maxcount 21)
(count 0)
(cut 2)
(with-temp-buffer
(insert references)
(goto-char (point-min))
- ;; Cons a list of valid references.
- (while (re-search-forward "<[^>]+>" nil t)
+ ;; Cons a list of valid references. GNKSA says we must not include MIDs
+ ;; with whitespace or missing brackets (7.a "Does not propagate broken
+ ;; Message-IDs in original References").
+ (while (re-search-forward "<[^ <]+@[^ <]+>" nil t)
(push (match-string 0) refs))
(setq refs (nreverse refs)
count (length refs)))
between beginning of field and beginning of line."
(interactive "p")
(let ((zrs 'zmacs-region-stays))
- (when (and (interactive-p) (boundp zrs))
+ (when (and (featurep 'xemacs) (interactive-p) (boundp zrs))
(set zrs t)))
(if (and message-beginning-of-line
(message-point-in-header-p))
"Return a new (unique) buffer name based on TYPE and TO."
(cond
;; Generate a new buffer name The Message Way.
- ((eq message-generate-new-buffers 'unique)
+ ((memq message-generate-new-buffers '(unique t))
(generate-new-buffer-name
(concat "*" type
(if to
"")
(if (and group (not (string= group ""))) (concat " on " group) "")
"*")))
- ;; Use standard name.
+ ;; Search for the existing message buffer with the specified name.
(t
- (format "*%s message*" type))))
-
-(defun message-pop-to-buffer (name)
+ (let* ((new (if (eq message-generate-new-buffers 'standard)
+ (generate-new-buffer-name (concat "*" type " message*"))
+ (let ((message-generate-new-buffers 'unique))
+ (message-buffer-name type to group))))
+ (regexp (concat "\\`"
+ (regexp-quote
+ (if (string-match "<[0-9]+>\\'" new)
+ (substring new 0 (match-beginning 0))
+ new))
+ "\\(?:<\\([0-9]+\\)>\\)?\\'"))
+ (case-fold-search nil))
+ (or (cdar
+ (last
+ (sort
+ (delq nil
+ (mapcar
+ (lambda (b)
+ (when (and (string-match regexp (setq b (buffer-name b)))
+ (eq (with-current-buffer b major-mode)
+ 'message-mode))
+ (cons (string-to-number (or (match-string 1 b) "1"))
+ b)))
+ (buffer-list)))
+ 'car-less-than-car)))
+ new)))))
+
+(defun message-pop-to-buffer (name &optional switch-function)
"Pop to buffer NAME, and warn if it already exists and is modified."
(let ((buffer (get-buffer name)))
(if (and buffer
(buffer-name buffer))
- (progn
- (set-buffer (pop-to-buffer buffer))
+ (let ((window (get-buffer-window buffer 0)))
+ (if window
+ ;; Raise the frame already displaying the message buffer.
+ (progn
+ (gnus-select-frame-set-input-focus (window-frame window))
+ (select-window window))
+ (funcall (or switch-function 'pop-to-buffer) buffer)
+ (set-buffer buffer))
(when (and (buffer-modified-p)
- (not (y-or-n-p
- "Message already being composed; erase? ")))
+ (not (prog1
+ (y-or-n-p
+ "Message already being composed; erase? ")
+ (message nil))))
(error "Message being composed")))
- (set-buffer (pop-to-buffer name)))
+ (funcall (or switch-function 'pop-to-buffer) name)
+ (set-buffer name))
(erase-buffer)
(message-mode)))
nil
mua)))
-(defun message-setup (headers &optional replybuffer actions switch-function)
+;; YANK-ACTION, if non-nil, can be a buffer or a yank action of the
+;; form (FUNCTION . ARGS).
+(defun message-setup (headers &optional yank-action actions
+ continue switch-function)
(let ((mua (message-mail-user-agent))
- subject to field yank-action)
+ subject to field)
(if (not (and message-this-is-mail mua))
- (message-setup-1 headers replybuffer actions)
- (if replybuffer
- (setq yank-action (list 'insert-buffer replybuffer)))
+ (message-setup-1 headers yank-action actions)
(setq headers (copy-sequence headers))
(setq field (assq 'Subject headers))
(when field
(format "%s" (car item))
(cdr item)))
headers)
- nil switch-function yank-action actions)))))
+ continue switch-function
+ (if (bufferp yank-action)
+ (list 'insert-buffer yank-action)
+ yank-action)
+ actions)))))
(defun message-headers-to-generate (headers included-headers excluded-headers)
"Return a list that includes all headers from HEADERS.
-If INCLUDED-HEADERS is a list, just include those headers. If if is
+If INCLUDED-HEADERS is a list, just include those headers. If it is
t, include all headers. In any case, headers from EXCLUDED-HEADERS
are not included."
(let ((result nil)
(push header result)))
(nreverse result)))
-(defun message-setup-1 (headers &optional replybuffer actions)
+(defun message-setup-1 (headers &optional yank-action actions)
(dolist (action actions)
(condition-case nil
(add-to-list 'message-send-actions
`(apply ',(car action) ',(cdr action)))))
- (setq message-reply-buffer replybuffer)
+ (setq message-reply-buffer
+ (if (and (consp yank-action)
+ (eq (car yank-action) 'insert-buffer))
+ (nth 1 yank-action)
+ yank-action))
(goto-char (point-min))
;; Insert all the headers.
(mail-header-format
headers)
(delete-region (point) (progn (forward-line -1) (point)))
(when message-default-headers
- (insert message-default-headers)
+ (insert
+ (if (functionp message-default-headers)
+ (funcall message-default-headers)
+ message-default-headers))
(or (bolp) (insert ?\n)))
(insert mail-header-separator "\n")
(forward-line -1)
(save-restriction
(message-narrow-to-headers)
(run-hooks 'message-header-setup-hook))
- (set-buffer-modified-p nil)
(setq buffer-undo-list nil)
(when message-generate-hashcash
;; Generate hashcash headers for recipients already known
(mail-add-payment-async))
+ ;; Gnus posting styles are applied via buffer-local `message-setup-hook'
+ ;; values.
(run-hooks 'message-setup-hook)
;; Do this last to give it precedence over posting styles, etc.
(when (message-mail-p)
(if message-alternative-emails
(message-use-alternative-email-as-from))))
(message-position-point)
+ ;; Allow correct handling of `message-checksum' in `message-yank-original':
+ (set-buffer-modified-p nil)
(undo-boundary))
(defun message-set-auto-save-file-name ()
(if (gnus-alive-p)
(setq message-draft-article
(nndraft-request-associate-buffer "drafts"))
+
+ ;; If Gnus were alive, draft messages would be saved in the drafts folder.
+ ;; But Gnus is not alive, so arrange to save the draft message in a
+ ;; regular file in message-auto-save-directory. Append a unique
+ ;; time-based suffix to the filename to allow multiple drafts to be saved
+ ;; simultaneously without overwriting each other (which mimics the
+ ;; functionality of the Gnus drafts folder).
(setq buffer-file-name (expand-file-name
+ (concat
(if (memq system-type
- '(ms-dos ms-windows windows-nt
- cygwin cygwin32 win32 w32
- mswindows))
+ '(ms-dos windows-nt cygwin))
"message"
"*message*")
+ (format-time-string "-%Y%m%d-%H%M%S"))
message-auto-save-directory))
(setq buffer-auto-save-file-name (make-auto-save-file-name)))
(clear-visited-file-modtime)
other-headers continue switch-function
yank-action send-actions)
"Start editing a mail message to be sent.
-OTHER-HEADERS is an alist of header/value pairs."
+OTHER-HEADERS is an alist of header/value pairs. CONTINUE says whether
+to continue editing a message already being composed. SWITCH-FUNCTION
+is a function used to switch to and display the mail buffer."
(interactive)
- (let ((message-this-is-mail t) replybuffer)
+ (let ((message-this-is-mail t))
(unless (message-mail-user-agent)
- (message-pop-to-buffer (message-buffer-name "mail" to)))
- ;; FIXME: message-mail should do something if YANK-ACTION is not
- ;; insert-buffer.
- (and (consp yank-action) (eq (car yank-action) 'insert-buffer)
- (setq replybuffer (nth 1 yank-action)))
+ (message-pop-to-buffer
+ ;; Search for the existing message buffer if `continue' is non-nil.
+ (let ((message-generate-new-buffers
+ (when (or (not continue)
+ (eq message-generate-new-buffers 'standard)
+ (functionp message-generate-new-buffers))
+ message-generate-new-buffers)))
+ (message-buffer-name "mail" to))
+ switch-function))
(message-setup
(nconc
`((To . ,(or to "")) (Subject . ,(or subject "")))
(when other-headers other-headers))
- replybuffer send-actions)
+ yank-action send-actions continue switch-function)
;; FIXME: Should return nil if failure.
t))
(message-setup `((Newsgroups . ,(or newsgroups ""))
(Subject . ,(or subject ""))))))
+(defun message-alter-recipients-discard-bogus-full-name (addrcell)
+ "Discard mail address in full names.
+When the full name in reply headers contains the mail
+address (e.g. \"foo@bar <foo@bar>\"), discard full name.
+ADDRCELL is a cons cell where the car is the mail address and the
+cdr is the complete address (full name and mail address)."
+ (if (string-match (concat (regexp-quote (car addrcell)) ".*"
+ (regexp-quote (car addrcell)))
+ (cdr addrcell))
+ (cons (car addrcell) (car addrcell))
+ addrcell))
+
+(defcustom message-alter-recipients-function nil
+ "Function called to allow alteration of reply header structures.
+It is called in `message-get-reply-headers' for each recipient.
+The function is called with one parameter, a cons cell ..."
+ :type '(choice (const :tag "None" nil)
+ (const :tag "Discard bogus full name"
+ message-alter-recipients-discard-bogus-full-name)
+ function)
+ :version "23.1" ;; No Gnus
+ :group 'message-headers)
+
(defun message-get-reply-headers (wide &optional to-address address-headers)
(let (follow-to mct never-mct to cc author mft recipients extra)
;; Find all relevant headers we need.
(save-match-data
;; Build (textual) list of new recipient addresses.
(cond
+ (to-address
+ (setq recipients (concat ", " to-address))
+ ;; If the author explicitly asked for a copy, we don't deny it to them.
+ (if mct (setq recipients (concat recipients ", " mct))))
((not wide)
(setq recipients (concat ", " author)))
(address-headers
You may customize the variable `message-use-mail-followup-to', if you
want to get rid of this query permanently.")))
(setq recipients (concat ", " mft)))
- (to-address
- (setq recipients (concat ", " to-address))
- ;; If the author explicitly asked for a copy, we don't deny it to them.
- (if mct (setq recipients (concat recipients ", " mct))))
(t
(setq recipients (if never-mct "" (concat ", " author)))
(if to (setq recipients (concat recipients ", " to)))
(while (string-match "[ \t][ \t]+" recipients)
(setq recipients (replace-match " " t t recipients)))
;; Remove addresses that match `rmail-dont-reply-to-names'.
- (let ((rmail-dont-reply-to-names message-dont-reply-to-names))
+ (let ((rmail-dont-reply-to-names (message-dont-reply-to-names)))
(setq recipients (rmail-dont-reply-to recipients)))
;; Perhaps "Mail-Copies-To: never" removed the only address?
(if (string-equal recipients "")
(setq recipients
(mapcar
(lambda (addr)
- (cons (downcase (mail-strip-quoted-names addr)) addr))
+ (if message-alter-recipients-function
+ (funcall message-alter-recipients-function
+ (cons (downcase (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))
(if recip
(setq recipients (delq recip recipients))))))))
+ (setq recipients (message-prune-recipients 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)))))
(push (cons 'Cc recipients) follow-to)))
follow-to))
+(defun message-prune-recipients (recipients)
+ (dolist (rule message-prune-recipient-rules)
+ (let ((match (car rule))
+ dup-match
+ address)
+ (dolist (recipient recipients)
+ (setq address (car recipient))
+ (when (string-match match address)
+ (setq dup-match (replace-match (cadr rule) nil nil address))
+ (dolist (recipient recipients)
+ ;; Don't delete the address that triggered this.
+ (when (and (not (eq address (car recipient)))
+ (string-match dup-match (car recipient)))
+ (setq recipients (delq recipient recipients))))))))
+ recipients)
+
+(defcustom message-simplify-subject-functions
+ '(message-strip-list-identifiers
+ message-strip-subject-re
+ message-strip-subject-trailing-was
+ message-strip-subject-encoded-words)
+ "List of functions taking a string argument that simplify subjects.
+The functions are applied when replying to a message.
+
+Useful functions to put in this list include:
+`message-strip-list-identifiers', `message-strip-subject-re',
+`message-strip-subject-trailing-was', and
+`message-strip-subject-encoded-words'."
+ :version "22.1" ;; Gnus 5.10.9
+ :group 'message-various
+ :type '(repeat function))
+
+(defun message-simplify-subject (subject &optional functions)
+ "Return simplified SUBJECT."
+ (unless functions
+ ;; Simplify fully:
+ (setq functions message-simplify-subject-functions))
+ (when (and (memq 'message-strip-list-identifiers functions)
+ gnus-list-identifiers)
+ (setq subject (message-strip-list-identifiers subject)))
+ (when (memq 'message-strip-subject-re functions)
+ (setq subject (concat "Re: " (message-strip-subject-re subject))))
+ (when (and (memq 'message-strip-subject-trailing-was functions)
+ message-subject-trailing-was-query)
+ (setq subject (message-strip-subject-trailing-was subject)))
+ (when (memq 'message-strip-subject-encoded-words functions)
+ (setq subject (message-strip-subject-encoded-words subject)))
+ subject)
+
;;;###autoload
(defun message-reply (&optional to-address wide)
"Start editing a reply to the article in the current buffer."
(setq message-id (message-fetch-field "message-id" t)
references (message-fetch-field "references")
date (message-fetch-field "date")
- from (message-fetch-field "from")
+ from (or (message-fetch-field "from") "nobody")
subject (or (message-fetch-field "subject") "none"))
- (when gnus-list-identifiers
- (setq subject (message-strip-list-identifiers subject)))
- (setq subject (concat "Re: " (message-strip-subject-re subject)))
- (when message-subject-trailing-was-query
- (setq subject (message-strip-subject-trailing-was subject)))
+
+ ;; Strip list identifiers, "Re: ", and "was:"
+ (setq subject (message-simplify-subject subject))
(when (and (setq gnus-warning (message-fetch-field "gnus-warning"))
(string-match "<[^>]+>" gnus-warning))
(let ((case-fold-search t))
(string-match "world" distribution)))
(setq distribution nil))
- (if gnus-list-identifiers
- (setq subject (message-strip-list-identifiers subject)))
- (setq subject (concat "Re: " (message-strip-subject-re subject)))
- (when message-subject-trailing-was-query
- (setq subject (message-strip-subject-trailing-was subject)))
+ ;; Strip list identifiers, "Re: ", and "was:"
+ (setq subject (message-simplify-subject subject))
(widen))
(message-pop-to-buffer (message-buffer-name "followup" from newsgroups))
;; Email address in From field equals to our address
(and (setq from (message-fetch-field "from"))
(string-equal
- (downcase (cadr (mail-extract-address-components from)))
- (downcase (cadr (mail-extract-address-components
- (message-make-from))))))
+ (downcase (car (mail-header-parse-address from)))
+ (downcase (car (mail-header-parse-address
+ (message-make-from))))))
;; Email address in From field matches
;; 'message-alternative-emails' regexp
(and from
message-alternative-emails
(string-match
message-alternative-emails
- (cadr (mail-extract-address-components from))))))))))
+ (car (mail-header-parse-address from))))))))))
;;;###autoload
(defun message-cancel-news (&optional arg)
(interactive)
(let ((file-name (make-auto-save-file-name)))
(cond ((save-window-excursion
- (if (not (eq system-type 'vax-vms))
- (with-output-to-temp-buffer "*Directory*"
- (with-current-buffer standard-output
- (fundamental-mode)) ; for Emacs 20.4+
- (buffer-disable-undo standard-output)
- (let ((default-directory "/"))
- (call-process
- "ls" nil standard-output nil "-l" file-name))))
+ (with-output-to-temp-buffer "*Directory*"
+ (with-current-buffer standard-output
+ (fundamental-mode)) ; for Emacs 20.4+
+ (buffer-disable-undo standard-output)
+ (let ((default-directory "/"))
+ (call-process
+ "ls" nil standard-output nil "-l" file-name)))
(yes-or-no-p (format "Recover auto save file %s? " file-name)))
(let ((buffer-read-only nil))
(erase-buffer)
(setq subject (funcall func subject))))
subject))))
-(eval-when-compile
- (defvar gnus-article-decoded-p))
+(defvar gnus-article-decoded-p)
;;;###autoload
(defun message-forward-make-body-plain (forward-buffer)
(insert
"\n-------------------- Start of forwarded message --------------------\n")
- (let ((b (point)) e)
- (insert
- (with-temp-buffer
- (mm-disable-multibyte)
- (insert
- (with-current-buffer forward-buffer
- (mm-with-unibyte-current-buffer (buffer-string))))
- (mm-enable-multibyte)
- (mime-to-mml)
- (goto-char (point-min))
- (when (looking-at "From ")
- (replace-match "X-From-Line: "))
- (buffer-string)))
+ (let ((b (point))
+ (contents (with-current-buffer forward-buffer (buffer-string)))
+ e)
+ (unless (featurep 'xemacs)
+ (unless (mm-multibyte-string-p contents)
+ (error "Attempt to insert unibyte string from the buffer \"%s\"\
+ to the multibyte buffer \"%s\""
+ (if (bufferp forward-buffer)
+ (buffer-name forward-buffer)
+ forward-buffer)
+ (buffer-name))))
+ (insert (mm-with-multibyte-buffer
+ (insert contents)
+ (mime-to-mml)
+ (goto-char (point-min))
+ (when (looking-at "From ")
+ (replace-match "X-From-Line: "))
+ (buffer-string)))
+ (unless (bolp) (insert "\n"))
(setq e (point))
(insert
- "\n-------------------- End of forwarded message --------------------\n")
- (when message-forward-ignored-headers
- (save-restriction
- (narrow-to-region b e)
- (goto-char b)
- (narrow-to-region (point)
- (or (search-forward "\n\n" nil t) (point)))
- (message-remove-header message-forward-ignored-headers t)))))
+ "-------------------- End of forwarded message --------------------\n")
+ (message-remove-ignored-headers b e)))
+
+(defun message-remove-ignored-headers (b e)
+ (when message-forward-ignored-headers
+ (save-restriction
+ (narrow-to-region b e)
+ (goto-char b)
+ (narrow-to-region (point)
+ (or (search-forward "\n\n" nil t) (point)))
+ (let ((ignored (if (stringp message-forward-ignored-headers)
+ (list message-forward-ignored-headers)
+ message-forward-ignored-headers)))
+ (dolist (elem ignored)
+ (message-remove-header elem t))))))
(defun message-forward-make-body-mime (forward-buffer)
- (insert "\n\n<#part type=message/rfc822 disposition=inline raw=t>\n")
- (let ((b (point)) e)
+ (let ((b (point)))
+ (insert "\n\n<#part type=message/rfc822 disposition=inline raw=t>\n")
(save-restriction
(narrow-to-region (point) (point))
(mml-insert-buffer forward-buffer)
(when (looking-at "From ")
(replace-match "X-From-Line: "))
(goto-char (point-max)))
- (setq e (point))
- (insert "<#/part>\n")))
+ (insert "<#/part>\n")
+ ;; Consider there is no illegible text.
+ (add-text-properties
+ b (point)
+ `(no-illegible-text t rear-nonsticky t start-open t))))
(defun message-forward-make-body-mml (forward-buffer)
(insert "\n\n<#mml type=message/rfc822 disposition=inline>\n")
(let ((b (point)) e)
(if (not message-forward-decoded-p)
- (insert
- (with-temp-buffer
- (mm-disable-multibyte)
- (insert
- (with-current-buffer forward-buffer
- (mm-with-unibyte-current-buffer (buffer-string))))
- (mm-enable-multibyte)
- (mime-to-mml)
- (goto-char (point-min))
- (when (looking-at "From ")
- (replace-match "X-From-Line: "))
- (buffer-string)))
+ (let ((contents (with-current-buffer forward-buffer (buffer-string))))
+ (unless (featurep 'xemacs)
+ (unless (mm-multibyte-string-p contents)
+ (error "Attempt to insert unibyte string from the buffer \"%s\"\
+ to the multibyte buffer \"%s\""
+ (if (bufferp forward-buffer)
+ (buffer-name forward-buffer)
+ forward-buffer)
+ (buffer-name))))
+ (insert (mm-with-multibyte-buffer
+ (insert contents)
+ (mime-to-mml)
+ (goto-char (point-min))
+ (when (looking-at "From ")
+ (replace-match "X-From-Line: "))
+ (buffer-string))))
(save-restriction
(narrow-to-region (point) (point))
(mml-insert-buffer forward-buffer)
(insert "<#/mml>\n")
(when (and (not message-forward-decoded-p)
message-forward-ignored-headers)
- (save-restriction
- (narrow-to-region b e)
- (goto-char b)
- (narrow-to-region (point)
- (or (search-forward "\n\n" nil t) (point)))
- (message-remove-header message-forward-ignored-headers t)))))
+ (message-remove-ignored-headers b e))))
(defun message-forward-make-body-digest-plain (forward-buffer)
(insert
(message-forward-make-body-digest-mime forward-buffer)
(message-forward-make-body-digest-plain forward-buffer)))
+(autoload 'mm-uu-dissect-text-parts "mm-uu")
+(autoload 'mm-uu-dissect "mm-uu")
+
+(defun message-signed-or-encrypted-p (&optional dont-emulate-mime handles)
+ "Say whether the current buffer contains signed or encrypted message.
+If DONT-EMULATE-MIME is nil, this function does the MIME emulation on
+messages that don't conform to PGP/MIME described in RFC2015. HANDLES
+is for the internal use."
+ (unless handles
+ (let ((mm-decrypt-option 'never)
+ (mm-verify-option 'never))
+ (if (setq handles (mm-dissect-buffer nil t))
+ (unless dont-emulate-mime
+ (mm-uu-dissect-text-parts handles))
+ (unless dont-emulate-mime
+ (setq handles (mm-uu-dissect))))))
+ ;; Check text/plain message in which there is a signed or encrypted
+ ;; body that has been encoded by B or Q.
+ (unless (or handles dont-emulate-mime)
+ (let ((cur (current-buffer))
+ (mm-decrypt-option 'never)
+ (mm-verify-option 'never))
+ (with-temp-buffer
+ (insert-buffer-substring cur)
+ (when (setq handles (mm-dissect-buffer t t))
+ (if (and (prog1
+ (bufferp (car handles))
+ (mm-destroy-parts handles))
+ (equal (mm-handle-media-type handles) "text/plain"))
+ (progn
+ (mm-decode-content-transfer-encoding
+ (mm-handle-encoding handles))
+ (setq handles (mm-uu-dissect)))
+ (setq handles nil))))))
+ (when handles
+ (prog1
+ (catch 'found
+ (dolist (handle (if (stringp (car handles))
+ (if (member (car handles)
+ '("multipart/signed"
+ "multipart/encrypted"))
+ (throw 'found t)
+ (cdr handles))
+ (list handles)))
+ (if (stringp (car handle))
+ (when (message-signed-or-encrypted-p dont-emulate-mime handle)
+ (throw 'found t))
+ (when (and (bufferp (car handle))
+ (equal (mm-handle-media-type handle)
+ "message/rfc822"))
+ (with-current-buffer (mm-handle-buffer handle)
+ (when (message-signed-or-encrypted-p dont-emulate-mime)
+ (throw 'found t)))))))
+ (mm-destroy-parts handles))))
+
;;;###autoload
(defun message-forward-make-body (forward-buffer &optional digest)
;; Put point where we want it before inserting the forwarded
(if message-forward-as-mime
(if (and message-forward-show-mml
(not (and (eq message-forward-show-mml 'best)
+ ;; Use the raw form in the body if it contains
+ ;; signed or encrypted message so as not to be
+ ;; destroyed by re-encoding.
(with-current-buffer forward-buffer
- (goto-char (point-min))
- (re-search-forward
- "Content-Type: *multipart/\\(signed\\|encrypted\\)"
- nil t)))))
+ (condition-case nil
+ (message-signed-or-encrypted-p)
+ (error t))))))
(message-forward-make-body-mml forward-buffer)
(message-forward-make-body-mime forward-buffer))
(message-forward-make-body-plain forward-buffer)))
(message-position-point))
+(declare-function rmail-toggle-header "rmail" (&optional arg))
+
;;;###autoload
(defun message-forward-rmail-make-body (forward-buffer)
(save-window-excursion
(set-buffer forward-buffer)
(if (rmail-msg-is-pruned)
- (rmail-msg-restore-non-pruned-header)))
+ (if (fboundp 'rmail-msg-restore-non-pruned-header)
+ (rmail-msg-restore-non-pruned-header) ; Emacs 22
+ (rmail-toggle-header 0)))) ; Emacs 23
(message-forward-make-body forward-buffer))
-(eval-when-compile (defvar rmail-enable-mime-composing))
-
;; Fixme: Should have defcustom.
;;;###autoload
(defun message-insinuate-rmail ()
(replace-match "X-From-Line: "))
;; Send it.
(let ((message-inhibit-body-encoding t)
+ (message-inhibit-ecomplete t)
message-required-mail-headers
message-generate-hashcash
rfc2047-encode-encoded-words)
(goto-char boundary)
(when (re-search-backward "^.?From .*\n" nil t)
(delete-region (match-beginning 0) (match-end 0)))))
- (mm-enable-multibyte)
+ (mime-to-mml)
(save-restriction
(message-narrow-to-head-1)
(message-remove-header message-ignored-bounced-headers t)
(message-pop-to-buffer (message-buffer-name "mail" to))))
(let ((message-this-is-mail t))
(message-setup `((To . ,(or to "")) (Subject . ,(or subject "")))
- nil nil 'switch-to-buffer-other-window)))
+ nil nil nil 'switch-to-buffer-other-window)))
;;;###autoload
(defun message-mail-other-frame (&optional to subject)
(message-pop-to-buffer (message-buffer-name "mail" to))))
(let ((message-this-is-mail t))
(message-setup `((To . ,(or to "")) (Subject . ,(or subject "")))
- nil nil 'switch-to-buffer-other-frame)))
+ nil nil nil 'switch-to-buffer-other-frame)))
;;;###autoload
(defun message-news-other-window (&optional newsgroups subject)
(defun message-exchange-point-and-mark ()
"Exchange point and mark, but don't activate region if it was inactive."
- (unless (prog1
- (message-mark-active-p)
- (exchange-point-and-mark))
- (setq mark-active nil)))
+ (goto-char (prog1 (mark t)
+ (set-marker (mark-marker) (point)))))
(defalias 'message-make-overlay 'make-overlay)
(defalias 'message-delete-overlay 'delete-overlay)
(mapcar #'delete-overlay (overlays-in (point-min) (point-max)))))
;; Support for toolbar
-(eval-when-compile
- (defvar tool-bar-mode))
+(defvar tool-bar-mode)
;; Note: The :set function in the `message-tool-bar*' variables will only
;; affect _new_ message buffers. We might add a function that walks thru all
(const :tag "Retro look" message-tool-bar-retro)
(repeat :tag "User defined list" gmm-tool-bar-item)
(symbol))
- :version "23.0" ;; No Gnus
+ :version "23.1" ;; No Gnus
:initialize 'custom-initialize-default
:set 'message-tool-bar-update
:group 'message)
See `gmm-tool-bar-from-list' for details on the format of the list."
:type '(repeat gmm-tool-bar-item)
- :version "23.0" ;; No Gnus
+ :version "23.1" ;; No Gnus
:initialize 'custom-initialize-default
:set 'message-tool-bar-update
:group 'message)
(defcustom message-tool-bar-retro
'(;; Old Emacs 21 icon for consistency.
- (message-send-and-exit "gnus/mail_send")
+ (message-send-and-exit "gnus/mail-send")
(message-kill-buffer "close")
(message-dont-send "cancel")
(mml-attach-file "attach" mml-mode-map)
See `gmm-tool-bar-from-list' for details on the format of the list."
:type '(repeat gmm-tool-bar-item)
- :version "23.0" ;; No Gnus
+ :version "23.1" ;; No Gnus
:initialize 'custom-initialize-default
:set 'message-tool-bar-update
:group 'message)
See `gmm-tool-bar-from-list' for the format of the list."
:type 'gmm-tool-bar-zap-list
- :version "23.0" ;; No Gnus
+ :version "23.1" ;; No Gnus
:initialize 'custom-initialize-default
:set 'message-tool-bar-update
:group 'message)
:type '(choice (const nil)
function))
+(declare-function mail-abbrev-in-expansion-header-p "mailabbrev" ())
+
(defun message-tab ()
"Complete names according to `message-completion-alist'.
Execute function specified by `message-tab-body-function' when not in
(point))
(skip-chars-backward "^, \t\n") (point))))
(completion-ignore-case t)
- (string (buffer-substring b (progn (skip-chars-forward "^,\t\n ")
- (point))))
- (hashtb (and (boundp 'gnus-active-hashtb) gnus-active-hashtb))
- (completions (all-completions string hashtb))
- comp)
- (delete-region b (point))
- (cond
- ((= (length completions) 1)
- (if (string= (car completions) string)
- (progn
- (insert string)
- (message "Only matching group"))
- (insert (car completions))))
- ((and (setq comp (try-completion string hashtb))
- (not (string= comp string)))
- (insert comp))
- (t
- (insert string)
- (if (not comp)
- (message "No matching groups")
- (save-selected-window
- (pop-to-buffer "*Completions*")
- (buffer-disable-undo)
- (let ((buffer-read-only nil))
- (erase-buffer)
- (let ((standard-output (current-buffer)))
- (message-display-completion-list (sort completions 'string<)
- string))
- (setq buffer-read-only nil)
- (goto-char (point-min))
- (delete-region (point) (progn (forward-line 3) (point))))))))))
+ (e (progn (skip-chars-forward "^,\t\n ") (point)))
+ (hashtb (and (boundp 'gnus-active-hashtb) gnus-active-hashtb)))
+ (message-completion-in-region e b hashtb)))
+
+(defalias 'message-completion-in-region
+ (if (fboundp 'completion-in-region)
+ 'completion-in-region
+ (lambda (e b hashtb)
+ (let* ((string (buffer-substring b e))
+ (completions (all-completions string hashtb))
+ comp)
+ (delete-region b (point))
+ (cond
+ ((= (length completions) 1)
+ (if (string= (car completions) string)
+ (progn
+ (insert string)
+ (message "Only matching group"))
+ (insert (car completions))))
+ ((and (setq comp (try-completion string hashtb))
+ (not (string= comp string)))
+ (insert comp))
+ (t
+ (insert string)
+ (if (not comp)
+ (message "No matching groups")
+ (save-selected-window
+ (pop-to-buffer "*Completions*")
+ (buffer-disable-undo)
+ (let ((buffer-read-only nil))
+ (erase-buffer)
+ (let ((standard-output (current-buffer)))
+ (message-display-completion-list (sort completions 'string<)
+ string))
+ (setq buffer-read-only nil)
+ (goto-char (point-min))
+ (delete-region (point)
+ (progn (forward-line 3) (point))))))))))))
(defun message-expand-name ()
(cond ((and (memq 'eudc message-expand-name-databases)
(if (and show
(setq text (message-flatten-list text)))
(save-window-excursion
- (save-excursion
- (with-output-to-temp-buffer " *MESSAGE information message*"
- (set-buffer " *MESSAGE information message*")
+ (with-output-to-temp-buffer " *MESSAGE information message*"
+ (with-current-buffer " *MESSAGE information message*"
(fundamental-mode) ; for Emacs 20.4+
- (mapcar 'princ text)
+ (mapc 'princ text)
(goto-char (point-min))))
(funcall ask question))
(funcall ask question)))
new one, cloning only the locals having a substring matching the
regexp VARSTR."
(let ((oldbuf (current-buffer)))
- (save-excursion
- (set-buffer (generate-new-buffer name))
+ (with-current-buffer (generate-new-buffer name)
(message-clone-locals oldbuf varstr)
(current-buffer))))
(defun message-clone-locals (buffer &optional varstr)
"Clone the local variables from BUFFER to the current buffer."
- (let ((locals (save-excursion
- (set-buffer buffer)
- (buffer-local-variables)))
+ (let ((locals (with-current-buffer buffer (buffer-local-variables)))
(regexp "^gnus\\|^nn\\|^message\\|^sendmail\\|^smtp\\|^user-mail-address"))
(mapcar
(lambda (local)
message-hidden-headers))
(inhibit-point-motion-hooks t)
(after-change-functions nil)
- (end-of-headers 0))
+ (end-of-headers (point-min)))
(when regexps
(save-excursion
(save-restriction
(setq header (buffer-substring begin (point))
header-len (- (point) begin))
(delete-region begin (point))
- (goto-char (1+ end-of-headers))
+ (goto-char end-of-headers)
(insert header)
(setq end-of-headers
(+ end-of-headers header-len))))))))
- (narrow-to-region (1+ end-of-headers) (point-max))))
+ (narrow-to-region end-of-headers (point-max))))
(defun message-hide-header-p (regexps)
(let ((result nil)
(not result)
result)))
+(declare-function ecomplete-add-item "ecomplete" (type key text))
+(declare-function ecomplete-save "ecomplete" ())
+
(defun message-put-addresses-in-ecomplete ()
+ (require 'ecomplete)
(dolist (header '("to" "cc" "from" "reply-to"))
- (let ((value (message-fetch-field header)))
+ (let ((value (message-field-value header)))
(dolist (string (mail-header-parse-addresses value 'raw))
(setq string
- (replace-regexp-in-string
- "\n" ""
- (replace-regexp-in-string "^ +\\| *$" "" string)))
+ (gnus-replace-in-string
+ (gnus-replace-in-string string "^ +\\| +$" "") "\n" ""))
(ecomplete-add-item 'mail (car (mail-header-parse-address string))
string))))
(ecomplete-save))
-(defun message-display-abbrev ()
+(autoload 'ecomplete-display-matches "ecomplete")
+
+(defun message-display-abbrev (&optional choose)
"Display the next possible abbrev for the text before point."
- (interactive)
- (when (and (message-point-in-header-p)
+ (interactive (list t))
+ (when (and (memq (char-after (point-at-bol)) '(?C ?T ?\t ? ))
+ (message-point-in-header-p)
(save-excursion
- (save-restriction
- (message-narrow-to-field)
- (goto-char (point-min))
- (looking-at "To\\|Cc"))))
+ (beginning-of-line)
+ (while (and (memq (char-after) '(?\t ? ))
+ (zerop (forward-line -1))))
+ (looking-at "To:\\|Cc:")))
(let* ((end (point))
(start (save-excursion
- (re-search-backward "[\n\t ]" nil t)
- (1+ (point))))
- (word (buffer-substring start end))
- (match (ecomplete-display-matches 'mail word)))
- (when match
+ (and (re-search-backward "[\n\t ]" nil t)
+ (1+ (point)))))
+ (word (when start (buffer-substring start end)))
+ (match (when (and word
+ (not (zerop (length word))))
+ (ecomplete-display-matches 'mail word choose))))
+ (when (and choose match)
(delete-region start end)
(insert match)))))
+;; To send pre-formatted letters like the example below, you can use
+;; `message-send-form-letter':
+;; --8<---------------cut here---------------start------------->8---
+;; To: alice@invalid.invalid
+;; Subject: Verification of your contact information
+;; From: Contact verification <admin@foo.invalid>
+;; --text follows this line--
+;; Hi Alice,
+;; please verify that your contact information is still valid:
+;; Alice A, A avenue 11, 1111 A town, Austria
+;; ----------next form letter message follows this line----------
+;; To: bob@invalid.invalid
+;; Subject: Verification of your contact information
+;; From: Contact verification <admin@foo.invalid>
+;; --text follows this line--
+;; Hi Bob,
+;; please verify that your contact information is still valid:
+;; Bob, B street 22, 22222 Be town, Belgium
+;; ----------next form letter message follows this line----------
+;; To: charlie@invalid.invalid
+;; Subject: Verification of your contact information
+;; From: Contact verification <admin@foo.invalid>
+;; --text follows this line--
+;; Hi Charlie,
+;; please verify that your contact information is still valid:
+;; Charlie Chaplin, C plaza 33, 33333 C town, Chile
+;; --8<---------------cut here---------------end--------------->8---
+
+;; FIXME: What is the most common term (circular letter, form letter, serial
+;; letter, standard letter) for such kind of letter? See also
+;; <http://en.wikipedia.org/wiki/Form_letter>
+
+;; FIXME: Maybe extent message-mode's font-lock support to recognize
+;; `message-form-letter-separator', i.e. highlight each message like a single
+;; message.
+
+(defcustom message-form-letter-separator
+ "\n----------next form letter message follows this line----------\n"
+ "Separator for `message-send-form-letter'."
+ ;; :group 'message-form-letter
+ :group 'message-various
+ :version "23.1" ;; No Gnus
+ :type 'string)
+
+(defcustom message-send-form-letter-delay 1
+ "Delay in seconds when sending a message with `message-send-form-letter'.
+Only used when `message-send-form-letter' is called with non-nil
+argument `force'."
+ ;; :group 'message-form-letter
+ :group 'message-various
+ :version "23.1" ;; No Gnus
+ :type 'integer)
+
+(defun message-send-form-letter (&optional force)
+ "Sent all form letter messages from current buffer.
+Unless FORCE, prompt before sending.
+
+The messages are separated by `message-form-letter-separator'.
+Header and body are separated by `mail-header-separator'."
+ (interactive "P")
+ (let ((sent 0) (skipped 0)
+ start end text
+ buff
+ to done)
+ (goto-char (point-min))
+ (while (not done)
+ (setq start (point)
+ end (if (search-forward message-form-letter-separator nil t)
+ (- (point) (length message-form-letter-separator) -1)
+ (setq done t)
+ (point-max)))
+ (setq text
+ (buffer-substring-no-properties start end))
+ (setq buff (generate-new-buffer "*mail - form letter*"))
+ (with-current-buffer buff
+ (insert text)
+ (message-mode)
+ (setq to (message-fetch-field "To"))
+ (switch-to-buffer buff)
+ (when force
+ (sit-for message-send-form-letter-delay))
+ (if (or force
+ (y-or-n-p (format "Send message to `%s'? " to)))
+ (progn
+ (setq sent (1+ sent))
+ (message-send-and-exit))
+ (message (format "Message to `%s' skipped." to))
+ (setq skipped (1+ skipped)))
+ (when (buffer-live-p buff)
+ (kill-buffer buff))))
+ (message "%s message(s) sent, %s skipped." sent skipped)))
+
+(defun message-replace-header (header new-value &optional after force)
+ "Remove HEADER and insert the NEW-VALUE.
+If AFTER, insert after this header. If FORCE, insert new field
+even if NEW-VALUE is empty."
+ ;; Similar to `nnheader-replace-header' but for message buffers.
+ (save-excursion
+ (save-restriction
+ (message-narrow-to-headers)
+ (message-remove-header header))
+ (when (or force (> (length new-value) 0))
+ (if after
+ (message-position-on-field header after)
+ (message-position-on-field header))
+ (insert new-value))))
+
+(defcustom message-recipients-without-full-name
+ (list "ding@gnus.org"
+ "bugs@gnus.org"
+ "emacs-devel@gnu.org"
+ "emacs-pretest-bug@gnu.org"
+ "bug-gnu-emacs@gnu.org")
+ "Mail addresses that have no full name.
+Used in `message-simplify-recipients'."
+ ;; Maybe the addresses could be extracted from
+ ;; `gnus-parameter-to-list-alist'?
+ :type '(choice (const :tag "None" nil)
+ (repeat string))
+ :version "23.1" ;; No Gnus
+ :group 'message-headers)
+
+(defun message-simplify-recipients ()
+ (interactive)
+ (dolist (hdr '("Cc" "To"))
+ (message-replace-header
+ hdr
+ (mapconcat
+ (lambda (addrcomp)
+ (if (and message-recipients-without-full-name
+ (string-match
+ (regexp-opt message-recipients-without-full-name)
+ (cadr addrcomp)))
+ (cadr addrcomp)
+ (if (car addrcomp)
+ (message-make-from (car addrcomp) (cadr addrcomp))
+ (cadr addrcomp))))
+ (when (message-fetch-field hdr)
+ (mail-extract-address-components
+ (message-fetch-field hdr) t))
+ ", "))))
+
(when (featurep 'xemacs)
(require 'messagexmas)
(message-xmas-redefine))
;; coding: iso-8859-1
;; End:
-;; arch-tag: 94b32cac-4504-4b6c-8181-030ebf380ee0
;;; message.el ends here