X-Git-Url: http://cgit.sxemacs.org/?a=blobdiff_plain;f=lisp%2Fmessage.el;h=1952a4fae68a606465c7589361a2fcde67555712;hb=789f295cf95c5affbfab4c9b81b38199809a3593;hp=97fa87fd777c8cfda6fe4ddab579dfe18be80345;hpb=72b3a7aec512a91105f92dd7a43f4d3e5d3739ed;p=gnus diff --git a/lisp/message.el b/lisp/message.el index 97fa87fd7..1952a4fae 100644 --- a/lisp/message.el +++ b/lisp/message.el @@ -1,7 +1,7 @@ ;;; message.el --- composing mail and news messages ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, -;; 2005 Free Software Foundation, Inc. +;; 2005, 2006, 2007 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: mail, news @@ -34,10 +34,11 @@ (eval-when-compile (require 'cl) (defvar gnus-message-group-art) - (defvar gnus-list-identifiers) ; gnus-sum is required where necessary - (require 'hashcash)) + (defvar gnus-list-identifiers)) ; gnus-sum is required where necessary +(require 'hashcash) (require 'canlock) (require 'mailheader) +(require 'gmm-utils) (require 'nnheader) ;; This is apparently necessary even though things are autoloaded. ;; Because we dynamically bind mail-abbrev-mode-regexp, we'd better @@ -48,6 +49,7 @@ (require 'mail-parse) (require 'mml) (require 'rfc822) +(require 'ecomplete) (defgroup message '((user-mail-address custom-variable) (user-full-name custom-variable)) @@ -152,7 +154,6 @@ If this variable is nil, no such courtesy message will be added." :group 'message-interface :type 'regexp) -;;;###autoload (defcustom message-from-style 'default "*Specifies how \"From\" headers look. @@ -186,14 +187,13 @@ To disable checking of long signatures, for instance, add 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', `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 @@ -208,7 +208,7 @@ Also see `message-required-news-headers' and :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 @@ -408,7 +408,6 @@ for `message-cross-post-insert-note'." ;;; End of variables adopted from `message-utils.el'. -;;;###autoload (defcustom message-signature-separator "^-- *$" "Regexp matching the signature separator." :type 'regexp @@ -468,8 +467,14 @@ If t, use `message-user-organization-file'." :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") @@ -578,7 +583,13 @@ Done before generating the new subject of a forward." :version "22.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." @@ -587,7 +598,6 @@ Done before generating the new subject of a forward." :type 'string) ;; Useful to set in site-init.el -;;;###autoload (defcustom message-send-mail-function (let ((program (if (boundp 'sendmail-program) ;; see paths.el @@ -757,6 +767,14 @@ If this is nil, use `user-mail-address'. If it is the symbol :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.0" ;; 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." @@ -862,19 +880,51 @@ the signature is inserted." :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-formated-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-formated-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:" + "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 \". + %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.0" ;; No Gnus + :group 'message-insertion) + (defcustom message-yank-prefix "> " "*Prefix inserted on the lines of yanked messages. Fix `message-cite-prefix-regexp' if it is set to an abnormal value. @@ -907,7 +957,6 @@ Used by `message-yank-original' via `message-yank-cite'." :link '(custom-manual "(message)Insertion Variables") :type 'integer) -;;;###autoload (defcustom message-cite-function 'message-cite-original "*Function for citing an original message. Predefined functions include `message-cite-original' and @@ -920,7 +969,6 @@ Note that these functions use `mail-citation-hook' if that is non-nil." :link '(custom-manual "(message)Insertion Variables") :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 @@ -930,7 +978,6 @@ point and mark around the citation text as modified." :link '(custom-manual "(message)Insertion Variables") :group 'message-insertion) -;;;###autoload (defcustom message-signature t "*String to be inserted at the end of the message buffer. If t, the `message-signature-file' file will be inserted instead. @@ -940,7 +987,6 @@ If a form, the result from the form will be used instead." :link '(custom-manual "(message)Insertion Variables") :group 'message-insertion) -;;;###autoload (defcustom message-signature-file "~/.signature" "*Name of file containing the text inserted at end of message buffer. Ignored if the named file doesn't exist. @@ -949,7 +995,6 @@ If nil, don't insert a signature." :link '(custom-manual "(message)Insertion Variables") :group 'message-insertion) -;;;###autoload (defcustom message-signature-insert-empty-line t "*If non-nil, insert an empty line before the signature separator." :version "22.1" @@ -1079,13 +1124,25 @@ the prefix.") (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")) "*Directory where Message auto-saves buffers if Gnus isn't running. @@ -1105,13 +1162,28 @@ If nil, you might be asked to input the charset." (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))) + +;; #### FIXME: this might become a generally usefull function at some point +;; --dlv. +(defsubst message-dont-reply-to-names () + "Potentially convert a list of regexps into a single one." + (cond ((null message-dont-reply-to-names) + nil) + ((stringp message-dont-reply-to-names) + message-dont-reply-to-names) + ((listp message-dont-reply-to-names) + (mapconcat (lambda (elt) (concat "\\(" elt "\\)")) + message-dont-reply-to-names + "\\|")))) (defvar message-shoot-gnksa-feet nil "*A list of GNKSA feet you are allowed to shoot. @@ -1129,7 +1201,8 @@ candidates: (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." @@ -1510,12 +1583,18 @@ functionality to work." (const :tag "Never" nil) (const :tag "Always" t))) -(defcustom message-generate-hashcash nil +(defcustom message-generate-hashcash (if (executable-find "hashcash") t) "*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'." :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. @@ -1630,17 +1709,22 @@ You must have the \"hashcash\" binary installed, see `hashcash-path'." (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" @@ -1834,6 +1918,96 @@ see `message-narrow-to-headers-or-head'." (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) @@ -2087,14 +2261,6 @@ With prefix-argument just set Follow-Up, don't cross-post." ;;; End of functions adopted from `message-utils.el'. -(defun message-remove-duplicates (list) - (let (new) - (while list - (or (member (car list) new) - (setq new (cons (car list) new))) - (setq list (cdr list))) - (nreverse new))) - (defun message-remove-header (header &optional is-regexp first reverse) "Remove HEADER in the narrowed buffer. If IS-REGEXP, HEADER is a regular expression. @@ -2260,6 +2426,17 @@ Point is left at the beginning of the narrowed-to region." (message-skip-to-next-address) (kill-region start (point)))) + +(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." + (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")))) + ;;; @@ -2343,7 +2520,9 @@ Point is left at the beginning of the narrowed-to region." (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-;" 'comment-region) + + (define-key message-mode-map "\M-n" 'message-display-abbrev)) (easy-menu-define message-mode-menu message-mode-map "Message Menu." @@ -2387,7 +2566,11 @@ Point is left at the beginning of the narrowed-to region." '(:help "Ask, then arrange to send message at that time"))] ["Kill Message" message-kill-buffer ,@(if (featurep 'xemacs) '(t) - '(:help "Delete this message without sending"))])) + '(:help "Delete this message without sending"))] + "----" + ["Message manual" message-info + ,@(if (featurep 'xemacs) '(t) + '(:help "Display the Message manual"))])) (easy-menu-define message-mode-field-menu message-mode-map "" @@ -2441,6 +2624,8 @@ Point is left at the beginning of the narrowed-to region." "----" ["Sort Headers" message-sort-headers t] ["Encode non-ASCII domain names" message-idna-to-ascii-rhs t] + ;; We hide `message-hidden-headers' by narrowing the buffer. + ["Show Hidden Headers" widen t] ["Goto Body" message-goto-body t] ["Goto Signature" message-goto-signature t])) @@ -2499,10 +2684,18 @@ These properties are essential to work, so we should never strip them." (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) @@ -2584,7 +2777,7 @@ M-RET `message-newline-and-reformat' (break the line and reformat)." (set (make-local-variable 'font-lock-defaults) '(message-font-lock-keywords t)) (if (boundp 'tool-bar-map) - (set (make-local-variable 'tool-bar-map) (message-tool-bar-map)))) + (set (make-local-variable 'tool-bar-map) (message-make-tool-bar)))) (easy-menu-add message-mode-menu message-mode-map) (easy-menu-add message-mode-field-menu message-mode-map) (gnus-make-local-hook 'after-change-functions) @@ -2592,11 +2785,14 @@ M-RET `message-newline-and-reformat' (break the line and reformat)." (add-hook 'after-change-functions 'message-strip-forbidden-properties nil 'local) ;; Allow mail alias things. - (when (eq message-mail-alias-type 'abbrev) + (cond + ((message-mail-alias-type-p 'abbrev) (if (fboundp 'mail-abbrevs-setup) (mail-abbrevs-setup) (if (fboundp 'mail-aliases-setup) ; warning avoidance (mail-aliases-setup)))) + ((message-mail-alias-type-p 'ecomplete) + (ecomplete-setup))) (unless buffer-file-name (message-set-auto-save-file-name)) (unless (buffer-base-buffer) @@ -2733,6 +2929,11 @@ M-RET `message-newline-and-reformat' (break the line and reformat)." (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) @@ -2804,7 +3005,8 @@ prefix FORCE is given." (message-carefully-insert-headers headers))) (defcustom message-header-synonyms - '((To Cc Bcc)) + '((To Cc Bcc) + (Original-To)) "List of lists of header synonyms. E.g., if this list contains a member list with elements `Cc' and `To', then `message-carefully-insert-headers' will not insert a `To' header @@ -2900,7 +3102,7 @@ or in the synonym headers, defined by `message-header-synonyms'." "Kill all text up to the signature. If a numberic argument or prefix arg is given, leave that number of lines before the signature intact." - (interactive "p") + (interactive "P") (save-excursion (save-restriction (let ((point (point))) @@ -3015,15 +3217,11 @@ Message buffers and is not meant to be called directly." (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." @@ -3169,17 +3367,17 @@ text was killed." (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)))) @@ -3226,14 +3424,15 @@ Numeric argument means justify as well." (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) @@ -3261,21 +3460,32 @@ However, if `message-yank-prefix' is non-nil, insert that prefix on each line." (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)) + +(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. @@ -3288,16 +3498,36 @@ This function uses `message-cite-function' to do the actual citing. 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) + (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))) (delete-windows-on message-reply-buffer t) (push-mark (save-excursion (insert-buffer-substring message-reply-buffer) (point))) (unless arg (funcall message-cite-function)) - (message-exchange-point-and-mark) + (if message-cite-reply-above + (progn + (message-goto-body) + (insert body-text) + (newline) + (message-goto-body) + (message-exchange-point-and-mark)) + (message-exchange-point-and-mark)) (unless (bolp) (insert ?\n)) (unless modified @@ -3350,7 +3580,7 @@ This function uses `mail-citation-hook' if that is non-nil." (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") @@ -3382,12 +3612,100 @@ This function uses `mail-citation-hook' if that is non-nil." (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)) +(defun message-insert-formated-citation-line (&optional from date) + "Function that inserts a formated citation line. + +See `message-citation-line-format'." + ;; The optional args are for testing/debugging. They will disappear later. + ;; Example: + ;; (with-temp-buffer + ;; (message-insert-formated-citation-line + ;; "John Doe " + ;; (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 "" lst) + (push ?F lst) (push fname lst) + ;; We might want to use "" instead of "" later. + (push ?J lst) (push "" lst) + (push ?K lst) (push "" lst) + (push ?L lst) (push lname lst) + (push ?N lst) (push name-or-net lst) + (push ?O lst) (push "" lst) + (push ?P lst) (push "

