entered into RCS
[bpt/emacs.git] / lisp / mail / mh-comp.el
CommitLineData
c26cf6c8
RS
1;;; mh-comp --- mh-e functions for composing messages
2;; Time-stamp: <94/03/08 10:05:20 gildea>
3
4;; Copyright 1993 Free Software Foundation, Inc.
5
6;; This file is part of mh-e.
7
8;; mh-e is free software; you can redistribute it and/or modify
9;; it under the terms of the GNU General Public License as published by
10;; the Free Software Foundation; either version 2, or (at your option)
11;; any later version.
12
13;; mh-e is distributed in the hope that it will be useful,
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;; GNU General Public License for more details.
17
18;; You should have received a copy of the GNU General Public License
19;; along with mh-e; see the file COPYING. If not, write to
20;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
21
22;;; Commentary:
23
24;; Internal support for mh-e package.
25
26;;; Code:
27
28(provide 'mh-comp)
29(require 'mh-utils)
30
31(defvar mh-note-repl "-"
32 "String whose first character is used to notate replied to messages.")
33
34(defvar mh-note-forw "F"
35 "String whose first character is used to notate forwarded messages.")
36
37(defvar mh-note-dist "R"
38 "String whose first character is used to notate redistributed messages.")
39
40(defvar mh-send-prog "send"
41 "Name of the MH send program.
42Some sites need to change this because of a name conflict.")
43
44(defvar mh-yank-hooks nil
45 "Obsolete hook for modifying a citation just inserted in the mail buffer.
46Each hook function can find the citation between point and mark.
47And each hook function should leave point and mark around the citation
48text as modified.
49
50This is a normal hook, misnamed for historical reasons.
51It is semi-obsolete and is only used if mail-citation-hook is nil.")
52
53(defvar mail-citation-hook nil
54 "*Hook for modifying a citation just inserted in the mail buffer.
55Each hook function can find the citation between point and mark.
56And each hook function should leave point and mark around the citation
57text as modified.
58
59If this hook is entirely empty (nil), the text of the message is inserted
60with mh-ins-buf-prefix prefixed to each line.
61
62See also the variable mh-yank-from-start-of-msg, which controls how
63much of the message passed to the hook.")
64
65;;; Copied from sendmail.el for Hyperbole
66(defvar mail-header-separator "--------"
67 "*Line used by MH to separate headers from text in messages being composed.")
68
69;;; Personal preferences:
70
71(defvar mh-delete-yanked-msg-window nil
72 "*Controls window display when a message is yanked by \\<mh-letter-mode-map>\\[mh-yank-cur-msg].
73If non-nil, yanking the current message into a draft letter deletes any
74windows displaying the message.")
75
76(defvar mh-yank-from-start-of-msg t
77 "*Controls which part of a message is yanked by \\<mh-letter-mode-map>\\[mh-yank-cur-msg].
78If non-nil, include the entire message. If the symbol `body', then yank the
79message minus the header. If nil, yank only the portion of the message
80following the point. If the show buffer has a region, this variable is
81ignored.")
82
83(defvar mh-reply-default-reply-to nil
84 "*Sets the person or persons to whom a reply will be sent.
85If nil, prompt for recipient. If non-nil, then \\<mh-folder-mode-map>`\\[mh-reply]' will use this
86value and it should be one of \"from\", \"to\", or \"cc\".")
87
88(defvar mh-signature-file-name "~/.signature"
89 "*Name of file containing the user's signature.
90Inserted into message by \\<mh-letter-mode-map>\\[mh-insert-signature].")
91
92(defvar mh-forward-subject-format "%s: %s"
93 "*Format to generate the Subject: line contents for a forwarded message.
94The two string arguments to the format are the sender of the original
95message and the original subject line.")
96
97(defvar mh-comp-formfile "components"
98 "Name of file to be used as a skeleton for composing messages.
99Default is \"components\". If not a complete path name, the file
100is searched for first in the user's MH directory, then in the
101system MH lib directory.")
102
103;;; Hooks:
104
105(defvar mh-letter-mode-hook nil
106 "Invoked in `mh-letter-mode' on a new letter.")
107
108(defvar mh-compose-letter-function nil
109 "Invoked in `mh-compose-and-send-mail' on a draft letter.
110It is passed three arguments: TO recipients, SUBJECT, and CC recipients.")
111
112(defvar mh-before-send-letter-hook nil
113 "Invoked at the beginning of the \\<mh-letter-mode-map>\\[mh-send-letter] command.")
114
115
116(defvar mh-rejected-letter-start
117 (concat "^ ----- Unsent message follows -----$" ;from mail system
118 "\\|^------- Unsent Draft$" ;from MH itself
119 "\\|^---------- Original Message ----------$" ;from zmailer
120 "\\|^ --- The unsent message follows ---$" ;from AIX mail system
121 "\\|^ Your message follows:$") ;from MMDF-II
122 "Regexp specifying the beginning of the wrapper around a returned letter.
123This wrapper is generated by the mail system when rejecting a letter.")
124
125(defvar mh-new-draft-cleaned-headers
126 "^Date:\\|^Received:\\|^Message-Id:\\|^From:\\|^Sender:\\|^Delivery-Date:\\|^Return-Path:"
127 "Regexp of header lines to remove before offering a message as a new draft.
128Used by the \\<mh-folder-mode-map>`\\[mh-edit-again]' and `\\[mh-extract-rejected-mail]' commands.")
129
130(defvar mh-to-field-choices '((?t . "To:") (?s . "Subject:") (?c . "Cc:")
131 (?b . "Bcc:") (?f . "Fcc:"))
132 "Alist of (final-character . field-name) choices for mh-to-field.")
133
134(defvar mh-letter-mode-map (copy-keymap text-mode-map)
135 "Keymap for composing mail.")
136
137(defvar mh-letter-mode-syntax-table nil
138 "Syntax table used by mh-e while in MH-Letter mode.")
139
140(if mh-letter-mode-syntax-table
141 ()
142 (setq mh-letter-mode-syntax-table
143 (make-syntax-table text-mode-syntax-table))
144 (modify-syntax-entry ?% "." mh-letter-mode-syntax-table))
145
146
147;;;###autoload
148(defun mh-smail ()
149 "Compose and send mail with the MH mail system.
150This function is an entry point to mh-e, the Emacs front end
151to the MH mail system."
152 (interactive)
153 (mh-find-path)
154 (call-interactively 'mh-send))
155
156
157(defun mh-edit-again (msg)
158 "Clean-up a draft or a message previously sent and make it resendable.
159The variable mh-new-draft-cleaned-headers specifies the headers to remove.
160See also documentation for `\\[mh-send]' function."
161 (interactive (list (mh-get-msg-num t)))
162 (let* ((from-folder mh-current-folder)
163 (config (current-window-configuration))
164 (draft
165 (cond ((and mh-draft-folder (equal from-folder mh-draft-folder))
166 (pop-to-buffer (find-file-noselect (mh-msg-filename msg)) t)
167 (rename-buffer (format "draft-%d" msg))
168 (buffer-name))
169 (t
170 (mh-read-draft "clean-up" (mh-msg-filename msg) nil)))))
171 (mh-clean-msg-header (point-min) mh-new-draft-cleaned-headers nil)
172 (goto-char (point-min))
173 (set-buffer-modified-p nil)
174 (mh-compose-and-send-mail draft "" from-folder nil nil nil nil nil nil
175 config)))
176
177
178(defun mh-extract-rejected-mail (msg)
179 "Extract a letter returned by the mail system and make it resendable.
180Default is the displayed message. The variable mh-new-draft-cleaned-headers
181gives the headers to clean out of the original message.
182See also documentation for `\\[mh-send]' function."
183 (interactive (list (mh-get-msg-num t)))
184 (let ((from-folder mh-current-folder)
185 (config (current-window-configuration))
186 (draft (mh-read-draft "extraction" (mh-msg-filename msg) nil)))
187 (goto-char (point-min))
188 (cond ((re-search-forward mh-rejected-letter-start nil t)
189 (skip-chars-forward " \t\n")
190 (delete-region (point-min) (point))
191 (mh-clean-msg-header (point-min) mh-new-draft-cleaned-headers nil))
192 (t
193 (message "Does not appear to be a rejected letter.")))
194 (goto-char (point-min))
195 (set-buffer-modified-p nil)
196 (mh-compose-and-send-mail draft "" from-folder msg (mh-get-field "To:")
197 (mh-get-field "From:") (mh-get-field "Cc:")
198 nil nil config)))
199
200
201(defun mh-forward (to cc &optional msg-or-seq)
202 "Forward a message or message sequence. Defaults to displayed message.
203If optional prefix argument provided, then prompt for the message sequence.
204See also documentation for `\\[mh-send]' function."
205 (interactive (list (mh-read-address "To: ")
206 (mh-read-address "Cc: ")
207 (if current-prefix-arg
208 (mh-read-seq-default "Forward" t)
209 (mh-get-msg-num t))))
210 (or msg-or-seq
211 (setq msg-or-seq (mh-get-msg-num t)))
212 (let* ((folder mh-current-folder)
213 (config (current-window-configuration))
214 ;; forw always leaves file in "draft" since it doesn't have -draft
215 (draft-name (expand-file-name "draft" mh-user-path))
216 (draft (cond ((or (not (file-exists-p draft-name))
217 (y-or-n-p "The file 'draft' exists. Discard it? "))
218 (mh-exec-cmd "forw"
219 "-build" mh-current-folder msg-or-seq)
220 (prog1
221 (mh-read-draft "" draft-name t)
222 (mh-insert-fields "To:" to "Cc:" cc)
223 (set-buffer-modified-p nil)))
224 (t
225 (mh-read-draft "" draft-name nil)))))
226 (goto-char (point-min))
227 (re-search-forward "^------- Forwarded Message")
228 (forward-line -1)
229 (narrow-to-region (point) (point-max))
230 (let ((orig-from (save-excursion (mh-get-field "From:")))
231 (orig-subject (save-excursion (mh-get-field "Subject:"))))
232 (widen)
233 (let ((forw-subject
234 (mh-forwarded-letter-subject orig-from orig-subject)))
235 (save-excursion (mh-insert-fields "Subject:" forw-subject))
236 (delete-other-windows)
237 (if (numberp msg-or-seq)
238 (mh-add-msgs-to-seq msg-or-seq 'forwarded t)
239 (mh-add-msgs-to-seq (mh-seq-to-msgs msg-or-seq) 'forwarded t))
240 (mh-compose-and-send-mail draft "" folder msg-or-seq
241 to forw-subject cc
242 mh-note-forw "Forwarded:"
243 config)))))
244
245(defun mh-forwarded-letter-subject (from subject)
246 ;; Return a Subject suitable for a forwarded message.
247 ;; Original message has headers FROM and SUBJECT.
248 (let ((addr-start (string-match "<" from))
249 (comment (string-match "(" from)))
250 (cond ((and addr-start (> addr-start 0))
251 ;; Full Name <luser@host>
252 (setq from (substring from 0 (1- addr-start))))
253 (comment
254 ;; luser@host (Full Name)
255 (setq from (substring from (1+ comment) (1- (length from)))))))
256 (format mh-forward-subject-format from subject))
257
258
259;;;###autoload
260(defun mh-smail-other-window ()
261 "Compose and send mail in other window with the MH mail system.
262This function is an entry point to mh-e, the Emacs front end
263to the MH mail system."
264 (interactive)
265 (mh-find-path)
266 (call-interactively 'mh-send-other-window))
267
268
269(defun mh-redistribute (to cc &optional msg)
270 "Redistribute a letter.
271Depending on how your copy of MH was compiled, you may need to change the
272setting of the variable mh-redist-full-contents. See its documentation."
273 (interactive (list (mh-read-address "Redist-To: ")
274 (mh-read-address "Redist-Cc: ")
275 (mh-get-msg-num t)))
276 (or msg
277 (setq msg (mh-get-msg-num t)))
278 (save-window-excursion
279 (let ((folder mh-current-folder)
280 (draft (mh-read-draft "redistribution"
281 (if mh-redist-full-contents
282 (mh-msg-filename msg)
283 nil)
284 nil)))
285 (mh-goto-header-end 0)
286 (insert "Resent-To: " to "\n")
287 (if (not (equal cc "")) (insert "Resent-cc: " cc "\n"))
288 (mh-clean-msg-header (point-min)
289 "^Message-Id:\\|^Received:\\|^Return-Path:\\|^Sender:\\|^Date:\\|^From:"
290 nil)
291 (save-buffer)
292 (message "Redistributing...")
293 (if mh-redist-full-contents
294 (call-process "/bin/sh" nil 0 nil "-c"
295 (format "mhdist=1 mhaltmsg=%s %s -push %s"
296 (buffer-file-name)
297 (expand-file-name mh-send-prog mh-progs)
298 (buffer-file-name)))
299 (call-process "/bin/sh" nil 0 nil "-c"
300 (format "mhdist=1 mhaltmsg=%s mhannotate=1 %s -push %s"
301 (mh-msg-filename msg folder)
302 (expand-file-name mh-send-prog mh-progs)
303 (buffer-file-name))))
304 (mh-annotate-msg msg folder mh-note-dist
305 "-component" "Resent:"
306 "-text" (format "\"%s %s\"" to cc))
307 (kill-buffer draft)
308 (message "Redistributing...done"))))
309
310
311(defun mh-reply (msg &optional includep)
312 "Reply to a MESSAGE (default: displayed message).
313If optional prefix argument INCLUDEP provided, then include the message
314in the reply using filter mhl.reply in your MH directory.
315Prompts for type of addresses to reply to:
316 from sender only,
317 to sender and primary recipients,
318 cc/all sender and all recipients.
319See also documentation for `\\[mh-send]' function."
320 (interactive (list (mh-get-msg-num t) current-prefix-arg))
321 (let ((minibuffer-help-form
322 "from => Sender only\nto => Sender and primary recipients\ncc or all => Sender and all recipients"))
323 (let ((reply-to (or mh-reply-default-reply-to
324 (completing-read "Reply to whom: "
325 '(("from") ("to") ("cc") ("all"))
326 nil
327 t)))
328 (folder mh-current-folder)
329 (show-buffer mh-show-buffer)
330 (config (current-window-configuration)))
331 (message "Composing a reply...")
332 (mh-exec-cmd "repl" "-build" "-noquery" "-nodraftfolder"
333 mh-current-folder msg
334 (cond ((or (equal reply-to "from") (equal reply-to ""))
335 '("-nocc" "all"))
336 ((equal reply-to "to")
337 '("-cc" "to"))
338 ((or (equal reply-to "cc") (equal reply-to "all"))
339 '("-cc" "all" "-nocc" "me")))
340 (if includep
341 '("-filter" "mhl.reply")))
342 (let ((draft (mh-read-draft "reply"
343 (expand-file-name "reply" mh-user-path)
344 t)))
345 (delete-other-windows)
346 (set-buffer-modified-p nil)
347
348 (let ((to (mh-get-field "To:"))
349 (subject (mh-get-field "Subject:"))
350 (cc (mh-get-field "Cc:")))
351 (goto-char (point-min))
352 (mh-goto-header-end 1)
353 (or includep
354 (mh-in-show-buffer (show-buffer)
355 (mh-display-msg msg folder)))
356 (mh-add-msgs-to-seq msg 'answered t)
357 (message "Composing a reply...done")
358 (mh-compose-and-send-mail draft "" folder msg to subject cc
359 mh-note-repl "Replied:" config))))))
360
361
362(defun mh-send (to cc subject)
363 "Compose and send a letter.
364The file named by `mh-comp-formfile' will be used as the form.
365The letter is composed in mh-letter-mode; see its documentation for more
366details. If `mh-compose-letter-function' is defined, it is called on the
367draft and passed three arguments: to, subject, and cc.
368Do not call this function from outside mh-e; use \\[mh-smail] instead."
369 (interactive (list
370 (mh-read-address "To: ")
371 (mh-read-address "Cc: ")
372 (read-string "Subject: ")))
373 (let ((config (current-window-configuration)))
374 (delete-other-windows)
375 (mh-send-sub to cc subject config)))
376
377
378(defun mh-send-other-window (to cc subject)
379 "Compose and send a letter in another window.
380Do not call this function from outside mh-e;
381use \\[mh-smail-other-window] instead.
382See also documentation for `\\[mh-send]' function."
383 (interactive (list
384 (mh-read-address "To: ")
385 (mh-read-address "Cc: ")
386 (read-string "Subject: ")))
387 (let ((pop-up-windows t))
388 (mh-send-sub to cc subject (current-window-configuration))))
389
390
391(defun mh-send-sub (to cc subject config)
392 "Do the real work of composing and sending a letter.
393Expects the TO, CC, and SUBJECT fields as arguments.
394CONFIG is the window configuration before sending mail."
395 (let ((folder mh-current-folder)
396 (msg-num (mh-get-msg-num nil)))
397 (message "Composing a message...")
398 (let ((draft (mh-read-draft
399 "message"
400 (let (components)
401 (cond
402 ((file-exists-p
403 (setq components
404 (expand-file-name mh-comp-formfile mh-user-path)))
405 components)
406 ((file-exists-p
407 (setq components
408 (expand-file-name mh-comp-formfile mh-lib)))
409 components)
410 (t
411 (error (format "Can't find components file \"%s\""
412 components)))))
413 nil)))
414 (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc)
415 (goto-char (point-max))
416 (message "Composing a message...done")
417 (mh-compose-and-send-mail draft "" folder msg-num
418 to subject cc
419 nil nil config))))
420
421
422(defun mh-read-draft (use initial-contents delete-contents-file)
423 ;; Read draft file into a draft buffer and make that buffer the current one.
424 ;; USE is a message used for prompting about the intended use of the message.
425 ;; INITIAL-CONTENTS is filename that is read into an empty buffer, or NIL
426 ;; if buffer should not be modified. Delete the initial-contents file if
427 ;; DELETE-CONTENTS-FILE flag is set.
428 ;; Returns the draft folder's name.
429 ;; If the draft folder facility is enabled in ~/.mh_profile, a new buffer is
430 ;; used each time and saved in the draft folder. The draft file can then be
431 ;; reused.
432 (cond (mh-draft-folder
433 (let ((orig-default-dir default-directory)
434 (draft-file-name (mh-new-draft-name)))
435 (pop-to-buffer (generate-new-buffer
436 (format "draft-%s"
437 (file-name-nondirectory draft-file-name))))
438 (condition-case ()
439 (insert-file-contents draft-file-name t)
440 (file-error))
441 (setq default-directory orig-default-dir)))
442 (t
443 (let ((draft-name (expand-file-name "draft" mh-user-path)))
444 (pop-to-buffer "draft") ; Create if necessary
445 (if (buffer-modified-p)
446 (if (y-or-n-p "Draft has been modified; kill anyway? ")
447 (set-buffer-modified-p nil)
448 (error "Draft preserved")))
449 (setq buffer-file-name draft-name)
450 (clear-visited-file-modtime)
451 (unlock-buffer)
452 (cond ((and (file-exists-p draft-name)
453 (not (equal draft-name initial-contents)))
454 (insert-file-contents draft-name)
455 (delete-file draft-name))))))
456 (cond ((and initial-contents
457 (or (zerop (buffer-size))
458 (not (y-or-n-p
459 (format "A draft exists. Use for %s? " use)))))
460 (erase-buffer)
461 (insert-file-contents initial-contents)
462 (if delete-contents-file (delete-file initial-contents))))
463 (auto-save-mode 1)
464 (if mh-draft-folder
465 (save-buffer)) ; Do not reuse draft name
466 (buffer-name))
467
468
469(defun mh-new-draft-name ()
470 ;; Returns the pathname of folder for draft messages.
471 (save-excursion
472 (mh-exec-cmd-quiet t "mhpath" mh-draft-folder "new")
473 (buffer-substring (point-min) (1- (point-max)))))
474
475
476(defun mh-annotate-msg (msg buffer note &rest args)
477 ;; Mark the MESSAGE in BUFFER listing with the character NOTE and annotate
478 ;; the saved message with ARGS.
479 (apply 'mh-exec-cmd "anno" buffer msg args)
480 (save-excursion
481 (cond ((get-buffer buffer) ; Buffer may be deleted
482 (set-buffer buffer)
483 (if (symbolp msg)
484 (mh-notate-seq msg note (1+ mh-cmd-note))
485 (mh-notate msg note (1+ mh-cmd-note)))))))
486
487
488(defun mh-insert-fields (&rest name-values)
489 ;; Insert the NAME-VALUE pairs in the current buffer.
490 ;; If field NAME exists, append VALUE to it.
491 ;; Do not insert any pairs whose value is the empty string.
492 (let ((case-fold-search t))
493 (while name-values
494 (let ((field-name (car name-values))
495 (value (car (cdr name-values))))
496 (cond ((equal value "")
497 nil)
498 ((mh-position-on-field field-name)
499 (insert " " value))
500 (t
501 (insert field-name " " value "\n")))
502 (setq name-values (cdr (cdr name-values)))))))
503
504
505(defun mh-position-on-field (field &optional ignore)
506 ;; Move to the end of the FIELD in the header.
507 ;; Move to end of entire header if FIELD not found.
508 ;; Returns non-nil iff FIELD was found.
509 ;; The optional second arg is for pre-version 4 compatibility.
510 (let ((case-fold-search t))
511 (goto-char (point-min))
512 (mh-goto-header-end 0)
513 (if (re-search-backward (format "^%s" field) nil t)
514 (progn
515 (forward-line 1)
516 (while (looking-at "^[ \t]")
517 (forward-line 1))
518 (backward-char 1) ;to end of previous line
519 t)
520 nil)))
521
522
523(defun mh-goto-header-end (arg)
524 ;; Find the end of the message header in the current buffer and position
525 ;; the cursor at the ARG'th newline after the header.
526 (if (re-search-forward "^$\\|^-+$" nil nil)
527 (forward-line arg)))
528
529
530(defun mh-read-address (prompt)
531 ;; Read a To: or Cc: address, prompting in the minibuffer with PROMPT.
532 ;; May someday do completion on aliases.
533 (read-string prompt))
534
535\f
536
537;;; Mode for composing and sending a draft message.
538
539(defvar mh-sent-from-folder nil
540 "Folder of msg associated with this letter.")
541
542(defvar mh-sent-from-msg nil
543 "Number of msg associated with this letter.")
544
545(defvar mh-send-args nil
546 "Extra arguments to pass to \"send\" command.")
547
548(defvar mh-annotate-char nil
549 "Character to use to annotate mh-sent-from-msg.")
550
551(defvar mh-annotate-field nil
552 "Field name for message annotation.")
553
554(put 'mh-letter-mode 'mode-class 'special)
555
556;;;###autoload
557(defun mh-letter-mode ()
558 "Mode for composing letters in mh-e.\\<mh-letter-mode-map>
559When you have finished composing, type \\[mh-send-letter] to send the letter.
560
561\\{mh-letter-mode-map}
562
563Variables controlling this mode (defaults in parentheses):
564
565 mh-delete-yanked-msg-window (nil)
566 If non-nil, \\[mh-yank-cur-msg] will delete any windows displaying
567 the yanked message.
568
569 mh-yank-from-start-of-msg (t)
570 If non-nil, \\[mh-yank-cur-msg] will include the entire message.
571 If `body', just yank the body (no header).
572 If nil, only the portion of the message following the point will be yanked.
573 If there is a region, this variable is ignored.
574
575 mh-signature-file-name (\"~/.signature\")
576 File to be inserted into message by \\[mh-insert-signature].
577
578Upon invoking mh-letter-mode, text-mode-hook and mh-letter-mode-hook are
579invoked with no args, if those values are non-nil."
580
581 (interactive)
582 (or mh-user-path (mh-find-path))
583 (kill-all-local-variables)
584 (make-local-variable 'paragraph-start)
585 (setq paragraph-start (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-start))
586 (make-local-variable 'paragraph-separate)
587 (setq paragraph-separate
588 (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-separate))
589 (make-local-variable 'mh-send-args)
590 (make-local-variable 'mh-annotate-char)
591 (make-local-variable 'mh-annotate-field)
592 (make-local-variable 'mh-previous-window-config)
593 (make-local-variable 'mh-sent-from-folder)
594 (make-local-variable 'mh-sent-from-msg)
595 (make-local-variable 'mail-header-separator)
596 (setq mail-header-separator "--------") ;for Hyperbole
597 (use-local-map mh-letter-mode-map)
598 (setq major-mode 'mh-letter-mode)
599 (mh-set-mode-name "MH-Letter")
600 (set-syntax-table mh-letter-mode-syntax-table)
601 (run-hooks 'text-mode-hook)
602 ;; if text-mode-hook turned on auto-fill, tune it for messages
603 (cond ((and (boundp 'auto-fill-hook) auto-fill-hook) ;emacs 18
604 (make-local-variable 'auto-fill-hook)
605 (setq auto-fill-hook 'mh-auto-fill-for-letter)))
606 (cond ((and (boundp 'auto-fill-function) auto-fill-function) ;emacs 19
607 (make-local-variable 'auto-fill-function)
608 (setq auto-fill-function 'mh-auto-fill-for-letter)))
609 (run-hooks 'mh-letter-mode-hook))
610
611
612(defun mh-auto-fill-for-letter ()
613 ;; Auto-fill in letters treats the header specially by inserting a tab
614 ;; before continuation line.
615 (do-auto-fill)
616 (if (mh-in-header-p)
617 (save-excursion
618 (beginning-of-line nil)
619 (insert-char ?\t 1))))
620
621
622(defun mh-in-header-p ()
623 ;; Return non-nil if the point is in the header of a draft message.
624 (save-excursion
625 (let ((cur-point (point)))
626 (goto-char (point-min))
627 (re-search-forward "^-*$" nil t)
628 (< cur-point (point)))))
629
630
631(defun mh-to-field ()
632 "Move point to the end of a specified header field.
633The field is indicated by the previous keystroke (the last keystroke
634of the command) according to the list in mh-to-field-choices.
635Create the field if it does not exist. Set the mark to point before moving."
636 (interactive)
637 (expand-abbrev)
638 (let ((target (cdr (assoc (logior last-input-char ?`) mh-to-field-choices)))
639 (case-fold-search t))
640 (push-mark)
641 (cond ((mh-position-on-field target)
642 (let ((eol (point)))
643 (skip-chars-backward " \t")
644 (delete-region (point) eol))
645 (if (and (not (eq (logior last-input-char ?`) ?s))
646 (save-excursion
647 (backward-char 1)
648 (not (looking-at "[:,]"))))
649 (insert ", ")
650 (insert " ")))
651 (t
652 (if (mh-position-on-field "To:")
653 (forward-line 1))
654 (insert (format "%s \n" target))
655 (backward-char 1)))))
656
657
658(defun mh-to-fcc (&optional folder)
659 "Insert an Fcc: FOLDER field in the current message.
660Prompt for the field name with a completion list of the current folders."
661 (interactive)
662 (or folder
663 (setq folder (mh-prompt-for-folder
664 "Fcc"
665 (or (and mh-msg-folder-hook
666 (save-excursion
667 (goto-char (point-min))
668 (funcall mh-msg-folder-hook)))
669 "")
670 t)))
671 (let ((last-input-char ?\C-f))
672 (expand-abbrev)
673 (save-excursion
674 (mh-to-field)
675 (insert (if (mh-folder-name-p folder)
676 (substring folder 1)
677 folder)))))
678
679
680(defun mh-insert-signature ()
681 "Insert the file named by mh-signature-file-name at the current point."
682 (interactive)
683 (insert-file-contents mh-signature-file-name)
684 (set-buffer-modified-p (buffer-modified-p))) ; force mode line update
685
686
687(defun mh-check-whom ()
688 "Verify recipients of the current letter."
689 (interactive)
690 (let ((file-name (buffer-file-name)))
691 (save-buffer)
692 (message "Checking recipients...")
693 (mh-in-show-buffer ("*Recipients*")
694 (bury-buffer (current-buffer))
695 (erase-buffer)
696 (mh-exec-cmd-output "whom" t file-name))
697 (message "Checking recipients...done")))
698
699\f
700
701;;; Routines to compose and send a letter.
702
703(defun mh-compose-and-send-mail (draft send-args
704 sent-from-folder sent-from-msg
705 to subject cc
706 annotate-char annotate-field
707 config)
708 ;; Edit and compose a draft message in buffer DRAFT and send or save it.
709 ;; SENT-FROM-FOLDER is buffer containing scan listing of current folder, or
710 ;; nil if none exists.
711 ;; SENT-FROM-MSG is the message number or sequence name or nil.
712 ;; SEND-ARGS is an optional argument passed to the send command.
713 ;; The TO, SUBJECT, and CC fields are passed to the
714 ;; mh-compose-letter-function.
715 ;; If ANNOTATE-CHAR is non-null, it is used to notate the scan listing of the
716 ;; message. In that case, the ANNOTATE-FIELD is used to build a string
717 ;; for mh-annotate-msg.
718 ;; CONFIG is the window configuration to restore after sending the letter.
719 (pop-to-buffer draft)
720 (mh-letter-mode)
721 (setq mh-sent-from-folder sent-from-folder)
722 (setq mh-sent-from-msg sent-from-msg)
723 (setq mh-send-args send-args)
724 (setq mh-annotate-char annotate-char)
725 (setq mh-annotate-field annotate-field)
726 (setq mh-previous-window-config config)
727 (setq mode-line-buffer-identification (list "{%b}"))
728 (if (and (boundp 'mh-compose-letter-function)
729 (symbol-value 'mh-compose-letter-function))
730 ;; run-hooks will not pass arguments.
731 (let ((value (symbol-value 'mh-compose-letter-function)))
732 (if (and (listp value) (not (eq (car value) 'lambda)))
733 (while value
734 (funcall (car value) to subject cc)
735 (setq value (cdr value)))
736 (funcall mh-compose-letter-function to subject cc)))))
737
738
739(defun mh-send-letter (&optional arg)
740 "Send the draft letter in the current buffer.
741If optional prefix argument is provided, monitor delivery.
742Run mh-before-send-letter-hook before doing anything."
743 (interactive "P")
744 (run-hooks 'mh-before-send-letter-hook)
745 (set-buffer-modified-p t) ; Make sure buffer is written
746 (save-buffer)
747 (message "Sending...")
748 (let ((draft-buffer (current-buffer))
749 (file-name (buffer-file-name))
750 (config mh-previous-window-config))
751 (cond (arg
752 (pop-to-buffer "MH mail delivery")
753 (erase-buffer)
754 (mh-exec-cmd-output mh-send-prog t "-watch" "-nopush"
755 "-nodraftfolder" mh-send-args file-name)
756 (goto-char (point-max)) ; show the interesting part
757 (recenter -1)
758 (set-buffer draft-buffer)) ; for annotation below
759 (t
760 (mh-exec-cmd-daemon mh-send-prog "-nodraftfolder" "-noverbose"
761 mh-send-args file-name)))
762 (if mh-annotate-char
763 (mh-annotate-msg mh-sent-from-msg
764 mh-sent-from-folder
765 mh-annotate-char
766 "-component" mh-annotate-field
767 "-text" (format "\"%s %s\""
768 (mh-get-field "To:")
769 (mh-get-field "Cc:"))))
770
771 (cond ((or (not arg)
772 (y-or-n-p "Kill draft buffer? "))
773 (kill-buffer draft-buffer)
774 (if config
775 (set-window-configuration config))))
776 (if arg
777 (message "Sending...done")
778 (message "Sending...backgrounded"))))
779
780
781(defun mh-insert-letter (msg folder verbatum)
782 "Insert a MESSAGE from any FOLDER into the current letter.
783Removes the message's headers using mh-invisible-headers.
784Prefixes each non-blank line with mh-ins-buf-prefix (default \"> \").
785If prefix argument VERBATUM provided, do not indent and do not delete
786headers. Leaves the mark before the letter and point after it."
787 (interactive
788 (list (read-input (format "Message number%s: "
789 (if mh-sent-from-msg
790 (format " [%d]" mh-sent-from-msg)
791 "")))
792 (mh-prompt-for-folder "Message from" mh-sent-from-folder nil)
793 current-prefix-arg))
794 (save-restriction
795 (narrow-to-region (point) (point))
796 (let ((start (point-min)))
797 (if (equal msg "") (setq msg (int-to-string mh-sent-from-msg)))
798 (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear"
799 (expand-file-name msg
800 (mh-expand-file-name folder)))
801 (cond ((not verbatum)
802 (mh-clean-msg-header start mh-invisible-headers mh-visible-headers)
803 (set-mark start) ; since mh-clean-msg-header moves it
804 (mh-insert-prefix-string mh-ins-buf-prefix))))))
805
806
807(defun mh-yank-cur-msg ()
808 "Insert the current message into the draft buffer.
809Prefix each non-blank line in the message with the string in
810`mh-ins-buf-prefix'. If a region is set in the message's buffer, then
811only the region will be inserted. Otherwise, the entire message will
812be inserted if `mh-yank-from-start-of-msg' is non-nil. If this variable
813is nil, the portion of the message following the point will be yanked.
814If `mh-delete-yanked-msg-window' is non-nil, any window displaying the
815yanked message will be deleted."
816 (interactive)
817 (if (and mh-sent-from-folder mh-sent-from-msg)
818 (let ((to-point (point))
819 (to-buffer (current-buffer)))
820 (set-buffer mh-sent-from-folder)
821 (if mh-delete-yanked-msg-window
822 (delete-windows-on mh-show-buffer))
823 (set-buffer mh-show-buffer) ; Find displayed message
824 (let ((mh-ins-str (cond ((if (boundp 'mark-active)
825 mark-active ;Emacs 19
826 (mark)) ;Emacs 18
827 (buffer-substring (region-beginning)
828 (region-end)))
829 ((eq 'body mh-yank-from-start-of-msg)
830 (buffer-substring
831 (save-excursion
832 (goto-char (point-min))
833 (mh-goto-header-end 1)
834 (point))
835 (point-max)))
836 (mh-yank-from-start-of-msg
837 (buffer-substring (point-min) (point-max)))
838 (t
839 (buffer-substring (point) (point-max))))))
840 (set-buffer to-buffer)
841 (narrow-to-region to-point to-point)
842 (push-mark)
843 (insert mh-ins-str)
844 (mh-insert-prefix-string mh-ins-buf-prefix)
845 (insert "\n")
846 (widen)))
847 (error "There is no current message")))
848
849
850(defun mh-insert-prefix-string (mh-ins-string)
851 ;; Run MAIL-CITATION-HOOK to insert a prefix string before each line
852 ;; in the buffer. Generality for supercite users.
853 (save-excursion
854 (set-mark (point-max))
855 (goto-char (point-min))
856 (cond (mail-citation-hook
857 (run-hooks 'mail-citation-hook))
858 (mh-yank-hooks ;old hook name
859 (run-hooks 'mh-yank-hooks))
860 (t
861 (or (bolp) (forward-line 1))
862 (while (< (point) (mark))
863 (insert mh-ins-string)
864 (forward-line 1))))))
865
866
867(defun mh-fully-kill-draft ()
868 "Kill the draft message file and the draft message buffer.
869Use \\[kill-buffer] if you don't want to delete the draft message file."
870 (interactive)
871 (if (y-or-n-p "Kill draft message? ")
872 (let ((config mh-previous-window-config))
873 (if (file-exists-p (buffer-file-name))
874 (delete-file (buffer-file-name)))
875 (set-buffer-modified-p nil)
876 (kill-buffer (buffer-name))
877 (message "")
878 (if config
879 (set-window-configuration config)))
880 (error "Message not killed")))
881
882;;; Build the letter-mode keymap:
883
884(define-key mh-letter-mode-map "\C-c\C-f\C-b" 'mh-to-field)
885(define-key mh-letter-mode-map "\C-c\C-f\C-c" 'mh-to-field)
886(define-key mh-letter-mode-map "\C-c\C-f\C-f" 'mh-to-fcc)
887(define-key mh-letter-mode-map "\C-c\C-f\C-s" 'mh-to-field)
888(define-key mh-letter-mode-map "\C-c\C-f\C-t" 'mh-to-field)
889(define-key mh-letter-mode-map "\C-c\C-fb" 'mh-to-field)
890(define-key mh-letter-mode-map "\C-c\C-fc" 'mh-to-field)
891(define-key mh-letter-mode-map "\C-c\C-ff" 'mh-to-fcc)
892(define-key mh-letter-mode-map "\C-c\C-fs" 'mh-to-field)
893(define-key mh-letter-mode-map "\C-c\C-ft" 'mh-to-field)
894(define-key mh-letter-mode-map "\C-c\C-q" 'mh-fully-kill-draft)
895(define-key mh-letter-mode-map "\C-c\C-w" 'mh-check-whom)
896(define-key mh-letter-mode-map "\C-c\C-i" 'mh-insert-letter)
897(define-key mh-letter-mode-map "\C-c\C-y" 'mh-yank-cur-msg)
898(define-key mh-letter-mode-map "\C-c\C-s" 'mh-insert-signature)
899(define-key mh-letter-mode-map "\C-c\C-c" 'mh-send-letter)
900(define-key mh-letter-mode-map "\C-c\C-m\C-f" 'mh-mhn-compose-forw)
901(define-key mh-letter-mode-map "\C-c\C-m\C-e" 'mh-mhn-compose-anon-ftp)
902(define-key mh-letter-mode-map "\C-c\C-m\C-t" 'mh-mhn-compose-external-compressed-tar)
903(define-key mh-letter-mode-map "\C-c\C-m\C-i" 'mh-mhn-compose-insertion)
904(define-key mh-letter-mode-map "\C-c\C-e" 'mh-edit-mhn)
905(define-key mh-letter-mode-map "\C-c\C-m\C-u" 'mh-revert-mhn-edit)
906
907
908;;; autoloads from mh-mime
909
910(autoload 'mh-mhn-compose-insertion "mh-mime"
911 "Add a directive to insert a message part from a file." t)
912(autoload 'mh-mhn-compose-anon-ftp "mh-mime"
913 "Add a directive for an anonymous ftp external body part." t)
914(autoload 'mh-mhn-compose-external-compressed-tar "mh-mime"
915 "Add a directive to include a reference to a compressed tar file." t)
916(autoload 'mh-mhn-compose-forw "mh-mime"
917 "Add a forw directive to this message." t)
918(autoload 'mh-edit-mhn "mh-mime"
919 "Filter the current draft through the mhn program for MIME formatting.
920Using directives already inserted in the draft, fills in
921all the MIME components and header fields.
922This step should be done last just before sending the message.
923The mhn program is part of MH version 6.8 or later.
924The `\\[mh-revert-mhn-edit]' command undoes this command.
925For assistance with creating MIME directives to insert
926various types of components in a message, see
927\\[mh-mhn-compose-insertion] (generic insertion from a file),
928\\[mh-mhn-compose-anon-ftp] (external reference to file via anonymous ftp),
929\\[mh-mhn-compose-external-compressed-tar] \
930\(reference to compressed tar file via anonymous ftp), and
931\\[mh-mhn-compose-forw] (forward message)." t)
932
933(autoload 'mh-revert-mhn-edit "mh-mime"
934 "Undoes the effect of \\[mh-edit-mhn] by reverting to the backup file." t)