;;; message.el --- composing mail and news messages
-;; Copyright (C) 1996-2014 Free Software Foundation, Inc.
+;; Copyright (C) 1996-2015 Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; Keywords: mail, news
(require 'mml)
(require 'rfc822)
(require 'format-spec)
+(require 'dired)
(autoload 'mailclient-send-it "mailclient") ;; Emacs 22 or contrib/
newsgroup), in brackets followed by the subject
* `message-forward-subject-name-subject' Source of article (name of author
or newsgroup), in brackets followed by the subject
-* `message-forward-subject-fwd' Subject of article with 'Fwd:' prepended
+* `message-forward-subject-fwd' Subject of article with `Fwd:' prepended
to it."
:group 'message-forwarding
:link '(custom-manual "(message)Forwarding")
"If non-nil, delete non-matching headers when forwarding a message.
Only headers that match this regexp will be included. This
variable should be a regexp or a list of regexps."
- :version "24.5"
+ :version "25.1"
:group 'message-forwarding
:type '(repeat :value-to-internal (lambda (widget value)
(custom-split-regexp-maybe value))
(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:
+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\".
+ %F The first name if present, e.g.: \"John\", else fall
+ back to the mail address.
%L The last name if present, e.g.: \"Doe\".
+ %Z, %z The time zone in the numeric form, e.g.:\"+0000\".
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.
+which is called using the date from the article your replying to, but
+the date in the formatted string will be expressed in the author's
+time zone as much as possible.
+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'."
(defcustom message-cite-style nil
"*The overall style to be used when yanking cited text.
-Value is either `nil' (no variable overrides) or a let-style list
+Value is either nil (no variable overrides) or a let-style list
of pairs (VARIABLE VALUE) that will be bound in
`message-yank-original' to do the quoting.
message-cite-style-* variables. This variable is intended for
use in `gnus-posting-styles', such as:
- ((posting-from-work-p) (eval (set (make-local-variable 'message-cite-style) message-cite-style-outlook)))"
+ ((posting-from-work-p) (eval (set (make-local-variable \\='message-cite-style) message-cite-style-outlook)))"
:version "24.1"
:group 'message-insertion
:type '(choice (const :tag "Do not override variables" :value nil)
:type '(radio (const :format "%v " nil)
(string :format "FQDN: %v")))
-(defcustom message-use-idna (and (condition-case nil (require 'idna)
- (file-error))
- (mm-coding-system-p 'utf-8)
- (executable-find idna-program)
- (string= (idna-to-ascii "räksmörgås")
- "xn--rksmrgs-5wao1o")
- t)
+(defcustom message-use-idna
+ (and (or (mm-coding-system-p 'utf-8)
+ (condition-case nil
+ (let (mucs-ignore-version-incompatibilities)
+ (require 'un-define))
+ (error)))
+ (condition-case nil
+ (require 'idna)
+ (file-error)
+ (invalid-operation))
+ idna-program
+ (executable-find idna-program)
+ (string= (idna-to-ascii "räksmörgås") "xn--rksmrgs-5wao1o")
+ t)
"Whether to encode non-ASCII in domain names into ASCII according to IDNA.
GNU Libidn, and in particular the elisp package \"idna.el\" and
the external program \"idn\", must be installed for this
"cat\\|com\\|coop\\|edu\\|gov\\|"
"info\\|int\\|jobs\\|"
"mil\\|mobi\\|museum\\|name\\|net\\|"
- "org\\|pro\\|tel\\|travel\\|uucp\\)")
+ "org\\|pro\\|tel\\|travel\\|uucp\\|"
+ ;; ICANN-era generic top-level domains
+ "academy\\|actor\\|agency\\|airforce\\|archi\\|associates\\|axa\\|"
+ "bar\\|bargains\\|bayern\\|beer\\|berlin\\|best\\|bid\\|bike\\|"
+ "biz\\|black\\|blackfriday\\|blue\\|boutique\\|build\\|builders\\|"
+ "buzz\\|cab\\|camera\\|camp\\|capital\\|cards\\|care\\|career\\|"
+ "careers\\|cash\\|catering\\|center\\|ceo\\|cheap\\|christmas\\|"
+ "church\\|citic\\|cleaning\\|clinic\\|clothing\\|club\\|codes\\|"
+ "coffee\\|college\\|cologne\\|com\\|community\\|company\\|computer\\|"
+ "construction\\|contractors\\|cooking\\|cool\\|country\\|creditcard\\|"
+ "cruises\\|dance\\|dating\\|democrat\\|dental\\|desi\\|design\\|"
+ "diamonds\\|directory\\|discount\\|domains\\|education\\|email\\|"
+ "engineering\\|enterprises\\|equipment\\|estate\\|eus\\|events\\|"
+ "exchange\\|expert\\|exposed\\|fail\\|farm\\|feedback\\|finance\\|"
+ "financial\\|fish\\|fishing\\|fitness\\|flights\\|florist\\|foo\\|"
+ "foundation\\|frogans\\|fund\\|furniture\\|futbol\\|gal\\|"
+ "gallery\\|gift\\|glass\\|globo\\|gmo\\|gop\\|graphics\\|gratis\\|"
+ "gripe\\|guide\\|guitars\\|guru\\|hamburg\\|haus\\|hiphop\\|"
+ "holdings\\|holiday\\|homes\\|horse\\|house\\|immobilien\\|"
+ "industries\\|info\\|ink\\|institute\\|insure\\|international\\|"
+ "investments\\|jetzt\\|juegos\\|kaufen\\|kim\\|kitchen\\|kiwi\\|"
+ "koeln\\|kred\\|land\\|lat\\|latino\\|lease\\|life\\|lighting\\|"
+ "limited\\|limo\\|link\\|loans\\|london\\|luxe\\|luxury\\|"
+ "management\\|mango\\|marketing\\|media\\|meet\\|menu\\|miami\\|"
+ "moda\\|moe\\|monash\\|moscow\\|motorcycles\\|nagoya\\|name\\|"
+ "net\\|neustar\\|ninja\\|nyc\\|okinawa\\|onl\\|org\\|paris\\|"
+ "partners\\|parts\\|photo\\|photography\\|photos\\|pics\\|"
+ "pictures\\|pink\\|plumbing\\|pro\\|productions\\|properties\\|"
+ "pub\\|qpon\\|quebec\\|recipes\\|red\\|reisen\\|ren\\|rentals\\|"
+ "repair\\|report\\|rest\\|reviews\\|rich\\|rocks\\|rodeo\\|"
+ "ruhr\\|ryukyu\\|saarland\\|schule\\|scot\\|services\\|sexy\\|"
+ "shiksha\\|shoes\\|singles\\|social\\|sohu\\|solar\\|solutions\\|"
+ "soy\\|supplies\\|supply\\|support\\|surgery\\|systems\\|tattoo\\|"
+ "tax\\|technology\\|tienda\\|tips\\|today\\|tokyo\\|tools\\|"
+ "town\\|toys\\|trade\\|training\\|university\\|uno\\|vacations\\|"
+ "vegas\\|ventures\\|viajes\\|villas\\|vision\\|vodka\\|vote\\|"
+ "voting\\|voto\\|voyage\\|wang\\|watch\\|webcam\\|wed\\|wien\\|"
+ "wiki\\|works\\|wtc\\|wtf\\|xyz\\|yachts\\|yokohama\\|you\\|"
+ "zone\\)")
;; http://en.wikipedia.org/wiki/List_of_Internet_top-level_domains
;; http://en.wikipedia.org/wiki/GTLD
;; `approved, but not yet in operation': .xxx
;; "dead" nato bitnet uucp
"Regular expression that matches a valid FQDN."
;; see also: gnus-button-valid-fqdn-regexp
- :version "22.1"
+ :version "25.1"
:group 'message-headers
:type 'regexp)
(unless (fboundp 'mail-dont-reply-to)
(defalias 'mail-dont-reply-to 'rmail-dont-reply-to))
+(eval-and-compile
+ (if (featurep 'emacs)
+ (progn
+ (defun message-kill-all-overlays ()
+ (mapcar #'delete-overlay (overlays-in (point-min) (point-max))))
+ (defalias 'message-window-inside-pixel-edges
+ 'window-inside-pixel-edges))
+ (defun message-kill-all-overlays ()
+ (map-extents (lambda (extent ignore) (delete-extent extent))))
+ (defalias 'message-window-inside-pixel-edges 'ignore)))
+
\f
;;;
(unless cs-coding
(setq cs-coding
(mm-read-coding-system
- (format "\
+ (gnus-format-message "\
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
((not (string-match
(concat "^[ \t]*"
(regexp-quote new-subject)
- " \t]*$")
+ "[ \t]*$")
old-subject)) ; yes, it really is a new subject
;; delete eventual Re: prefix
(setq old-subject
(autoload 'ecomplete-setup "ecomplete") ;; for Emacs <23.
+(defvar message-smileys '(":-)" ":)"
+ ":-(" ":("
+ ";-)" ";)")
+ "A list of recognized smiley faces in `message-mode'.")
+
+(defun message--syntax-propertize (beg end)
+ "Syntax-propertize certain message text specially."
+ (let ((citation-regexp (concat "^" message-cite-prefix-regexp ".*$"))
+ (smiley-regexp (regexp-opt message-smileys)))
+ (goto-char beg)
+ (while (search-forward-regexp citation-regexp
+ end 'noerror)
+ (let ((start (match-beginning 0))
+ (end (match-end 0)))
+ (add-text-properties start (1+ start)
+ `(syntax-table ,(string-to-syntax "<")))
+ (add-text-properties end (min (1+ end) (point-max))
+ `(syntax-table ,(string-to-syntax ">")))))
+ (goto-char beg)
+ (while (search-forward-regexp smiley-regexp
+ end 'noerror)
+ (add-text-properties (match-beginning 0) (match-end 0)
+ `(syntax-table ,(string-to-syntax "."))))))
+
;;;###autoload
(define-derived-mode message-mode text-mode "Message"
"Major mode for editing mail and news to be sent.
;; multibyte is not necessary at all. -- zsh
(mm-enable-multibyte))
(set (make-local-variable 'indent-tabs-mode) nil) ;No tabs for indentation.
- (mml-mode))
+ (mml-mode)
+ ;; Syntactic fontification. Helps `show-paren-mode',
+ ;; `electric-pair-mode', and C-M-* navigation by syntactically
+ ;; excluding citations and other artifacts.
+ ;;
+ (set (make-local-variable 'syntax-propertize-function) 'message--syntax-propertize)
+ (set (make-local-variable 'parse-sexp-ignore-comments) t))
(defun message-setup-fill-variables ()
"Setup message fill variables."
(goto-char (point-max))
;; Insert the signature.
(unless (bolp)
- (insert "\n"))
+ (newline))
(when message-signature-insert-empty-line
- (insert "\n"))
- (insert "-- \n")
+ (newline))
+ (insert "-- ")
+ (newline)
(if (eq signature t)
(insert-file-contents signature-file)
(insert signature))
(goto-char (point-max))
- (or (bolp) (insert "\n")))))
+ (or (bolp) (newline)))))
(defun message-insert-importance-high ()
"Insert header to mark message as important."
"Cite function in the standard Message manner."
(message-cite-original-1 nil))
-(defvar gnus-extract-address-components)
-
(autoload 'format-spec "format-spec")
+(autoload 'gnus-date-get-time "gnus-util")
-(defun message-insert-formatted-citation-line (&optional from date)
+(defun message-insert-formatted-citation-line (&optional from date tz)
"Function that inserts a formatted citation line.
+The optional FROM, and DATE are strings containing the contents of
+the From header and the Date header respectively. The optional TZ
+is a number of seconds, overrides the time zone of DATE.
See `message-citation-line-format'."
;; The optional args are for testing/debugging. They will disappear later.
;; (with-temp-buffer
;; (message-insert-formatted-citation-line
;; "John Doe <john.doe@example.invalid>"
- ;; (current-time))
+ ;; (message-make-date))
;; (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)
+ (funcall (if (boundp 'gnus-extract-address-components)
gnus-extract-address-components
'mail-extract-address-components)
from)
(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)))))
+ (time
+ (when (string-match "%[^fnNFL]" message-citation-line-format)
+ (cond ((numberp (car-safe date)) date) ;; backward compatibility
+ (date (gnus-date-get-time date))
+ (t
+ (gnus-date-get-time
+ (setq date (mail-header-date message-reply-headers)))))))
+ (tz (or tz
+ (when (stringp date)
+ (nth 8 (parse-time-string date)))))
(flist
(let ((i ?A) lst)
(when (stringp name)
;; Guess first name and last name:
- (let* ((names (delq nil (mapcar (lambda (x)
- (if (string-match "\\`\\(\\w\\|[-.]\\)+\\'" x) x nil))
- (split-string name "[ \t]+"))))
- (count (length names)))
- (cond ((= count 1) (setq fname (car names)
- lname ""))
- ((or (= count 2) (= count 3)) (setq fname (car names)
- lname (mapconcat 'identity (cdr names) " ")))
- ((> count 3) (setq fname (mapconcat 'identity (butlast names (- count 2)) " ")
- lname (mapconcat 'identity (nthcdr 2 names) " "))) )
+ (let* ((names (delq
+ nil
+ (mapcar
+ (lambda (x)
+ (if (string-match "\\`\\(\\w\\|[-.]\\)+\\'"
+ x)
+ x
+ nil))
+ (split-string name "[ \t]+"))))
+ (count (length names)))
+ (cond ((= count 1)
+ (setq fname (car names)
+ lname ""))
+ ((or (= count 2) (= count 3))
+ (setq fname (car names)
+ lname (mapconcat 'identity (cdr names) " ")))
+ ((> count 3)
+ (setq fname (mapconcat 'identity
+ (butlast names (- count 2))
+ " ")
+ lname (mapconcat 'identity
+ (nthcdr 2 names)
+ " "))))
(when (string-match "\\(.*\\),\\'" fname)
(let ((newlname (match-string 1 fname)))
(setq fname lname lname newlname)))))
;; The following letters are not used in `format-time-string':
(push ?E lst) (push "<E>" lst)
- (push ?F lst) (push fname lst)
+ (push ?F lst) (push (or fname name-or-net) lst)
;; We might want to use "" instead of "<X>" later.
(push ?J lst) (push "<J>" lst)
(push ?K lst) (push "<K>" lst)
(>= i ?a)))
(push i lst)
(push (condition-case nil
- (format-time-string (format "%%%c" i) replydate)
+ (gmm-format-time-string (format "%%%c" i) time tz)
(error (format ">%c<" i)))
lst))
(setq i (1+ i)))
to (cdar regions)
regions (cdr regions))
(put-text-property from to 'invisible nil)
- (message-overlay-put (message-make-overlay from to)
- 'face 'highlight))
+ (overlay-put (make-overlay from to) 'face 'highlight))
(unless (yes-or-no-p
"Invisible text found and made visible; continue sending? ")
(error "Invisible text found and made visible")))))
control-1))
(not (get-text-property
(point) 'untranslated-utf-8))))
- (message-overlay-put (message-make-overlay (point) (1+ (point)))
- 'face 'highlight)
+ (overlay-put (make-overlay (point) (1+ (point))) 'face 'highlight)
(setq found t))
(forward-char))
(when found
(dolist (bog (message-bogus-recipient-p addr))
(and bog
(not (y-or-n-p
- (format
+ (gnus-format-message
"Address `%s'%s might be bogus. Continue? "
bog
;; If the encoded version of the email address
It is useful if your ISP requires the POP-before-SMTP
authentication. See the Gnus manual for details."
(run-hooks 'message-send-mail-hook)
+ ;; Change header-delimiter to be what smtpmail expects.
+ (goto-char (point-min))
+ (when (re-search-forward
+ (concat "^" (regexp-quote mail-header-separator) "\n"))
+ (replace-match "\n"))
(smtpmail-send-it))
(defun message-send-mail-with-mailclient ()
"Make date string for the Expires header. Expiry in DAYS days.
In posting styles use `(\"Expires\" (make-expires-date 30))'."
- (let* ((cur (decode-time (current-time)))
+ (let* ((cur (decode-time))
(nday (+ days (nth 3 cur))))
(setf (nth 3 cur) nday)
(message-make-date (apply 'encode-time cur))))
(defun message-make-fqdn ()
"Return user's fully qualified domain name."
- (let* ((system-name (system-name))
+ (let* ((sysname (system-name))
(user-mail (message-user-mail-address))
(user-domain
(if (and user-mail
(not (string-match message-bogus-system-names message-user-fqdn)))
;; `message-user-fqdn' seems to be valid
message-user-fqdn)
- ((and (string-match message-valid-fqdn-regexp system-name)
- (not (string-match message-bogus-system-names system-name)))
+ ((and (string-match message-valid-fqdn-regexp sysname)
+ (not (string-match message-bogus-system-names sysname)))
;; `system-name' returned the right result.
- system-name)
+ sysname)
;; Try `mail-host-address'.
((and (boundp 'mail-host-address)
(stringp mail-host-address)
user-domain)
;; Default to this bogus thing.
(t
- (concat system-name
+ (concat sysname
".i-did-not-set--mail-host-address--so-tickle-me")))))
(defun message-make-domain ()
(defun message-is-yours-p ()
"Non-nil means current article is yours.
-If you have added 'cancel-messages to `message-shoot-gnksa-feet', all articles
+If you have added `cancel-messages' to `message-shoot-gnksa-feet', all articles
are yours except those that have Cancel-Lock header not belonging to you.
Instead of shooting GNKSA feet, you should modify `message-alternative-emails'
regexp to match all of yours addresses."
(goto-char (prog1 (mark t)
(set-marker (mark-marker) (point)))))
-(defalias 'message-make-overlay 'make-overlay)
-(defalias 'message-delete-overlay 'delete-overlay)
-(defalias 'message-overlay-put 'overlay-put)
-(defun message-kill-all-overlays ()
- (if (featurep 'xemacs)
- (map-extents (lambda (extent ignore) (delete-extent extent)))
- (mapcar #'delete-overlay (overlays-in (point-min) (point-max)))))
-
;; Support for toolbar
(defvar tool-bar-mode)
(defun message-tab ()
"Complete names according to `message-completion-alist'.
-Execute function specified by `message-tab-body-function' when not in
-those headers."
+Execute function specified by `message-tab-body-function' when
+not in those headers. If that variable is nil, indent with the
+regular text mode tabbing command."
(interactive)
(cond
((if (and (boundp 'completion-fail-discreetly)
(defun message-flatten-list (list)
"Return a new, flat list that contains all elements of LIST.
-\(message-flatten-list '(1 (2 3 (4 5 (6))) 7))
+\(message-flatten-list \\='(1 (2 3 (4 5 (6))) 7))
=> (1 2 3 4 5 6 7)"
(cond ((consp list)
(apply 'append (mapcar 'message-flatten-list list)))
(list message-hidden-headers)
message-hidden-headers))
(inhibit-point-motion-hooks t)
- (after-change-functions nil)
+ (inhibit-modification-hooks t)
(end-of-headers (point-min)))
(when regexps
(save-excursion
(when force
(sit-for message-send-form-letter-delay))
(if (or force
- (y-or-n-p (format "Send message to `%s'? " to)))
+ (y-or-n-p (gnus-format-message "Send message to `%s'? " to)))
(progn
(setq sent (1+ sent))
(message-send-and-exit))
- (message (format "Message to `%s' skipped." to))
+ (message "Message to `%s' skipped." to)
(setq skipped (1+ skipped)))
(when (buffer-live-p buff)
(kill-buffer buff))))
(message-fetch-field hdr) t))
", "))))
+;;; multipart/related and HTML support.
+
+(defun message-make-html-message-with-image-files (files)
+ "Make a message containing the current dired-marked image files."
+ (interactive (list (dired-get-marked-files nil current-prefix-arg)))
+ (message-mail)
+ (message-goto-body)
+ (insert "<#part type=text/html>\n\n")
+ (dolist (file files)
+ (insert (format "<img src=%S>\n\n" file)))
+ (message-toggle-image-thumbnails)
+ (message-goto-to))
+
+(defun message-toggle-image-thumbnails ()
+ "For any included image files, insert a thumbnail of that image."
+ (interactive)
+ (let ((overlays (overlays-in (point-min) (point-max)))
+ (displayed nil))
+ (while overlays
+ (let ((overlay (car overlays)))
+ (when (overlay-get overlay 'put-image)
+ (delete-overlay overlay)
+ (setq displayed t)))
+ (setq overlays (cdr overlays)))
+ (unless displayed
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward "<img.*src=\"\\([^\"]+\\)" nil t)
+ (let ((file (match-string 1))
+ (edges (message-window-inside-pixel-edges
+ (get-buffer-window (current-buffer)))))
+ (put-image
+ (create-image
+ file 'imagemagick nil
+ :max-width (truncate
+ (* 0.7 (- (nth 2 edges) (nth 0 edges))))
+ :max-height (truncate
+ (* 0.5 (- (nth 3 edges) (nth 1 edges)))))
+ (match-beginning 0)
+ " ")))))))
+
(when (featurep 'xemacs)
(require 'messagexmas)
(message-xmas-redefine))