Merge from trunk.
[emacs.git] / lisp / mh-e / mh-comp.el
blob9c3d83eda2556dea540b50b95a23dd1909144cff
1 ;;; mh-comp.el --- MH-E functions for composing and sending messages
3 ;; Copyright (C) 1993, 1995, 1997,
4 ;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
5 ;; Free Software Foundation, Inc.
7 ;; Author: Bill Wohler <wohler@newt.com>
8 ;; Maintainer: Bill Wohler <wohler@newt.com>
9 ;; Keywords: mail
10 ;; See: mh-e.el
12 ;; This file is part of GNU Emacs.
14 ;; GNU Emacs is free software: you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation, either version 3 of the License, or
17 ;; (at your option) any later version.
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
27 ;;; Commentary:
29 ;; This file includes the functions in the MH-Folder maps that get us
30 ;; into MH-Letter mode, as well the functions in the MH-Letter mode
31 ;; that are used to send the mail. Other that those, functions that
32 ;; are needed in mh-letter.el should be found there.
34 ;;; Change Log:
36 ;;; Code:
38 (require 'mh-e)
39 (require 'mh-gnus) ;needed because mh-gnus.el not compiled
40 (require 'mh-scan)
42 (require 'sendmail)
44 (autoload 'easy-menu-add "easymenu")
45 (autoload 'mml-insert-tag "mml")
49 ;;; Site Customization
51 (defvar mh-send-prog "send"
52 "Name of the MH send program.
53 Some sites need to change this because of a name conflict.")
55 (defvar mh-send-uses-spost-flag nil
56 "Non-nil means \"send\" uses \"spost\" to submit messages.
58 If the value of \"postproc:\" is \"spost\", you may need to set
59 this variable to t to tell MH-E to avoid using features of
60 \"post\" that are not supported by \"spost\". You'll know that
61 you'll need to do this if sending mail fails with an error of
62 \"spost: -msgid unknown\".")
64 (defvar mh-redist-background nil
65 "If non-nil redist will be done in background like send.
66 This allows transaction log to be visible if -watch, -verbose or
67 -snoop are used.")
71 ;;; Variables
73 (defvar mh-comp-formfile "components"
74 "Name of file to be used as a skeleton for composing messages.
76 Default is \"components\".
78 If not an absolute file name, the file is searched for first in the
79 user's MH directory, then in the system MH lib directory.")
81 (defvar mh-repl-formfile "replcomps"
82 "Name of file to be used as a skeleton for replying to messages.
84 Default is \"replcomps\".
86 If not an absolute file name, the file is searched for first in the
87 user's MH directory, then in the system MH lib directory.")
89 (defvar mh-repl-group-formfile "replgroupcomps"
90 "Name of file to be used as a skeleton for replying to messages.
92 Default is \"replgroupcomps\".
94 This file is used to form replies to the sender and all recipients of
95 a message. Only used if `(mh-variant-p 'nmh)' is non-nil.
96 If not an absolute file name, the file is searched for first in the
97 user's MH directory, then in the system MH lib directory.")
99 (defvar mh-rejected-letter-start
100 (format "^%s$"
101 (regexp-opt
102 '("Content-Type: message/rfc822" ;MIME MDN
103 "------ This is a copy of the message, including all the headers. ------";from exim
104 "--- Below this line is a copy of the message."; from qmail
105 " ----- Unsent message follows -----" ;from sendmail V5
106 " --------Unsent Message below:" ; from sendmail at BU
107 " ----- Original message follows -----" ;from sendmail V8
108 "------- Unsent Draft" ;from MH itself
109 "---------- Original Message ----------" ;from zmailer
110 " --- The unsent message follows ---" ;from AIX mail system
111 " Your message follows:" ;from MMDF-II
112 "Content-Description: Returned Content" ;1993 KJ sendmail
113 ))))
115 (defvar mh-new-draft-cleaned-headers
116 "^Date:\\|^Received:\\|^Message-Id:\\|^From:\\|^Sender:\\|^Errors-To:\\|^Delivery-Date:\\|^Return-Path:"
117 "Regexp of header lines to remove before offering a message as a new draft\\<mh-folder-mode-map>.
118 Used by the \\[mh-edit-again] and \\[mh-extract-rejected-mail] commands.")
120 (defvar mh-letter-mode-syntax-table
121 (let ((syntax-table (make-syntax-table text-mode-syntax-table)))
122 (modify-syntax-entry ?% "." syntax-table)
123 syntax-table)
124 "Syntax table used by MH-E while in MH-Letter mode.")
126 (defvar mh-send-args ""
127 "Extra args to pass to \"send\" command.")
129 (defvar mh-annotate-char nil
130 "Character to use to annotate `mh-sent-from-msg'.")
132 (defvar mh-annotate-field nil
133 "Field name for message annotation.")
135 (defvar mh-annotate-list nil
136 "Messages annotated, either a sequence name or a list of message numbers.
137 This variable can be used by `mh-annotate-msg-hook'.")
139 (defvar mh-insert-auto-fields-done-local nil
140 "Buffer-local variable set when `mh-insert-auto-fields' called successfully.")
141 (make-variable-buffer-local 'mh-insert-auto-fields-done-local)
145 ;;; MH-E Entry Points
147 ;;;###autoload
148 (defun mh-smail ()
149 "Compose a message with the MH mail system.
150 See `mh-send' for more details on composing mail."
151 (interactive)
152 (mh-find-path)
153 (call-interactively 'mh-send))
155 ;;;###autoload
156 (defun mh-smail-other-window ()
157 "Compose a message with the MH mail system in other window.
158 See `mh-send' for more details on composing mail."
159 (interactive)
160 (mh-find-path)
161 (call-interactively 'mh-send-other-window))
163 (defun mh-send-other-window (to cc subject)
164 "Compose a message in another window.
166 See `mh-send' for more information and a description of how the
167 TO, CC, and SUBJECT arguments are used."
168 (interactive (list
169 (mh-interactive-read-address "To: ")
170 (mh-interactive-read-address "Cc: ")
171 (mh-interactive-read-string "Subject: ")))
172 (mh-send-sub to cc subject (current-window-configuration) t))
174 (defvar mh-error-if-no-draft nil) ;raise error over using old draft
176 ;;;###autoload
177 (defun mh-smail-batch (&optional to subject other-headers &rest ignored)
178 "Compose a message with the MH mail system.
180 This function does not prompt the user for any header fields, and
181 thus is suitable for use by programs that want to create a mail
182 buffer. Users should use \\[mh-smail] to compose mail.
184 Optional arguments for setting certain fields include TO,
185 SUBJECT, and OTHER-HEADERS. Additional arguments are IGNORED.
187 This function remains for Emacs 21 compatibility. New
188 applications should use `mh-user-agent-compose'."
189 (mh-find-path)
190 (let ((mh-error-if-no-draft t))
191 (mh-send (or to "") "" (or subject ""))))
193 ;;;###autoload
194 (define-mail-user-agent 'mh-e-user-agent
195 'mh-user-agent-compose 'mh-send-letter 'mh-fully-kill-draft
196 'mh-before-send-letter-hook)
198 ;;;###autoload
199 (defun mh-user-agent-compose (&optional to subject other-headers continue
200 switch-function yank-action
201 send-actions)
202 "Set up mail composition draft with the MH mail system.
203 This is the `mail-user-agent' entry point to MH-E. This function
204 conforms to the contract specified by `define-mail-user-agent'
205 which means that this function should accept the same arguments
206 as `compose-mail'.
208 The optional arguments TO and SUBJECT specify recipients and the
209 initial Subject field, respectively.
211 OTHER-HEADERS is an alist specifying additional header fields.
212 Elements look like (HEADER . VALUE) where both HEADER and VALUE
213 are strings.
215 CONTINUE, SWITCH-FUNCTION, YANK-ACTION and SEND-ACTIONS are
216 ignored."
217 (mh-find-path)
218 (let ((mh-error-if-no-draft t))
219 (mh-send to "" subject)
220 (while other-headers
221 (mh-insert-fields (concat (car (car other-headers)) ":")
222 (cdr (car other-headers)))
223 (setq other-headers (cdr other-headers)))))
225 ;; Shush compiler.
226 (defvar sendmail-coding-system) ; XEmacs
228 ;;;###autoload
229 (defun mh-send-letter (&optional arg)
230 "Save draft and send message.
232 When you are all through editing a message, you send it with this
233 command. You can give a prefix argument ARG to monitor the first stage
234 of the delivery\; this output can be found in a buffer called \"*MH-E
235 Mail Delivery*\".
237 The hook `mh-before-send-letter-hook' is run at the beginning of
238 this command. For example, if you want to check your spelling in
239 your message before sending, add the function `ispell-message'.
241 Unless `mh-insert-auto-fields' had previously been called
242 manually, the function `mh-insert-auto-fields' is called to
243 insert fields based upon the recipients. If fields are added, you
244 are given a chance to see and to confirm these fields before the
245 message is actually sent. You can do away with this confirmation
246 by turning off the option `mh-auto-fields-prompt-flag'.
248 In case the MH \"send\" program is installed under a different name,
249 use `mh-send-prog' to tell MH-E the name.
251 The hook `mh-annotate-msg-hook' is run after annotating the
252 message and scan line."
253 (interactive "P")
254 (run-hooks 'mh-before-send-letter-hook)
255 (if (and (mh-insert-auto-fields t)
256 mh-auto-fields-prompt-flag
257 (goto-char (point-min)))
258 (if (not (y-or-n-p "Auto fields inserted, send? "))
259 (error "Send aborted")))
260 (cond ((mh-mh-directive-present-p)
261 (mh-mh-to-mime))
262 ((or (mh-mml-tag-present-p) (not (mh-ascii-buffer-p)))
263 (mh-mml-to-mime)))
264 (save-buffer)
265 (message "Sending...")
266 (let ((draft-buffer (current-buffer))
267 (file-name buffer-file-name)
268 (config mh-previous-window-config)
269 (coding-system-for-write
270 (if (and (local-variable-p 'buffer-file-coding-system
271 (current-buffer)) ;XEmacs needs two args
272 ;; We're not sure why, but buffer-file-coding-system
273 ;; tends to get set to undecided-unix.
274 (not (memq buffer-file-coding-system
275 '(undecided undecided-unix undecided-dos))))
276 buffer-file-coding-system
277 (or (and (boundp 'sendmail-coding-system) sendmail-coding-system)
278 (and (default-boundp 'buffer-file-coding-system)
279 (default-value 'buffer-file-coding-system))
280 'iso-latin-1))))
281 ;; Older versions of spost do not support -msgid and -mime.
282 (unless mh-send-uses-spost-flag
283 ;; Adding a Message-ID field looks good, makes it easier to search for
284 ;; message in your +outbox, and best of all doesn't break threading for
285 ;; the recipient if you reply to a message in your +outbox.
286 (setq mh-send-args (concat "-msgid " mh-send-args))
287 ;; The default BCC encapsulation will make a MIME message unreadable.
288 ;; With nmh use the -mime arg to prevent this.
289 (if (and (mh-variant-p 'nmh)
290 (mh-goto-header-field "Bcc:")
291 (mh-goto-header-field "Content-Type:"))
292 (setq mh-send-args (concat "-mime " mh-send-args))))
293 (cond (arg
294 (pop-to-buffer mh-mail-delivery-buffer)
295 (erase-buffer)
296 (mh-exec-cmd-output mh-send-prog t
297 "-nodraftfolder" "-watch" "-nopush"
298 (split-string mh-send-args) file-name)
299 (goto-char (point-max)) ; show the interesting part
300 (recenter -1)
301 (set-buffer draft-buffer)) ; for annotation below
303 (mh-exec-cmd-daemon mh-send-prog nil
304 "-nodraftfolder" "-noverbose"
305 (split-string mh-send-args) file-name)))
306 (if mh-annotate-char
307 (mh-annotate-msg mh-sent-from-msg
308 mh-sent-from-folder
309 mh-annotate-char
310 "-component" mh-annotate-field
311 "-text" (format "\"%s %s\""
312 (mh-get-header-field "To:")
313 (mh-get-header-field "Cc:"))))
315 (cond ((or (not arg)
316 (y-or-n-p "Kill draft buffer? "))
317 (kill-buffer draft-buffer)
318 (if config
319 (set-window-configuration config))))
320 (if arg
321 (message "Sending...done")
322 (message "Sending...backgrounded"))))
324 ;;;###autoload
325 (defun mh-fully-kill-draft ()
326 "Quit editing and delete draft message.
328 If for some reason you are not happy with the draft, you can use
329 this command to kill the draft buffer and delete the draft
330 message. Use the command \\[kill-buffer] if you don't want to
331 delete the draft message."
332 (interactive)
333 (if (y-or-n-p "Kill draft message? ")
334 (let ((config mh-previous-window-config))
335 (if (file-exists-p buffer-file-name)
336 (delete-file buffer-file-name))
337 (set-buffer-modified-p nil)
338 (kill-buffer (buffer-name))
339 (message "")
340 (if config
341 (set-window-configuration config)))
342 (error "Message not killed")))
346 ;;; MH-Folder Commands
348 ;; Alphabetical.
350 ;;;###mh-autoload
351 (defun mh-edit-again (message)
352 "Edit a MESSAGE to send it again.
354 If you don't complete a draft for one reason or another, and if
355 the draft buffer is no longer available, you can pick your draft
356 up again with this command. If you don't use a draft folder, your
357 last \"draft\" file will be used. If you use draft folders,
358 you'll need to visit the draft folder with \"\\[mh-visit-folder]
359 drafts <RET>\", use \\[mh-next-undeleted-msg] to move to the
360 appropriate message, and then use \\[mh-edit-again] to prepare
361 the message for editing.
363 This command can also be used to take messages that were sent to
364 you and to send them to more people.
366 Don't use this command to re-edit a message from a Mailer-Daemon
367 who complained that your mail wasn't posted for some reason or
368 another (see `mh-extract-rejected-mail').
370 The default message is the current message.
372 See also `mh-send'."
373 (interactive (list (mh-get-msg-num t)))
374 (let* ((from-folder mh-current-folder)
375 (config (current-window-configuration))
376 (draft
377 (cond ((and mh-draft-folder (equal from-folder mh-draft-folder))
378 (pop-to-buffer (find-file-noselect (mh-msg-filename message))
380 (rename-buffer (format "draft-%d" message))
381 ;; Make buffer writable...
382 (setq buffer-read-only nil)
383 ;; If buffer was being used to display the message reinsert
384 ;; from file...
385 (when (eq major-mode 'mh-show-mode)
386 (erase-buffer)
387 (insert-file-contents buffer-file-name))
388 (buffer-name))
390 (mh-read-draft "clean-up" (mh-msg-filename message) nil)))))
391 (mh-clean-msg-header (point-min) mh-new-draft-cleaned-headers nil)
392 (mh-insert-header-separator)
393 (goto-char (point-min))
394 (save-buffer)
395 (mh-compose-and-send-mail draft "" from-folder nil nil nil nil nil nil
396 config)
397 (mh-letter-mode-message)
398 (mh-letter-adjust-point)))
400 ;;;###mh-autoload
401 (defun mh-extract-rejected-mail (message)
402 "Edit a MESSAGE that was returned by the mail system.
404 This command prepares the message for editing by removing the
405 Mailer-Daemon envelope and unneeded header fields. Fix whatever
406 addressing problem you had, and send the message again with
407 \\[mh-send-letter].
409 The default message is the current message.
411 See also `mh-send'."
412 (interactive (list (mh-get-msg-num t)))
413 (let ((from-folder mh-current-folder)
414 (config (current-window-configuration))
415 (draft (mh-read-draft "extraction" (mh-msg-filename message) nil)))
416 (goto-char (point-min))
417 (cond ((re-search-forward mh-rejected-letter-start nil t)
418 (skip-chars-forward " \t\n")
419 (delete-region (point-min) (point))
420 (mh-clean-msg-header (point-min) mh-new-draft-cleaned-headers nil))
422 (message "Does not appear to be a rejected letter")))
423 (mh-insert-header-separator)
424 (goto-char (point-min))
425 (save-buffer)
426 (mh-compose-and-send-mail draft "" from-folder message
427 (mh-get-header-field "To:")
428 (mh-get-header-field "From:")
429 (mh-get-header-field "Cc:")
430 nil nil config)
431 (mh-letter-mode-message)))
433 ;;;###mh-autoload
434 (defun mh-forward (to cc &optional range)
435 "Forward message.
437 You are prompted for the TO and CC recipients. You are given a
438 draft to edit that looks like it would if you had run the MH
439 command \"forw\". You can then add some text.
441 You can forward several messages by using a RANGE. All of the
442 messages in the range are inserted into your draft. Check the
443 documentation of `mh-interactive-range' to see how RANGE is read
444 in interactive use.
446 The hook `mh-forward-hook' is called on the draft.
448 See also `mh-compose-forward-as-mime-flag',
449 `mh-forward-subject-format', and `mh-send'."
450 (interactive (list (mh-interactive-read-address "To: ")
451 (mh-interactive-read-address "Cc: ")
452 (mh-interactive-range "Forward")))
453 (let* ((folder mh-current-folder)
454 (msgs (mh-range-to-msg-list range))
455 (config (current-window-configuration))
456 (fwd-msg-file (mh-msg-filename (car msgs) folder))
457 ;; forw always leaves file in "draft" since it doesn't have -draft
458 (draft-name (expand-file-name "draft" mh-user-path))
459 (draft (cond ((or (not (file-exists-p draft-name))
460 (y-or-n-p "The file draft exists; discard it? "))
461 (mh-exec-cmd "forw" "-build"
462 (if (and (mh-variant-p 'nmh)
463 mh-compose-forward-as-mime-flag)
464 "-mime")
465 mh-current-folder
466 (mh-coalesce-msg-list msgs))
467 (prog1
468 (mh-read-draft "" draft-name t)
469 (mh-insert-fields "To:" to "Cc:" cc)
470 (save-buffer)))
472 (mh-read-draft "" draft-name nil)))))
473 (let (orig-from
474 orig-subject)
475 (with-current-buffer (get-buffer-create mh-temp-buffer)
476 (erase-buffer)
477 (insert-file-contents fwd-msg-file)
478 (setq orig-from (mh-get-header-field "From:"))
479 (setq orig-subject (mh-get-header-field "Subject:")))
480 (let ((forw-subject
481 (mh-forwarded-letter-subject orig-from orig-subject)))
482 (mh-insert-fields "Subject:" forw-subject)
483 (goto-char (point-min))
484 ;; If using MML, translate MH-style directive
485 (if (equal mh-compose-insertion 'mml)
486 (save-excursion
487 (goto-char (mh-mail-header-end))
488 (while
489 (re-search-forward
490 "^#forw \\[\\([^]]+\\)\\] \\(+\\S-+\\) \\(.*\\)$"
491 (point-max) t)
492 (let ((description (if (equal (match-string 1)
493 "forwarded messages")
494 "forwarded message %d"
495 (match-string 1)))
496 (msgs (split-string (match-string 3)))
497 (i 0))
498 (beginning-of-line)
499 (delete-region (point) (progn (forward-line 1) (point)))
500 (dolist (msg msgs)
501 (setq i (1+ i))
502 (mh-mml-forward-message (format description i)
503 folder msg)
504 ;; Was inserted before us, move to end of file to preserve order
505 (goto-char (point-max)))))))
506 ;; Postition just before forwarded message
507 (if (re-search-forward "^------- Forwarded Message" nil t)
508 (forward-line -1)
509 (goto-char (mh-mail-header-end))
510 (forward-line 1))
511 (delete-other-windows)
512 (mh-add-msgs-to-seq msgs 'forwarded t)
513 (mh-compose-and-send-mail draft "" folder msgs
514 to forw-subject cc
515 mh-note-forw "Forwarded:"
516 config)
517 (mh-letter-mode-message)
518 (mh-letter-adjust-point)
519 (run-hooks 'mh-forward-hook)))))
521 (defun mh-forwarded-letter-subject (from subject)
522 "Return a Subject suitable for a forwarded message.
523 Original message has headers FROM and SUBJECT."
524 (let ((addr-start (string-match "<" from))
525 (comment (string-match "(" from)))
526 (cond ((and addr-start (> addr-start 0))
527 ;; Full Name <luser@host>
528 (setq from (substring from 0 (1- addr-start))))
529 (comment
530 ;; luser@host (Full Name)
531 (setq from (substring from (1+ comment) (1- (length from)))))))
532 (format mh-forward-subject-format from subject))
534 ;;;###mh-autoload
535 (defun mh-redistribute (to cc &optional message)
536 "Redistribute a message.
538 This command is similar in function to forwarding mail, but it
539 does not allow you to edit the message, nor does it add your name
540 to the \"From\" header field. It appears to the recipient as if
541 the message had come from the original sender. When you run this
542 command, you are prompted for the TO and CC recipients. The
543 default MESSAGE is the current message.
545 Also investigate the command \\[mh-edit-again] for another way to
546 redistribute messages.
548 See also `mh-redist-full-contents-flag'.
550 The hook `mh-annotate-msg-hook' is run after annotating the
551 message and scan line."
552 (interactive (list (mh-read-address "Redist-To: ")
553 (mh-read-address "Redist-Cc: ")
554 (mh-get-msg-num t)))
555 (or message
556 (setq message (mh-get-msg-num t)))
557 (save-window-excursion
558 (let ((folder mh-current-folder)
559 (draft (mh-read-draft "redistribution"
560 (if mh-redist-full-contents-flag
561 (mh-msg-filename message)
562 nil)
563 nil)))
564 (mh-goto-header-end 0)
565 (insert "Resent-To: " to "\n")
566 (if (not (equal cc "")) (insert "Resent-cc: " cc "\n"))
567 (mh-clean-msg-header
568 (point-min)
569 "^Message-Id:\\|^Received:\\|^Return-Path:\\|^Sender:\\|^Date:\\|^From:"
570 nil)
571 (save-buffer)
572 (message "Redistributing...")
573 (let ((env "mhdist=1"))
574 ;; Setup environment...
575 (setq env (concat env " mhaltmsg="
576 (if mh-redist-full-contents-flag
577 buffer-file-name
578 (mh-msg-filename message folder))))
579 (unless mh-redist-full-contents-flag
580 (setq env (concat env " mhannotate=1")))
581 ;; Redistribute...
582 (if mh-redist-background
583 (mh-exec-cmd-env-daemon env mh-send-prog nil buffer-file-name)
584 (mh-exec-cmd-error env mh-send-prog "-push" buffer-file-name))
585 ;; Annotate...
586 (mh-annotate-msg message folder mh-note-dist
587 "-component" "Resent:"
588 "-text" (format "\"%s %s\"" to cc)))
589 (kill-buffer draft)
590 (message "Redistributing...done"))))
592 ;;;###mh-autoload
593 (defun mh-reply (message &optional reply-to includep)
594 "Reply to a MESSAGE.
596 When you reply to a message, you are first prompted with \"Reply
597 to whom?\" (unless the optional argument REPLY-TO is provided).
598 You have several choices here.
600 Response Reply Goes To
602 from The person who sent the message. This is the
603 default, so <RET> is sufficient.
605 to Replies to the sender, plus all recipients in the
606 \"To:\" header field.
608 all cc Forms a reply to the addresses in the
609 \"Mail-Followup-To:\" header field if one
610 exists; otherwise forms a reply to the sender,
611 plus all recipients.
613 Depending on your answer, \"repl\" is given a different argument
614 to form your reply. Specifically, a choice of \"from\" or none at
615 all runs \"repl -nocc all\", and a choice of \"to\" runs \"repl
616 -cc to\". Finally, either \"cc\" or \"all\" runs \"repl -cc all
617 -nocc me\".
619 Two windows are then created. One window contains the message to
620 which you are replying in an MH-Show buffer. Your draft, in
621 MH-Letter mode (*note `mh-letter-mode'), is in the other window.
622 If the reply draft was not one that you expected, check the
623 things that affect the behavior of \"repl\" which include the
624 \"repl:\" profile component and the \"replcomps\" and
625 \"replgroupcomps\" files.
627 If you supply a prefix argument INCLUDEP, the message you are
628 replying to is inserted in your reply after having first been run
629 through \"mhl\" with the format file \"mhl.reply\".
631 Alternatively, you can customize the option `mh-yank-behavior'
632 and choose one of its \"Automatically\" variants to do the same
633 thing. If you do so, the prefix argument has no effect.
635 Another way to include the message automatically in your draft is
636 to use \"repl: -filter repl.filter\" in your MH profile.
638 If you wish to customize the header or other parts of the reply
639 draft, please see \"repl\" and \"mh-format\".
641 See also `mh-reply-show-message-flag',
642 `mh-reply-default-reply-to', and `mh-send'."
643 (interactive (list
644 (mh-get-msg-num t)
645 (let ((minibuffer-help-form
646 "from => Sender only\nto => Sender and primary recipients\ncc or all => Sender and all recipients"))
647 (or mh-reply-default-reply-to
648 (completing-read "Reply to whom (default from): "
649 '(("from") ("to") ("cc") ("all"))
651 t)))
652 current-prefix-arg))
653 (let* ((folder mh-current-folder)
654 (show-buffer mh-show-buffer)
655 (config (current-window-configuration))
656 (group-reply (or (equal reply-to "cc") (equal reply-to "all")))
657 (form-file (cond ((and (mh-variant-p 'nmh 'gnu-mh) group-reply
658 (stringp mh-repl-group-formfile))
659 mh-repl-group-formfile)
660 ((stringp mh-repl-formfile) mh-repl-formfile)
661 (t nil))))
662 (message "Composing a reply...")
663 (mh-exec-cmd "repl" "-build" "-noquery" "-nodraftfolder"
664 (if form-file
665 (list "-form" form-file))
666 mh-current-folder message
667 (cond ((or (equal reply-to "from") (equal reply-to ""))
668 '("-nocc" "all"))
669 ((equal reply-to "to")
670 '("-cc" "to"))
671 (group-reply (if (mh-variant-p 'nmh 'gnu-mh)
672 '("-group" "-nocc" "me")
673 '("-cc" "all" "-nocc" "me"))))
674 (cond ((or (eq mh-yank-behavior 'autosupercite)
675 (eq mh-yank-behavior 'autoattrib))
676 '("-noformat"))
677 (includep '("-filter" "mhl.reply"))
678 (t '())))
679 (let ((draft (mh-read-draft "reply"
680 (expand-file-name "reply" mh-user-path)
681 t)))
682 (delete-other-windows)
683 (save-buffer)
685 (let ((to (mh-get-header-field "To:"))
686 (subject (mh-get-header-field "Subject:"))
687 (cc (mh-get-header-field "Cc:")))
688 (goto-char (point-min))
689 (mh-goto-header-end 1)
690 (or includep
691 (not mh-reply-show-message-flag)
692 (mh-in-show-buffer (show-buffer)
693 (mh-display-msg message folder)))
694 (mh-add-msgs-to-seq message 'answered t)
695 (message "Composing a reply...done")
696 (mh-compose-and-send-mail draft "" folder message to subject cc
697 mh-note-repl "Replied:" config))
698 (when (and (or (eq 'autosupercite mh-yank-behavior)
699 (eq 'autoattrib mh-yank-behavior))
700 (eq (mh-show-buffer-message-number) mh-sent-from-msg))
701 (undo-boundary)
702 (mh-yank-cur-msg))
703 (mh-letter-mode-message))))
705 ;;;###mh-autoload
706 (defun mh-send (to cc subject)
707 "Compose a message.
709 Your letter appears in an Emacs buffer whose mode is
710 MH-Letter (see `mh-letter-mode').
712 The arguments TO, CC, and SUBJECT can be used to prefill the
713 draft fields or suppress the prompts if `mh-compose-prompt-flag'
714 is on. They are also passed to the function set in the option
715 `mh-compose-letter-function'.
717 See also `mh-insert-x-mailer-flag' and `mh-letter-mode-hook'.
719 Outside of an MH-Folder buffer (`mh-folder-mode'), you must call
720 either \\[mh-smail] or \\[mh-smail-other-window] to compose a new
721 message."
722 (interactive (list
723 (mh-interactive-read-address "To: ")
724 (mh-interactive-read-address "Cc: ")
725 (mh-interactive-read-string "Subject: ")))
726 (let ((config (current-window-configuration)))
727 (delete-other-windows)
728 (mh-send-sub to cc subject config)))
732 ;;; Support Routines
734 (defun mh-interactive-read-address (prompt)
735 "Read an address.
736 If `mh-compose-prompt-flag' is non-nil, then read an address with
737 PROMPT.
738 Otherwise return the empty string."
739 (if mh-compose-prompt-flag (mh-read-address prompt) ""))
741 (defun mh-interactive-read-string (prompt)
742 "Read a string.
743 If `mh-compose-prompt-flag' is non-nil, then read a string with
744 PROMPT.
745 Otherwise return the empty string."
746 (if mh-compose-prompt-flag (read-string prompt) ""))
748 ;;;###mh-autoload
749 (defun mh-show-buffer-message-number (&optional buffer)
750 "Message number of displayed message in corresponding show buffer.
752 Return nil if show buffer not displayed.
753 If in `mh-letter-mode', don't display the message number being replied
754 to, but rather the message number of the show buffer associated with
755 our originating folder buffer.
756 Optional argument BUFFER can be used to specify the buffer."
757 (save-excursion
758 (if buffer
759 (set-buffer buffer))
760 (cond ((eq major-mode 'mh-show-mode)
761 (let ((number-start (mh-search-from-end ?/ buffer-file-name)))
762 (string-to-number (substring buffer-file-name
763 (1+ number-start)))))
764 ((and (eq major-mode 'mh-folder-mode)
765 mh-show-buffer
766 (get-buffer mh-show-buffer))
767 (mh-show-buffer-message-number mh-show-buffer))
768 ((and (eq major-mode 'mh-letter-mode)
769 mh-sent-from-folder
770 (get-buffer mh-sent-from-folder))
771 (mh-show-buffer-message-number mh-sent-from-folder))
773 nil))))
775 (defun mh-send-sub (to cc subject config &optional other-window)
776 "Do the real work of composing and sending a letter.
777 Expects the TO, CC, and SUBJECT fields as arguments.
778 CONFIG is the window configuration before sending mail.
779 OTHER-WINDOW non-nil means use another window for composing."
780 (let ((folder mh-current-folder)
781 (msg-num (mh-get-msg-num nil)))
782 (message "Composing a message...")
783 (let ((draft (mh-read-draft
784 "message"
785 (let (components)
786 (cond
787 ((file-exists-p
788 (setq components
789 (expand-file-name mh-comp-formfile mh-user-path)))
790 components)
791 ((file-exists-p
792 (setq components
793 (expand-file-name mh-comp-formfile mh-lib)))
794 components)
796 (error "Can't find %s in %s or %s"
797 mh-comp-formfile mh-user-path mh-lib))))
798 nil (and other-window t))))
799 (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc)
800 (goto-char (point-max))
801 (mh-compose-and-send-mail draft "" folder msg-num
802 to subject cc
803 nil nil config)
804 (mh-letter-mode-message)
805 (mh-letter-adjust-point))))
807 (defun mh-read-draft (use initial-contents delete-contents-file &optional other-window)
808 "Read draft file into a draft buffer and make that buffer the current one.
810 USE is a message used for prompting about the intended use of the
811 message.
812 INITIAL-CONTENTS is filename that is read into an empty buffer, or nil
813 if buffer should not be modified. Delete the initial-contents file if
814 DELETE-CONTENTS-FILE flag is set.
815 OTHER-WINDOW non-nil means use another window for displaying the
816 draft file.
817 Returns the draft folder's name.
818 If the draft folder facility is enabled in ~/.mh_profile, a new buffer
819 is used each time and saved in the draft folder. The draft file can
820 then be reused."
821 (cond (mh-draft-folder
822 (let ((orig-default-dir default-directory)
823 (draft-file-name (mh-new-draft-name)))
824 (pop-to-buffer (generate-new-buffer
825 (format "draft-%s"
826 (file-name-nondirectory draft-file-name)))
827 (and other-window t))
828 (condition-case ()
829 (insert-file-contents draft-file-name t)
830 (file-error))
831 (setq default-directory orig-default-dir)))
833 (let ((draft-name (expand-file-name "draft" mh-user-path)))
834 (pop-to-buffer "draft" (and other-window t)) ; Create if necessary
835 (if (buffer-modified-p)
836 (if (y-or-n-p "Draft has been modified; kill anyway? ")
837 (set-buffer-modified-p nil)
838 (error "Draft preserved")))
839 (setq buffer-file-name draft-name)
840 (clear-visited-file-modtime)
841 (unlock-buffer)
842 (cond ((and (file-exists-p draft-name)
843 (not (equal draft-name initial-contents)))
844 (insert-file-contents draft-name)
845 (delete-file draft-name))))))
846 (cond ((and initial-contents
847 (or (zerop (buffer-size))
848 (if (y-or-n-p
849 (format "A draft exists. Use for %s? " use))
850 (if mh-error-if-no-draft
851 (error "A prior draft exists"))
852 t)))
853 (erase-buffer)
854 (insert-file-contents initial-contents)
855 (if delete-contents-file (delete-file initial-contents))))
856 (auto-save-mode 1)
857 (if mh-draft-folder
858 (save-buffer)) ; Do not reuse draft name
859 (buffer-name))
861 (defun mh-new-draft-name ()
862 "Return the pathname of folder for draft messages."
863 (save-excursion
864 (mh-exec-cmd-quiet t "mhpath" mh-draft-folder "new")
865 (buffer-substring (point-min) (1- (point-max)))))
867 (defun mh-insert-fields (&rest name-values)
868 "Insert the NAME-VALUES pairs in the current buffer.
869 If the field exists, append the value to it.
870 Do not insert any pairs whose value is the empty string."
871 (let ((case-fold-search t))
872 (while name-values
873 (let ((field-name (car name-values))
874 (value (car (cdr name-values))))
875 (if (not (string-match "^.*:$" field-name))
876 (setq field-name (concat field-name ":")))
877 (cond ((or (null value)
878 (equal value ""))
879 nil)
880 ((mh-position-on-field field-name)
881 (insert " " (or value "")))
883 (insert field-name " " value "\n")))
884 (setq name-values (cdr (cdr name-values)))))))
886 (defun mh-compose-and-send-mail (draft send-args
887 sent-from-folder sent-from-msg
888 to subject cc
889 annotate-char annotate-field
890 config)
891 "Edit and compose a draft message in buffer DRAFT and send or save it.
892 SEND-ARGS is the argument passed to the send command.
893 SENT-FROM-FOLDER is buffer containing scan listing of current folder,
894 or nil if none exists.
895 SENT-FROM-MSG is the message number or sequence name or nil.
896 The TO, SUBJECT, and CC fields are passed to the
897 `mh-compose-letter-function'.
898 If ANNOTATE-CHAR is non-null, it is used to notate the scan listing of
899 the message. In that case, the ANNOTATE-FIELD is used to build a
900 string for `mh-annotate-msg'.
901 CONFIG is the window configuration to restore after sending the
902 letter."
903 (pop-to-buffer draft)
904 (mh-letter-mode)
906 ;; Insert identity.
907 (mh-insert-identity mh-identity-default t)
908 (mh-identity-make-menu)
909 (mh-identity-add-menu)
911 ;; Cleanup possibly RFC2047 encoded subject header
912 (mh-decode-message-subject)
914 ;; Insert extra fields.
915 (mh-insert-x-mailer)
916 (mh-insert-x-face)
918 (mh-letter-hide-all-skipped-fields)
920 (setq mh-sent-from-folder sent-from-folder)
921 (setq mh-sent-from-msg sent-from-msg)
922 (setq mh-send-args send-args)
923 (setq mh-annotate-char annotate-char)
924 (setq mh-annotate-field annotate-field)
925 (setq mh-previous-window-config config)
926 (setq mode-line-buffer-identification (list " {%b}"))
927 (mh-logo-display)
928 (mh-make-local-hook 'kill-buffer-hook)
929 (add-hook 'kill-buffer-hook 'mh-tidy-draft-buffer nil t)
930 (run-hook-with-args 'mh-compose-letter-function to subject cc))
932 (defun mh-insert-x-mailer ()
933 "Append an X-Mailer field to the header.
934 The versions of MH-E, Emacs, and MH are shown."
935 ;; Lazily initialize mh-x-mailer-string.
936 (when (and mh-insert-x-mailer-flag (null mh-x-mailer-string))
937 (setq mh-x-mailer-string
938 (format "MH-E %s; %s; %sEmacs %s"
939 mh-version mh-variant-in-use
940 (if (featurep 'xemacs) "X" "GNU ")
941 (cond ((not (featurep 'xemacs))
942 (string-match "[0-9]+\\.[0-9]+\\(\\.[0-9]+\\)?"
943 emacs-version)
944 (match-string 0 emacs-version))
945 ((string-match "[0-9.]*\\( +\([ a-z]+[0-9]+\)\\)?"
946 emacs-version)
947 (match-string 0 emacs-version))
948 (t (format "%s.%s" emacs-major-version
949 emacs-minor-version))))))
950 ;; Insert X-Mailer, but only if it doesn't already exist.
951 (save-excursion
952 (when (and mh-insert-x-mailer-flag
953 (null (mh-goto-header-field "X-Mailer")))
954 (mh-insert-fields "X-Mailer:" mh-x-mailer-string))))
956 (defun mh-insert-x-face ()
957 "Append X-Face, Face or X-Image-URL field to header.
958 If the field already exists, this function does nothing."
959 (when (and (file-exists-p mh-x-face-file)
960 (file-readable-p mh-x-face-file))
961 (save-excursion
962 (unless (or (mh-position-on-field "X-Face")
963 (mh-position-on-field "Face")
964 (mh-position-on-field "X-Image-URL"))
965 (save-excursion
966 (goto-char (+ (point) (cadr (insert-file-contents mh-x-face-file))))
967 (if (not (looking-at "^"))
968 (insert "\n")))
969 (unless (looking-at "\\(X-Face\\|Face\\|X-Image-URL\\): ")
970 (insert "X-Face: "))))))
972 (defun mh-tidy-draft-buffer ()
973 "Run when a draft buffer is destroyed."
974 (let ((buffer (get-buffer mh-recipients-buffer)))
975 (if buffer
976 (kill-buffer buffer))))
978 (defun mh-letter-mode-message ()
979 "Display a help message for users of `mh-letter-mode'.
980 This should be the last function called when composing the draft."
981 (message "%s" (substitute-command-keys
982 (concat "Type \\[mh-send-letter] to send message, "
983 "\\[mh-help] for help"))))
985 (defun mh-letter-adjust-point ()
986 "Move cursor to first header field if are using the no prompt mode."
987 (unless mh-compose-prompt-flag
988 (goto-char (point-max))
989 (mh-letter-next-header-field)))
991 (defun mh-annotate-msg (msg folder note &rest args)
992 "Mark MSG in FOLDER with character NOTE and annotate message with ARGS.
993 MSG can be a message number, a list of message numbers, or a sequence.
994 The hook `mh-annotate-msg-hook' is run after annotating; see its
995 documentation for variables it can use."
996 (apply 'mh-exec-cmd "anno" folder
997 (if (listp msg) (append msg args) (cons msg args)))
998 (save-excursion
999 (cond ((get-buffer folder) ; Buffer may be deleted
1000 (set-buffer folder)
1001 (mh-iterate-on-range nil msg
1002 (mh-notate nil note
1003 (+ mh-cmd-note mh-scan-field-destination-offset))))))
1004 (let ((mh-current-folder folder)
1005 ;; mh-annotate-list is a sequence name or a list of message numbers
1006 (mh-annotate-list (if (numberp msg) (list msg) msg)))
1007 (run-hooks 'mh-annotate-msg-hook)))
1009 (defun mh-insert-header-separator ()
1010 "Insert `mh-mail-header-separator', if absent."
1011 (save-excursion
1012 (goto-char (point-min))
1013 (rfc822-goto-eoh)
1014 (if (looking-at "$")
1015 (insert mh-mail-header-separator))))
1017 ;;;###mh-autoload
1018 (defun mh-insert-auto-fields (&optional non-interactive)
1019 "Insert custom fields if recipient is found in `mh-auto-fields-list'.
1021 Once the header contains one or more recipients, you may run this
1022 command to insert these fields manually. However, if you use this
1023 command, the automatic insertion when the message is sent is
1024 disabled.
1026 In a program, set buffer-local `mh-insert-auto-fields-done-local'
1027 if header fields were added. If NON-INTERACTIVE is non-nil,
1028 perform actions quietly and only if
1029 `mh-insert-auto-fields-done-local' is nil. Return t if fields
1030 added; otherwise return nil."
1031 (interactive)
1032 (when (or (not non-interactive)
1033 (not mh-insert-auto-fields-done-local))
1034 (save-excursion
1035 (when (and (or (mh-goto-header-field "To:")
1036 (mh-goto-header-field "cc:")))
1037 (let ((list mh-auto-fields-list)
1038 (fields-inserted nil))
1039 (while list
1040 (let ((regexp (nth 0 (car list)))
1041 (entries (nth 1 (car list))))
1042 (when (mh-regexp-in-field-p regexp "To:" "cc:")
1043 (setq mh-insert-auto-fields-done-local t)
1044 (setq fields-inserted t)
1045 (if (not non-interactive)
1046 (message "Fields for %s added" regexp))
1047 (let ((entry-list entries))
1048 (while entry-list
1049 (let ((field (caar entry-list))
1050 (value (cdar entry-list)))
1051 (cond
1052 ((equal ":identity" field)
1053 (when
1054 ;;(and (not mh-identity-local)
1055 ;; Bug 1204506. But do we need to be able
1056 ;; to set an identity manually that won't be
1057 ;; overridden by mh-insert-auto-fields?
1058 (assoc value mh-identity-list)
1060 (mh-insert-identity value)))
1062 (mh-modify-header-field field value
1063 (equal field "From")))))
1064 (setq entry-list (cdr entry-list))))))
1065 (setq list (cdr list)))
1066 fields-inserted)))))
1068 (defun mh-modify-header-field (field value &optional overwrite-flag)
1069 "To header FIELD add VALUE.
1070 If OVERWRITE-FLAG is non-nil then the old value, if present, is
1071 discarded."
1072 (cond ((and overwrite-flag
1073 (mh-goto-header-field (concat field ":")))
1074 (insert " " value)
1075 (delete-region (point) (mh-line-end-position)))
1076 ((and (not overwrite-flag)
1077 (mh-regexp-in-field-p (concat "\\b" value "\\b") field))
1078 ;; Already there, do nothing.
1080 ((and (not overwrite-flag)
1081 (mh-goto-header-field (concat field ":")))
1082 (insert " " value ","))
1084 (mh-goto-header-end 0)
1085 (insert field ": " value "\n"))))
1087 (defun mh-regexp-in-field-p (regexp &rest fields)
1088 "Non-nil means REGEXP was found in FIELDS."
1089 (save-excursion
1090 (let ((search-result nil)
1091 (field))
1092 (while fields
1093 (setq field (car fields))
1094 (if (and (mh-goto-header-field field)
1095 (re-search-forward
1096 regexp (save-excursion (mh-header-field-end)(point)) t))
1097 (setq fields nil
1098 search-result t)
1099 (setq fields (cdr fields))))
1100 search-result)))
1102 (defun mh-ascii-buffer-p ()
1103 "Check if current buffer is entirely composed of ASCII.
1104 The function doesn't work for XEmacs since `find-charset-region'
1105 doesn't exist there."
1106 (loop for charset in (mh-funcall-if-exists
1107 find-charset-region (point-min) (point-max))
1108 unless (eq charset 'ascii) return nil
1109 finally return t))
1111 (provide 'mh-comp)
1113 ;; Local Variables:
1114 ;; indent-tabs-mode: nil
1115 ;; sentence-end-double-space: nil
1116 ;; End:
1118 ;; arch-tag: 62865511-e610-4923-b0b5-f45a8ab70a34
1119 ;;; mh-comp.el ends here