|
|
1.1 root 1: ;;; mh-e.el (Version: 3.1a for GNU Emacs Version 17)
2:
3: ;;; Copyright (C) James Larus ([email protected], ucbvax!larus), 1985
4: ;;; Please send suggestions and corrections to the above address.
5: ;;;
6: ;;; This file contains mh-e, a GNU Emacs front end to the MH mail system.
7:
8:
9: ;; GNU Emacs is distributed in the hope that it will be useful,
10: ;; but without any warranty. No author or distributor
11: ;; accepts responsibility to anyone for the consequences of using it
12: ;; or for whether it serves any particular purpose or works at all,
13: ;; unless he says so in writing.
14:
15: ;; Everyone is granted permission to copy, modify and redistribute
16: ;; GNU Emacs, but only under the conditions described in the
17: ;; document "GNU Emacs copying permission notice". An exact copy
18: ;; of the document is supposed to have been given to you along with
19: ;; GNU Emacs so that you can know how you may redistribute it all.
20: ;; It should be in a file named COPYING. Among other things, the
21: ;; copyright notice and this notice must be preserved on all copies.
22:
23:
24: ;;; Original version for Gosling emacs by Brian Reid, Stanford, 1982.
25: ;;; Modified by James Larus, BBN, July 1984 and UCB, 1984 & 1985.
26: ;;; Rewritten for GNU Emacs, James Larus 1985.
27:
28:
29: ;;; NB MH must have been compiled with the MHE compiler flag or several
30: ;;; features necessary to this program will be missing.
31:
32:
33:
34: ;;; Constants:
35:
36: ;;; Set for local environment:
37: ;;;* These are now in paths.el.
38: (defvar mh-progs "/usr/new/mh/" "Directory containing MH commands")
39: (defvar mh-lib "/usr/new/lib/mh/" "Directory of MH library")
40:
41:
42: ;;; Mode hooks:
43:
44: (defvar mh-folder-mode-hook nil
45: "*Invoked in mh-folder-mode on a new folder.")
46: (defvar mh-letter-mode-hook nil
47: "*Invoked in mh-letter-mode on a new letter.")
48: (defvar mh-compose-letter-hook nil
49: "*Invoked in mh-compose-and-send-mail on an outgoing letter. It is passed
50: three arguments: TO recipients, SUBJECT, and CC recipients.")
51:
52:
53: ;;; Personal preferences:
54:
55: (defvar mh-auto-fill-letters t
56: "*Non-nil means invoke auto-fill-mode in draft messages.")
57: (defvar mh-clean-message-header nil
58: "*Non-nil means remove invisible header lines in messages.")
59: (defvar mh-use-mhl nil
60: "*Non-nil means use mhl to format messages.")
61: (defvar mh-lpr-command-format "lpr -p -J '%s'"
62: "*Format for Unix command line to print a message. The format should be
63: a unix command line, with the string \"%s\" where the folder and message
64: number should appear.")
65: (defvar mh-summary-height 4
66: "*Number of lines in summary window.")
67: (defvar mh-ins-buf-prefix ">> "
68: "*String to put before each non-blank line of the the current message
69: as it is inserted in an outgoing letter.")
70:
71:
72: ;;; Real constants:
73:
74: (defvar mh-cmd-note 4 "Offset to insert notation")
75: (defvar mh-invisible-headers
76: "^Received: \\|^Message-Id: \\|^Remailed-\\|^Via: \\|^Mail-from: \\|\^Return-Path: \\|^In-Reply-To: \\|^Resent-"
77: "Regexp specifying headers that are not to be shown.")
78:
79:
80: ;;; Global variables:
81:
82: (defvar mh-user-path "" "User's mail folder.")
83: (defvar mh-last-destination nil "Destination of last "move" command.")
84: (defvar mh-folder-mode-map (make-sparse-keymap) "Keymap for MH folders.")
85: (defvar mh-letter-mode-map (make-sparse-keymap) "Keymap for composing mail.")
86: (defvar mh-pick-mode-map (make-sparse-keymap) "Keymap for searching folder.")
87: (defvar mh-folder-list nil "List of folder names for completion.")
88:
89: ;;; Macros:
90:
91: (defmacro push (v l)
92: (list 'setq l (list 'cons v l)))
93:
94: (defmacro caar (l)
95: (list 'car (list 'car l)))
96:
97: (defmacro cadr (l)
98: (list 'car (list 'cdr l)))
99:
100: (defmacro cdar (l)
101: (list 'cdr (list 'car l)))
102:
103: (defmacro cddr (l)
104: (list 'cdr (list 'cdr l)))
105:
106: (defmacro when (pred &rest body)
107: (list 'cond (cons pred body)))
108:
109:
110:
111: ;;; Entry points:
112:
113: (defun mh-rmail (&optional arg)
114: "Inc(orporate) new mail if optional ARG omitted, or scan a MH mail box
115: if arg is present. This front end uses the MH mail system, which uses
116: different conventions from the usual mail system."
117: (interactive "P")
118: (mh-find-path)
119: (if (null mh-folder-list)
120: (setq mh-folder-list (mh-make-folder-list)))
121: (cond (arg
122: (let ((folder (mh-get-folder-name "mh" "+inbox" t))
123: (range (read-string "Range [all]? ")))
124: (mh-scan-folder folder (if (equal range "") "all" range))
125: (delete-other-windows)))
126: (t
127: (mh-inc-folder))))
128:
129:
130: (defun mh-smail ()
131: "Send mail using the MH mail system."
132: (interactive)
133: (mh-find-path)
134: (call-interactively 'mh-send))
135:
136:
137:
138: ;;; User executable mh-e commands:
139:
140: (defun mh-answer (&optional arg)
141: "Answer a letter. If given an argument, then include the current message
142: in the reply."
143: (interactive "P")
144: (let ((msg-filename (mh-msg-filename))
145: (msg (mh-get-msg-num t))
146: (minibuffer-help-form "from => Sender\n to => Sender and primary recipients\n cc => Sender and all recipients")
147: (folder mh-current-folder))
148: (let ((reply-to (completing-read "Reply to whom: "
149: '(("from") ("to") ("cc"))
150: nil t)))
151: (message "Composing a reply...")
152: (cond ((or (equal reply-to "from") (equal reply-to ""))
153: (apply 'mh-exec-cmd
154: (nconc
155: (list "repl" "-build" mh-current-folder msg "-nocc" "all")
156: (if arg (list "-filter" "mhl.reply")))))
157: ((equal reply-to "to")
158: (apply 'mh-exec-cmd
159: (nconc
160: (list "repl" "-build" mh-current-folder msg "-cc" "to"
161: "-nocc" "me")
162: (if arg (list "-filter" "mhl.reply")))))
163: ((equal reply-to "cc")
164: (apply 'mh-exec-cmd
165: (nconc
166: (list "repl" "-build" mh-current-folder msg "-cc" "all")
167: (if arg (list "-filter" "mhl.reply"))))))
168:
169: (mh-read-draft)
170: (delete-other-windows)
171: (when (or (zerop (buffer-size))
172: (not (y-or-n-p "The file 'draft' exists. Use for reply? ")))
173: (erase-buffer)
174: (insert-file-contents (format "%sreply" mh-user-path))
175: (delete-file (format "%sreply" mh-user-path)))
176: (set-buffer-modified-p nil)
177:
178: (let ((to (mh-get-field "To:"))
179: (subject (mh-get-field "Subject:"))
180: (cc (mh-get-field "Cc:")))
181: (goto-char (point-min))
182: (re-search-forward "^$" (point-max) nil)
183: (when (not arg)
184: (switch-to-buffer-other-window "*message*")
185: (erase-buffer)
186: (if (file-exists-p msg-filename)
187: (insert-file-contents msg-filename)
188: (error "File %s does not exist" msg-filename))
189: (set-buffer-modified-p nil)
190: (goto-char (point-min))
191: (re-search-forward "^$\\|^-*$")
192: (recenter 0))
193: (message "Composing a reply...done")
194: (mh-compose-and-send-mail "" folder to subject cc "-" "Replied:")))))
195:
196:
197: (defun mh-copy-msg (&optional arg)
198: "Copy specified message(s) to another folder without deleting them."
199: (interactive "P")
200: (let ((msgs (if arg
201: (mh-seq-to-msgs (mh-read-seq "Copy"))
202: (mh-get-msg-num t))))
203: (mh-exec-cmd-no-wait "refile" msgs "-link" "-src"
204: mh-current-folder
205: (mh-get-folder-name "Copy to" "" t))
206: (if arg
207: (mh-notate-seq msgs ?C mh-cmd-note)
208: (mh-notate ?C mh-cmd-note))))
209:
210:
211: (defun mh-delete-msg (&optional arg)
212: "Marks the specified message(s) for later deletion."
213: (interactive "P")
214: (if arg
215: (mh-apply-to-seq (mh-read-seq "Delete") 'mh-delete-one-msg)
216: (mh-delete-one-msg))
217: (mh-next-message))
218:
219:
220: (defun mh-execute-commands ()
221: "Process outstanding delete and move commands."
222: (interactive)
223: (save-excursion
224: (mh-process-commands mh-current-folder))
225: (delete-other-windows)
226: (setq mh-summarize t)
227: (setq mode-name "Mh-Summary")
228: (setq mode-line-format (mh-make-mode-line)))
229:
230:
231: (defun mh-forward (to cc subject)
232: "Forward a letter."
233: (interactive "sTo: \nsCc: \nsSubject: ")
234: (let ((msg-filename (mh-msg-filename))
235: (msg (mh-get-msg-num t))
236: (folder mh-current-folder))
237: (cond ((or (not (file-exists-p (format "%sdraft" mh-user-path)))
238: (y-or-n-p "The file 'draft' exists. Discard it? "))
239: (mh-exec-cmd "forw" "-build" mh-current-folder msg)
240: (mh-read-draft)
241: (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc))
242: (t
243: (mh-read-draft)))
244:
245: (goto-char (point-min))
246: (delete-other-windows)
247: (mh-compose-and-send-mail "" folder to subject cc "F" "Forwarded-To:")))
248:
249:
250: (defun mh-goto (number &optional no-error-if-no-message)
251: "Position the cursor at message NUMBER. Do not signal an error if optional
252: ARG is t. Return non-nil if cursor is at message."
253: (interactive "nMessage number? ")
254: (let ((starting-place (point)))
255: (goto-char (point-min))
256: (cond ((not (re-search-forward (mh-msg-search-pat number) nil t))
257: (goto-char starting-place)
258: (if (not no-error-if-no-message) (error "No message %d " number))
259: nil)
260: (t
261: (beginning-of-line)
262: (mh-maybe-show)
263: t))))
264:
265:
266: (defun mh-inc-folder ()
267: "inc(orporate) new mail into inbox."
268: (interactive)
269: (switch-to-buffer-other-window "+inbox")
270: (if (or (not (boundp 'mh-current-folder)) (null mh-current-folder))
271: (mh-make-folder "+inbox"))
272: (if (mh-get-new-mail)
273: (mh-show)))
274:
275:
276: (defun mh-indicate-seq (&optional arg)
277: "Add the specified message(s) to a sequence."
278: (interactive "P")
279: (let ((new-seq (mh-letter-to-seq last-input-char))
280: (old-seq (if (looking-at "^[0-9a-z]")
281: (mh-letter-to-seq (char-after (point))))))
282: (if old-seq
283: (if arg
284: (mh-remove-seq old-seq)
285: (mh-remove-msg-from-seq (mh-get-msg-num t) old-seq)))
286: (if (and (not arg)
287: (or (not old-seq) (not (equal new-seq old-seq))))
288: (mh-add-msg-to-seq (mh-get-msg-num t) new-seq)))
289: (mh-next-message))
290:
291:
292: (defun mh-kill-folder ()
293: "Removes the current folder."
294: (interactive)
295: (if (yes-or-no-p (format "Remove folder %s? " mh-current-folder))
296: (let ((buffer mh-current-folder))
297: (switch-to-buffer-other-window " *mh-temp*")
298: (mh-exec-cmd "rmf" buffer)
299: (mh-remove-folder-from-folder-list buffer)
300: (message "Folder removed")
301: (kill-buffer buffer))
302: (message "Folder not removed")))
303:
304:
305: (defun mh-list-folders ()
306: "List mail folders."
307: (interactive)
308: (message "listing folders...")
309: (switch-to-buffer-other-window " *mh-temp*")
310: (erase-buffer)
311: (mh-exec-cmd-output "folders")
312: (goto-char (point-min))
313: (message "listing folders...done"))
314:
315:
316: (defun mh-move-msg (&optional arg)
317: "Move specified message(s) to another folder."
318: (interactive "P")
319: (setq mh-last-destination (mh-get-folder-name "Destination" "" t))
320: (if arg
321: (mh-apply-to-seq (mh-read-seq "Move") 'mh-move-one-msg)
322: (mh-move-one-msg))
323: (mh-next-message))
324:
325:
326: (defun mh-next-line (&optional arg)
327: "Move to next undeleted message in window and display body if summary
328: flag set."
329: (interactive "p")
330: (forward-line (if arg arg 1))
331: (setq mh-next-direction 'forward)
332: (cond ((re-search-forward "^....[^D^]" nil 0 arg)
333: (beginning-of-line)
334: (mh-maybe-show))
335: (t
336: (forward-line -1)
337: (message "No more messages"))))
338:
339:
340: (defun mh-renumber-folder ()
341: "Renumber messages in folder to be 1..N."
342: (interactive)
343: (message "packing buffer...")
344: (mh-pack-folder)
345: (mh-unmark-all-headers nil)
346: (mh-position-to-current)
347: (message "packing buffer...done"))
348:
349:
350: (defun mh-page-digest ()
351: "Advance displayed message to next digested message."
352: (interactive)
353: (save-excursion
354: (switch-to-buffer-other-window mh-show-buffer)
355: (move-to-window-line nil)
356: (let ((case-fold-search nil))
357: (when (not (search-forward "\nFrom:" nil t))
358: (other-window -1)
359: (error "No more messages")))
360: (recenter 0)
361: (other-window -1)))
362:
363:
364: (defun mh-page-msg (&optional arg)
365: (interactive "P")
366: (scroll-other-window arg))
367:
368:
369: (defun mh-previous-line (&optional arg)
370: "Move to previous message in window and display body if summary flag set."
371: (interactive "p")
372: (setq mh-next-direction 'backward)
373: (if (not (re-search-backward "^....[^D^]" nil 0 arg))
374: (message "Beginning of messages")
375: (mh-maybe-show)))
376:
377:
378: (defun mh-previous-page ()
379: "Page the displayed message backwards."
380: (interactive)
381: (save-excursion
382: (switch-to-buffer-other-window mh-show-buffer)
383: (scroll-down nil)
384: (other-window -1)))
385:
386:
387: (defun mh-print-msg (&optional arg)
388: "Print specified message(s) on a line printer."
389: (interactive "P")
390: (let ((msgs (if arg
391: (reverse (mh-seq-to-msgs (mh-read-seq "Print")))
392: (list (mh-get-msg-num t)))))
393: (message "printing message...")
394: (call-process "/bin/sh" nil 0 nil "-c"
395: (format "%smhl -nobell -clear %s | %s" mh-lib
396: (mh-msg-filenames msgs mh-folder-filename)
397: (format mh-lpr-command-format
398: (if arg
399: "Mail"
400: (format "%s/%d" mh-current-folder
401: (mh-get-msg-num t))))))
402: (message "printing message...done")))
403:
404:
405: (defun mh-rescan-folder (&optional arg)
406: "Optionally process commands in current folder and (re)scan it."
407: (interactive "P")
408: (if (and (or mh-delete-list mh-move-list)
409: (y-or-n-p "Process commands? "))
410: (mh-process-commands mh-current-folder))
411: (setq mh-next-direction 'forward)
412: (mh-scan-folder mh-current-folder
413: (if arg (read-string "Range [all]? ") "all")))
414:
415:
416: (defun mh-redistribute (to cc)
417: "Redistribute a letter."
418: (interactive "sTo: \nsCc: ")
419: (let ((msg-filename (mh-msg-filename))
420: (msg (mh-get-msg-num t))
421: (folder mh-current-folder))
422: (mh-read-draft)
423: (delete-other-windows)
424: (when (or (zerop (buffer-size))
425: (not (y-or-n-p
426: "The file 'draft' exists. Redistribute old version? ")))
427: (erase-buffer)
428: (insert-file-contents msg-filename)
429: (goto-char (point-min))
430: (insert "Resent-To: " to "\n")
431: (if (not (equal cc ""))
432: (insert "Resent-cc: " cc "\n")))
433: (mh-compose-and-send-mail "-dist" folder to (mh-get-field "Subject:") cc
434: "F" "Distributed-to:")))
435:
436:
437: (defun mh-re-move ()
438: "Move specified message to same folder as last move."
439: (interactive)
440: (if (null mh-last-destination)
441: (error "No previous move"))
442: (mh-move-one-msg)
443: (mh-next-message))
444:
445:
446: (defun mh-save-message (file)
447: "Append the current message to the end of a file."
448: (interactive "FSave message in file: ")
449: (let ((msg-filename (mh-msg-filename)))
450: (call-process "/bin/csh" nil 0 nil "-c"
451: (format "cat %s >> %s " msg-filename file))))
452:
453:
454: (defun mh-search-folder ()
455: "Search the current folder for messages matching a pattern."
456: (interactive)
457: (let ((folder mh-current-folder))
458: (switch-to-buffer-other-window "pick-pattern")
459: (if (or (zerop (buffer-size))
460: (not (y-or-n-p "Reuse pattern? ")))
461: (mh-make-pick-template)
462: (message ""))
463: (setq mh-searching-folder folder)))
464:
465:
466: (defun mh-send (to cc subject)
467: "Compose and send a letter."
468: (interactive "sTo: \nsCc: \nsSubject: ")
469: (let ((folder (if (boundp 'mh-current-folder) mh-current-folder)))
470: (message "Composing a message...")
471: (mh-read-draft)
472: (delete-other-windows)
473: (when (or (zerop (buffer-size))
474: (not (y-or-n-p "The file 'draft' exists. Use it? ")))
475: (erase-buffer)
476: (if (file-exists-p (format "%scomponents" mh-user-path))
477: (insert-file-contents (format "%scomponents" mh-user-path))
478: (if (file-exists-p (format "%scomponents" mh-lib))
479: (insert-file-contents (format "%scomponents" mh-lib))
480: (error "Can't find components")))
481: (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc)
482: (goto-char (point-max))
483: (message "Composing a message...done"))
484: (mh-compose-and-send-mail "" folder to subject cc)))
485:
486:
487: (defun mh-show ()
488: "Show message indicated by cursor in scan buffer."
489: (interactive)
490: (setq mh-summarize nil)
491: (setq mode-name "Mh-Show")
492: (let ((msgn (mh-get-msg-num t))
493: (msg-filename (mh-msg-filename))
494: (folder mh-current-folder))
495: (if (not (file-exists-p msg-filename))
496: (error "Message %d does not exist." msgn))
497: (switch-to-buffer mh-show-buffer)
498: (erase-buffer)
499: (if mh-use-mhl
500: (mh-exec-lib-cmd-output "mhl" "-nobell" msg-filename)
501: (insert-file-contents msg-filename))
502: (setq buffer-file-name msg-filename)
503: (goto-char (point-min))
504: (cond (mh-clean-message-header
505: (mh-clean-message-header (point-min))
506: (goto-char (point-min)))
507: (t
508: (let ((case-fold-search t))
509: (re-search-forward "^To:\\|^From:\\|^Subject:\\|^Date:" nil t)
510: (beginning-of-line)
511: (recenter 0))))
512: (set-buffer-modified-p nil)
513: (setq mode-line-format
514: (concat "{%b} %[%p of " folder "/" msgn "%] %M"))
515: ;; These contortions are to force the summary line to be the top window.
516: (switch-to-buffer-other-window folder)
517: (delete-other-windows)
518: (switch-to-buffer-other-window mh-show-buffer)
519: (switch-to-buffer-other-window folder)
520: (shrink-window (- (window-height) mh-summary-height))
521: (recenter 1)
522: ;; Remove from unseen seq.
523: (mh-exec-cmd-no-wait "mark" mh-current-folder msgn "-seq" "unseen"
524: "-delete" "-nolist")))
525:
526:
527: (defun mh-sort-folder (&optional arg)
528: "Sort the messages in the current folder by date."
529: (interactive "P")
530: (mh-process-commands mh-current-folder)
531: (setq mh-next-direction 'forward)
532: (message "sorting folder...")
533: (mh-exec-cmd "sortm" mh-current-folder)
534: (message "sorting folder...done")
535: (mh-scan-folder mh-current-folder "all"))
536:
537:
538: (defun mh-summary ()
539: "Show a summary of mh-e commands."
540: (interactive)
541: (message
542: "Next Prev Go Del ^ ! Copy Undo . Toggle Ans Forw Redist Send List Execute")
543: (sit-for 5))
544:
545:
546: (defun mh-toggle-summarize ()
547: "Turn the summary mode of displaying messages on or off."
548: (interactive)
549: (setq mh-summarize (not mh-summarize))
550: (cond (mh-summarize
551: (delete-other-windows)
552: (setq mode-name "Mh-Summarize")
553: (recenter))
554: (t
555: (setq mode-name "Mh-Show")
556: (mh-show))))
557:
558:
559: (defun mh-undo (&optional arg)
560: "Undo the deletion or move of the specified message(s)."
561: (interactive "P")
562: (cond ((looking-at "^....D")
563: (let ((msgs (if arg (mh-read-seq "Undelete") (mh-get-msg-num t))))
564: (setq mh-delete-list (delq msgs mh-delete-list))
565: (if arg
566: (mh-notate-seq msgs ? mh-cmd-note)
567: (mh-notate ? mh-cmd-note))))
568:
569: ((looking-at "^....^")
570: (let ((msgs (if arg (mh-read-seq "Unmove") (mh-get-msg-num t))))
571: (mapcar
572: (function (lambda (move) (setcdr move (delq msgs (cdr move)))))
573: mh-move-list)
574: (if arg
575: (mh-notate-seq msgs ? mh-cmd-note)
576: (mh-notate ? mh-cmd-note))))
577:
578: (t nil)))
579:
580:
581: (defun mh-visit-folder (&optional arg)
582: "Visit a new folder."
583: (interactive "p")
584: (let ((folder (mh-get-folder-name "Visit" "" t))
585: (range (if arg (read-string "Range [all]? ") "all")))
586: (mh-scan-folder folder (if (equal range "") "all" range))
587: (delete-other-windows)))
588:
589:
590:
591: ;;; Support routines.
592:
593: (defun mh-delete-one-msg ()
594: "Delete the message pointed to by the cursor."
595: (if (looking-at "....^")
596: (error "Message %d already moved. Undo move before deleting."
597: (mh-get-msg-num t)))
598: (push (mh-get-msg-num t) mh-delete-list)
599: (mh-notate ?D mh-cmd-note))
600:
601:
602: (defun mh-move-one-msg ()
603: "Move the message pointed to by the cursor."
604: (if (looking-at "....D")
605: (error "Message %d is already deleted. Undo delete before moving."
606: (mh-get-msg-num nil))
607: (let ((others (assoc mh-last-destination mh-move-list))
608: (msg (mh-get-msg-num t)))
609: (if others
610: (setcdr others (cons msg (cdr others)))
611: (push (cons mh-last-destination (list msg)) mh-move-list))
612: (mh-notate ?^ mh-cmd-note))))
613:
614:
615: (defun mh-clean-message-header (start)
616: "Flush extraneous lines in a message header. The variable
617: mh-invisible-headers contains a regular expression specifying these lines."
618: (save-restriction
619: (goto-char start)
620: (search-forward "\n\n" nil t)
621: (narrow-to-region start (point))
622: (goto-char (point-min))
623: (while (re-search-forward mh-invisible-headers nil t)
624: (beginning-of-line)
625: (kill-line 1)
626: (while (looking-at "^[ \t]+")
627: (beginning-of-line)
628: (kill-line 1)))))
629:
630:
631: (defun mh-read-draft ()
632: "Read draft file into buffer draft. No errors if disk file has been
633: modified."
634: (switch-to-buffer "draft")
635: (set-buffer-modified-p nil)
636: (kill-buffer "draft")
637: (switch-to-buffer-other-window
638: (find-file-noselect (format "%sdraft" mh-user-path))))
639:
640:
641: (defun mh-next-message ()
642: "Move backward or forward to the next message in the buffer."
643: (if (eq mh-next-direction 'forward)
644: (mh-next-line 1)
645: (mh-previous-line 1)))
646:
647:
648: (defun mh-maybe-show ()
649: "If the scan listing is not summarized, show the message pointed to
650: by the cursor."
651: (if (not mh-summarize) (mh-show)))
652:
653:
654:
655: ;;; The folder data abstraction.
656:
657: (defun mh-make-folder (name)
658: "Create and initialize a new mail folder called NAME and make
659: it the current folder."
660: (switch-to-buffer name)
661: (kill-all-local-variables)
662: (setq buffer-read-only nil)
663: (erase-buffer)
664: (make-local-variable 'mh-current-folder) ; Name of folder
665: (setq mh-current-folder name)
666: (make-local-variable 'mh-show-buffer) ; Buffer that displays messages
667: (setq mh-show-buffer (format "show-%s" mh-current-folder))
668: (make-local-variable 'mh-folder-filename) ; e.g. /usr/foobar/Mail/inbox/
669: (setq mh-folder-filename (format "%s%s/" mh-user-path (substring name 1)))
670: (make-local-variable 'mh-summarize) ; Show scan list only?
671: (setq mh-summarize t)
672: (make-local-variable 'mh-next-seq-num) ; Index of free sequence id
673: (setq mh-next-seq-num 0)
674: (make-local-variable 'mh-delete-list) ; List of msgs nums to delete
675: (setq mh-delete-list nil)
676: (make-local-variable 'mh-move-list) ; Alist of dest . msgs nums
677: (setq mh-move-list nil)
678: (make-local-variable 'mh-seq-list) ; Alist of seq . msgs nums
679: (setq mh-seq-list nil)
680: (make-local-variable 'mh-next-direction) ; Direction to move to next message
681: (setq mh-next-direction 'forward)
682: (mh-folder-mode)
683: (setq buffer-read-only t)
684: (setq mode-name "Mh-Summarize"))
685:
686:
687: (defun mh-folder-mode ()
688: " \\[mh-next-line]: next message \\[mh-previous-line]: previous message
689: \\[mh-delete-msg]: delete (mark for deletion) \\[mh-move-msg]: put (mark for moving)
690: \\[mh-undo]: undo last delete or mark \\[mh-re-move]: repeat last ^ command
691: \\[mh-copy-msg]: copy message to another folder
692: \\[mh-show]: type message \\[mh-toggle-summarize]: toggle summarize mode
693: \\[mh-page-msg] page message \\[mh-previous-page]: page message backwards
694: \\[mh-print-msg]: print message \\[mh-goto]: goto a message
695: \\[mh-execute-commands]: execute pending delete and move commands
696: \\[mh-send]: send a message \\[mh-redistribute]: redistribute a message
697: \\[mh-answer]: answer a message \\[mh-forward]: forward a message
698: \\[mh-visit-folder]: visit folder \\[mh-inc-folder]: inc mail
699: \\[mh-kill-folder]: kill folder \\[mh-list-folders]: list folders
700: \\[mh-renumber-folder]: pack folder \\[mh-rescan-folder]: rescan folder
701: \\[mh-search-folder]: search folder \\[mh-sort-folder]: sorts the letters in the folder
702:
703: 0..9 Add a message to a numbered sequence
704:
705: A prefix argument to delete, move, list, or undo applies to a sequence.
706:
707: Edit the scan list, marking messages. Moving and deleting messages is
708: deferred until you type \\[mh-execute-commands]."
709: (use-local-map mh-folder-mode-map)
710: (setq major-mode 'mh-folder-mode)
711: (setq mode-name "mh-folder")
712: (if (and (boundp 'mh-folder-mode-hook) mh-folder-mode-hook)
713: (funcall mh-folder-mode-hook)))
714:
715:
716: (defun mh-scan-folder (folder range)
717: "Scan the folder FOLDER over the range RANGE. Return in the folder."
718: (if (null (get-buffer folder))
719: (mh-make-folder folder)
720: (switch-to-buffer-other-window folder))
721: (mh-regenerate-headers range)
722: (when (looking-at "scan: no messages ")
723: (let ((buffer-read-only nil))
724: (erase-buffer))
725: (if (equal range "all")
726: (message "Folder %s is empty" folder)
727: (message "No messages in %s, range %s" folder range))
728: (sit-for 5))
729: (setq mode-line-format (mh-make-mode-line))
730: (mh-unmark-all-headers nil)
731: (mh-position-to-current))
732:
733:
734: (defun mh-regenerate-headers (range)
735: "Replace buffer with scan of its contents over range RANGE."
736: (let ((buffer-read-only nil))
737: (message (format "scanning %s..." (buffer-name)))
738: (delete-other-windows)
739: (erase-buffer)
740: (mh-exec-cmd-output "scan" (buffer-name) range)
741: (goto-char (point-min))
742: (message (format "scanning %s...done" (buffer-name)))
743: ))
744:
745:
746: (defun mh-get-new-mail ()
747: "Read new mail into the current buffer. Return t if there was new mail,
748: nil otherwise. Return in the current buffer."
749: (let ((buffer-read-only nil))
750: (message (format "inc %s..." (buffer-name)))
751: (mh-unmark-all-headers nil)
752: (setq mh-next-direction 'forward)
753: (goto-char (point-max))
754: (let ((start-of-inc (point)))
755: (mh-exec-cmd-output "inc")
756: (message (format "inc %s...done" (buffer-name)))
757: (goto-char start-of-inc)
758: (cond ((looking-at "inc: no mail")
759: (kill-line 1)
760: (setq mode-line-format (mh-make-mode-line))
761: (previous-line 1)
762: (message "No new mail")
763: (sit-for 5)
764: nil)
765: (t
766: (kill-line 2)
767: (setq mode-line-format (mh-make-mode-line))
768: t)))))
769:
770:
771: (defun mh-make-mode-line ()
772: "Returns a string for mode-line-format."
773: (save-excursion
774: (let ((lines (count-lines (point-min) (point-max))))
775: (goto-char (point-min))
776: (let* ((first (mh-get-msg-num nil))
777: (case-fold-search nil)
778: (current (and (re-search-forward "....\\+" nil t)
779: (mh-get-msg-num nil))))
780: (goto-char (point-max))
781: (previous-line 1)
782: (let ((last (mh-get-msg-num nil)))
783: (concat "{%b} %[" lines " messages"
784: (if (> lines 0)
785: (format " (%d - %d)" first last)
786: "")
787: (if current
788: (format " cur = %d" current)
789: "")
790: "%] (%p%% - %m)"))))))
791:
792:
793: (defun mh-unmark-all-headers (remove-all-flags)
794: "This function removes all + flags from the headers, and if called
795: with a non-nil argument, removes all D and ^ flags too."
796: (let ((buffer-read-only nil)
797: (case-fold-search nil))
798: (goto-char (point-min))
799: (while (if remove-all-flags
800: (re-search-forward "^....\\D\\|^....\\^\\|^....\\+" nil t)
801: (re-search-forward "^....\\+" nil t))
802: (delete-backward-char 1)
803: (insert " "))))
804:
805:
806: (defun mh-position-to-current ()
807: "Position the cursor at the current message."
808: (let ((curmsg (mh-get-cur-msg mh-folder-filename)))
809: (cond ((or (zerop curmsg) (mh-goto curmsg t))
810: (goto-char (point-max))
811: (forward-line -1)
812: (mh-maybe-show)
813: (message "No current message"))
814: (t
815: (mh-notate ?+ 4)
816: (recenter 0)))))
817:
818:
819: (defun mh-pack-folder ()
820: "Closes and packs the current folder."
821: (let ((buffer-read-only nil))
822: (message "closing folder...")
823: (mh-process-commands mh-current-folder)
824: (message "packing folder...")
825: (mh-exec-cmd-quiet "folder" mh-current-folder "-pack")
826: (mh-regenerate-headers "all")
827: (message "packing done"))
828: (setq mode-line-format (mh-make-mode-line)))
829:
830:
831: (defun mh-apply-to-message-list (func list)
832: "Apply function FUNC to each item in a message-list LIST,
833: passing the name and list of messages as arguments."
834: (mapcar (function (lambda (l) (apply func (list (car l) (cdr l))))) list))
835:
836:
837: (defun mh-process-commands (buffer)
838: "Process outstanding commands for the buffer BUFFER."
839: (message "Processing deletes and moves...")
840: (switch-to-buffer buffer)
841: (let ((buffer-read-only nil))
842: ;; Sequences must be first
843: (mh-process-seq-commands mh-seq-list)
844:
845: ;; Then refile messages
846: (mh-apply-to-message-list
847: (function (lambda (dest msgs)
848: (apply 'mh-exec-cmd
849: (nconc (cons "refile" msgs)
850: (list "-src" (format "%s" buffer) dest)))))
851: mh-move-list)
852:
853: ;; Now delete messages
854: (if mh-delete-list
855: (apply 'mh-exec-cmd
856: (nconc (list "rmm" (format "%s" buffer)) mh-delete-list)))
857:
858: ;; Mark as cur message.
859: (if (mh-get-msg-num nil)
860: (mh-exec-cmd-no-wait "mark" mh-current-folder (mh-get-msg-num nil)
861: "-seq" "cur" "-add" "-zero" "-nolist")
862: (mh-exec-cmd-no-wait "mark" mh-current-folder "-seq" "cur" "-delete"
863: "all" "-nolist"))
864:
865: (switch-to-buffer buffer)
866: (goto-char (point-min))
867: (flush-lines "^....D")
868: (goto-char (point-min))
869: (flush-lines "^....^")
870: (setq mh-delete-list nil
871: mh-move-list nil
872: mh-seq-list nil))
873: (message "Processing deletes and moves...done"))
874:
875:
876:
877: ;;; A mode for composing and sending a message.
878:
879: (defun mh-letter-mode ()
880: "Mode for composing letters in mh-e.
881: Like text-mode, but with these additional commands:
882: \\[mh-send-letter]: sends the message.
883: \\[mh-insert-letter]: inserts a message into the current letter.
884: \\[mh-to-to]: move to the To: field \\[mh-to-subject]: move to the Subject: field
885: \\[mh-to-cc]: move to the Cc: field \\[mh-to-bcc]: move to the Bcc: field
886: \\[mh-to-fcc]: move to the Fcc: field
887: \\[mh-check-whom]: report who a message will go to
888: \\[kill-buffer]: quit draft and delete it."
889: (text-mode)
890: (if mh-auto-fill-letters
891: (auto-fill-mode 1))
892: (make-local-variable 'paragraph-start)
893: (setq paragraph-start (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-start))
894: (make-local-variable 'paragraph-separate)
895: (setq paragraph-separate
896: (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-separate))
897: (use-local-map mh-letter-mode-map)
898: (setq major-mode 'mh-letter-mode)
899: (setq mode-name "mh-letter")
900: (if (and (boundp 'mh-letter-mode-hook) mh-letter-mode-hook)
901: (funcall mh-letter-mode-hook)))
902:
903:
904: (defun mh-to-to ()
905: "Move point to end of To: field."
906: (interactive)
907: (expand-abbrev)
908: (mh-position-on-field "To:" t))
909:
910:
911: (defun mh-to-subject ()
912: "Move point to end of Subject: field."
913: (interactive)
914: (expand-abbrev)
915: (mh-position-on-field "Subject:" t))
916:
917:
918: (defun mh-to-cc ()
919: "Move point to end of Cc: field. Creates the field if necessary"
920: (interactive)
921: (expand-abbrev)
922: (when (not (mh-position-on-field "Cc:" t))
923: (mh-position-on-field "To:" nil)
924: (insert-string "\nCc: ")))
925:
926:
927: (defun mh-to-bcc ()
928: "Move point to end of Bcc: field. Creates the field if necessary"
929: (interactive)
930: (expand-abbrev)
931: (when (not (mh-position-on-field "Bcc:" t))
932: (mh-position-on-field "To:" nil)
933: (insert-string "\nBcc: ")))
934:
935:
936: (defun mh-to-fcc ()
937: "Move point to end of Fcc: field. Creates the field if necessary"
938: (interactive)
939: (expand-abbrev)
940: (when (not (mh-position-on-field "Fcc:" t))
941: (mh-position-on-field "To:" nil)
942: (insert-string "\nFcc: ")))
943:
944:
945: (defun mh-check-whom ()
946: "List recipients of the current message."
947: (interactive)
948: (let ((file-name (buffer-file-name)))
949: (save-buffer)
950: (message "Checking recipients...")
951: (switch-to-buffer-other-window "*Mail Recipients*")
952: (erase-buffer)
953: (mh-exec-cmd-output "whom" file-name)
954: (previous-window)))
955:
956:
957:
958: ;;; Routines to make a search pattern and search for a message.
959:
960: (defun mh-make-pick-template ()
961: "Initialize a buffer with a template for a pick pattern."
962: (erase-buffer)
963: (kill-all-local-variables)
964: (make-local-variable 'mh-searching-folder)
965: (insert "From: \n"
966: "To: \n"
967: "Cc: \n"
968: "Date: \n"
969: "Subject: \n"
970: "---------\n")
971: (mh-letter-mode)
972: (use-local-map mh-pick-mode-map)
973: (setq mode-line-format "{%b}\tPick Pattern\t(^C^C to do search)")
974: (goto-char (point-min))
975: (end-of-line))
976:
977:
978: (defun mh-do-pick-search ()
979: "Search for the messages in the current folder meeting the qualification
980: in the current buffer and make them into a sequence."
981: (interactive)
982: (let* ((pattern-buffer (buffer-name))
983: (searching-buffer mh-searching-folder)
984: (range "all")
985: (seq (mh-new-seq mh-searching-folder))
986: (pattern nil))
987: (message "Searching...")
988: (goto-char (point-min))
989: (while (setq pattern (mh-next-pick-field pattern-buffer))
990: (setq msgs
991: (mh-seq-from-command searching-buffer
992: seq
993: (nconc (cons "pick" pattern)
994: (list searching-buffer
995: range
996: "-sequence" seq "-list"))))
997: (setq range seq))
998: (message "Searching...done")
999: (switch-to-buffer searching-buffer)
1000: (mh-notate-seq seq (mh-seq-to-notation seq) 0)))
1001:
1002:
1003: (defun mh-next-pick-field (buffer)
1004: "Return the next piece of a pick argument that can be extracted from the
1005: BUFFER. Returns nil if no pieces remain."
1006: (switch-to-buffer buffer)
1007: (let ((case-fold-search t))
1008: (cond ((eobp)
1009: nil)
1010: ((re-search-forward "^\\([a-z].*\\):[ \t]*\\([a-z0-9].*\\)$" nil t)
1011: (let* ((component
1012: (format "-%s"
1013: (downcase (buffer-substring (match-beginning 1)
1014: (match-end 1)))))
1015: (pat (buffer-substring (match-beginning 2) (match-end 2))))
1016: (forward-line 1)
1017: (list component pat)))
1018: ((re-search-forward "^-*$" nil t)
1019: (forward-char 1)
1020: (let ((body (buffer-substring (point) (point-max))))
1021: (if (and (> (length body) 0) (not (equal body "\n")))
1022: (list "-search" body)
1023: nil)))
1024: (t
1025: nil))))
1026:
1027:
1028:
1029: ;;; Routines compose and send a letter.
1030:
1031: (defun mh-compose-and-send-mail (send-args sent-from-folder to subject cc
1032: &optional annotate-char
1033: annotate-field)
1034: "Edit and compose a draft message and send or save it.
1035: SENT-FROM-FOLDER is buffer containing summary of current folder, if any.
1036: SEND-ARGS is an optional argument passed to the send command.
1037: The TO, SUBJECT, and CC fields are passed to the mh-compose-letter-hook.
1038: If ANNOTATE-CHAR is non-null, it is used to notate the scan listing of the
1039: current message. In that case, the ANNOTATE-FIELD is used to build a string
1040: for mh-annotate-msg."
1041: (let ((sent-from-msg))
1042: (save-window-excursion
1043: (when sent-from-folder
1044: (switch-to-buffer sent-from-folder)
1045: (setq sent-from-msg (mh-get-msg-num nil))))
1046: (pop-to-buffer "draft")
1047: (mh-letter-mode)
1048: (make-local-variable 'mh-send-args)
1049: (setq mh-send-args send-args)
1050: (make-local-variable 'mh-sent-from-folder)
1051: (setq mh-sent-from-folder sent-from-folder)
1052: (make-local-variable 'mh-sent-from-msg)
1053: (setq mh-sent-from-msg sent-from-msg)
1054: (make-local-variable 'mh-annotate-field)
1055: (setq mh-annotate-field annotate-field)
1056: (make-local-variable 'mh-annotate-char)
1057: (setq mh-annotate-char annotate-char)
1058: (setq mode-line-format "{%b} %[Mail/draft%] (%p - %m) (^C^C to send) %M")
1059: (if (and (boundp 'mh-compose-letter-hook) mh-compose-letter-hook)
1060: (funcall mh-compose-letter-hook to subject cc))))
1061:
1062:
1063: (defun mh-send-letter ()
1064: "Send the letter in the current buffer."
1065: (interactive)
1066: (save-buffer)
1067: (message "Sending...")
1068: (if mh-send-args
1069: (mh-exec-cmd-no-wait "send" "-push" "-unique" mh-send-args
1070: (buffer-file-name))
1071: (mh-exec-cmd-no-wait "send" "-push" "-unique" (buffer-file-name)))
1072: (if mh-annotate-char
1073: (mh-annotate-msg mh-sent-from-msg mh-sent-from-folder
1074: mh-annotate-char
1075: "-component" mh-annotate-field
1076: "-text" (format "\"%s %s\"" (mh-get-field "To:")
1077: (mh-get-field "Cc:"))))
1078: (message "Sending...done")
1079: (kill-buffer (buffer-name)))
1080:
1081:
1082: (defun mh-insert-letter (&optional arg)
1083: "Insert a message in the current letter, asking for folder and number.
1084: Removes headers using mh-invisible-headers.
1085: Prefixes each non-blank line with mh-ins-buf-prefix (default \">> \").
1086: Just \\[universal-argument] means do not indent and do not delete any
1087: header fields. Leaves point before the text and mark after it."
1088: (interactive "p")
1089: (let ((folder (mh-get-folder-name "Message from" mh-sent-from-folder nil))
1090: (message (read-input (format "Message number%s: "
1091: (if mh-sent-from-msg
1092: (format " [%d]" mh-sent-from-msg)
1093: ""))))
1094: (start (point)))
1095: (if (equal message "") (setq message (format "%d" mh-sent-from-msg)))
1096: (mh-exec-lib-cmd-output "mhl" "-nobell"
1097: (format "%s%s/%s" mh-user-path
1098: (substring folder 1) message))
1099: (when (not (equal arg 4))
1100: (mh-clean-message-header start)
1101: (narrow-to-region start (mark))
1102: (mh-insert-prefix-string mh-ins-buf-prefix)
1103: (widen))
1104: (exchange-point-and-mark)))
1105:
1106:
1107: (defun mh-insert-cur-message ()
1108: "Inserts the currently visible message into the current buffer.
1109: Prefixes the string mh-ins-buf-prefix to each non-blank line
1110: of the inserted text. If there is a region set in the
1111: currently visible message's buffer, only the region will be grabbed.
1112: Otherwise, the region from (point) to the end will be grabbed."
1113: (interactive)
1114: (let ((to-point (point))
1115: (to-buffer (current-buffer)))
1116: (set-buffer "*message*")
1117: (let ((mh-ins-str (if (mark)
1118: (buffer-substring (point) (mark))
1119: (buffer-substring (point) (point-max)))))
1120: (set-buffer to-buffer)
1121: (narrow-to-region to-point to-point)
1122: (insert-string mh-ins-str)
1123: (mh-insert-prefix-string mh-ins-buf-prefix)
1124: (widen))))
1125:
1126:
1127: (defun mh-insert-prefix-string (ins-string)
1128: "Preface each line in the current buffer with STRING."
1129: (goto-char (point-min))
1130: (replace-regexp "^.." (concat ins-string "\\&") nil)
1131: (goto-char (point-min)))
1132:
1133:
1134:
1135: ;;; Commands to manipulate sequences.
1136:
1137: (defmacro mh-seq-name (pair)
1138: (list 'car pair))
1139:
1140: (defmacro mh-seq-msgs (pair)
1141: (list 'cdr pair))
1142:
1143:
1144: (defun mh-seq-to-msgs (seq)
1145: "Returns the list of messages in sequence SEQ."
1146: (mh-seq-msgs (assoc seq mh-seq-list)))
1147:
1148:
1149: (defun mh-read-seq (prompt)
1150: "Prompt the user with PROMPT and read a sequence name."
1151: (mh-letter-to-seq
1152: (string-to-char (read-string (format "%s %s" prompt "sequence: ")))))
1153:
1154:
1155: (defun mh-seq-from-command (folder seq command)
1156: "In FOLDER, make a sequence named SEQ by executing COMMAND."
1157: (let ((msgs ())
1158: (case-fold-search t))
1159: (save-excursion
1160: (save-window-excursion
1161: (apply 'mh-exec-cmd-quiet command)
1162: (switch-to-buffer " *mh-temp*")
1163: (goto-char (point-min))
1164: (while (re-search-forward "\\([0-9]+\\)" nil t)
1165: (let ((num (string-to-int (buffer-substring (match-beginning 1)
1166: (match-end 1)))))
1167: (if (not (zerop num))
1168: (push num msgs)))))
1169: (switch-to-buffer folder)
1170: (push (cons seq msgs) mh-seq-list)
1171: msgs)))
1172:
1173:
1174: (defun mh-remove-seq (seq)
1175: "Delete the sequence SEQ."
1176: (let ((entry (assoc seq mh-seq-list)))
1177: (setq mh-seq-list (delq (car entry) mh-seq-list))
1178: (mh-notate-seq (mh-seq-msgs (car entry)) ? 0)))
1179:
1180:
1181: (defun mh-remove-msg-from-seq (msg-num seq)
1182: "Remove a message MSG-NUM from the sequence SEQ."
1183: (let ((seq (assoc seq mh-seq-list)))
1184: (if seq
1185: (setcdr seq (delq msg-num (mh-seq-msgs seq)))))
1186: (mh-notate ? 0))
1187:
1188:
1189: (defun mh-add-msg-to-seq (msg-num seq)
1190: "Add a message MSG-NUM to a sequence SEQ."
1191: (let ((seq-list (assoc seq mh-seq-list)))
1192: (mh-notate (mh-seq-to-notation seq) 0)
1193: (if (null seq-list)
1194: (push (cons seq (list msg-num)) mh-seq-list)
1195: (setcdr seq-list (cons msg-num (cdr seq-list))))))
1196:
1197:
1198:
1199: (defun mh-new-seq (folder)
1200: "Return a new sequence name for FOLDER."
1201: (save-excursion
1202: (switch-to-buffer folder)
1203: (if (= mh-next-seq-num 10)
1204: (error "No more sequences"))
1205: (setq mh-next-seq-num (+ mh-next-seq-num 1))
1206: (mh-letter-to-seq (+ (1- mh-next-seq-num) ?a))))
1207:
1208:
1209: (defun mh-letter-to-seq (letter)
1210: "Given a LETTER, return a string that is a valid sequence name."
1211: (cond ((and (>= letter ?0) (<= letter ?9))
1212: (intern (format "mhe%c" letter)))
1213: ((and (>= letter ?a) (<= letter ?z))
1214: (intern (format "mhe%c" letter)))
1215: (t
1216: (error "A sequence is named 0...9"))))
1217:
1218:
1219: (defun mh-seq-to-notation (seq)
1220: "Return the string used to indicate sequence SEQ in a scan listing."
1221: (string-to-char (substring (symbol-name seq) 3 4)))
1222:
1223:
1224: (defun mh-notate-seq (seq notation offset)
1225: "Mark all messages in the sequence SEQ with the NOTATION at character
1226: OFFSET."
1227: (mh-apply-to-seq seq 'mh-notate notation offset))
1228:
1229:
1230: (defun mh-apply-to-seq (seq function &rest args)
1231: "For each message in sequence SEQ, evaluate the FUNCTION with ARGS."
1232: (mapcar (function (lambda (msg) (mh-goto msg) (apply function args)))
1233: (mh-seq-to-msgs seq)))
1234:
1235:
1236: (defun mh-process-seq-commands (seq-list)
1237: "Process outstanding sequence commands for the sequences in SEQ-LIST."
1238: (mh-apply-to-message-list
1239: (function (lambda (seq msgs)
1240: (apply 'mh-exec-cmd-quiet
1241: (nconc (list "mark" "-zero" "-seq" (format "%s" seq)
1242: "-add" "-nolist")
1243: msgs))))
1244: seq-list))
1245:
1246:
1247:
1248: ;;; Issue commands to mh.
1249:
1250: (defun mh-exec-cmd (command &rest args)
1251: "Execute MH command COMMAND with ARGS. Any output is shown to the user."
1252: (save-excursion
1253: (switch-to-buffer-other-window " *mh-temp*")
1254: (erase-buffer)
1255: (apply 'call-process (nconc (list (format "%s%s" mh-progs command)
1256: nil t nil)
1257: (mh-list-to-string args)))
1258: (if (> (buffer-size) 0)
1259: (sit-for 5))))
1260:
1261:
1262: (defun mh-exec-cmd-quiet (command &rest args)
1263: "Execute MH command COMMAND with ARGS. Output is collected, but not shown
1264: to the user."
1265: (save-excursion
1266: (switch-to-buffer " *mh-temp*")
1267: (erase-buffer)
1268: (apply 'call-process (nconc (list (format "%s%s" mh-progs command)
1269: nil t nil)
1270: (mh-list-to-string args)))))
1271:
1272:
1273: (defun mh-exec-cmd-output (command &rest args)
1274: "Execute MH command COMMAND with ARGS putting the output into buffer after
1275: point. Set mark after inserted text."
1276: (set-mark (point))
1277: (apply 'call-process (nconc (list (format "%s%s" mh-progs command) nil t nil)
1278: (mh-list-to-string args)))
1279: (exchange-point-and-mark))
1280:
1281:
1282: (defun mh-exec-cmd-no-wait (command &rest args)
1283: "Execute MH command COMMAND with ARGS and do not wait until it finishes."
1284: (apply 'call-process (nconc (list (format "%s%s" mh-progs command) nil 0 nil)
1285: (mh-list-to-string args))))
1286:
1287:
1288:
1289: (defun mh-exec-lib-cmd-output (command &rest args)
1290: "Execute MH library command COMMAND with ARGS. Put the output into
1291: buffer after point. Set mark after inserted text."
1292: (set-mark (point))
1293: (apply 'call-process (nconc (list (format "%s%s" mh-lib command) nil t nil)
1294: (mh-list-to-string args)))
1295: (exchange-point-and-mark))
1296:
1297:
1298: (defun mh-list-to-string (l)
1299: "Flattens the list L and makes every element a string."
1300: (let ((new-list nil))
1301: (while l
1302: (cond ((symbolp (car l)) (push (format "%s" (car l)) new-list))
1303: ((numberp (car l)) (push (format "%d" (car l)) new-list))
1304: ((equal (car l) ""))
1305: ((stringp (car l)) (push (car l) new-list))
1306: ((null (car l)))
1307: ((listp (car l)) (setq new-list
1308: (nconc (mh-list-to-string (car l))
1309: new-list)))
1310: (t (error "Bad argument %s" (car l))))
1311: (setq l (cdr l)))
1312: (nreverse new-list)))
1313:
1314:
1315:
1316: ;;; Commands to annotate a message.
1317:
1318: (defun mh-annotate-msg (msg buffer note &rest args)
1319: "Mark the MESSAGE in BUFFER listing with the character NOTE and annotate
1320: the saved message with ARGS."
1321: ;; Wait for annotation to finish, to avoid race condition with reading msg.
1322: (apply 'mh-exec-cmd (cons "anno" (nconc (list buffer msg) args)))
1323: (save-excursion
1324: (switch-to-buffer buffer)
1325: (if (mh-goto msg t)
1326: (mh-notate note 5))))
1327:
1328:
1329: (defun mh-notate (notation offset)
1330: "Marks the current message with the character NOTATION at position OFFSET."
1331: (let ((buffer-read-only nil))
1332: (beginning-of-line)
1333: (goto-char (+ (point) offset))
1334: (delete-char 1)
1335: (insert notation)
1336: (beginning-of-line)))
1337:
1338:
1339:
1340: ;;; User prompting commands.
1341:
1342: (defun mh-get-folder-name (prompt default can-create)
1343: "Prompt for a folder name with PROMPT. DEFAULT is used if the folder
1344: exists and the user types CR. If the CAN-CREATE flag is t,
1345: then a non-existant folder is made."
1346: (let* ((prompt (format "%s folder%s" prompt
1347: (if (equal "" default)
1348: "? "
1349: (format " [%s]? " default))))
1350: name)
1351: (while (and (setq name (completing-read prompt mh-folder-list
1352: nil (not can-create)))
1353: (equal name "")
1354: (equal default "")))
1355: (cond ((equal name "")
1356: (setq name default))
1357: ((not (equal (substring name 0 1) "+"))
1358: (setq name (format "+%s" name))))
1359: (let ((new-file-p
1360: (not
1361: (file-exists-p (format "%s%s" mh-user-path (substring name 1))))))
1362: (cond ((and new-file-p
1363: (y-or-n-p
1364: (format "Folder %s does not exist. Create it? " name)))
1365: (message "Creating %s" name)
1366: (call-process "mkdir" nil nil nil
1367: (format "%s%s" mh-user-path (substring name 1)))
1368: (message "Creating %s...done" name)
1369: (push (list name) mh-folder-list)
1370: (push (list (substring name 1 nil)) mh-folder-list))
1371: (new-file-p
1372: (error ""))
1373: (t
1374: (when (null (assoc name mh-folder-list))
1375: (push (list name) mh-folder-list)
1376: (push (list (substring name 1 nil)) mh-folder-list)))))
1377: name))
1378:
1379:
1380: (defun mh-make-folder-list ()
1381: "Returns a list of the user's folders in a form suitable for completing
1382: read."
1383: (interactive)
1384: (save-window-excursion
1385: (mh-exec-cmd-quiet "folders" "-fast" "-norecurse")
1386: (switch-to-buffer " *mh-temp*")
1387: (goto-char (point-min))
1388: (let ((list nil))
1389: (while (not (eobp))
1390: (let ((start (point)))
1391: (search-forward "\n" nil t)
1392: (let ((folder (buffer-substring start (- (point) 1))))
1393: (push (list folder) list)
1394: (push (list (format "+%s" folder)) list))))
1395: list)))
1396:
1397:
1398: (defun mh-remove-folder-from-folder-list (folder)
1399: "Remove FOLDER from the list of folders."
1400: (setq mh-folder-list
1401: (delq (assoc (substring folder 1 nil) mh-folder-list)
1402: mh-folder-list)))
1403:
1404:
1405:
1406: ;;; Misc. functions.
1407:
1408: (defun mh-get-msg-num (error-if-no-message)
1409: "Returns the message number of the current message. If the argument
1410: ERROR-IF-NO-MESSAGE is t, then complain if the cursor is not pointing to a
1411: message."
1412: (save-excursion
1413: (beginning-of-line)
1414: (cond ((looking-at "^[0-9a-z]?[ ]+\\([0-9]+\\)")
1415: (string-to-int (buffer-substring (match-beginning 1)
1416: (match-end 1))))
1417: ((looking-at "^\\([0-9]+\\)")
1418: (string-to-int (buffer-substring (match-beginning 1)
1419: (match-end 1))))
1420: (error-if-no-message
1421: (error "Cursor not pointing to message"))
1422: (t nil))))
1423:
1424:
1425: (defun mh-msg-search-pat (n)
1426: "Returns a search pattern for message N in the scan listing."
1427: (cond ((< n 10) (format "^...%d" n))
1428: ((< n 100) (format "^..%d" n))
1429: ((< n 1000) (format "^.%d" n))
1430: (t (format "^%d" n))))
1431:
1432:
1433: (defun mh-msg-filename ()
1434: "Returns a string containing the pathname for the file containing the
1435: current message."
1436: (format "%s%d" mh-folder-filename (mh-get-msg-num t)))
1437:
1438:
1439: (defun mh-msg-filenames (msgs folder)
1440: "Returns a string of filenames specifying MSGS in FOLDER."
1441: (if msgs
1442: (let ((args ""))
1443: (while (cdr msgs)
1444: (setq args (format "%s%s%d " args folder (car msgs)))
1445: (setq msgs (cdr msgs)))
1446: (format "%s%s%d" args folder (car msgs)))
1447: ""))
1448:
1449:
1450: (defun mh-find-path ()
1451: "Set mh_path from ~/.mh_profile."
1452: (save-window-excursion
1453: (if (not (file-exists-p "~/.mh_profile"))
1454: (error "Cannot find .mh_profile file."))
1455: (switch-to-buffer " *mh_profile*")
1456: (erase-buffer)
1457: (insert-file-contents "~/.mh_profile")
1458: (if (equal (setq mh-user-path (mh-get-field "Path:")) "")
1459: (setq mh-user-path "Mail/")
1460: (setq mh-user-path (format "%s/" mh-user-path)))
1461: (if (not (equal (substring mh-user-path 0 1) "/"))
1462: (setq mh-user-path (format "%s/%s" (getenv "HOME") mh-user-path)))))
1463:
1464:
1465: (defun mh-get-cur-msg (folder)
1466: "Returns the number of the 'cur' message in FOLDER."
1467: (save-excursion
1468: (switch-to-buffer " *mh_temp*")
1469: (erase-buffer)
1470: (mh-exec-cmd-output "pick" folder "cur")
1471: (string-to-int (buffer-substring (point-min) (point)))))
1472:
1473:
1474: (defun mh-get-field (field)
1475: "Find and return the value of field FIELD in the current buffer.
1476: Returns the empty string if the field is not in the message."
1477: (let ((case-fold-search t))
1478: (goto-char (point-min))
1479: (cond ((not (search-forward field nil t)) "")
1480: ((looking-at "[\t ]*$") "")
1481: (t
1482: (re-search-forward "[\t ]*\\([a-zA-z0-9/].*\\)$" nil t)
1483: (let ((field (buffer-substring (match-beginning 1)
1484: (match-end 1)))
1485: (end-of-match (point)))
1486: (forward-line)
1487: (while (looking-at "[ \t]") (forward-line 1))
1488: (backward-char 1)
1489: (format "%s%s" field (buffer-substring end-of-match (point))))))))
1490:
1491:
1492: (defun mh-insert-fields (&rest name-values)
1493: "Insert the NAME-VALUE pairs in the current buffer."
1494: (let ((case-fold-search t))
1495: (while name-values
1496: (let ((field-name (car name-values))
1497: (value (cadr name-values)))
1498: (goto-char (point-min))
1499: (cond ((not (re-search-forward (format "^%s" field-name) nil t))
1500: (re-search-forward "^---\\|^$")
1501: (beginning-of-line)
1502: (insert field-name " " value "\n"))
1503: (t
1504: (end-of-line)
1505: (insert " " value)))
1506: (setq name-values (cddr name-values))))))
1507:
1508:
1509: (defun mh-position-on-field (field set-mark)
1510: "Set point to the end of the line beginning with FIELD. Sets the mark
1511: to the point, if SET-MARK is non-nil."
1512: (if set-mark (set-mark (point)))
1513: (goto-char (point-min))
1514: (if (re-search-forward (format "^%s" field) nil t)
1515: (progn (end-of-line) t)
1516: nil))
1517:
1518:
1519:
1520: ;;; Build the folder-mode keymap:
1521:
1522: (define-key mh-folder-mode-map " " 'mh-page-msg)
1523: (define-key mh-folder-mode-map "!" 'mh-re-move)
1524: (define-key mh-folder-mode-map "." 'mh-show)
1525: (define-key mh-folder-mode-map "0" 'mh-indicate-seq)
1526: (define-key mh-folder-mode-map "1" 'mh-indicate-seq)
1527: (define-key mh-folder-mode-map "2" 'mh-indicate-seq)
1528: (define-key mh-folder-mode-map "3" 'mh-indicate-seq)
1529: (define-key mh-folder-mode-map "4" 'mh-indicate-seq)
1530: (define-key mh-folder-mode-map "5" 'mh-indicate-seq)
1531: (define-key mh-folder-mode-map "6" 'mh-indicate-seq)
1532: (define-key mh-folder-mode-map "7" 'mh-indicate-seq)
1533: (define-key mh-folder-mode-map "8" 'mh-indicate-seq)
1534: (define-key mh-folder-mode-map "9" 'mh-indicate-seq)
1535: (define-key mh-folder-mode-map ">" 'mh-save-message)
1536: (define-key mh-folder-mode-map "?" 'mh-summary)
1537: (define-key mh-folder-mode-map "\177" 'mh-previous-page)
1538: (define-key mh-folder-mode-map "\e " 'mh-page-digest)
1539: (define-key mh-folder-mode-map "\ef" 'mh-visit-folder)
1540: (define-key mh-folder-mode-map "\ei" 'mh-inc-folder)
1541: (define-key mh-folder-mode-map "\ek" 'mh-kill-folder)
1542: (define-key mh-folder-mode-map "\el" 'mh-list-folders)
1543: (define-key mh-folder-mode-map "\ep" 'mh-renumber-folder)
1544: (define-key mh-folder-mode-map "\er" 'mh-rescan-folder)
1545: (define-key mh-folder-mode-map "\es" 'mh-search-folder)
1546: (define-key mh-folder-mode-map "^" 'mh-move-msg)
1547: (define-key mh-folder-mode-map "a" 'mh-answer)
1548: (define-key mh-folder-mode-map "c" 'mh-copy-msg)
1549: (define-key mh-folder-mode-map "d" 'mh-delete-msg)
1550: (define-key mh-folder-mode-map "e" 'mh-execute-commands)
1551: (define-key mh-folder-mode-map "f" 'mh-forward)
1552: (define-key mh-folder-mode-map "g" 'mh-goto)
1553: (define-key mh-folder-mode-map "l" 'mh-print-msg)
1554: (define-key mh-folder-mode-map "n" 'mh-next-line)
1555: (define-key mh-folder-mode-map "p" 'mh-previous-line)
1556: (define-key mh-folder-mode-map "r" 'mh-redistribute)
1557: (define-key mh-folder-mode-map "s" 'mh-send)
1558: (define-key mh-folder-mode-map "t" 'mh-toggle-summarize)
1559: (define-key mh-folder-mode-map "u" 'mh-undo)
1560: (define-key mh-folder-mode-map "x" 'mh-execute-commands)
1561:
1562: ;;; Build the letter-mode keymap:
1563:
1564: (define-key mh-letter-mode-map "\^C\^C" 'mh-send-letter)
1565: (define-key mh-letter-mode-map "\^C\^Y" 'mh-insert-letter)
1566: (define-key mh-letter-mode-map "\^Cb" 'mh-to-bcc)
1567: (define-key mh-letter-mode-map "\^Cc" 'mh-to-cc)
1568: (define-key mh-letter-mode-map "\^Cf" 'mh-to-fcc)
1569: (define-key mh-letter-mode-map "\^Cq" 'kill-buffer)
1570: (define-key mh-letter-mode-map "\^Cs" 'mh-to-subject)
1571: (define-key mh-letter-mode-map "\^Ct" 'mh-to-to)
1572: (define-key mh-letter-mode-map "\^Cw" 'mh-check-whom)
1573: (define-key mh-letter-mode-map "\^Cy" 'mh-insert-cur-message)
1574:
1575: ;;; Build the pick-mode keymap:
1576:
1577: (define-key mh-pick-mode-map "\^C\^C" 'mh-do-pick-search)
1578: (define-key mh-pick-mode-map "\^Cb" 'mh-to-bcc)
1579: (define-key mh-pick-mode-map "\^Cc" 'mh-to-cc)
1580: (define-key mh-pick-mode-map "\^Cf" 'mh-to-fcc)
1581: (define-key mh-pick-mode-map "\^Cs" 'mh-to-subject)
1582: (define-key mh-pick-mode-map "\^Ct" 'mh-to-to)
1583: (define-key mh-pick-mode-map "\^Cw" 'mh-check-whom)
1584:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.