" lst) + (push ?Q lst) (push "" lst) + (push ?f lst) (push from lst) + (push ?i lst) (push "" lst) + (push ?n lst) (push net lst) + (push ?o lst) (push "" lst) + (push ?q lst) (push "" lst) + (push ?t lst) (push "" lst) + (push ?v lst) (push "" 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 + (progn (format-time-string (format "%%%c" i) + replydate)) + (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) + (newline))) + (defun message-cite-original-without-signature () "Cite function in the standard Message manner. This function strips off the signature from the original message." @@ -3396,7 +3714,9 @@ This function strips off the signature from the original message." (defun message-insert-citation-line () "Insert a simple citation line." (when message-reply-headers - (insert (mail-header-from message-reply-headers) " writes:\n\n"))) + (insert (mail-header-from message-reply-headers) " writes:") + (newline) + (newline))) (defun message-position-on-field (header &rest afters) (let ((case-fold-search t)) @@ -3576,6 +3896,9 @@ It should typically alter the sending method in some way or other." (save-excursion (run-hooks 'message-sent-hook)) (message "Sending...done") + ;; Do ecomplete address snarfing. + (when (message-mail-alias-type-p 'ecomplete) + (message-put-addresses-in-ecomplete)) ;; Mark the buffer as unmodified and delete auto-save. (set-buffer-modified-p nil) (delete-auto-save-file-if-necessary t) @@ -3678,8 +4001,10 @@ not have PROP." (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") + `((?d "Remove non-printable characters and send") + (?r ,(format + "Replace non-printable characters with \"%s\" and send" + message-replacement-char)) (?i "Ignore non-printable characters and send") (?e "Continue editing")))) (if (eq choice ?e) @@ -3690,8 +4015,8 @@ not have PROP." (when (let ((char (char-after))) (or (< (mm-char-int char) 128) (and (mm-multibyte-p) - ;; Fixme: Wrong for Emacs 22 and for things - ;; like undecable utf-8. Should at least + ;; FIXME: Wrong for Emacs 23 (unicode) and for + ;; things like undecable utf-8. Should at least ;; use find-coding-systems-region. (memq (char-charset char) '(eight-bit-control eight-bit-graphic @@ -3702,7 +4027,7 @@ not have PROP." (message-kill-all-overlays) (delete-char 1) (when (eq choice ?r) - (insert ".")))) + (insert message-replacement-char)))) (forward-char) (skip-chars-forward mm-7bit-chars)))))) @@ -3815,7 +4140,8 @@ not have PROP." (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...") ;; Wait for calculations already started to finish... (hashcash-wait-async) @@ -3839,6 +4165,16 @@ not have PROP." (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 @@ -3957,10 +4293,17 @@ If you always want Gnus to send messages in one piece, set '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 @@ -4395,11 +4738,11 @@ Otherwise, generate and save a value for `canlock-password' first." (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)) @@ -4712,8 +5055,7 @@ In posting styles use `(\"Expires\" (make-expires-date 30))'." (let* ((cur (decode-time (current-time))) (nday (+ days (nth 3 cur)))) (setf (nth 3 cur) nday) - (format-time-string "%a, %d %b %Y %H:%M:%S %Z" - (apply 'encode-time cur)))) + (message-make-date (apply 'encode-time cur)))) (defun message-make-message-id () "Make a unique Message-ID." @@ -4751,7 +5093,9 @@ In posting styles use `(\"Expires\" (make-expires-date 30))'." (* 25 25))) (let ((tm (current-time))) (concat - (if (memq system-type '(ms-dos emx vax-vms)) + (if (or (memq system-type '(ms-dos emx vax-vms)) + ;; message-number-base36 doesn't handle bigints. + (floatp (user-uid))) (let ((user (downcase (user-login-name)))) (while (string-match "[^a-z0-9_]" user) (aset user (match-beginning 0) ?_)) @@ -4857,14 +5201,14 @@ In posting styles use `(\"Expires\" (make-expires-date 30))'." (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 @@ -4959,8 +5303,8 @@ give as trustworthy answer as possible." (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. @@ -5055,7 +5399,7 @@ subscribed address (and not the additional To and Cc header contents)." rhs ace address) (when field (dolist (rhs - (message-remove-duplicates + (mm-delete-duplicates (mapcar (lambda (rhs) (or (cadr (split-string rhs "@")) "")) (mapcar 'downcase (mapcar @@ -5177,7 +5521,8 @@ Headers already prepared in the buffer are not modified." ;; The element is a symbol. We insert the value ;; of this symbol, if any. (symbol-value header)) - ((not (message-check-element header)) + ((not (message-check-element + (intern (downcase (symbol-name header))))) ;; We couldn't generate a value for this header, ;; so we just ask the user. (read-from-minibuffer @@ -5205,7 +5550,7 @@ Headers already prepared in the buffer are not modified." ;; totally and insert the new value. (delete-region (point) (point-at-eol)) ;; If the header is optional, and the header was - ;; empty, we con't insert it anyway. + ;; empty, we can't insert it anyway. (unless optionalp (push header-string message-inserted-headers) (insert value) @@ -5584,7 +5929,7 @@ between beginning of field and beginning of line." (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) @@ -5755,7 +6100,7 @@ OTHER-HEADERS is an alist of header/value pairs." (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. + ;; Find all relevant headers we need. (save-restriction (message-narrow-to-headers-or-head) ;; Gmane renames "To". Look at "Original-To", too, if it is present in @@ -5844,7 +6189,7 @@ want to get rid of this query permanently."))) (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 "") @@ -5890,6 +6235,39 @@ want to get rid of this query permanently."))) (push (cons 'Cc recipients) follow-to))) follow-to)) +(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." @@ -5917,13 +6295,11 @@ want to get rid of this query permanently."))) (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)) @@ -5993,11 +6369,8 @@ If TO-NEWSGROUPS, use that as the new Newsgroups line." (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)) @@ -6101,16 +6474,16 @@ regexp to match all of yours addresses." ;; 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) @@ -6250,7 +6623,9 @@ news, Source is the list of newsgroups is was posted to." (prefix (if group (gnus-group-decoded-name group) - (or (and from (car (gnus-extract-address-components from))) + (or (and from (or + (car (gnus-extract-address-components from)) + (cadr (gnus-extract-address-components from)))) "(nowhere)")))) (concat "[" (if message-forward-decoded-p @@ -6349,13 +6724,20 @@ Optional DIGEST will use digest to forward." (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))))) + (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") @@ -6397,12 +6779,7 @@ Optional DIGEST will use digest to forward." (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 @@ -6431,6 +6808,62 @@ Optional DIGEST will use digest to forward." (message-forward-make-body-digest-mime forward-buffer) (message-forward-make-body-digest-plain forward-buffer))) +(eval-and-compile + (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 @@ -6443,11 +6876,13 @@ Optional DIGEST will use digest to forward." (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))) @@ -6486,6 +6921,7 @@ Optional DIGEST will use digest to forward." (set-buffer (get-buffer-create " *message resend*")) (erase-buffer)) (let ((message-this-is-mail t) + message-generate-hashcash message-setup-hook) (message-setup `((To . ,address)))) ;; Insert our usual headers. @@ -6523,6 +6959,7 @@ Optional DIGEST will use digest to forward." ;; Send it. (let ((message-inhibit-body-encoding t) message-required-mail-headers + message-generate-hashcash rfc2047-encode-encoded-words) (message-send-mail)) (kill-buffer (current-buffer))) @@ -6683,54 +7120,123 @@ which specify the range to operate on." ;; Support for toolbar (eval-when-compile - (defvar tool-bar-map) (defvar tool-bar-mode)) -(defun message-tool-bar-local-item-from-menu (command icon in-map &optional from-map &rest props) - ;; We need to make tool bar entries in local keymaps with - ;; `tool-bar-local-item-from-menu' in Emacs > 21.3 - (if (fboundp 'tool-bar-local-item-from-menu) - ;; This is for Emacs 21.3 - (tool-bar-local-item-from-menu command icon in-map from-map props) - (tool-bar-add-item-from-menu command icon from-map props))) - -(defun message-tool-bar-map () - (or message-tool-bar-map - (setq message-tool-bar-map - (and - (condition-case nil (require 'tool-bar) (error nil)) - (fboundp 'tool-bar-add-item-from-menu) +;; 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 +;; message-mode buffers and force the update. +(defun message-tool-bar-update (&optional symbol value) + "Update message mode toolbar. +Setter function for custom variables." + (setq-default message-tool-bar-map nil) + (when symbol + ;; When used as ":set" function: + (set-default symbol value))) + +(defcustom message-tool-bar (if (eq gmm-tool-bar-style 'gnome) + 'message-tool-bar-gnome + 'message-tool-bar-retro) + "Specifies the message mode tool bar. + +It can be either a list or a symbol refering to a list. See +`gmm-tool-bar-from-list' for the format of the list. The +default key map is `message-mode-map'. + +Pre-defined symbols include `message-tool-bar-gnome' and +`message-tool-bar-retro'." + :type '(repeat gmm-tool-bar-list-item) + :type '(choice (const :tag "GNOME style" message-tool-bar-gnome) + (const :tag "Retro look" message-tool-bar-retro) + (repeat :tag "User defined list" gmm-tool-bar-item) + (symbol)) + :version "23.0" ;; No Gnus + :initialize 'custom-initialize-default + :set 'message-tool-bar-update + :group 'message) + +(defcustom message-tool-bar-gnome + '((ispell-message "spell" nil + :visible (or (not (boundp 'flyspell-mode)) + (not flyspell-mode))) + (flyspell-buffer "spell" t + :visible (and (boundp 'flyspell-mode) + flyspell-mode) + :help "Flyspell whole buffer") + (gmm-ignore "separator") + (message-send-and-exit "mail/send") + (message-dont-send "mail/save-draft") + (message-kill-buffer "close") ;; stock_cancel + (mml-attach-file "attach" mml-mode-map) + (mml-preview "mail/preview" mml-mode-map) + (mml-secure-message-sign-encrypt "lock" mml-mode-map :visible nil) + (message-insert-importance-high "important" nil :visible nil) + (message-insert-importance-low "unimportant" nil :visible nil) + (message-insert-disposition-notification-to "receipt" nil :visible nil) + (gmm-customize-mode "preferences" t :help "Edit mode preferences") + (message-info "help" t :help "Message manual")) + "List of items for the message tool bar (GNOME style). + +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 + :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-kill-buffer "close") + (message-dont-send "cancel") + (mml-attach-file "attach" mml-mode-map) + (ispell-message "spell") + (mml-preview "preview" mml-mode-map) + (message-insert-importance-high "gnus/important") + (message-insert-importance-low "gnus/unimportant") + (message-insert-disposition-notification-to "gnus/receipt")) + "List of items for the message tool bar (retro style). + +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 + :initialize 'custom-initialize-default + :set 'message-tool-bar-update + :group 'message) + +(defcustom message-tool-bar-zap-list + '(new-file open-file dired kill-buffer write-file + print-buffer customize help) + "List of icon items from the global tool bar. +These items are not displayed on the message mode tool bar. + +See `gmm-tool-bar-from-list' for the format of the list." + :type 'gmm-tool-bar-zap-list + :version "23.0" ;; No Gnus + :initialize 'custom-initialize-default + :set 'message-tool-bar-update + :group 'message) + +(defvar image-load-path) + +(defun message-make-tool-bar (&optional force) + "Make a message mode tool bar from `message-tool-bar-list'. +When FORCE, rebuild the tool bar." + (when (and (not (featurep 'xemacs)) + (boundp 'tool-bar-mode) tool-bar-mode - (let ((tool-bar-map (copy-keymap tool-bar-map)) - (load-path (mm-image-load-path))) - ;; Zap some items which aren't so relevant and take - ;; up space. - (dolist (key '(print-buffer kill-buffer save-buffer - write-file dired open-file)) - (define-key tool-bar-map (vector key) nil)) - (message-tool-bar-local-item-from-menu - 'message-send-and-exit "mail_send" tool-bar-map message-mode-map) - (message-tool-bar-local-item-from-menu - 'message-kill-buffer "close" tool-bar-map message-mode-map) - (message-tool-bar-local-item-from-menu - 'message-dont-send "cancel" tool-bar-map message-mode-map) - (message-tool-bar-local-item-from-menu - 'mml-attach-file "attach" tool-bar-map mml-mode-map) - (message-tool-bar-local-item-from-menu - 'ispell-message "spell" tool-bar-map message-mode-map) - (message-tool-bar-local-item-from-menu - 'mml-preview "preview" - tool-bar-map mml-mode-map) - (message-tool-bar-local-item-from-menu - 'message-insert-importance-high "important" - tool-bar-map message-mode-map) - (message-tool-bar-local-item-from-menu - 'message-insert-importance-low "unimportant" - tool-bar-map message-mode-map) - (message-tool-bar-local-item-from-menu - 'message-insert-disposition-notification-to "receipt" - tool-bar-map message-mode-map) - tool-bar-map))))) + (or (not message-tool-bar-map) force)) + (setq message-tool-bar-map + (let* ((load-path + (gmm-image-load-path-for-library "message" + "mail/save-draft.xpm" + nil t)) + (image-load-path (cons (car load-path) + (when (boundp 'image-load-path) + image-load-path)))) + (gmm-tool-bar-from-list message-tool-bar + message-tool-bar-zap-list + 'message-mode-map)))) + message-tool-bar-map) ;;; Group name completion. @@ -6783,6 +7289,17 @@ those headers." (lookup-key global-map "\t") 'indent-relative)))) +(eval-and-compile + (condition-case nil + (with-temp-buffer + (let ((standard-output (current-buffer))) + (eval '(display-completion-list nil ""))) + (defalias 'message-display-completion-list 'display-completion-list)) + (error ;; Don't use `wrong-number-of-arguments' here because of XEmacs. + (defun message-display-completion-list (completions &optional ignore) + "Display the list of completions, COMPLETIONS, using `standard-output'." + (display-completion-list completions))))) + (defun message-expand-group () "Expand the group name under point." (let* ((b (save-excursion @@ -6821,7 +7338,9 @@ those headers." (let ((buffer-read-only nil)) (erase-buffer) (let ((standard-output (current-buffer))) - (display-completion-list (sort completions 'string<))) + (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)))))))))) @@ -7049,6 +7568,39 @@ From headers in the original article." (not result) result))) +(defun message-put-addresses-in-ecomplete () + (dolist (header '("to" "cc" "from" "reply-to")) + (let ((value (message-fetch-field header))) + (dolist (string (mail-header-parse-addresses value 'raw)) + (setq 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 (&optional choose) + "Display the next possible abbrev for the text before point." + (interactive (list t)) + (when (and (member (char-after (point-at-bol)) '(?C ?T ? )) + (message-point-in-header-p) + (save-excursion + (save-restriction + (message-narrow-to-field) + (goto-char (point-min)) + (looking-at "To\\|Cc")))) + (let* ((end (point)) + (start (save-excursion + (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))))) + (when (featurep 'xemacs) (require 'messagexmas) (message-xmas-redefine))