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