|
|
1.1 root 1: ;;; mh-e.el (Version: 3.6 for GNU Emacs Version 18 and MH.5 and MH.6)
2:
3: (defvar mh-e-RCS-id)
4: (setq mh-e-RCS-id "$Header: mh-e.el,v 2.24 88/08/29 12:07:53 larus Exp $")
5: (provide 'mh-e)
6:
7: ;;; Copyright (C) 1985, 1986, 1987, 1988 Free Software Foundation, Inc.
8: ;;; Author: James Larus ([email protected] or ucbvax!larus)
9: ;;; Please send suggestions and corrections to the above address.
10: ;;;
11: ;;; This file contains mh-e, a GNU Emacs front end to the MH mail system.
12:
13:
14: ;; GNU Emacs is distributed in the hope that it will be useful,
15: ;; but without any warranty. No author or distributor
16: ;; accepts responsibility to anyone for the consequences of using it
17: ;; or for whether it serves any particular purpose or works at all,
18: ;; unless he says so in writing.
19:
20: ;; Everyone is granted permission to copy, modify and redistribute
21: ;; GNU Emacs, but only under the conditions described in the
22: ;; document "GNU Emacs copying permission notice". An exact copy
23: ;; of the document is supposed to have been given to you along with
24: ;; GNU Emacs so that you can know how you may redistribute it all.
25: ;; It should be in a file named COPYING. Among other things, the
26: ;; copyright notice and this notice must be preserved on all copies.
27:
28:
29: ;;; Original version for Gosling emacs by Brian Reid, Stanford, 1982.
30: ;;; Modified by James Larus, BBN, July 1984 and UCB, 1984 & 1985.
31: ;;; Rewritten for GNU Emacs, James Larus 1985. [email protected]
32: ;;; Modified by Stephen Gildea 1988. [email protected]
33:
34:
35: ;;; NB. MH must have been compiled with the MHE compiler flag or several
36: ;;; features necessary mh-e will be missing from MH commands, specifically
37: ;;; the -build switch to repl and forw.
38:
39:
40:
41: ;;; Constants:
42:
43: ;;; Set for local environment:
44: ;;;* These are now in paths.el.
45: ;;;(defvar mh-progs "/usr/new/mh/" "Directory containing MH commands")
46: ;;;(defvar mh-lib "/usr/new/lib/mh/" "Directory of MH library")
47:
48: (defvar mh-redist-full-contents t
49: "Non-nil if the `dist' command needs whole letter for redistribution (i.e.,
50: when `send' is compiled with the BERK option). Nil otherwise.")
51:
52:
53: ;;; Mode hooks:
54:
55: (defvar mh-folder-mode-hook nil
56: "*Invoked in mh-folder-mode on a new folder.")
57:
58: (defvar mh-letter-mode-hook nil
59: "*Invoked in mh-letter-mode on a new letter.")
60:
61: (defvar mh-compose-letter-hook nil
62: "*Invoked in mh-compose-and-send-mail on an outgoing letter. It is passed
63: three arguments: TO recipients, SUBJECT, and CC recipients.")
64:
65: (defvar mh-inc-folder-hook nil
66: "*Invoked after incorporating new mail into a folder.")
67:
68:
69:
70: ;;; Personal preferences:
71:
72: (defvar mh-clean-message-header nil
73: "*Non-nil means remove invisible header lines or only show visible header
74: lines in messages.")
75:
76: (defvar mh-visible-headers nil
77: "*If non-nil, it contains a regexp specifying the headers that are shown in
78: a message if mh-clean-message-header is non-nil. Setting this variable
79: overrides mh-invisible-headers.")
80:
81: (defvar mhl-formfile nil
82: "*Name of format file to be used by mhl to show messages.
83: A value of T means use the default format file.
84: Nil means don't use mhl to format messages.")
85:
86: (defvar mh-lpr-command-format "lpr -p -J '%s'"
87: "*Format for Unix command line to print a message. The format should be
88: a unix command line, with the string \"%s\" where the folder and message
89: number should appear.")
90:
91: (defvar mh-print-background nil
92: "*Print messages in the background if non-nil. WARNING: do not delete
93: the messages until printing is finished; otherwise, your output may be
94: truncated.")
95:
96: (defvar mh-summary-height 4
97: "*Number of lines in summary window.")
98:
99: (defvar mh-recenter-summary-p nil
100: "*Recenter summary window when the show window is toggled off if
101: this is non-nil.")
102:
103: (defvar mh-ins-buf-prefix ">> "
104: "*String to put before each non-blank line of the the current message
105: as it is inserted in an outgoing letter.")
106:
107: (defvar mh-do-not-confirm nil
108: "*Non-nil means do not prompt for confirmation before executing some
109: innocuous commands.")
110:
111: (defvar mh-bury-show-buffer t
112: "*Non-nil means that the displayed show buffer for a folder is buried.")
113:
114: (defvar mh-delete-yanked-msg-window nil
115: "*If non-nil, yanking the current message into a letter being composed,
116: with \\[mh-yank-cur-msg], deletes any windows displaying the message.")
117:
118: (defvar mh-yank-from-start-of-msg t
119: "*If non-nil, \\[mh-yank-cur-msg] will include the entire message. If
120: `body' then the message minus the header will be yanked. If nil, only the
121: portion of the message following the point will be yanked. If there is a
122: region in the show buffer, this variable is ignored.")
123:
124: (defvar mh-reply-default-reply-to nil
125: "*If non-nil, then \\[mh-reply] will use this as the person or persons to
126: which the reply will be sent. The value should be one of \"from\", \"to\", or
127: \"cc\".")
128:
129: (defvar mh-recursive-folders nil
130: "*If non-nil, then commands which operate on folders do so recursively.")
131:
132:
133: ;;; Parameterize mh-e to work with different scan formats. The defaults work
134: ;;; the standard MH scan listings.
135:
136: (defvar mh-cmd-note 4
137: "Offset to insert notation")
138:
139: (defvar mh-good-msg-regexp "^....[^D^]"
140: "Regexp specifiying the scan lines that are 'good' messages.")
141:
142: (defvar mh-deleted-msg-regexp "^....D"
143: "Regexp matching scan lines of deleted messages.")
144:
145: (defvar mh-refiled-msg-regexp "^....\\^"
146: "Regexp matching scan lines of refiled messages.")
147:
148: (defvar mh-valid-scan-line "^[ ]*[0-9]"
149: "Regexp matching scan lines for messages (not error messages).")
150:
151: (defvar mh-msg-number-regexp "^[ ]*\\([0-9]+\\)"
152: "Regexp matching the number of a message in a scan line. It must surround
153: the number with \\( \\)")
154:
155: (defvar mh-msg-search-regexp "^[^0-9]*%d[^0-9]"
156: "String for format that will return a regexp matching the scan listing for
157: a given message number.")
158:
159: (defvar mh-flagged-scan-msg-regexp "^....\\D\\|^....\\^\\|^....\\+\\|^.....%"
160: "Regexp matching scan lines marked as deleted, refiled, in a sequence, or
161: the cur message.")
162:
163: (defvar mh-cur-scan-msg-regexp "^....\\+"
164: "regexp matching scan line for the cur message.")
165:
166:
167: ;;; Real constants:
168:
169: (defvar mh-invisible-headers
170: "^Received: \\|^Message-Id: \\|^Remailed-\\|^Via: \\|^Mail-from: \\|^Return-Path: \\|^In-Reply-To: \\|^Resent-"
171: "Regexp specifying headers that are not to be shown.")
172:
173: (defvar mh-rejected-letter-start "^ ----- Unsent message follows -----$"
174: "Regexp specifying the beginning of the wrapper around a letter returned
175: by the mail system.")
176:
177: (defvar mh-to-field-choices '((?t . "To:") (?s . "Subject:") (?c . "Cc:")
178: (?b . "Bcc:") (?f . "Fcc:"))
179: "A-list of (character . field name) strings for mh-to-field.")
180:
181:
182: ;;; Global variables:
183:
184: (defvar mh-user-path ""
185: "User's mail folder.")
186:
187: (defvar mh-last-destination nil
188: "Destination of last `refile' command.")
189:
190: (defvar mh-folder-mode-map (make-keymap)
191: "Keymap for MH folders.")
192:
193: (defvar mh-letter-mode-map (make-sparse-keymap)
194: "Keymap for composing mail.")
195:
196: (defvar mh-pick-mode-map (make-sparse-keymap)
197: "Keymap for searching folder.")
198:
199: (defvar mh-letter-mode-syntax-table nil
200: "Syntax table used while in mh-e letter mode.")
201:
202: (if mh-letter-mode-syntax-table
203: ()
204: (setq mh-letter-mode-syntax-table
205: (make-syntax-table text-mode-syntax-table))
206: (set-syntax-table mh-letter-mode-syntax-table)
207: (modify-syntax-entry ?% "." mh-letter-mode-syntax-table))
208:
209: (defvar mh-folder-list nil
210: "List of folder names for completion.")
211:
212: (defvar mh-draft-folder nil
213: "Name of folder containing draft messages.
214: NIL means do not use draft folder.")
215:
216: (defvar mh-unseen-seq nil
217: "Name of the unseen sequence.")
218:
219:
220: ;;; Macros and generic functions:
221:
222: (defmacro mh-push (v l)
223: (list 'setq l (list 'cons v l)))
224:
225: (defmacro when (pred &rest body)
226: (list 'cond (cons pred body)))
227:
228: (defun mapc (func list)
229: (while list
230: (funcall func (car list))
231: (setq list (cdr list))))
232:
233:
234: (defun mh-list* (&rest args) (mh-make-list* args))
235:
236: (defun mh-make-list* (arglist)
237: (cond ((null arglist) ())
238: ((null (cdr arglist)) (car arglist))
239: (t (cons (car arglist) (mh-make-list* (cdr arglist))))))
240:
241:
242:
243: ;;; Entry points:
244:
245: (defun mh-rmail (&optional arg)
246: "Inc(orporate) new mail (no arg) or scan a MH mail box (arg given).
247: This front end uses the MH mail system, which uses different conventions
248: from the usual mail system."
249: (interactive "P")
250: (mh-find-path)
251: (if arg
252: (call-interactively 'mh-visit-folder)
253: (mh-inc-folder)))
254:
255:
256: (defun mh-smail ()
257: "Send mail using the MH mail system."
258: (interactive)
259: (mh-find-path)
260: (call-interactively 'mh-send))
261:
262:
263: (defun mh-smail-other-window ()
264: "Send mail in other window using the MH mail system."
265: (interactive)
266: (mh-find-path)
267: (call-interactively 'mh-send-other-window))
268:
269:
270:
271: ;;; User executable mh-e commands:
272:
273: (defun mh-burst-digest ()
274: "Burst apart the current message, which should be a digest. Message is
275: replaced by its table of contents and the letters from the digest are inserted
276: into the folder after that message."
277: (interactive)
278: (let ((digest (mh-get-msg-num t)))
279: (mh-process-or-undo-commands mh-current-folder)
280: (message "Bursting digest...")
281: (mh-exec-cmd "burst" mh-current-folder digest "-inplace")
282: (mh-scan-folder mh-current-folder (format "%d-last" mh-first-msg-num))
283: (message "Bursting digest...done")))
284:
285:
286: (defun mh-copy-msg (prefix-provided msg-or-seq dest)
287: "Copy specified MESSAGE(s) (default: displayed message) to another
288: FOLDER without deleting them.
289: If (optional) prefix argument provided, then prompt for the message sequence."
290: (interactive (list current-prefix-arg
291: (if current-prefix-arg
292: (mh-read-seq "Copy" t mh-narrowed-to-seq)
293: (mh-get-msg-num t))
294: (mh-prompt-for-folder "Copy to" "" t)))
295: (mh-exec-cmd "refile" msg-or-seq "-link" "-src" mh-current-folder dest)
296: (if prefix-provided
297: (mh-notate-seq msg-or-seq ?C mh-cmd-note)
298: (mh-notate msg-or-seq ?C mh-cmd-note)))
299:
300:
301: (defun mh-delete-msg (prefix-provided msg-or-seq)
302: "Mark the specified MESSAGE(s) (default: displayed message) for later
303: deletion.
304: If (optional) prefix argument provided, then prompt for the message sequence."
305: (interactive (list current-prefix-arg
306: (if current-prefix-arg
307: (mh-read-seq "Delete" t mh-narrowed-to-seq)
308: (mh-get-msg-num t))))
309: (if prefix-provided
310: (mh-map-to-seq-msgs 'mh-delete-a-msg msg-or-seq)
311: (mh-delete-a-msg msg-or-seq))
312: (mh-next-msg))
313:
314:
315: (defun mh-delete-msg-from-seq (prefix-provided msg-or-seq &optional from-seq)
316: "Delete MESSAGE (default: displayed message) from SEQUENCE.
317: If (optional) prefix argument provided, then delete all messages from a
318: sequence."
319: (interactive (let ((argp current-prefix-arg))
320: (list argp
321: (if argp
322: (mh-read-seq "Delete" t mh-narrowed-to-seq)
323: (mh-get-msg-num t))
324: (if (not argp)
325: (mh-read-seq "Delete from" t mh-narrowed-to-seq)))))
326: (if prefix-provided
327: (mh-remove-seq msg-or-seq)
328: (mh-remove-msg-from-seq msg-or-seq from-seq)))
329:
330:
331: (defun mh-edit-again (msg)
332: "Clean-up a draft or a message previously sent and make it resendable."
333: (interactive (list (mh-get-msg-num t)))
334: (let* ((from-folder mh-current-folder)
335: (config (current-window-configuration))
336: (draft
337: (cond ((and mh-draft-folder (equal from-folder mh-draft-folder))
338: (find-file (mh-msg-filename msg))
339: (rename-buffer (format "draft-%d" msg))
340: (buffer-name))
341: (t
342: (mh-read-draft "clean-up" (mh-msg-filename msg) nil)))))
343: (mh-clean-msg-header (point-min)
344: "^Date:\\|^Received:\\|^Message-Id:\\|^From:"
345: nil)
346: (goto-char (point-min))
347: (set-buffer-modified-p nil)
348: (mh-compose-and-send-mail draft "" from-folder nil nil nil nil nil nil
349: config)))
350:
351:
352: (defun mh-execute-commands ()
353: "Process outstanding delete and refile requests."
354: (interactive)
355: (if mh-narrowed-to-seq (mh-widen))
356: (save-excursion
357: (mh-process-commands mh-current-folder))
358: (mh-goto-cur-msg)
359: (mh-set-scan-mode)
360: (mh-make-folder-mode-line))
361:
362:
363: (defun mh-extract-rejected-mail (msg)
364: "Extract a letter returned by the mail system (default: displayed message)
365: and make it resendable."
366: (interactive (list (mh-get-msg-num t)))
367: (let ((from-folder mh-current-folder)
368: (config (current-window-configuration))
369: (draft (mh-read-draft "extraction" (mh-msg-filename msg) nil)))
370: (goto-char (point-min))
371: (cond ((re-search-forward mh-rejected-letter-start nil t)
372: (forward-char 1)
373: (delete-region (point-min) (point))
374: (mh-clean-msg-header (point-min)
375: "^Date:\\|^Received:\\|^Message-Id:\\|^From:\\|^Sender:"
376: nil))
377: (t
378: (message "Does not appear to be a rejected letter.")))
379: (goto-char (point-min))
380: (set-buffer-modified-p nil)
381: (mh-compose-and-send-mail draft "" from-folder msg (mh-get-field "To")
382: (mh-get-field "From") (mh-get-field "cc")
383: nil nil config)))
384:
385:
386: (defun mh-forward (prefix-provided msg-or-seq to cc)
387: "Forward MESSAGE(s) (default: displayed message).
388: If (optional) prefix argument provided, then prompt for the message sequence."
389: (interactive (list current-prefix-arg
390: (if current-prefix-arg
391: (mh-read-seq "Forward" t mh-narrowed-to-seq)
392: (mh-get-msg-num t))
393: (read-string "To: ")
394: (read-string "Cc: ")))
395: (let* ((folder mh-current-folder)
396: (config (current-window-configuration))
397: ;; forw always leaves file in "draft" since it doesn't have -draft
398: (draft-name (mh-expand-file-name "draft" mh-user-path))
399: (draft (cond ((or (not (file-exists-p draft-name))
400: (y-or-n-p "The file 'draft' exists. Discard it? "))
401: (mh-exec-cmd "forw" "-build"
402: mh-current-folder msg-or-seq)
403: (prog1
404: (mh-read-draft "" draft-name t)
405: (mh-insert-fields "To:" to "Cc:" cc)
406: (set-buffer-modified-p nil)))
407: (t
408: (mh-read-draft "" draft-name nil)))))
409: (goto-char (point-min))
410: (re-search-forward "^------- Forwarded Message")
411: (previous-line 1)
412: (narrow-to-region (point) (point-max))
413: (let* ((subject (save-excursion (mh-get-field "From:")))
414: (trim (string-match "<" subject))
415: (forw-subject (save-excursion (mh-get-field "Subject:"))))
416: (if trim
417: (setq subject (substring subject 0 (- trim 1))))
418: (widen)
419: (save-excursion
420: (mh-insert-fields "Subject:" (format "[%s: %s]" subject forw-subject)))
421: (delete-other-windows)
422: (if prefix-provided
423: (mh-add-msgs-to-seq (mh-seq-to-msgs msg-or-seq) 'forwarded t)
424: (mh-add-msgs-to-seq msg-or-seq 'forwarded t))
425: (mh-compose-and-send-mail draft "" folder msg-or-seq
426: to subject cc
427: "F" "Forwarded:"
428: config))))
429:
430:
431: (defun mh-goto-msg (number &optional no-error-if-no-message dont-show)
432: "Position the cursor at message NUMBER.
433: Non-nil second argument means do not signal an error if message does not exist.
434: Non-nil third argument means not to show the message.
435: Return non-nil if cursor is at message."
436: (interactive "NMessage number? ")
437: (let ((cur-msg (mh-get-msg-num nil))
438: (starting-place (point))
439: (msg-pattern (mh-msg-search-pat number)))
440: (cond ((cond ((and cur-msg (= cur-msg number)) t)
441: ((and cur-msg
442: (< cur-msg number)
443: (re-search-forward msg-pattern nil t)) t)
444: ((and cur-msg
445: (> cur-msg number)
446: (re-search-backward msg-pattern nil t)) t)
447: (t ; Do thorough search of buffer
448: (goto-char (point-min))
449: (re-search-forward msg-pattern nil t)))
450: (beginning-of-line)
451: (if (not dont-show) (mh-maybe-show number))
452: t)
453: (t
454: (goto-char starting-place)
455: (if (not no-error-if-no-message)
456: (error "No message %d " number))
457: nil))))
458:
459:
460: (defun mh-inc-folder (&optional maildrop-name)
461: "Inc(orporate) new mail into +inbox.
462: Optional prefix argument specifies an alternate maildrop from the default.
463: If this is given, mail is incorporated into the current folder, rather
464: than +inbox."
465: (interactive (list (if current-prefix-arg
466: (expand-file-name
467: (read-file-name "inc mail from file: "
468: mh-user-path)))))
469: (let ((config (current-window-configuration)))
470: (if (not maildrop-name)
471: (cond ((not (get-buffer "+inbox"))
472: (mh-make-folder "+inbox")
473: (setq mh-previous-window-config config))
474: ((not (eq (current-buffer) (get-buffer "+inbox")))
475: (switch-to-buffer "+inbox")
476: (setq mh-previous-window-config config)))))
477: (mh-get-new-mail maildrop-name)
478: (run-hooks 'mh-inc-folder-hook))
479:
480:
481: (defun mh-kill-folder ()
482: "Remove the current folder."
483: (interactive)
484: (if (or mh-do-not-confirm
485: (yes-or-no-p (format "Remove folder %s? " mh-current-folder)))
486: (let ((folder mh-current-folder))
487: (mh-exec-cmd-demon "rmf" folder)
488: (mh-remove-folder-from-folder-list folder)
489: (message "Folder removed")
490: (kill-buffer folder))
491: (message "Folder not removed")))
492:
493:
494: (defun mh-list-folders ()
495: "List mail folders."
496: (interactive)
497: (with-output-to-temp-buffer " *mh-temp*"
498: (save-excursion
499: (switch-to-buffer " *mh-temp*")
500: (erase-buffer)
501: (message "listing folders...")
502: (mh-exec-cmd-output "folders" t)
503: (goto-char (point-min))
504: (message "listing folders...done"))))
505:
506:
507: (defun mh-msg-is-in-seq (msg)
508: "Display the sequences that contain MESSAGE (default: displayed message)."
509: (interactive (list (mh-get-msg-num t)))
510: (message "Message %d is in sequences: %s"
511: msg
512: (mapconcat 'concat
513: (mh-list-to-string (mh-seq-containing-msg msg))
514: " ")))
515:
516:
517: (defun mh-narrow-to-seq (seq)
518: "Restrict display of this folder to just messages in a sequence.
519: Reads which sequence. Use \\[mh-widen] to undo this command."
520: (interactive (list (mh-read-seq "Narrow to" t)))
521: (let ((eob (point-max))
522: (buffer-read-only nil))
523: (cond ((mh-seq-to-msgs seq)
524: (mh-copy-seq-to-point seq eob)
525: (narrow-to-region eob (point-max))
526: (mh-make-folder-mode-line (symbol-name seq))
527: (recenter)
528: (setq mh-narrowed-to-seq seq))
529: (t
530: (error "No messages in sequence `%s'" (symbol-name seq))))))
531:
532:
533: (defun mh-next-undeleted-msg (&optional arg)
534: "Move to next undeleted message in window."
535: (interactive "p")
536: (forward-line (if arg arg 1))
537: (setq mh-next-direction 'forward)
538: (cond ((re-search-forward mh-good-msg-regexp nil 0 arg)
539: (beginning-of-line)
540: (mh-maybe-show (mh-get-msg-num t)))
541: (t
542: (forward-line -1)
543: (if (get-buffer mh-show-buffer)
544: (delete-windows-on mh-show-buffer)))))
545:
546:
547: (defun mh-pack-folder ()
548: "Execute any outstanding commands for the current folder, then renumber the
549: remaining messages to be 1..N."
550: (interactive)
551: (message "packing buffer...")
552: (mh-pack-folder-1)
553: (mh-goto-cur-msg)
554: (message "packing buffer...done"))
555:
556:
557: (defun mh-refile-msg (prefix-provided msg-or-seq dest)
558: "Refile MESSAGE(s) (default: displayed message) in FOLDER.
559: If (optional) prefix argument provided, then prompt for message sequence."
560: (interactive
561: (list current-prefix-arg
562: (if current-prefix-arg
563: (mh-read-seq "Refile" t mh-narrowed-to-seq)
564: (mh-get-msg-num t))
565: (intern
566: (mh-prompt-for-folder "Destination"
567: (if (eq 'refile (car mh-last-destination))
568: (symbol-name (cdr mh-last-destination))
569: "")
570: t))))
571: (setq mh-last-destination (cons 'refile dest))
572: (if prefix-provided
573: (mh-map-to-seq-msgs 'mh-refile-a-msg msg-or-seq dest)
574: (mh-refile-a-msg msg-or-seq dest))
575: (mh-next-msg))
576:
577:
578: (defun mh-refile-or-write-again (msg)
579: "Re-execution the last refile or write command on the given MESSAGE (default:
580: displayed message).
581: Use the same folder or file as the previous refile or write command."
582: (interactive (list (mh-get-msg-num t)))
583: (if (null mh-last-destination)
584: (error "No previous refile"))
585: (cond ((eq (car mh-last-destination) 'refile)
586: (mh-refile-a-msg msg (cdr mh-last-destination))
587: (message "Destination folder: %s" (cdr mh-last-destination)))
588: (t
589: (mh-write-msg-to-file msg (cdr mh-last-destination))
590: (message "Destination: %s" (cdr mh-last-destination))))
591: (mh-next-msg))
592:
593:
594: (defun mh-reply (prefix-provided msg)
595: "Reply to a MESSAGE (default: displayed message).
596: If (optional) prefix argument provided, then include the message in the reply."
597: (interactive (list current-prefix-arg (mh-get-msg-num t)))
598: (let ((minibuffer-help-form
599: "from => Sender only\nto => Sender and primary recipients\ncc or all => Sender and all recipients"))
600: (let ((reply-to (or mh-reply-default-reply-to
601: (completing-read "Reply to whom: "
602: '(("from") ("to") ("cc") ("all"))
603: nil
604: t)))
605: (msg-filename (mh-msg-filename msg))
606: (folder mh-current-folder)
607: (show-buffer mh-show-buffer)
608: (config (current-window-configuration)))
609: (message "Composing a reply...")
610: (cond ((or (equal reply-to "from") (equal reply-to ""))
611: (apply 'mh-exec-cmd
612: (mh-list* "repl" "-build"
613: "-nodraftfolder" mh-current-folder
614: msg
615: "-nocc" "all"
616: (if prefix-provided
617: (list "-filter" "mhl.reply")))))
618: ((equal reply-to "to")
619: (apply 'mh-exec-cmd
620: (mh-list* "repl" "-build"
621: "-nodraftfolder" mh-current-folder
622: msg
623: "-cc" "to"
624: (if prefix-provided
625: (list "-filter" "mhl.reply")))))
626: ((or (equal reply-to "cc") (equal reply-to "all"))
627: (apply 'mh-exec-cmd
628: (mh-list* "repl" "-build"
629: "-nodraftfolder" mh-current-folder
630: msg
631: "-cc" "all" "-nocc" "me"
632: (if prefix-provided
633: (list "-filter" "mhl.reply"))))))
634:
635: (let ((draft (mh-read-draft "reply"
636: (mh-expand-file-name "reply" mh-user-path)
637: t)))
638: (delete-other-windows)
639: (set-buffer-modified-p nil)
640:
641: (let ((to (mh-get-field "To:"))
642: (subject (mh-get-field "Subject:"))
643: (cc (mh-get-field "Cc:")))
644: (goto-char (point-min))
645: (mh-goto-header-end 1)
646: (if (not prefix-provided)
647: (mh-display-msg msg msg-filename show-buffer))
648: (mh-add-msgs-to-seq msg 'answered t)
649: (message "Composing a reply...done")
650: (mh-compose-and-send-mail draft "" folder msg to subject cc
651: "-" "Replied:" config))))))
652:
653:
654: (defun mh-restore-window-config ()
655: "Restore the previous window configuration, if one exists."
656: (interactive)
657: (if mh-previous-window-config
658: (set-window-configuration mh-previous-window-config)))
659:
660:
661: (defun mh-page-digest ()
662: "Advance displayed message to next digested message."
663: (interactive)
664: (save-excursion
665: (mh-show-message-in-other-window)
666: ;; Go to top of screen (in case user moved point).
667: (move-to-window-line 0)
668: (let ((case-fold-search nil))
669: ;; Search for blank line and then for From:
670: (when (not (and (search-forward "\n\n" nil t)
671: (search-forward "From:" nil t)))
672: (other-window -1)
673: (error "No more messages.")))
674: ;; Go back to previous blank line, then forward to the first non-blank.
675: (search-backward "\n\n" nil t)
676: (forward-line 2)
677: (recenter 0)
678: (other-window -1)))
679:
680:
681: (defun mh-page-digest-backwards ()
682: "Back up displayed message to previous digested message."
683: (interactive)
684: (save-excursion
685: (mh-show-message-in-other-window)
686: ;; Go to top of screen (in case user moved point).
687: (move-to-window-line 0)
688: (let ((case-fold-search nil))
689: (beginning-of-line)
690: (when (not (and (search-backward "\n\n" nil t)
691: (search-backward "From:" nil t)))
692: (other-window -1)
693: (error "No more messages.")))
694: ;; Go back to previous blank line, then forward to the first non-blank.
695: (search-backward "\n\n" nil t)
696: (forward-line 2)
697: (recenter 0)
698: (other-window -1)))
699:
700:
701: (defun mh-page-msg (&optional arg)
702: "Page the displayed message forwards ARG lines or a full screen if no
703: argument is supplied."
704: (interactive "P")
705: (scroll-other-window arg))
706:
707:
708: (defun mh-previous-page (&optional arg)
709: "Page the displayed message backwards ARG lines or a full screen if no
710: argument is supplied."
711: (interactive "P")
712: (save-excursion
713: (mh-show-message-in-other-window)
714: (unwind-protect
715: (scroll-down arg)
716: (other-window -1))))
717:
718:
719: (defun mh-previous-undeleted-msg (&optional arg)
720: "Move to previous undeleted message in window."
721: (interactive "p")
722: (setq mh-next-direction 'backward)
723: (beginning-of-line 1)
724: (cond ((re-search-backward mh-good-msg-regexp nil 0 arg)
725: (mh-maybe-show (mh-get-msg-num t)))
726: (t
727: (if (get-buffer mh-show-buffer)
728: (delete-windows-on mh-show-buffer)))))
729:
730:
731: (defun mh-print-msg (prefix-provided msg-or-seq)
732: "Print MESSAGE(s) (default: displayed message) on a line printer.
733: If (optional) prefix argument provided, then prompt for the message sequence."
734: (interactive (list current-prefix-arg
735: (if current-prefix-arg
736: (reverse (mh-seq-to-msgs
737: (mh-read-seq "Print" t mh-narrowed-to-seq)))
738: (list (mh-get-msg-num t)))))
739: (if prefix-provided
740: (message "printing sequence...")
741: (message "printing message..."))
742: (let ((command
743: (if prefix-provided
744: (format "(scan -clear %s ; %s -nobell -clear %s %s) | %s"
745: (mapconcat (function (lambda (msg) msg)) msg-or-seq " ")
746: (mh-expand-file-name "mhl" mh-lib)
747: (if (stringp mhl-formfile)
748: (format "-form %s" mhl-formfile)
749: "")
750: (mh-msg-filenames msg-or-seq mh-folder-filename)
751: (format mh-lpr-command-format
752: (if prefix-provided
753: (format "Sequence from %s" mh-current-folder)
754: (format "%s/%d" mh-current-folder
755: (car msg-or-seq)))))
756: (format "%s -nobell -clear %s %s | %s"
757: (mh-expand-file-name "mhl" mh-lib)
758: (mh-msg-filenames msg-or-seq mh-folder-filename)
759: (if (stringp mhl-formfile)
760: (format "-form %s" mhl-formfile)
761: "")
762: (format mh-lpr-command-format
763: (if prefix-provided
764: (format "Sequence from %s" mh-current-folder)
765: (format "%s/%d" mh-current-folder
766: (car msg-or-seq))))))))
767: (if mh-print-background
768: (mh-exec-cmd-demon shell-file-name "-c" command)
769: (call-process shell-file-name nil nil nil "-c" command))
770: (if prefix-provided
771: (mh-notate-seq msg-or-seq ?P mh-cmd-note)
772: (mh-notate (car msg-or-seq) ?P mh-cmd-note))
773: (mh-add-msgs-to-seq msg-or-seq 'printed t)
774: (if prefix-provided
775: (message "printing sequence...done")
776: (message "printing message...done"))))
777:
778:
779: (defun mh-put-msg-in-seq (prefix-provided from to)
780: "Add MESSAGE(s) (default: displayed message) to SEQUENCE.
781: If (optional) prefix argument provided, then prompt for the message sequence."
782: (interactive (list current-prefix-arg
783: (if current-prefix-arg
784: (mh-seq-to-msgs
785: (mh-read-seq "Add messages from" t
786: mh-narrowed-to-seq))
787: (mh-get-msg-num t))
788: (mh-read-seq "Add to" nil mh-narrowed-to-seq)))
789: (mh-add-msgs-to-seq from to))
790:
791:
792: (defun mh-rescan-folder (range)
793: "Rescan a folder after optionally processing the outstanding commands.
794: If (optional) prefix argument provided, prompt for the range of messages to
795: display. Otherwise show the entire folder."
796: (interactive (list (if current-prefix-arg
797: (read-string "Range [all]? ")
798: "all")))
799: (setq mh-next-direction 'forward)
800: (mh-scan-folder mh-current-folder range))
801:
802:
803: (defun mh-redistribute (to cc msg)
804: "Redistribute a letter."
805: (interactive (list (read-string "Redist-To: ")
806: (read-string "Redist-Cc: ")
807: (mh-get-msg-num t)))
808: (save-window-excursion
809: (let ((msg-filename (mh-msg-filename msg))
810: (folder mh-current-folder)
811: (draft (mh-read-draft "redistribution"
812: (if mh-redist-full-contents
813: (mh-msg-filename msg)
814: nil)
815: nil)))
816: (mh-goto-header-end 0)
817: (insert "Resent-To: " to "\n")
818: (if (not (equal cc "")) (insert "Resent-cc: " cc "\n"))
819: (mh-clean-msg-header (point-min)
820: "^Message-Id:\\|^Received:\\|^Return-Path:\\|^Sender:\\|^Date:\\|^From:"
821: nil)
822: (save-buffer)
823: (message "Redistributing...")
824: (if mh-redist-full-contents
825: (call-process "/bin/sh" nil 0 nil "-c"
826: (format "mhdist=1 mhaltmsg=%s %s -push %s"
827: (buffer-file-name)
828: (mh-expand-file-name "send" mh-progs)
829: (buffer-file-name)))
830: (call-process "/bin/sh" nil 0 nil "-c"
831: (format "mhdist=1 mhaltmsg=%s mhannotate=1 %s -push %s"
832: msg-filename
833: (mh-expand-file-name "send" mh-progs)
834: (buffer-file-name))))
835: (mh-annotate-msg msg folder "R"
836: "-component" "Resent:"
837: "-text" (format "\"%s %s\"" to cc))
838: (kill-buffer draft)
839: (message "Redistributing...done"))))
840:
841:
842: (defun mh-write-msg-to-file (msg file)
843: "Append MESSAGE to the end of a FILE."
844: (interactive (list (mh-get-msg-num t)
845: (expand-file-name
846: (read-file-name "Save message in file: "
847: (if (eq 'write (car mh-last-destination))
848: (cdr mh-last-destination)
849: "")))))
850: (setq mh-last-destination (cons 'write file))
851: (let ((file-name (mh-msg-filename msg)))
852: (save-excursion
853: (set-buffer (get-buffer-create " *mh-temp*"))
854: (erase-buffer)
855: (insert-file-contents file-name)
856: (append-to-file (point-min) (point-max) file))))
857:
858:
859: (defun mh-search-folder (folder)
860: "Search FOLDER for messages matching a pattern."
861: (interactive (list (mh-prompt-for-folder "Search"
862: mh-current-folder
863: t)))
864: (switch-to-buffer-other-window "pick-pattern")
865: (if (or (zerop (buffer-size))
866: (not (y-or-n-p "Reuse pattern? ")))
867: (mh-make-pick-template)
868: (message ""))
869: (setq mh-searching-folder folder))
870:
871:
872: (defun mh-send (to cc subject)
873: "Compose and send a letter."
874: (interactive "sTo: \nsCc: \nsSubject: ")
875: (let ((config (current-window-configuration)))
876: (delete-other-windows)
877: (mh-send-sub to cc subject config)))
878:
879:
880: (defun mh-send-other-window (to cc subject)
881: "Compose and send a letter in another window.."
882: (interactive "sTo: \nsCc: \nsSubject: ")
883: (let ((pop-up-windows t))
884: (mh-send-sub to cc subject (current-window-configuration))))
885:
886:
887: (defun mh-send-sub (to cc subject config)
888: "Do the real work of composing and sending a letter.
889: Expects the TO, CC, and SUBJECT fields as arguments.
890: CONFIG is the window configuration before sending mail."
891: (let ((folder (if (boundp 'mh-current-folder) mh-current-folder))
892: (msg-num (mh-get-msg-num nil)))
893: (message "Composing a message...")
894: (let ((draft (mh-read-draft
895: "message"
896: (if (file-exists-p (mh-expand-file-name "components"
897: mh-user-path))
898: (mh-expand-file-name "components" mh-user-path)
899: (if (file-exists-p (mh-expand-file-name "components"
900: mh-lib))
901: (mh-expand-file-name "components" mh-lib)
902: (error "Can't find components file")))
903: nil)))
904: (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc)
905: (set-buffer-modified-p nil)
906: (goto-char (point-max))
907: (message "Composing a message...done")
908: (mh-compose-and-send-mail draft "" folder msg-num
909: to subject cc
910: nil nil config))))
911:
912:
913: (defun mh-show (msg)
914: "Show MESSAGE (default: displayed message)."
915: (interactive (list (mh-get-msg-num t)))
916: (setq mh-summarize nil)
917: (mh-set-mode-name "mh-e show")
918: (let ((folder mh-current-folder))
919: (mh-display-msg msg (mh-msg-filename msg) mh-show-buffer)
920:
921: ;; These contortions are to force the summary line to be the top window.
922: (switch-to-buffer-other-window folder)
923: (delete-other-windows)
924: (mh-show-message-in-other-window)
925: (switch-to-buffer-other-window folder)
926: (shrink-window (- (window-height) mh-summary-height))
927: (recenter '(4)) ;center this line
928: (if mh-bury-show-buffer (bury-buffer mh-show-buffer))
929: (if (not (memq msg mh-seen-list)) (mh-push msg mh-seen-list))))
930:
931:
932: (defun mh-sort-folder ()
933: "Sort the messages in the current folder by date."
934: (interactive "")
935: (mh-process-or-undo-commands mh-current-folder)
936: (setq mh-next-direction 'forward)
937: (message "sorting folder...")
938: (mh-exec-cmd "sortm" mh-current-folder)
939: (message "sorting folder...done")
940: (mh-scan-folder mh-current-folder "all"))
941:
942:
943: (defun mh-toggle-summarize ()
944: "Turn the summary mode of displaying messages on or off."
945: (interactive)
946: (if mh-summarize
947: (mh-show (mh-get-msg-num t))
948: (mh-set-scan-mode)))
949:
950:
951: (defun mh-undo (prefix-provided msg-or-seq)
952: "Undo the deletion or refile of the specified MESSAGE(s)
953: \(default: displayed message).
954: If (optional) prefix argument provided, then prompt for the message sequence."
955: (interactive (list current-prefix-arg
956: (if current-prefix-arg
957: (mh-read-seq "Undo" t mh-narrowed-to-seq)
958: (mh-get-msg-num t))))
959: (beginning-of-line)
960: (cond ((looking-at mh-deleted-msg-regexp)
961: (cond (prefix-provided
962: (mapc (function (lambda (msg)
963: (setq mh-delete-list
964: (delq msg mh-delete-list))
965: (mh-remove-msg-from-seq msg 'deleted t)))
966: (mh-seq-to-msgs msg-or-seq))
967: (mh-notate-seq msg-or-seq ? mh-cmd-note))
968: (t
969: (setq mh-delete-list (delq msg-or-seq mh-delete-list))
970: (mh-remove-msg-from-seq msg-or-seq 'deleted t)
971: (mh-notate msg-or-seq ? mh-cmd-note))))
972:
973: ((looking-at mh-refiled-msg-regexp)
974: (cond (prefix-provided
975: (mapc (function (lambda (msg)
976: (mapc (function
977: (lambda (dest)
978: (mh-remove-msg-from-seq msg dest t)))
979: mh-refile-list)))
980: (mh-seq-to-msgs msg-or-seq))
981: (mh-notate-seq msg-or-seq ? mh-cmd-note))
982: (t
983: (mapc (function (lambda (dest)
984: (mh-remove-msg-from-seq msg-or-seq dest t)))
985: mh-refile-list)
986: (mh-notate msg-or-seq ? mh-cmd-note))))
987:
988: (t nil))
989: (if (mh-outstanding-commands-p)
990: (mh-set-folder-modified-p nil)))
991:
992:
993: (defun mh-undo-folder ()
994: "Undo all commands in current folder."
995: (interactive "")
996: (cond ((or mh-do-not-confirm
997: (yes-or-no-p "Undo all commands in folder? "))
998: (setq mh-delete-list nil
999: mh-refile-list nil
1000: mh-seq-list nil
1001: mh-next-direction 'forward)
1002: (mh-unmark-all-headers t)
1003: (mh-set-folder-modified-p nil))
1004: (t
1005: (message "Commands not undone.")
1006: (sit-for 2))))
1007:
1008:
1009: (defun mh-visit-folder (folder range config)
1010: "Visit FOLDER and display RANGE of messages."
1011: (interactive (list (mh-prompt-for-folder "Visit" "+inbox" t)
1012: (read-string "Range [all]? ")
1013: (current-window-configuration)))
1014: (mh-scan-folder folder (if (equal range "") "all" range))
1015: (setq mh-previous-window-config config))
1016:
1017:
1018: (defun mh-widen ()
1019: "Remove restrictions from the current folder, thereby showing all messages."
1020: (interactive "")
1021: (let ((buffer-read-only nil))
1022: (delete-region (point-min) (point-max))
1023: (widen)
1024: (mh-make-folder-mode-line))
1025: (setq mh-narrowed-to-seq nil))
1026:
1027:
1028:
1029: ;;; Support routines.
1030:
1031: (defun mh-delete-a-msg (msg)
1032: ;; Delete the MESSAGE.
1033: (save-excursion
1034: (mh-goto-msg msg nil nil)
1035: (if (looking-at mh-refiled-msg-regexp)
1036: (error "Message %d is refiled. Undo refile before deleting." msg))
1037: (mh-push msg mh-delete-list)
1038: (mh-add-msgs-to-seq msg 'deleted t)
1039: (mh-notate msg ?D mh-cmd-note)
1040: (mh-set-folder-modified-p t)))
1041:
1042:
1043: (defun mh-refile-a-msg (msg destination)
1044: ;; Refile the MESSAGE in the FOLDER.
1045: (save-excursion
1046: (mh-goto-msg msg nil nil)
1047: (cond ((looking-at mh-deleted-msg-regexp)
1048: (error "Message %d is deleted. Undo delete before moving." msg))
1049: (t
1050: (if (not (memq destination mh-refile-list))
1051: (mh-push destination mh-refile-list))
1052: (mh-add-msgs-to-seq msg destination t)
1053: (mh-notate msg ?^ mh-cmd-note)
1054: (mh-set-folder-modified-p t)))))
1055:
1056:
1057: (defun mh-display-msg (msg-num msg-filename show-buffer)
1058: ;; Display the message NUMBER and PATHNAME in BUFFER.
1059: (if (not (file-exists-p msg-filename))
1060: (error "Message %d does not exist." msg-num))
1061: ;; Bind these variables in case they are local to folder buffer.
1062: (let ((formfile mhl-formfile)
1063: (clean-message-header mh-clean-message-header)
1064: (invisible-headers mh-invisible-headers)
1065: (visible-headers mh-visible-headers))
1066: (switch-to-buffer show-buffer)
1067: (if mh-bury-show-buffer (bury-buffer (current-buffer)))
1068: (when (not (equal msg-filename buffer-file-name))
1069: ;; Buffer does not yet contain message.
1070: (clear-visited-file-modtime)
1071: (unlock-buffer)
1072: (erase-buffer)
1073: (if formfile
1074: (if (stringp formfile)
1075: (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear"
1076: "-form" formfile msg-filename)
1077: (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear"
1078: msg-filename))
1079: (insert-file-contents msg-filename t))
1080: (goto-char (point-min))
1081: (cond (clean-message-header
1082: (mh-clean-msg-header (point-min)
1083: invisible-headers
1084: visible-headers)
1085: (goto-char (point-min)))
1086: (t
1087: (let ((case-fold-search t))
1088: (re-search-forward
1089: "^To:\\|^From:\\|^Subject:\\|^Date:" nil t)
1090: (beginning-of-line)
1091: (recenter 0))))
1092: (set-buffer-modified-p nil)
1093: (setq buffer-file-name msg-filename)
1094: (set-mark nil)
1095: (setq mode-line-buffer-identification
1096: (list "{%b} " (format "%s" folder) "/" (format "%d" msg-num))))))
1097:
1098:
1099: (defun mh-show-message-in-other-window ()
1100: (let ((buffer mh-show-buffer))
1101: (switch-to-buffer-other-window buffer)
1102: (if mh-bury-show-buffer (bury-buffer (current-buffer)))))
1103:
1104:
1105: (defun mh-clean-msg-header (start invisible-headers visible-headers)
1106: ;; Flush extraneous lines in a message header, from the given POINT to the
1107: ;; end of the message header. If VISIBLE-HEADERS is non-nil, it contains a
1108: ;; regular expression specifying the lines to display, otherwise
1109: ;; INVISIBLE-HEADERS contains a regular expression specifying lines to
1110: ;; delete from the header.
1111: (let ((case-fold-search t))
1112: (save-restriction
1113: (goto-char start)
1114: (if (search-forward "\n\n" nil t)
1115: (backward-char 2))
1116: (narrow-to-region start (point))
1117: (goto-char (point-min))
1118: (if visible-headers
1119: (while (< (point) (point-max))
1120: (beginning-of-line)
1121: (cond ((looking-at visible-headers)
1122: (forward-line 1)
1123: (while (looking-at "^[ \t]+") (forward-line 1)))
1124: (t
1125: (mh-delete-line 1)
1126: (while (looking-at "^[ \t]+")
1127: (beginning-of-line)
1128: (mh-delete-line 1)))))
1129: (while (re-search-forward invisible-headers nil t)
1130: (beginning-of-line)
1131: (mh-delete-line 1)
1132: (while (looking-at "^[ \t]+")
1133: (beginning-of-line)
1134: (mh-delete-line 1))))
1135: (unlock-buffer))))
1136:
1137:
1138: (defun mh-delete-line (lines)
1139: ;; Delete version of kill-line.
1140: (delete-region (point) (save-excursion (forward-line lines) (point))))
1141:
1142:
1143: (defun mh-read-draft (use initial-contents delete-contents-file)
1144: ;; Read draft file into a draft buffer and make that buffer the current one.
1145: ;; USE is a message used for prompting about the intended use of the message.
1146: ;; INITIAL-CONTENTS is filename that is read into an empty buffer, or NIL
1147: ;; if buffer should not be modified. Delete the initial-contents file if
1148: ;; DELETE-CONTENTS-FILE flag is set.
1149: ;; Returns the draft folder's name.
1150: ;; If the draft folder facility is enabled in ~/.mh_profile, a new buffer is
1151: ;; used each time and saved in the draft folder. The draft file can then be
1152: ;; reused.
1153: (cond (mh-draft-folder
1154: (pop-to-buffer (find-file-noselect (mh-new-draft-name) t))
1155: (rename-buffer (format "draft-%s" (buffer-name))))
1156: (t
1157: (let ((draft-name (mh-expand-file-name "draft" mh-user-path)))
1158: (pop-to-buffer "draft") ; Create if necessary
1159: (if (buffer-modified-p)
1160: (if (y-or-n-p "Draft has been modified; kill anyway? ")
1161: (set-buffer-modified-p nil)
1162: (error "Draft preserved.")))
1163: (setq buffer-file-name draft-name)
1164: (clear-visited-file-modtime)
1165: (unlock-buffer)
1166: (when (and (file-exists-p draft-name)
1167: (not (equal draft-name initial-contents)))
1168: (insert-file-contents draft-name)
1169: (delete-file draft-name)))))
1170: (when (and initial-contents
1171: (or (zerop (buffer-size))
1172: (not (y-or-n-p
1173: (format "A draft exists. Use for %s? " use)))))
1174: (erase-buffer)
1175: (insert-file-contents initial-contents)
1176: (if delete-contents-file (delete-file initial-contents)))
1177: (auto-save-mode 1)
1178: (if mh-draft-folder
1179: (save-buffer)) ; Do not reuse draft name
1180: (buffer-name))
1181:
1182:
1183: (defun mh-new-draft-name ()
1184: ;; Returns the pathname of folder for draft messages.
1185: (save-excursion
1186: (set-buffer (get-buffer-create " *mh-temp*"))
1187: (erase-buffer)
1188: (mh-exec-cmd-output "mhpath" nil mh-draft-folder "new")
1189: (buffer-substring (point) (- (mark) 1))))
1190:
1191:
1192: (defun mh-next-msg ()
1193: ;; Move backward or forward to the next undeleted message in the buffer.
1194: (if (eq mh-next-direction 'forward)
1195: (mh-next-undeleted-msg 1)
1196: (mh-previous-undeleted-msg 1)))
1197:
1198:
1199: (defun mh-set-scan-mode ()
1200: ;; Display the scan listing buffer, but do not show a message.
1201: (if (get-buffer mh-show-buffer)
1202: (delete-windows-on mh-show-buffer))
1203: (mh-set-mode-name "mh-e scan")
1204: (setq mh-summarize t)
1205: (if mh-recenter-summary-p
1206: (recenter (/ (window-height) 2))))
1207:
1208:
1209: (defun mh-maybe-show (msg)
1210: ;; If the scan listing is not summarized, then display the message pointed
1211: ;; to by the cursor is the scan listing.
1212: (if (not mh-summarize) (mh-show msg)))
1213:
1214:
1215: (defun mh-set-mode-name (mode-name-string)
1216: ;; Set the mode-name and ensure that the mode line is updated.
1217: (setq mode-name mode-name-string)
1218: ;; Force redisplay of all buffers' mode lines to be considered.
1219: (save-excursion (set-buffer (other-buffer)))
1220: (set-buffer-modified-p (buffer-modified-p)))
1221:
1222:
1223:
1224: ;;; The folder data abstraction.
1225:
1226: (defvar mh-current-folder nil "Name of current folder")
1227: (defvar mh-show-buffer nil "Buffer that displays mesage for this folder")
1228: (defvar mh-folder-filename nil "Full path of directory for this folder")
1229: (defvar mh-summarize nil "If non-nil, show scan list only")
1230: (defvar mh-next-seq-num nil "Index of free sequence id")
1231: (defvar mh-delete-list nil "list of msg numbers to delete")
1232: (defvar mh-refile-list nil "list of folder names in mh-seq-list")
1233: (defvar mh-seq-list nil "alist of (seq .msgs ) numbers")
1234: (defvar mh-seen-list nil "list of displayed messages")
1235: (defvar mh-next-direction 'forward "direction to move to next message")
1236: (defvar mh-narrowed-to-seq nil "sequence display is narrowed to")
1237: (defvar mh-first-msg-num nil "number of first msg in buffer")
1238: (defvar mh-last-msg-num nil "number of last msg in buffer")
1239:
1240: (defun mh-make-folder (name)
1241: ;; Create and initialize a new mail folder called NAME and make it the
1242: ;; current folder.
1243: (switch-to-buffer name)
1244: (kill-all-local-variables)
1245: (setq buffer-read-only nil)
1246: (erase-buffer)
1247: (make-local-vars
1248: 'mh-current-folder name ; Name of folder
1249: 'mh-show-buffer (format "show-%s" name) ; Buffer that displays messages
1250: 'mh-folder-filename ; e.g. /usr/foobar/Mail/inbox/
1251: (file-name-as-directory (mh-expand-file-name name))
1252: 'mh-summarize t ; Show scan list only?
1253: 'mh-next-seq-num 0 ; Index of free sequence id
1254: 'mh-delete-list nil ; List of msgs nums to delete
1255: 'mh-refile-list nil ; List of folder names in mh-seq-list
1256: 'mh-seq-list nil ; Alist of (seq . msgs) nums
1257: 'mh-seen-list nil ; List of displayed messages
1258: 'mh-next-direction 'forward ; Direction to move to next message
1259: 'mh-narrowed-to-seq nil ; Sequence display is narrowed to
1260: 'mh-first-msg-num nil ; Number of first msg in buffer
1261: 'mh-last-msg-num nil ; Number of last msg in buffer
1262: 'mh-previous-window-config nil) ; Previous window configuration
1263: (mh-folder-mode)
1264: (setq buffer-read-only t)
1265: (mh-set-folder-modified-p nil)
1266: (auto-save-mode -1)
1267: (setq buffer-offer-save t)
1268: (mh-set-mode-name "mh-e scan"))
1269:
1270:
1271:
1272: (defun make-local-vars (&rest pairs)
1273: ;; Take VARIABLE-VALUE pairs and makes local variables initialized to the
1274: ;; value.
1275: (while pairs
1276: (make-variable-buffer-local (car pairs))
1277: (set (car pairs) (car (cdr pairs)))
1278: (setq pairs (cdr (cdr pairs)))))
1279:
1280:
1281: (defun mh-folder-mode ()
1282: "Major mode for \"editing\" an MH folder scan listing.
1283: Messages can be marked for refiling and deletion. However, both actions
1284: are deferred until you request execution with \\[mh-execute-commands].
1285: \\{mh-folder-mode-map}
1286: A prefix argument (\\[universal-argument]) to delete, refile, list, or undo applies the action to a message sequence.
1287:
1288: Variables controlling mh-e operation are (defaults in parentheses):
1289:
1290: mh-bury-show-buffer (t)
1291: Non-nil means that the buffer used to display message is buried.
1292: It will never be offered as the default other buffer.
1293:
1294: mh-clean-message-header (nil)
1295: Non-nil means remove header lines matching the regular expression
1296: specified in mh-invisible-headers from messages.
1297:
1298: mh-visible-headers (nil)
1299: If non-nil, it contains a regexp specifying the headers that are shown in
1300: a message if mh-clean-message-header is non-nil. Setting this variable
1301: overrides mh-invisible-headers.
1302:
1303: mh-do-not-confirm (nil)
1304: Non-nil means do not prompt for confirmation before executing some
1305: non-recoverable commands such as mh-kill-folder and mh-undo-folder.
1306:
1307: mhl-formfile (nil)
1308: Name of format file to be used by mhl to show messages.
1309: A value of T means use the default format file.
1310: Nil means don't use mhl to format messages.
1311:
1312: mh-lpr-command-format (\"lpr -p -J '%s'\")
1313: Format for command used to print a message on a system printer.
1314:
1315: mh-recenter-summary-p (nil)
1316: If non-nil, then the scan listing is recentered when the window displaying
1317: a messages is toggled off.
1318:
1319: mh-summary-height (4)
1320: Number of lines in the summary window.
1321:
1322: mh-ins-buf-prefix (\">> \")
1323: String to insert before each non-blank line of a message as it is
1324: inserted in a letter being composed."
1325:
1326: (use-local-map mh-folder-mode-map)
1327: (setq major-mode 'mh-folder-mode)
1328: (mh-set-mode-name "mh-e folder")
1329: (run-hooks 'mh-folder-mode-hook))
1330:
1331:
1332: (defun mh-scan-folder (folder range)
1333: ;; Scan the FOLDER over the RANGE. Return in the folder's buffer.
1334: (cond ((null (get-buffer folder))
1335: (mh-make-folder folder))
1336: (t
1337: (mh-process-or-undo-commands folder)
1338: (switch-to-buffer folder)))
1339: (mh-regenerate-headers range)
1340: (when (= (count-lines (point-min) (point-max)) 0)
1341: (if (equal range "all")
1342: (message "Folder %s is empty" folder)
1343: (message "No messages in %s, range %s" folder range))
1344: (sit-for 5))
1345: (mh-goto-cur-msg))
1346:
1347:
1348: (defun mh-regenerate-headers (range)
1349: ;; Replace buffer with scan of its contents over range RANGE.
1350: (let ((buffer-read-only nil)
1351: (folder (buffer-name)))
1352: (message (format "scanning %s..." folder))
1353: (erase-buffer)
1354: (mh-exec-cmd-output "scan" nil
1355: "-noclear" "-noheader"
1356: "-width" (window-width)
1357: folder range)
1358: (goto-char (point-min))
1359: (cond ((looking-at "scan: no messages in")
1360: (keep-lines mh-valid-scan-line)) ; Flush random scan lines
1361: ((looking-at "scan: ")) ; Keep error messages
1362: (t
1363: (keep-lines mh-valid-scan-line))) ; Flush random scan lines
1364: (mh-delete-seq-locally 'cur) ; To pick up new one
1365: (setq mh-seq-list (mh-read-folder-sequences folder t))
1366: (mh-notate-user-sequences)
1367: (mh-make-folder-mode-line)
1368: (mh-set-folder-modified-p nil)
1369: (message (format "scanning %s...done" folder))))
1370:
1371:
1372: (defun mh-get-new-mail (maildrop-name)
1373: ;; Read new mail from a maildrop into the current buffer.
1374: ;; Return T if there was new mail, NIL otherwise. Return in the current
1375: ;; buffer.
1376: (let ((buffer-read-only nil)
1377: (point-before-inc (point))
1378: (folder (buffer-name))
1379: (folder-modified-flag (buffer-modified-p)))
1380: (message (if maildrop-name
1381: (format "inc %s -file %s..." folder maildrop-name)
1382: (format "inc %s..." folder)))
1383: (mh-unmark-all-headers nil)
1384: (setq mh-next-direction 'forward)
1385: (keep-lines mh-valid-scan-line) ; Kill old error messages
1386: (goto-char (point-max))
1387: (let ((start-of-inc (point)))
1388: (if maildrop-name
1389: (mh-exec-cmd-output "inc" nil folder
1390: "-file" (expand-file-name maildrop-name)
1391: "-width" (window-width)
1392: "-truncate")
1393: (mh-exec-cmd-output "inc" nil
1394: "-width" (window-width)))
1395: (message
1396: (if maildrop-name
1397: (format "inc %s -file %s...done" folder maildrop-name)
1398: (format "inc %s...done" folder)))
1399: (mh-delete-seq-locally 'cur) ; To pick up new one
1400: (setq mh-seq-list (mh-read-folder-sequences folder t))
1401: (mh-notate-user-sequences)
1402: (goto-char start-of-inc)
1403: (cond ((looking-at "inc: no mail")
1404: (keep-lines mh-valid-scan-line) ; Flush random scan lines
1405: (mh-make-folder-mode-line)
1406: (goto-char point-before-inc)
1407: (message "No new mail%s%s." (if maildrop-name " in " "")
1408: (if maildrop-name maildrop-name ""))
1409: nil)
1410: ((looking-at "inc:") ; Error messages
1411: (mh-make-folder-mode-line)
1412: (goto-char point-before-inc)
1413: (message "inc error")
1414: nil)
1415: (t
1416: (keep-lines mh-valid-scan-line)
1417: (mh-make-folder-mode-line)
1418: (mh-goto-cur-msg)
1419: t)))
1420: (mh-set-folder-modified-p folder-modified-flag)))
1421:
1422:
1423: (defun mh-make-folder-mode-line (&optional annotation)
1424: ;; Set the fields of the mode line for a folder buffer.
1425: ;; The optional ANNOTATION string is displayed after the folder's name.
1426: (save-excursion
1427: (goto-char (point-min))
1428: (setq mh-first-msg-num (mh-get-msg-num nil))
1429: (let* ((lines (count-lines (point-min) (point-max)))
1430: (case-fold-search nil))
1431: (goto-char (point-max))
1432: (previous-line 1)
1433: (setq mh-last-msg-num (mh-get-msg-num nil))
1434: (setq mode-line-buffer-identification
1435: (list (format "{%%b%s} %d msg%s"
1436: (if annotation (format "/%s" annotation) "")
1437: lines
1438: (if (= lines 0)
1439: "s"
1440: (if (> lines 1)
1441: (format "s (%d-%d)" mh-first-msg-num
1442: mh-last-msg-num)
1443: (format " (%d)" mh-first-msg-num)))))))))
1444:
1445:
1446: (defun mh-unmark-all-headers (remove-all-flags)
1447: ;; Remove all '+' flags from the headers, and if called with a non-nil
1448: ;; argument, remove all 'D', '^' and '%' flags too.
1449: (save-excursion
1450: (let ((buffer-read-only nil)
1451: (case-fold-search nil))
1452: (goto-char (point-min))
1453: (while (if remove-all-flags
1454: (re-search-forward mh-flagged-scan-msg-regexp nil t)
1455: (re-search-forward mh-cur-scan-msg-regexp nil t))
1456: (delete-backward-char 1)
1457: (insert " ")
1458: (beginning-of-line))))) ; Check line again
1459:
1460:
1461: (defun mh-goto-cur-msg ()
1462: ;; Position the cursor at the current message.
1463: (let ((cur-msg (car (mh-seq-to-msgs 'cur))))
1464: (cond ((or (null cur-msg) (not (mh-goto-msg cur-msg t nil)))
1465: (goto-char (point-max))
1466: (forward-line -1)
1467: (message "No current message"))
1468: (t
1469: (mh-notate cur-msg ?+ mh-cmd-note)
1470: (recenter 0)
1471: (mh-maybe-show cur-msg)))))
1472:
1473:
1474: (defun mh-pack-folder-1 ()
1475: ;; Close and pack the current folder.
1476: (let ((buffer-read-only nil))
1477: (message "closing folder...")
1478: (mh-process-or-undo-commands mh-current-folder)
1479: (message "packing folder...")
1480: (save-excursion
1481: (mh-exec-cmd-quiet " *mh-temp*" "folder" mh-current-folder "-pack"))
1482: (mh-regenerate-headers "all")
1483: (message "packing done")))
1484:
1485:
1486: (defun mh-process-or-undo-commands (folder)
1487: ;; If FOLDER has outstanding commands, then either process or discard them.
1488: (set-buffer folder)
1489: (if (mh-outstanding-commands-p)
1490: (if (or mh-do-not-confirm
1491: (y-or-n-p
1492: "Process outstanding deletes and refiles (or lose them)? "))
1493: (mh-process-commands folder)
1494: (mh-undo-folder))
1495: (mh-invalidate-show-cache)))
1496:
1497:
1498: (defun mh-process-commands (folder)
1499: ;; Process outstanding commands for the folder FOLDER.
1500: (message "Processing deletes and refiles...")
1501: (set-buffer folder)
1502: (let ((buffer-read-only nil))
1503: ;; Update the unseen sequence if it exists
1504: (if (and mh-seen-list (mh-seq-to-msgs mh-unseen-seq))
1505: (mh-undefine-sequence mh-unseen-seq mh-seen-list))
1506:
1507: ;; Then refile messages
1508: (mapc (function
1509: (lambda (dest)
1510: (let ((msgs (mh-seq-to-msgs dest)))
1511: (when msgs
1512: (mh-delete-scan-msgs msgs)
1513: (apply 'mh-exec-cmd
1514: (nconc (cons "refile" msgs)
1515: (list "-src" folder (symbol-name dest))))))))
1516: mh-refile-list)
1517:
1518: ;; Now delete messages
1519: (when mh-delete-list
1520: (apply 'mh-exec-cmd (mh-list* "rmm" (format "%s" folder) mh-delete-list))
1521: (mh-delete-scan-msgs mh-delete-list))
1522:
1523: ;; Don't need to remove sequences since delete and refile do so.
1524:
1525: ;; Mark cur message
1526: (mh-define-sequence 'cur (list (or (mh-get-msg-num nil) "last")))
1527:
1528: (mh-invalidate-show-cache)
1529:
1530: (setq mh-delete-list nil
1531: mh-refile-list nil
1532: mh-seq-list (mh-read-folder-sequences mh-current-folder nil)
1533: mh-seen-list nil)
1534: (mh-unmark-all-headers t)
1535: (mh-notate-user-sequences)
1536: (mh-set-folder-modified-p nil)
1537: (message "Processing deletes and refiles...done")))
1538:
1539:
1540: (defun mh-invalidate-show-cache ()
1541: ;; Invalidate show buffer file cache.
1542: (if (get-buffer mh-show-buffer)
1543: (save-excursion
1544: (set-buffer mh-show-buffer)
1545: (setq buffer-file-name nil))))
1546:
1547:
1548: (defun mh-delete-scan-msgs (msgs)
1549: ;; Delete the scan listing lines for each of the msgs in the LIST.
1550: (save-excursion
1551: (goto-char (point-min))
1552: (flush-lines (mapconcat 'mh-msg-search-pat msgs "\\|"))))
1553:
1554:
1555: (defun mh-set-folder-modified-p (flag)
1556: "Mark current folder as modified or unmodified according to FLAG."
1557: (set-buffer-modified-p flag))
1558:
1559:
1560: (defun mh-outstanding-commands-p ()
1561: ;; Returns non-nil if there are outstanding deletes or refiles.
1562: (or mh-delete-list mh-refile-list))
1563:
1564:
1565:
1566: ;;; Mode for composing and sending a message.
1567:
1568: (defun mh-letter-mode ()
1569: "Mode for composing letters in mh-e.
1570: When you have finished composing, type \\[mh-send-letter] to send the letter.
1571:
1572: Variables controlling this mode (defaults in parentheses):
1573:
1574: mh-delete-yanked-msg-window (nil)
1575: If non-nil, \\[mh-yank-cur-msg] will delete any windows displaying
1576: the yanked message.
1577:
1578: mh-yank-from-start-of-msg (t)
1579: If non-nil, \\[mh-yank-cur-msg] will include the entire message.
1580: If `body', just yank the body (no header).
1581: If nil, only the portion of the message following the point will be yanked.
1582: If there is a region, this variable is ignored.
1583:
1584: Upon invoking mh-letter-mode, text-mode-hook and mh-letter-mode-hook are
1585: invoked with no args, if those values are non-nil.
1586:
1587: \\{mh-letter-mode-map}"
1588: (interactive)
1589: (kill-all-local-variables)
1590: (make-local-variable 'paragraph-start)
1591: (setq paragraph-start (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-start))
1592: (make-local-variable 'paragraph-separate)
1593: (setq paragraph-separate
1594: (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-separate))
1595: (make-local-variable 'mh-send-args)
1596: (make-local-variable 'mh-annotate-char)
1597: (make-local-variable 'mh-sent-from-folder)
1598: (make-local-variable 'mh-sent-from-msg)
1599: (use-local-map mh-letter-mode-map)
1600: (setq major-mode 'mh-letter-mode)
1601: (mh-set-mode-name "mh-e letter")
1602: (set-syntax-table mh-letter-mode-syntax-table)
1603: (run-hooks 'text-mode-hook 'mh-letter-mode-hook))
1604:
1605:
1606: (defun mh-to-field ()
1607: "Move point to the end of the header field indicated by the previous
1608: keystroke. Create the field if it does not exist. Set the mark to the
1609: point before moving."
1610: (interactive "")
1611: (expand-abbrev)
1612: (let ((target (cdr (assoc (logior last-input-char ?`) mh-to-field-choices)))
1613: (case-fold-search t))
1614: (cond ((mh-position-on-field target t)
1615: (if (not (looking-at "[ \t]")) (insert " ")))
1616: (t
1617: (goto-char (dot-min))
1618: (re-search-forward "^To:")
1619: (forward-line 1)
1620: (while (looking-at "^[ \t]") (forward-line 1))
1621: (insert (format "%s \n" target))
1622: (backward-char 1)))))
1623:
1624:
1625: (defun mh-to-fcc ()
1626: "Insert a Fcc: field in the current message, prompting for the field
1627: name with a completion list of the current folders."
1628: (interactive)
1629: (expand-abbrev)
1630: (save-excursion
1631: (mh-insert-fields "Fcc:"
1632: (substring (mh-prompt-for-folder "Fcc" "" t) 1 nil))))
1633:
1634:
1635: (defun mh-insert-signature ()
1636: "Insert the file ~/.signature at the current point."
1637: (interactive "")
1638: (insert-file-contents "~/.signature"))
1639:
1640:
1641: (defun mh-check-whom ()
1642: "Verify recipients of the current letter."
1643: (interactive)
1644: (let ((file-name (buffer-file-name)))
1645: (set-buffer-modified-p t) ; Force writing of contents
1646: (save-buffer)
1647: (message "Checking recipients...")
1648: (switch-to-buffer-other-window "*Mail Recipients*")
1649: (bury-buffer (current-buffer))
1650: (erase-buffer)
1651: (mh-exec-cmd-output "whom" t file-name)
1652: (other-window -1)
1653: (message "Checking recipients...done")))
1654:
1655:
1656:
1657: ;;; Routines to make a search pattern and search for a message.
1658:
1659: (defun mh-make-pick-template ()
1660: ;; Initialize the current buffer with a template for a pick pattern.
1661: (erase-buffer)
1662: (kill-all-local-variables)
1663: (make-local-variable 'mh-searching-folder)
1664: (insert "From: \n"
1665: "To: \n"
1666: "Cc: \n"
1667: "Date: \n"
1668: "Subject: \n"
1669: "---------\n")
1670: (mh-letter-mode)
1671: (use-local-map mh-pick-mode-map)
1672: (goto-char (point-min))
1673: (end-of-line))
1674:
1675:
1676: (defun mh-do-pick-search ()
1677: "Find messages in the folder named in mh-searching-folder that match the
1678: qualifications in current buffer. Put messages found in a sequence
1679: named `search'."
1680: (interactive)
1681: (let ((pattern-buffer (buffer-name))
1682: (searching-buffer mh-searching-folder)
1683: (range)
1684: (pattern nil)
1685: (new-buffer nil))
1686: (save-excursion
1687: (cond ((get-buffer searching-buffer)
1688: (set-buffer searching-buffer)
1689: (setq range (format "%d-%d" mh-first-msg-num mh-last-msg-num)))
1690: (t
1691: (mh-make-folder searching-buffer)
1692: (setq range "all")
1693: (setq new-buffer t))))
1694: (message "Searching...")
1695: (goto-char (point-min))
1696: (while (setq pattern (mh-next-pick-field pattern-buffer))
1697: (setq msgs (mh-seq-from-command searching-buffer
1698: 'search
1699: (nconc (cons "pick" pattern)
1700: (list searching-buffer
1701: range
1702: "-sequence" "search"
1703: "-list"))))
1704: (setq range "search"))
1705: (message "Searching...done")
1706: (if new-buffer
1707: (mh-scan-folder searching-buffer msgs)
1708: (switch-to-buffer searching-buffer))
1709: (delete-other-windows)
1710: (mh-notate-seq 'search ?% (+ mh-cmd-note 1))))
1711:
1712:
1713: (defun mh-next-pick-field (buffer)
1714: ;; Return the next piece of a pick argument that can be extracted from the
1715: ;; BUFFER. Returns nil if no pieces remain.
1716: (set-buffer buffer)
1717: (let ((case-fold-search t))
1718: (cond ((eobp)
1719: nil)
1720: ((re-search-forward "^\\([a-z].*\\):[ \t]*\\([a-z0-9].*\\)$" nil t)
1721: (let* ((component
1722: (format "-%s"
1723: (downcase (buffer-substring (match-beginning 1)
1724: (match-end 1)))))
1725: (pat (buffer-substring (match-beginning 2) (match-end 2))))
1726: (forward-line 1)
1727: (list component pat)))
1728: ((re-search-forward "^-*$" nil t)
1729: (forward-char 1)
1730: (let ((body (buffer-substring (point) (point-max))))
1731: (if (and (> (length body) 0) (not (equal body "\n")))
1732: (list "-search" body)
1733: nil)))
1734: (t
1735: nil))))
1736:
1737:
1738:
1739: ;;; Routines to compose and send a letter.
1740:
1741: (defun mh-compose-and-send-mail (draft send-args
1742: sent-from-folder sent-from-msg
1743: to subject cc
1744: annotate-char annotate-field
1745: config)
1746: ;; Edit and compose a draft message in buffer DRAFT and send or save it.
1747: ;; SENT-FROM-FOLDER is buffer containing scan listing of current folder, or
1748: ;; nil if none exists.
1749: ;; SENT-FROM-MSG is the message number or sequence name or nil.
1750: ;; SEND-ARGS is an optional argument passed to the send command.
1751: ;; nThe TO, SUBJECT, and CC fields are passed to the mh-compose-letter-hook.
1752: ;; If ANNOTATE-CHAR is non-null, it is used to notate the scan listing of the
1753: ;; message. In that case, the ANNOTATE-FIELD is used to build a string
1754: ;; for mh-annotate-msg.
1755: ;; CONFIG is the window configuration to restore after sending the letter.
1756: (pop-to-buffer draft)
1757: (mh-letter-mode)
1758: (make-local-vars
1759: 'mh-annotate-field annotate-field
1760: 'mh-previous-window-config config)
1761: (setq mh-sent-from-folder sent-from-folder)
1762: (setq mh-sent-from-msg sent-from-msg)
1763: (setq mh-send-args send-args)
1764: (setq mh-annotate-char annotate-char)
1765: (setq mode-line-buffer-identification (list "{%b}"))
1766: (if (and (boundp 'mh-compose-letter-hook)
1767: (symbol-value 'mh-compose-letter-hook))
1768: ;; run-hooks will not pass arguments.
1769: (let ((value (symbol-value 'mh-compose-letter-hook)))
1770: (if (and (listp value) (not (eq (car value) 'lambda)))
1771: (while value
1772: (funcall (car value) to subject cc)
1773: (setq value (cdr value)))
1774: (funcall mh-compose-letter-hook to subject cc)))))
1775:
1776:
1777: (defun mh-send-letter (&optional arg)
1778: "Send the draft letter in the current buffer.
1779: If (optional) prefix argument provided, monitor delivery."
1780: (interactive "P")
1781: (set-buffer-modified-p t) ; Make sure buffer is written
1782: (save-buffer)
1783: (message "Sending...")
1784: (let ((buffer-name (buffer-name))
1785: (file-name (buffer-file-name))
1786: (config mh-previous-window-config))
1787: (cond (arg
1788: (pop-to-buffer "MH mail delivery")
1789: (erase-buffer)
1790: (if mh-send-args
1791: (mh-exec-cmd-output "send" t "-watch" "-nopush"
1792: "-nodraftfolder" mh-send-args file-name)
1793: (mh-exec-cmd-output "send" t "-watch" "-nopush"
1794: "-nodraftfolder" file-name)))
1795:
1796: (mh-send-args
1797: (mh-exec-cmd-demon "send" "-nodraftfolder" "-noverbose"
1798: mh-send-args file-name))
1799: (t
1800: (mh-exec-cmd-demon "send" "-nodraftfolder" "-noverbose"
1801: file-name)))
1802:
1803: (if mh-annotate-char
1804: (mh-annotate-msg mh-sent-from-msg
1805: mh-sent-from-folder
1806: mh-annotate-char
1807: "-component" mh-annotate-field
1808: "-text" (format "\"%s %s\""
1809: (mh-get-field "To:")
1810: (mh-get-field "Cc:"))))
1811:
1812: (when (or (not arg)
1813: (y-or-n-p "Kill draft buffer? "))
1814: (kill-buffer buffer-name)
1815: (if config
1816: (set-window-configuration config)))
1817: (message "Sending...done")))
1818:
1819:
1820:
1821: (defun mh-insert-letter (prefix-provided folder msg)
1822: "Insert a message from any folder into the current letter.
1823: Removes the message's headers using mh-invisible-headers.
1824: Prefixes each non-blank line with mh-ins-buf-prefix (default \">> \").
1825: If (optional) prefix argument provided, do not indent and do not delete
1826: headers.
1827: Leaves the mark before the letter and point after it."
1828: (interactive
1829: (list current-prefix-arg
1830: (mh-prompt-for-folder "Message from" mh-sent-from-folder nil)
1831: (read-input (format "Message number%s: "
1832: (if mh-sent-from-msg
1833: (format " [%d]" mh-sent-from-msg)
1834: "")))))
1835: (save-restriction
1836: (narrow-to-region (point) (point))
1837: (let ((start (point-min)))
1838: (if (equal msg "") (setq msg (format "%d" mh-sent-from-msg)))
1839: (mh-exec-lib-cmd-output "mhl" "-nobell" "-noclear"
1840: (mh-expand-file-name msg
1841: (mh-expand-file-name
1842: folder)))
1843: (when (not prefix-provided)
1844: (mh-clean-msg-header start mh-invisible-headers mh-visible-headers)
1845: (set-mark start) ; since mh-clean-msg-header moves it
1846: (mh-insert-prefix-string mh-ins-buf-prefix)))))
1847:
1848:
1849: (defun mh-yank-cur-msg ()
1850: "Insert the currently displayed message into the draft buffer. Prefix each
1851: non-blank line in the message with the string in mh-ins-buf-prefix. If a
1852: region is set in the message's buffer, then only the region will be inserted.
1853: Otherwise, the entire message will be inserted if mh-yank-from-start-of-msg is
1854: non-nil. If this variable is nil, the portion of the message following the
1855: point will be yanked. If mh-delete-yanked-msg-window is non-nil, any window
1856: displaying the yanked message will be deleted."
1857: (interactive)
1858: (if (and (boundp 'mh-sent-from-folder) mh-sent-from-folder mh-sent-from-msg)
1859: (let ((to-point (point))
1860: (to-buffer (current-buffer)))
1861: (set-buffer mh-sent-from-folder)
1862: (if mh-delete-yanked-msg-window
1863: (delete-windows-on mh-show-buffer))
1864: (set-buffer mh-show-buffer) ; Find displayed message
1865: (let ((mh-ins-str (cond ((mark)
1866: (buffer-substring (point) (mark)))
1867: ((eq 'body mh-yank-from-start-of-msg)
1868: (buffer-substring
1869: (save-excursion
1870: (mh-goto-header-end 1)
1871: (point))
1872: (point-max)))
1873: (mh-yank-from-start-of-msg
1874: (buffer-substring (point-min) (point-max)))
1875: (t
1876: (buffer-substring (point) (point-max))))))
1877: (set-buffer to-buffer)
1878: (narrow-to-region to-point to-point)
1879: (insert mh-ins-str)
1880: (mh-insert-prefix-string mh-ins-buf-prefix)
1881: (insert "\n")
1882: (widen)))
1883: (error "There is no current message.")))
1884:
1885: (defun mh-insert-prefix-string (ins-string)
1886: ;; Preface each line in the current buffer with STRING.
1887: (goto-char (point-min))
1888: (while (not (eobp))
1889: (insert ins-string)
1890: (forward-line 1)))
1891:
1892:
1893: (defun mh-fully-kill-draft ()
1894: "Kill the draft message file and the draft message buffer.
1895: Use \\[kill-buffer] if you don't want to delete the draft message file."
1896: (interactive "")
1897: (if (y-or-n-p "Kill draft message? ")
1898: (let ((config mh-previous-window-config))
1899: (if (file-exists-p (buffer-file-name))
1900: (delete-file (buffer-file-name)))
1901: (set-buffer-modified-p nil)
1902: (kill-buffer (buffer-name))
1903: (if config
1904: (set-window-configuration config)))
1905: (error "Message not killed")))
1906:
1907:
1908:
1909: ;;; Commands to manipulate sequences. Sequences are stored in an alist
1910: ;;; of the form:
1911: ;;; ((seq-name msgs ...) (seq-name msgs ...) ...)
1912:
1913: (defun mh-make-seq (name msgs) (cons name msgs))
1914:
1915: (defmacro mh-seq-name (pair) (list 'car pair))
1916:
1917: (defmacro mh-seq-msgs (pair) (list 'cdr pair))
1918:
1919: (defun mh-find-seq (name) (assoc name mh-seq-list))
1920:
1921:
1922: (defun mh-seq-to-msgs (seq)
1923: "Return a list of the messages in SEQUENCE."
1924: (mh-seq-msgs (mh-find-seq seq)))
1925:
1926:
1927: (defun mh-seq-containing-msg (msg)
1928: ;; Return a list of the sequences containing MESSAGE.
1929: (let ((l mh-seq-list)
1930: (seqs ()))
1931: (while l
1932: (if (memq msg (mh-seq-msgs (car l)))
1933: (mh-push (mh-seq-name (car l)) seqs))
1934: (setq l (cdr l)))
1935: seqs))
1936:
1937:
1938: (defun mh-msg-to-seq (msg)
1939: ;; Given a MESSAGE number, return the first sequence in which it occurs.
1940: (car (mh-seq-containing-msg msg)))
1941:
1942:
1943: (defun mh-read-seq (prompt not-empty &optional default)
1944: ;; Read and return a sequence name. Prompt with PROMPT, raise an error
1945: ;; if the sequence is empty and the NOT-EMPTY flag is non-nil, and supply
1946: ;; an optional DEFAULT sequence.
1947: ;; A reply of '%' defaults to the first sequence containing the current
1948: ;; message.
1949: (let* ((input (completing-read (format "%s %s %s" prompt "sequence:"
1950: (if default
1951: (format "[%s] " default)
1952: ""))
1953: (mh-seq-names mh-seq-list)))
1954: (seq (cond ((equal input "%") (mh-msg-to-seq (mh-get-msg-num t)))
1955: ((equal input "") default)
1956: (t (intern input))))
1957: (msgs (mh-seq-to-msgs seq)))
1958: (if (and (null msgs) not-empty)
1959: (error (format "No messages in sequence `%s'" seq)))
1960: seq))
1961:
1962:
1963: (defun mh-read-folder-sequences (folder define-sequences)
1964: ;; Read and return the predefined sequences for a FOLDER. If
1965: ;; DEFINE-SEQUENCES is non-nil, then define mh-e's sequences before
1966: ;; reading MH's sequences.
1967: (let ((seqs ()))
1968: (when define-sequences
1969: (mh-define-sequences mh-seq-list)
1970: (mapc (function (lambda (seq) ; Save the internal sequences
1971: (if (mh-folder-name (mh-seq-name seq))
1972: (mh-push seq seqs))))
1973: mh-seq-list))
1974: (save-excursion
1975: (mh-exec-cmd-quiet " *mh-temp*" "mark" folder "-list")
1976: (goto-char (point-min))
1977: (while (re-search-forward "\\(^[a-zA-Z][a-zA-Z]*\\)" nil t)
1978: (mh-push (mh-make-seq (intern (buffer-substring (match-beginning 1)
1979: (match-end 1)))
1980: (mh-read-msg-list))
1981: seqs)))
1982: seqs))
1983:
1984:
1985: (defun mh-seq-names (seq-list)
1986: ;; Return an alist containing the names of the SEQUENCES.
1987: (mapcar (function (lambda (entry) (list (symbol-name (mh-seq-name entry)))))
1988: seq-list))
1989:
1990:
1991: (defun mh-seq-from-command (folder seq command)
1992: ;; In FOLDER, make a sequence named SEQ by executing COMMAND.
1993: (let ((msg)
1994: (msgs ())
1995: (case-fold-search t))
1996: (save-excursion
1997: (save-window-excursion
1998: (apply 'mh-exec-cmd-quiet (cons " *mh-temp*" command))
1999: (goto-char (point-min))
2000: (while (setq msg (car (mh-read-msg-list)))
2001: (mh-push msg msgs)
2002: (forward-line 1)))
2003: (set-buffer folder)
2004: (setq msgs (nreverse msgs)) ; Put in ascending order
2005: (mh-push (mh-make-seq seq msgs) mh-seq-list)
2006: msgs)))
2007:
2008:
2009: (defun mh-read-msg-list ()
2010: ;; Return a list of message numbers from the current point to the end of
2011: ;; the line.
2012: (let ((msgs ())
2013: (end-of-line (save-excursion (end-of-line) (point))))
2014: (while (re-search-forward "\\([0-9]+\\)" end-of-line t)
2015: (let ((num (string-to-int (buffer-substring (match-beginning 1)
2016: (match-end 1)))))
2017: (cond ((looking-at "-") ; Message range
2018: (forward-char 1)
2019: (re-search-forward "\\([0-9]+\\)" end-of-line t)
2020: (let ((num2 (string-to-int (buffer-substring (match-beginning 1)
2021: (match-end 1)))))
2022: (if (< num2 num)
2023: (error "Bad message range: %d-%d" num num2))
2024: (while (<= num num2)
2025: (mh-push num msgs)
2026: (setq num (+ num 1)))))
2027: ((not (zerop num)) (mh-push num msgs)))))
2028: msgs))
2029:
2030:
2031: (defun mh-remove-seq (seq)
2032: ;; Delete the SEQUENCE.
2033: (mh-map-to-seq-msgs 'mh-notate-if-in-one-seq seq ? (+ mh-cmd-note 1) seq)
2034: (mh-undefine-sequence seq (list "all"))
2035: (mh-delete-seq-locally seq))
2036:
2037:
2038: (defun mh-delete-seq-locally (seq)
2039: ;; Remove mh-e's record of SEQUENCE.
2040: (let ((entry (mh-find-seq seq)))
2041: (setq mh-seq-list (delq entry mh-seq-list))))
2042:
2043:
2044: (defun mh-remove-msg-from-seq (msg seq &optional internal-flag)
2045: ;; Remove MESSAGE from the SEQUENCE. If optional FLAG is non-nil, do not
2046: ;; inform MH of the change.
2047: (let ((entry (mh-find-seq seq)))
2048: (when entry
2049: (mh-notate-if-in-one-seq msg ? (+ mh-cmd-note 1) (mh-seq-name entry))
2050: (if (not internal-flag)
2051: (mh-undefine-sequence seq (list msg)))
2052: (setcdr entry (delq msg (mh-seq-msgs entry))))))
2053:
2054:
2055: (defun mh-add-msgs-to-seq (msgs seq &optional internal-flag)
2056: ;; Add MESSAGE(s) to the SEQUENCE. If optional FLAG is non-nil, do not mark
2057: ;; the message in the scan listing or inform MH of the addition.
2058: (let ((entry (mh-find-seq seq)))
2059: (if (and msgs (atom msgs)) (setq msgs (list msgs)))
2060: (if (null entry)
2061: (mh-push (mh-make-seq seq msgs) mh-seq-list)
2062: (if msgs (setcdr entry (append msgs (cdr entry)))))
2063: (when (not internal-flag)
2064: (mh-add-to-sequence seq msgs)
2065: (mh-notate-seq seq ?% (+ mh-cmd-note 1)))))
2066:
2067:
2068: (defun mh-rename-seq (seq new-name)
2069: "Rename a SEQUENCE to have a new NAME."
2070: (interactive "SOld sequence name: \nSNew name: ")
2071: (let ((old-seq (mh-find-seq seq)))
2072: (if old-seq
2073: (rplaca old-seq new-name)
2074: (error "Sequence %s does not exists" seq))
2075: (mh-undefine-sequence seq (mh-seq-msgs old-seq))
2076: (mh-define-sequence new-name (mh-seq-msgs old-seq))))
2077:
2078:
2079: (defun mh-notate-user-sequences ()
2080: ;; Mark the scan listing of all messages in user-defined sequences.
2081: (let ((seqs mh-seq-list))
2082: (while seqs
2083: (let ((name (mh-seq-name (car seqs))))
2084: (if (not (mh-internal-seq name))
2085: (mh-notate-seq name ?% (+ mh-cmd-note 1)))
2086: (setq seqs (cdr seqs))))))
2087:
2088:
2089: (defun mh-internal-seq (name)
2090: ;; Return non-NIL if NAME is the name of an internal mh-e sequence.
2091: (or (memq name '(answered cur deleted forwarded printed))
2092: (eq name mh-unseen-seq)
2093: (mh-folder-name name)))
2094:
2095:
2096: (defun mh-folder-name (name)
2097: ;; Return non-NIL if NAME is the possible name of a folder (i.e., begins
2098: ;; with "+").
2099: (if (symbolp name)
2100: (mh-folder-name (symbol-name name))
2101: (equal (substring name 0 1) "+")))
2102:
2103:
2104: (defun mh-notate-seq (seq notation offset)
2105: ;; Mark the scan listing of all messages in the SEQUENCE with the CHARACTER
2106: ;; at the given OFFSET from the beginning of the listing line.
2107: (mh-map-to-seq-msgs 'mh-notate seq notation offset))
2108:
2109:
2110: (defun mh-notate-if-in-one-seq (msg notation offset seq)
2111: ;; If the MESSAGE is in only the SEQUENCE, then mark the scan listing of the
2112: ;; message with the CHARACTER at the given OFFSET from the beginning of the
2113: ;; listing line.
2114: (let ((in-seqs (mh-seq-containing-msg msg)))
2115: (if (and (eq seq (car in-seqs)) (null (cdr in-seqs)))
2116: (mh-notate msg notation offset))))
2117:
2118:
2119: (defun mh-map-to-seq-msgs (func seq &rest args)
2120: ;; Invoke the FUNCTION at each message in the SEQUENCE, passing the
2121: ;; remaining ARGS as arguments.
2122: (save-excursion
2123: (let ((msgs (mh-seq-to-msgs seq)))
2124: (while msgs
2125: (if (mh-goto-msg (car msgs) t t)
2126: (apply func (cons (car msgs) args)))
2127: (setq msgs (cdr msgs))))))
2128:
2129:
2130: (defun mh-map-over-seqs (func seq-list)
2131: ;; Apply the FUNCTION to each element in the list of SEQUENCES,
2132: ;; passing the sequence name and the list of messages as arguments.
2133: (while seq-list
2134: (funcall func (mh-seq-name (car seq-list)) (mh-seq-msgs (car seq-list)))
2135: (setq seq-list (cdr seq-list))))
2136:
2137:
2138: (defun mh-define-sequences (seq-list)
2139: ;; Define the sequences in SEQ-LIST.
2140: (mh-map-over-seqs 'mh-define-sequence seq-list))
2141:
2142:
2143: (defun mh-add-to-sequence (seq msgs)
2144: ;; Add to a SEQUENCE each message the list of MSGS.
2145: (if (not (equal (substring (symbol-name seq) 0 1) "+"))
2146: (if msgs
2147: (apply 'mh-exec-cmd (mh-list* "mark" mh-current-folder
2148: "-sequence" (format "%s" seq)
2149: "-add" msgs)))))
2150:
2151: (defun mh-define-sequence (seq msgs)
2152: ;; Define the SEQUENCE to contain the list of MSGS. Do not mark
2153: ;; pseudo-sequences or empty sequences.
2154: (if (and msgs
2155: (not (equal (substring (symbol-name seq) 0 1) "+")))
2156: (save-excursion
2157: (apply 'mh-exec-cmd-quiet (mh-list* " *mh-temp*"
2158: "mark" mh-current-folder
2159: "-sequence" (format "%s" seq)
2160: "-add" "-zero" msgs)))))
2161:
2162:
2163: (defun mh-undefine-sequence (seq msgs)
2164: ;; Remove from the SEQUENCE the list of MSGS.
2165: (apply 'mh-exec-cmd (mh-list* "mark" mh-current-folder
2166: "-sequence" (format "%s" seq)
2167: "-delete" msgs)))
2168:
2169:
2170: (defun mh-copy-seq-to-point (seq location)
2171: ;; Copy the scan listing of the messages in SEQUENCE to after the point
2172: ;; LOCATION in the current buffer.
2173: (mh-map-to-seq-msgs 'mh-copy-line-to-point seq location))
2174:
2175:
2176: (defun mh-copy-line-to-point (msg location)
2177: ;; Copy the current line to the LOCATION in the current buffer.
2178: (beginning-of-line)
2179: (let ((beginning-of-line (point)))
2180: (forward-line 1)
2181: (copy-region-as-kill beginning-of-line (point))
2182: (goto-char location)
2183: (yank)
2184: (goto-char beginning-of-line)))
2185:
2186:
2187:
2188: ;;; Issue commands to MH.
2189:
2190: (defun mh-exec-cmd (command &rest args)
2191: ;; Execute MH command COMMAND with ARGS. Any output is shown to the user.
2192: (save-window-excursion
2193: (switch-to-buffer-other-window " *mh-temp*")
2194: (erase-buffer)
2195: (apply 'call-process
2196: (mh-list* (mh-expand-file-name command mh-progs) nil t nil
2197: (mh-list-to-string args)))
2198: (if (> (buffer-size) 0)
2199: (sit-for 5))))
2200:
2201:
2202: (defun mh-exec-cmd-quiet (buffer command &rest args)
2203: ;; In BUFFER, execute MH command COMMAND with ARGS. Return in buffer, if
2204: ;; one exists.
2205: (when (stringp buffer)
2206: (switch-to-buffer buffer)
2207: (erase-buffer))
2208: (apply 'call-process
2209: (mh-list* (mh-expand-file-name command mh-progs) nil buffer nil
2210: (mh-list-to-string args))))
2211:
2212:
2213: (defun mh-exec-cmd-output (command display &rest args)
2214: ;; Execute MH command COMMAND with DISPLAY flag and ARGS putting the output
2215: ;; into buffer after point. Set mark after inserted text.
2216: (push-mark (point) t)
2217: (apply 'call-process
2218: (mh-list* (mh-expand-file-name command mh-progs) nil t display
2219: (mh-list-to-string args)))
2220: (exchange-point-and-mark))
2221:
2222:
2223: (defun mh-exec-cmd-demon (command &rest args)
2224: ;; Execute MH command COMMAND with ARGS. Any output from command is
2225: ;; displayed in an asynchronous pop-up window.
2226: (save-excursion
2227: (switch-to-buffer " *mh-temp*")
2228: (erase-buffer))
2229: (let ((process (apply 'start-process
2230: (mh-list* "mh-output" nil
2231: (expand-file-name command mh-progs)
2232: (mh-list-to-string args)))))
2233: (set-process-filter process 'mh-process-demon)))
2234:
2235:
2236: (defun mh-process-demon (process output)
2237: ;; Process demon that puts output into a temporary buffer.
2238: (pop-to-buffer " *mh-temp*")
2239: (insert output)
2240: (other-window 1))
2241:
2242:
2243: (defun mh-exec-lib-cmd-output (command &rest args)
2244: ;; Execute MH library command COMMAND with ARGS. Put the output into
2245: ;; buffer after point. Set mark after inserted text.
2246: (push-mark (point) t)
2247: (apply 'call-process
2248: (mh-list* (mh-expand-file-name command mh-lib) nil t nil
2249: (mh-list-to-string args)))
2250: (exchange-point-and-mark))
2251:
2252:
2253: (defun mh-list-to-string (l)
2254: ;; Flattens the list L and makes every element of the new list into a string.
2255: (let ((new-list nil))
2256: (while l
2257: (cond ((null (car l)))
2258: ((symbolp (car l)) (mh-push (symbol-name (car l)) new-list))
2259: ((numberp (car l)) (mh-push (int-to-string (car l)) new-list))
2260: ((equal (car l) ""))
2261: ((stringp (car l)) (mh-push (car l) new-list))
2262: ((listp (car l))
2263: (setq new-list (nconc (nreverse (mh-list-to-string (car l)))
2264: new-list)))
2265: (t (error "Bad argument %s" (car l))))
2266: (setq l (cdr l)))
2267: (nreverse new-list)))
2268:
2269:
2270:
2271: ;;; Commands to annotate a message.
2272:
2273: (defun mh-annotate-msg (msg buffer note &rest args)
2274: ;; Mark the MESSAGE in BUFFER listing with the character NOTE and annotate
2275: ;; the saved message with ARGS.
2276: (apply 'mh-exec-cmd (mh-list* "anno" buffer msg args))
2277: (save-excursion
2278: (set-buffer buffer)
2279: (if (symbolp msg)
2280: (mh-notate-seq msg note (+ mh-cmd-note 1))
2281: (mh-notate msg note (+ mh-cmd-note 1)))))
2282:
2283:
2284: (defun mh-notate (msg notation offset)
2285: ;; Marks MESSAGE with the character NOTATION at position OFFSET.
2286: (save-excursion
2287: (if (mh-goto-msg msg t t)
2288: (let ((buffer-read-only nil)
2289: (folder-modified-flag (buffer-modified-p)))
2290: (beginning-of-line)
2291: (goto-char (+ (point) offset))
2292: (delete-char 1)
2293: (insert notation)
2294: (mh-set-folder-modified-p folder-modified-flag)))))
2295:
2296:
2297:
2298: ;;; User prompting commands.
2299:
2300: (defun mh-prompt-for-folder (prompt default can-create)
2301: ;; Prompt for a folder name with PROMPT. Returns the folder's name.
2302: ;; DEFAULT is used if the folder exists and the user types return.
2303: ;; If the CAN-CREATE flag is t, then a non-existant folder is made.
2304: (let* ((prompt (format "%s folder%s" prompt
2305: (if (equal "" default)
2306: "? "
2307: (format " [%s]? " default))))
2308: name)
2309: (if (null mh-folder-list)
2310: (setq mh-folder-list (mh-make-folder-list)))
2311: (while (and (setq name (completing-read prompt mh-folder-list
2312: nil nil "+"))
2313: (equal name "")
2314: (equal default "")))
2315: (cond ((or (equal name "") (equal name "+"))
2316: (setq name default))
2317: ((not (equal (substring name 0 1) "+"))
2318: (setq name (format "+%s" name))))
2319: (let ((new-file-p (not (file-exists-p (mh-expand-file-name name)))))
2320: (cond ((and new-file-p
2321: (y-or-n-p
2322: (format "Folder %s does not exist. Create it? " name)))
2323: (message "Creating %s" name)
2324: (call-process "mkdir" nil nil nil (mh-expand-file-name name))
2325: (message "Creating %s...done" name)
2326: (mh-push (list name) mh-folder-list)
2327: (mh-push (list (substring name 1 nil)) mh-folder-list))
2328: (new-file-p
2329: (error ""))
2330: (t
2331: (when (null (assoc name mh-folder-list))
2332: (mh-push (list name) mh-folder-list)
2333: (mh-push (list (substring name 1 nil)) mh-folder-list)))))
2334: name))
2335:
2336:
2337: (defun mh-make-folder-list ()
2338: "Return a list of the user's folders.
2339: Result is in a form suitable for completing read."
2340: (interactive)
2341: (message "Collecting folder names...")
2342: (save-window-excursion
2343: (mh-exec-cmd-quiet " *mh-temp*" "folders" "-fast"
2344: (if mh-recursive-folders
2345: "-recurse"
2346: "-norecurse"))
2347: (goto-char (point-min))
2348: (let ((list nil))
2349: (while (not (eobp))
2350: (let ((start (point)))
2351: (search-forward "\n" nil t)
2352: (let ((folder (buffer-substring start (- (point) 1))))
2353: (mh-push (list (format "+%s" folder)) list))))
2354: (message "Collecting folder names...done")
2355: list)))
2356:
2357:
2358: (defun mh-remove-folder-from-folder-list (folder)
2359: ;; Remove FOLDER from the list of folders.
2360: (setq mh-folder-list
2361: (delq (assoc (substring folder 1 nil) mh-folder-list)
2362: mh-folder-list)))
2363:
2364:
2365:
2366: ;;; Misc. functions.
2367:
2368: (defun mh-get-msg-num (error-if-no-message)
2369: ;; Return the message number of the displayed message. If the argument
2370: ;; ERROR-IF-NO-MESSAGE is non-nil, then complain if the cursor is not
2371: ;; pointing to a message.
2372: (save-excursion
2373: (beginning-of-line)
2374: (cond ((looking-at mh-msg-number-regexp)
2375: (string-to-int (buffer-substring (match-beginning 1)
2376: (match-end 1))))
2377: (error-if-no-message
2378: (error "Cursor not pointing to message"))
2379: (t nil))))
2380:
2381:
2382: (defun mh-msg-search-pat (n)
2383: ;; Return a search pattern for message N in the scan listing.
2384: (format mh-msg-search-regexp n))
2385:
2386:
2387: (defun mh-msg-filename (msg)
2388: ;; Returns a string containing the file name of the MESSAGE.
2389: (mh-expand-file-name (int-to-string msg) mh-folder-filename))
2390:
2391:
2392: (defun mh-msg-filenames (msgs folder)
2393: ;; Return a string of filenames for MSGS in FOLDER.
2394: (let ((mh-folder-filename folder))
2395: (mapconcat (function (lambda (msg) (mh-msg-filename msg))) msgs " ")))
2396:
2397:
2398: (defun mh-find-path ()
2399: ;; Set mh-user-path, mh-draft-folder, and mh-unseen-seq from ~/.mh_profile.
2400: (save-window-excursion
2401: (let ((profile (or (getenv "MH") "~/.mh_profile")))
2402: (if (not (file-exists-p profile))
2403: (error "Cannot find ~/.mh_profile"))
2404: (switch-to-buffer " *mh-temp*")
2405: (erase-buffer)
2406: (insert-file-contents profile)
2407: (setq mh-draft-folder (mh-get-field "Draft-Folder:" ))
2408: (cond ((equal mh-draft-folder "")
2409: (setq mh-draft-folder nil))
2410: ((not (equal (substring mh-draft-folder 0 1) "+"))
2411: (setq mh-draft-folder (format "+%s" mh-draft-folder))))
2412: (setq mh-user-path (mh-get-field "Path:"))
2413: (if (equal mh-user-path "")
2414: (setq mh-user-path "Mail"))
2415: (setq mh-user-path
2416: (file-name-as-directory
2417: (expand-file-name mh-user-path (expand-file-name "~"))))
2418: (if (and mh-draft-folder
2419: (not (file-exists-p (mh-expand-file-name mh-draft-folder))))
2420: (error "Draft folder does not exist. Create it and try again."))
2421: (setq mh-unseen-seq (mh-get-field "Unseen-Sequence:"))
2422: (if (equal mh-unseen-seq "")
2423: (setq mh-unseen-seq 'unseen)
2424: (setq mh-unseen-seq (intern mh-unseen-seq))))))
2425:
2426:
2427: (defun mh-expand-file-name (filename &optional default)
2428: "Just like expand-file-name, but also handles MH folder names.
2429: Assumes that any filename that starts with '+' is a folder name."
2430: (if (string-equal (substring filename 0 1) "+")
2431: (expand-file-name (substring filename 1) mh-user-path)
2432: (expand-file-name filename default)))
2433:
2434:
2435: (defun mh-get-field (field)
2436: ;; Find and return the value of field FIELD in the current buffer.
2437: ;; Returns the empty string if the field is not in the message.
2438: (let ((case-fold-search t))
2439: (goto-char (point-min))
2440: (cond ((not (search-forward field nil t)) "")
2441: ((looking-at "[\t ]*$") "")
2442: (t
2443: (re-search-forward "[\t ]*\\([^\t \n].*\\)$" nil t)
2444: (let ((field (buffer-substring (match-beginning 1)
2445: (match-end 1)))
2446: (end-of-match (point)))
2447: (forward-line)
2448: (while (looking-at "[ \t]") (forward-line 1))
2449: (backward-char 1)
2450: (if (<= (point) end-of-match)
2451: field
2452: (format "%s%s"
2453: field
2454: (buffer-substring end-of-match (point)))))))))
2455:
2456:
2457: (defun mh-insert-fields (&rest name-values)
2458: ;; Insert the NAME-VALUE pairs in the current buffer.
2459: ;; Do not insert any pairs whose value is the empty string.
2460: (let ((case-fold-search t))
2461: (while name-values
2462: (let ((field-name (car name-values))
2463: (value (car (cdr name-values))))
2464: (when (not (equal value ""))
2465: (goto-char (point-min))
2466: (cond ((not (re-search-forward (format "^%s" field-name) nil t))
2467: (mh-goto-header-end 0)
2468: (insert field-name " " value "\n"))
2469: (t
2470: (end-of-line)
2471: (insert " " value))))
2472: (setq name-values (cdr (cdr name-values)))))))
2473:
2474:
2475: (defun mh-position-on-field (field set-mark)
2476: ;; Set point to the end of the line beginning with FIELD.
2477: ;; Set the mark to the old value of point, if SET-MARK is non-nil.
2478: (let ((case-fold-search t))
2479: (if set-mark (push-mark))
2480: (goto-char (point-min))
2481: (mh-goto-header-end 0)
2482: (if (re-search-backward (format "^%s" field) nil t)
2483: (progn (end-of-line) t)
2484: nil)))
2485:
2486:
2487: (defun mh-goto-header-end (arg)
2488: ;; Find the end of the message header in the current buffer and position
2489: ;; the cursor at the ARG'th newline after the header.
2490: (if (re-search-forward "^$\\|^-+$" nil nil)
2491: (forward-line arg)))
2492:
2493:
2494:
2495: ;;; Build the folder-mode keymap:
2496:
2497: (suppress-keymap mh-folder-mode-map)
2498: (define-key mh-folder-mode-map "q" 'mh-restore-window-config)
2499: (define-key mh-folder-mode-map "b" 'mh-restore-window-config)
2500: (define-key mh-folder-mode-map "?" 'mh-msg-is-in-seq)
2501: (define-key mh-folder-mode-map "%" 'mh-put-msg-in-seq)
2502: (define-key mh-folder-mode-map "\ea" 'mh-edit-again)
2503: (define-key mh-folder-mode-map "\e%" 'mh-delete-msg-from-seq)
2504: (define-key mh-folder-mode-map "\C-Xn" 'mh-narrow-to-seq)
2505: (define-key mh-folder-mode-map "\C-Xw" 'mh-widen)
2506: (define-key mh-folder-mode-map "\eb" 'mh-burst-digest)
2507: (define-key mh-folder-mode-map "\eu" 'mh-undo-folder)
2508: (define-key mh-folder-mode-map "\e " 'mh-page-digest)
2509: (define-key mh-folder-mode-map "\e\177" 'mh-page-digest-backwards)
2510: (define-key mh-folder-mode-map "\ee" 'mh-extract-rejected-mail)
2511: (define-key mh-folder-mode-map "\ef" 'mh-visit-folder)
2512: (define-key mh-folder-mode-map "\ek" 'mh-kill-folder)
2513: (define-key mh-folder-mode-map "\el" 'mh-list-folders)
2514: (define-key mh-folder-mode-map "\ep" 'mh-pack-folder)
2515: (define-key mh-folder-mode-map "\es" 'mh-search-folder)
2516: (define-key mh-folder-mode-map "\er" 'mh-rescan-folder)
2517: (define-key mh-folder-mode-map "l" 'mh-print-msg)
2518: (define-key mh-folder-mode-map "t" 'mh-toggle-summarize)
2519: (define-key mh-folder-mode-map "c" 'mh-copy-msg)
2520: (define-key mh-folder-mode-map ">" 'mh-write-msg-to-file)
2521: (define-key mh-folder-mode-map "i" 'mh-inc-folder)
2522: (define-key mh-folder-mode-map "x" 'mh-execute-commands)
2523: (define-key mh-folder-mode-map "e" 'mh-execute-commands)
2524: (define-key mh-folder-mode-map "r" 'mh-redistribute)
2525: (define-key mh-folder-mode-map "f" 'mh-forward)
2526: (define-key mh-folder-mode-map "s" 'mh-send)
2527: (define-key mh-folder-mode-map "m" 'mh-send)
2528: (define-key mh-folder-mode-map "a" 'mh-reply)
2529: (define-key mh-folder-mode-map "j" 'mh-goto-msg)
2530: (define-key mh-folder-mode-map "g" 'mh-goto-msg)
2531: (define-key mh-folder-mode-map "\177" 'mh-previous-page)
2532: (define-key mh-folder-mode-map " " 'mh-page-msg)
2533: (define-key mh-folder-mode-map "." 'mh-show)
2534: (define-key mh-folder-mode-map "u" 'mh-undo)
2535: (define-key mh-folder-mode-map "!" 'mh-refile-or-write-again)
2536: (define-key mh-folder-mode-map "^" 'mh-refile-msg)
2537: (define-key mh-folder-mode-map "d" 'mh-delete-msg)
2538: (define-key mh-folder-mode-map "p" 'mh-previous-undeleted-msg)
2539: (define-key mh-folder-mode-map "n" 'mh-next-undeleted-msg)
2540:
2541:
2542: ;;; Build the letter-mode keymap:
2543:
2544: (define-key mh-letter-mode-map "\C-c\C-f\C-b" 'mh-to-field)
2545: (define-key mh-letter-mode-map "\C-c\C-f\C-c" 'mh-to-field)
2546: (define-key mh-letter-mode-map "\C-c\C-f\C-f" 'mh-to-field)
2547: (define-key mh-letter-mode-map "\C-c\C-f\C-s" 'mh-to-field)
2548: (define-key mh-letter-mode-map "\C-c\C-f\C-t" 'mh-to-field)
2549: (define-key mh-letter-mode-map "\C-c\C-fb" 'mh-to-field)
2550: (define-key mh-letter-mode-map "\C-c\C-fc" 'mh-to-field)
2551: (define-key mh-letter-mode-map "\C-c\C-ff" 'mh-to-field)
2552: (define-key mh-letter-mode-map "\C-c\C-fs" 'mh-to-field)
2553: (define-key mh-letter-mode-map "\C-c\C-ft" 'mh-to-field)
2554: (define-key mh-letter-mode-map "\C-c\C-q" 'mh-fully-kill-draft)
2555: (define-key mh-letter-mode-map "\C-c\C-w" 'mh-check-whom)
2556: (define-key mh-letter-mode-map "\C-c\C-i" 'mh-insert-letter)
2557: (define-key mh-letter-mode-map "\C-c\C-y" 'mh-yank-cur-msg)
2558: (define-key mh-letter-mode-map "\C-c\C-s" 'mh-insert-signature)
2559: (define-key mh-letter-mode-map "\C-c\C-c" 'mh-send-letter)
2560:
2561:
2562: ;;; Build the pick-mode keymap:
2563:
2564: (define-key mh-pick-mode-map "\C-c\C-c" 'mh-do-pick-search)
2565: (define-key mh-pick-mode-map "\C-c\C-f\C-b" 'mh-to-field)
2566: (define-key mh-pick-mode-map "\C-c\C-f\C-c" 'mh-to-field)
2567: (define-key mh-pick-mode-map "\C-c\C-f\C-f" 'mh-to-field)
2568: (define-key mh-pick-mode-map "\C-c\C-f\C-s" 'mh-to-field)
2569: (define-key mh-pick-mode-map "\C-c\C-f\C-t" 'mh-to-field)
2570: (define-key mh-pick-mode-map "\C-c\C-fb" 'mh-to-field)
2571: (define-key mh-pick-mode-map "\C-c\C-fc" 'mh-to-field)
2572: (define-key mh-pick-mode-map "\C-c\C-ff" 'mh-to-field)
2573: (define-key mh-pick-mode-map "\C-c\C-fs" 'mh-to-field)
2574: (define-key mh-pick-mode-map "\C-c\C-ft" 'mh-to-field)
2575: (define-key mh-pick-mode-map "\C-c\C-w" 'mh-check-whom)
2576:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.