(mh-letter-mode-syntax-table): Define within defvar.
[emacs.git] / lisp / mh-e / mh-comp.el
blob6dcd2d22147b71af70e4ca0ffba60e9d8e080bcb
1 ;;; mh-comp.el --- MH-E functions for composing and sending messages
3 ;; Copyright (C) 1993, 1995, 1997,
4 ;; 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
6 ;; Author: Bill Wohler <wohler@newt.com>
7 ;; Maintainer: Bill Wohler <wohler@newt.com>
8 ;; Keywords: mail
9 ;; See: mh-e.el
11 ;; This file is part of GNU Emacs.
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.
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.
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/>.
26 ;;; Commentary:
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.
33 ;;; Change Log:
35 ;;; Code:
37 (require 'mh-e)
38 (require 'mh-gnus) ;needed because mh-gnus.el not compiled
39 (require 'mh-scan)
41 (require 'sendmail)
43 (autoload 'easy-menu-add "easymenu")
44 (autoload 'mml-insert-tag "mml")
48 ;;; Site Customization
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.")
54 (defvar mh-send-uses-spost-flag nil
55 "Non-nil means \"send\" uses \"spost\" to submit messages.
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\".")
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.")
70 ;;; Variables
72 (defvar mh-comp-formfile "components"
73 "Name of file to be used as a skeleton for composing messages.
75 Default is \"components\".
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.")
80 (defvar mh-repl-formfile "replcomps"
81 "Name of file to be used as a skeleton for replying to messages.
83 Default is \"replcomps\".
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.")
88 (defvar mh-repl-group-formfile "replgroupcomps"
89 "Name of file to be used as a skeleton for replying to messages.
91 Default is \"replgroupcomps\".
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.")
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 ))))
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.")
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.")
125 (defvar mh-send-args ""
126 "Extra args to pass to \"send\" command.")
128 (defvar mh-annotate-char nil
129 "Character to use to annotate `mh-sent-from-msg'.")
131 (defvar mh-annotate-field nil
132 "Field name for message annotation.")
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'.")
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)
144 ;;; MH-E Entry Points
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))
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))
162 (defun mh-send-other-window (to cc subject)
163 "Compose a message in another window.
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))))
174 (defvar mh-error-if-no-draft nil) ;raise error over using old draft
176 ;;;###autoload
177 (defun mh-smail-batch (&optional to subject other-headers &rest ignored)
178 "Compose a message with the MH mail system.
180 This function does not prompt the user for any header fields, and
181 thus is suitable for use by programs that want to create a mail
182 buffer. Users should use \\[mh-smail] to compose mail.
184 Optional arguments for setting certain fields include TO,
185 SUBJECT, and OTHER-HEADERS. Additional arguments are IGNORED.
187 This function remains for Emacs 21 compatibility. New
188 applications should use `mh-user-agent-compose'."
189 (mh-find-path)
190 (let ((mh-error-if-no-draft t))
191 (mh-send (or to "") "" (or subject ""))))
193 ;;;###autoload
194 (define-mail-user-agent 'mh-e-user-agent
195 'mh-user-agent-compose 'mh-send-letter 'mh-fully-kill-draft
196 'mh-before-send-letter-hook)
198 ;;;###autoload
199 (defun mh-user-agent-compose (&optional to subject other-headers continue
200 switch-function yank-action
201 send-actions)
202 "Set up mail composition draft with the MH mail system.
203 This is the `mail-user-agent' entry point to MH-E. This function
204 conforms to the contract specified by `define-mail-user-agent'
205 which means that this function should accept the same arguments
206 as `compose-mail'.
208 The optional arguments TO and SUBJECT specify recipients and the
209 initial Subject field, respectively.
211 OTHER-HEADERS is an alist specifying additional header fields.
212 Elements look like (HEADER . VALUE) where both HEADER and VALUE
213 are strings.
215 CONTINUE, SWITCH-FUNCTION, YANK-ACTION and SEND-ACTIONS are
216 ignored."
217 (mh-find-path)
218 (let ((mh-error-if-no-draft t))
219 (mh-send to "" subject)
220 (while other-headers
221 (mh-insert-fields (concat (car (car other-headers)) ":")
222 (cdr (car other-headers)))
223 (setq other-headers (cdr other-headers)))))
225 ;; Shush compiler.
226 (defvar sendmail-coding-system) ; XEmacs
228 ;;;###autoload
229 (defun mh-send-letter (&optional arg)
230 "Save draft and send message.
232 When you are all through editing a message, you send it with this
233 command. You can give a prefix argument ARG to monitor the first stage
234 of the delivery\; this output can be found in a buffer called \"*MH-E
235 Mail Delivery*\".
237 The hook `mh-before-send-letter-hook' is run at the beginning of
238 this command. For example, if you want to check your spelling in
239 your message before sending, add the function `ispell-message'.
241 Unless `mh-insert-auto-fields' had previously been called
242 manually, the function `mh-insert-auto-fields' is called to
243 insert fields based upon the recipients. If fields are added, you
244 are given a chance to see and to confirm these fields before the
245 message is actually sent. You can do away with this confirmation
246 by turning off the option `mh-auto-fields-prompt-flag'.
248 In case the MH \"send\" program is installed under a different name,
249 use `mh-send-prog' to tell MH-E the name."
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
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:"))))
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"))))
321 ;;;###autoload
322 (defun mh-fully-kill-draft ()
323 "Quit editing and delete draft message.
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")))
343 ;;; MH-Folder Commands
345 ;; Alphabetical.
347 ;;;###mh-autoload
348 (defun mh-edit-again (message)
349 "Edit a MESSAGE to send it again.
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.
360 This command can also be used to take messages that were sent to
361 you and to send them to more people.
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').
367 The default message is the current message.
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))
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))
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)))
397 ;;;###mh-autoload
398 (defun mh-extract-rejected-mail (message)
399 "Edit a MESSAGE that was returned by the mail system.
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].
406 The default message is the current message.
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))
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)))
430 ;;;###mh-autoload
431 (defun mh-forward (to cc &optional range)
432 "Forward message.
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.
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.
443 The hook `mh-forward-hook' is called on the draft.
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)))
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)))))
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))
532 ;;;###mh-autoload
533 (defun mh-redistribute (to cc &optional message)
534 "Redistribute a message.
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.
543 Also investigate the command \\[mh-edit-again] for another way to
544 redistribute messages.
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"))))
587 ;;;###mh-autoload
588 (defun mh-reply (message &optional reply-to includep)
589 "Reply to a MESSAGE.
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.
595 Response Reply Goes To
597 from The person who sent the message. This is the
598 default, so <RET> is sufficient.
600 to Replies to the sender, plus all recipients in the
601 \"To:\" header field.
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.
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\".
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.
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\".
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.
630 Another way to include the message automatically in your draft is
631 to use \"repl: -filter repl.filter\" in your MH profile.
633 If you wish to customize the header or other parts of the reply
634 draft, please see \"repl\" and \"mh-format\".
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"))
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)
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))))
700 ;;;###mh-autoload
701 (defun mh-send (to cc subject)
702 "Compose a message.
704 Your letter appears in an Emacs buffer whose mode is
705 MH-Letter (see `mh-letter-mode').
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'.
712 See also `mh-insert-x-mailer-flag' and `mh-letter-mode-hook'.
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)))
727 ;;; Support Routines
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) ""))
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) ""))
743 ;;;###mh-autoload
744 (defun mh-show-buffer-message-number (&optional buffer)
745 "Message number of displayed message in corresponding show buffer.
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))
768 nil))))
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)
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))))
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.
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)))
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))
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)))))
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 "")))
874 (insert field-name " " value "\n")))
875 (setq name-values (cdr (cdr name-values)))))))
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)
897 ;; Insert identity.
898 (mh-insert-identity mh-identity-default t)
899 (mh-identity-make-menu)
900 (mh-identity-add-menu)
902 ;; Insert extra fields.
903 (mh-insert-x-mailer)
904 (mh-insert-x-face)
906 (mh-letter-hide-all-skipped-fields)
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))
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))))
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: "))))))
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))))
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"))))
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)))
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)))
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))))
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'.
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.
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)
1048 (mh-insert-identity value)))
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)))))
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.
1068 ((and (not overwrite-flag)
1069 (mh-goto-header-field (concat field ":")))
1070 (insert " " value ","))
1072 (mh-goto-header-end 0)
1073 (insert field ": " value "\n"))))
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)))
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))
1099 (provide 'mh-comp)
1101 ;; Local Variables:
1102 ;; indent-tabs-mode: nil
1103 ;; sentence-end-double-space: nil
1104 ;; End:
1106 ;; arch-tag: 62865511-e610-4923-b0b5-f45a8ab70a34
1107 ;;; mh-comp.el ends here