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