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