|
|
1.1 root 1: ;;; mh-e.el (Version: 2.7)
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:
38: (defvar mh-progs "/usr/local/mh/" "Directory containing MH commands")
39: (defvar mh-lib "/usr/local/lib/mh/" "Directory of MH library")
40:
41:
42: ;;; Mode hooks:
43:
44: (defvar mh-folder-mode-hook nil "Invoked in mh-folder-mode")
45: (defvar mh-letter-mode-hook nil "Invoked in mh-letter-mode")
46:
47:
48: ;;; Personal preferences:
49:
50: (defvar mh-auto-fill-letters t "Invoke auto-fill-mode in letters")
51: (defvar mh-clean-message-header nil
52: "Remove invisible header lines in messages")
53: (defvar mh-lpr-command-format "lpr -p -J '%s'"
54: "Format for Unix command line to print a message. The format should be
55: a unix command line, with the string "%s" where the folder and message
56: number should appear.")
57: (defvar mh-summary-height 4 "Number of lines in summary window")
58:
59: ;;; Real constants:
60:
61: (defvar mh-cmd-note 4 "Offset to insert notation")
62: (defvar mh-invisible-headers
63: "^Received: \\|^Message-Id: \\|^Remailed-\\|^Via: \\|^Mail-from: \\|\^Return-
64: Path: \\|^In-Reply-To: \\|^Resent-"
65: "Regexp specifying headers that are not to be shown.")
66:
67:
68: ;;; Global variables:
69:
70: (defvar mh-user-path "" "User's mail folder")
71: (defvar mh-last-folder "inbox" "Last folder read by mh-rmail")
72: (defvar mh-last-destination nil "Destination of last "move" command")
73: (defvar mh-current-folder nil "Currently active folder")
74: (defvar mh-folder-buffer nil "Buffer name of currently active folder")
75: (defvar mh-show-buffer nil "Name of buffer that displays messages")
76: (defvar mh-letter-mode-map nil "Command map for composing mail")
77:
78: ;;; Macros:
79:
80: (defmacro push (v l)
81: (list 'setq l (list 'cons v l)))
82:
83: (defmacro caar (l)
84: (list 'car (list 'car l)))
85:
86: (defmacro cadr (l)
87: (list 'car (list 'cdr l)))
88:
89: (defmacro cdar (l)
90: (list 'cdr (list 'car l)))
91:
92: (defmacro cddr (l)
93: (list 'cdr (list 'cdr l)))
94:
95: (defmacro when (pred &rest body)
96: (list 'cond (cons pred body)))
97:
98:
99:
100: ;;; Entry points:
101:
102: (defun mh-rmail (&optional arg)
103: "Inc(orporate) new mail if optional ARG omitted, or scan a MH mail box
104: if arg is present. This front end uses the MH mail system, which uses
105: different conventions from the usual mail system."
106: (interactive "P")
107: (let ((make-backup-files nil)
108: (pop-up-windows t)
109: mh-current-folder
110: mh-folder-buffer)
111:
112: (mh-find-path)
113: (save-window-excursion
114: (cond (arg
115: (let ((folder (mh-get-folder-name "mh" mh-last-folder t))
116: (range (read-string "range [all]? ")))
117: (mh-scan-folder folder (if (string= range "") "all" range))))
118: (t
119: (mh-make-folder "inbox")
120: (mh-inc-folder)))
121:
122: (let ((mh-show-buffer (concat "show-" mh-current-folder)))
123: (pop-to-buffer mh-show-buffer)
124: (unwind-protect
125: (mh-command-loop)
126: (kill-buffer mh-folder-buffer)
127: (kill-buffer mh-show-buffer)
128: (setq mh-last-folder mh-current-folder))))))
129:
130:
131: (defun mh-smail ()
132: "Send mail using the MH mail system."
133: (interactive)
134: (let ((make-backup-files nil)
135: (pop-up-windows t))
136: (mh-find-path)
137: (call-interactively 'mh-send)))
138:
139:
140:
141: ;;; User executable mh-e commands:
142:
143: (defun mh-answer ()
144: "Answer a letter."
145: (interactive)
146: (save-window-excursion
147: (let ((msg-filename (mh-msg-filename))
148: (msg (mh-get-msg-num t))
149: (reply-to
150: (mh-get-response
151: "Reply to (f, t, c, ?): "
152: '(?f ?t ?c)
153: "Reply to F(rom), T(o + From), C(c + To + From): ")))
154: (message "Composing a reply...")
155: (cond ((equal reply-to ?f)
156: (mh-exec-cmd "repl" "-build" mh-folder-buffer msg "-nocc" "all"))
157: ((equal reply-to ?t)
158: (mh-exec-cmd "repl" "-build" mh-folder-buffer msg "-cc" "to"
159: "-nocc" "me"))
160: ((equal reply-to ?c)
161: (mh-exec-cmd "repl" "-build" mh-folder-buffer msg "-cc" "all"
162: "-nocc" "me")))
163:
164: (mh-read-file (concat mh-user-path "draft") "draft")
165: (delete-other-windows)
166: (when (or (zerop (buffer-size))
167: (not (y-or-n-p "The file 'draft' exists. Use for reply? ")))
168: (erase-buffer)
169: (insert-file-contents (concat mh-user-path "reply"))
170: (delete-file (concat mh-user-path "reply")))
171:
172: (let ((to-names (mh-get-field "To:"))
173: (cc-names (mh-get-field "Cc:")))
174: (goto-char (dot-max))
175: (pop-to-buffer "*message*")
176: (erase-buffer)
177: (if (file-exists-p msg-filename)
178: (insert-file-contents msg-filename)
179: (error "File %s does not exist" msg-filename))
180: (goto-char (dot-min))
181: (let ((case-fold-search nil))
182: (re-search-forward "^$\\|^-*$"))
183: (recenter 0)
184: (message "Composing a reply...done")
185: (if (mh-compose-and-send-mail "")
186: (mh-annotate "R" mh-folder-buffer msg
187: "-component" "Replied-To:"
188: "-text" (concat to-names
189: (if (string= cc-names "")
190: ""
191: (concat ", " cc-names)))))))))
192:
193:
194: (defun mh-close-folder ()
195: "Process the outstanding delete and move commands in the current folder."
196: (interactive)
197: (message "closing folder...")
198: (mh-process-commands mh-folder-buffer)
199: (mh-unmark-all-headers t)
200: (mh-regenerate-headers "all")
201: (setq mode-line-format (mh-make-mode-line))
202: (message "closing folder...done"))
203:
204:
205: (defun mh-copy-msg (&optional arg)
206: "Copy specified message(s) to another folder without deleting them."
207: (interactive "P")
208: (let ((msgs (if arg
209: (mh-seq-to-msgs (mh-read-seq "Copy"))
210: (mh-get-msg-num t))))
211: (mh-exec-cmd-no-wait "refile" msgs "-link" "-src"
212: mh-folder-buffer
213: (format "+%s" (mh-get-folder-name "Copy to" "" t)))))
214:
215:
216: (defun mh-delete-msg (&optional arg)
217: "Marks the specified message(s) for later deletion."
218: (interactive "P")
219: (let ((msgs (if arg (mh-read-seq "Delete") (mh-get-msg-num t))))
220: (push msgs mh-delete-list)
221: (if arg
222: (mh-notate-seq msgs ?D mh-cmd-note)
223: (mh-notate ?D mh-cmd-note))
224: (mh-next-line 1)))
225:
226:
227: (defun mh-exit ()
228: "Exit mh-e and process outstanding delete and move commands."
229: (interactive)
230: (cond ((not (or mh-delete-list mh-move-list))
231: (throw 'exit nil))
232: ((yes-or-no-p "Exit? ")
233: (mh-process-commands mh-folder-buffer)
234: (throw 'exit nil))))
235:
236:
237: (defun mh-forward (to subject cc)
238: "Forward a letter."
239: (interactive "sTo: \nsSubject: \nsCc: ")
240: (save-window-excursion
241: (let ((msg-filename (mh-msg-filename))
242: (msg (mh-get-msg-num t)))
243: (cond ((or (not (file-exists-p (concat mh-user-path "draft")))
244: (y-or-n-p "The file 'draft' exists. Discard it? "))
245: (mh-exec-cmd "forw" "-build" mh-folder-buffer msg)
246: (mh-read-file (concat mh-user-path "draft") "draft")
247: (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc))
248: (t
249: (mh-read-file (concat mh-user-path "draft") "draft")))
250:
251: (goto-char (dot-min))
252: (delete-other-windows)
253: (if (mh-compose-and-send-mail "")
254: (mh-annotate "F" mh-folder-buffer msg
255: "-component" "Forwared-To:"
256: "-text" (concat to
257: (if (string= cc "")
258: ""
259: (concat ", " cc))))))))
260:
261:
262: (defun mh-goto (number &optional no-error-if-no-message)
263: "Position the cursor at a particular message."
264: (interactive "nMessage number? ")
265: (pop-to-buffer mh-folder-buffer)
266: (let ((starting-place (dot)))
267: (goto-char (dot-min))
268: (cond ((not (re-search-forward (concat "^\+?[0-9a-z]*[ ]*" number) nil t))
269: (goto-char starting-place)
270: (if (not no-error-if-no-message) (error "No message %d " number)))
271: (t
272: (beginning-of-line)
273: (if (not mh-summarize) (mh-show))))))
274:
275:
276: (defun mh-inc-folder ()
277: "inc(orporate) new mail in the current folder."
278: (interactive)
279: (mh-get-new-mail))
280:
281:
282: (defun mh-indicate-seq (&optional arg)
283: "Add the specified message(s) to a sequence."
284: (interactive "P")
285: (let ((seq (mh-letter-to-seq last-input-char)))
286: (if (looking-at "^[0-9a-j]")
287: (if arg
288: (mh-remove-seq seq)
289: (mh-remove-msg-from-seq (mh-get-msg-num t) seq))
290: (mh-add-msg-to-seq (mh-get-msg-num t) seq))))
291:
292:
293: (defun mh-kill-folder ()
294: "Removes the current folder."
295: (interactive)
296: (cond ((yes-or-no-p "Remove current folder ")
297: (pop-to-buffer " *mh-temp*")
298: (mh-exec-cmd "rmf" (buffer-name))
299: (message "Folder removed")
300: (throw 'exit nil))
301: (t
302: (message "Folder not removed"))))
303:
304:
305: (defun mh-list-folders ()
306: "List mail folders."
307: (interactive)
308: (message "listing folders...")
309: (pop-to-buffer " *mh-temp*")
310: (erase-buffer)
311: (mh-exec-cmd-output "folders")
312: (goto-char (dot-min))
313: (message "listing folders...done"))
314:
315:
316: (defun mh-print-msg (&optional arg)
317: "Print specified message(s) on a line printer."
318: (interactive "P")
319: (let ((msgs (if arg
320: (reverse (mh-seq-to-msgs (mh-read-seq "Print")))
321: (list (mh-get-msg-num t)))))
322: (message "printing message...")
323: (shell-command
324: (concat mh-lib "mhl -noclear -nobell "
325: (mh-msg-filenames msgs mh-folder-filename) " | "
326: (format mh-lpr-command-format
327: (if arg
328: "Mail"
329: (concat mh-current-folder "/" (mh-get-msg-num t))))))
330: (message "printing message...done")))
331:
332:
333: (defun mh-move-msg (&optional arg)
334: "Move specified message(s) to another folder."
335: (interactive "P")
336: (let ((msgs (if arg (mh-read-seq "Move") (mh-get-msg-num t))))
337: (setq mh-last-destination (mh-get-folder-name "Destination" "" t))
338: (mh-refile msgs mh-last-destination)
339: (mh-next-line 1)))
340:
341:
342: (defun mh-next-line (&optional arg)
343: "Move to next undeleted message in window and display body if summary
344: flag set."
345: (interactive "p")
346: (pop-to-buffer mh-folder-buffer)
347: (forward-line (if arg arg 1))
348: (if (not (re-search-forward "^....[^D^]" nil 0 arg))
349: (progn
350: (forward-line -1)
351: (message "No more messages"))
352: (beginning-of-line))
353: (if (not mh-summarize) (mh-show)))
354:
355:
356: (defun mh-renumber-folder ()
357: "Renumber messages in folder to be 1..N."
358: (interactive)
359: (message "packing buffer...")
360: (pop-to-buffer mh-folder-buffer)
361: (mh-pack-folder)
362: (mh-unmark-all-headers nil)
363: (mh-position-to-current)
364: (message "packing buffer...done"))
365:
366:
367: (defun mh-page-digest ()
368: "Advance displayed message to next digested message."
369: (interactive)
370: (save-excursion
371: (pop-to-buffer mh-show-buffer)
372: (move-to-window-line nil)
373: (let ((case-fold-search nil))
374: (when (not (search-forward "\nFrom:" nil t))
375: (other-window -1)
376: (error "No more messages")))
377: (recenter 0)
378: (other-window -1)))
379:
380:
381: (defun mh-previous-line (&optional arg)
382: "Move to previous message in window and display body if summary flag set."
383: (interactive "p")
384: (pop-to-buffer mh-folder-buffer)
385: (forward-line (- (if arg arg 1)))
386: (if (not (re-search-backward "^....[^D^]" nil 0 arg))
387: (message "Beginning of messages")
388: (if (not mh-summarize) (mh-show))))
389:
390:
391: (defun mh-previous-page ()
392: "Page the displayed message backwards."
393: (interactive)
394: (save-excursion
395: (pop-to-buffer mh-show-buffer)
396: (scroll-down nil)
397: (other-window -1)))
398:
399:
400: (defun mh-quit ()
401: "Quit mh-e without processing outstanding delete and move commands."
402: (interactive)
403: (if (and (or mh-delete-list mh-move-list)
404: (not (yes-or-no-p "Quit without processing? ")))
405: (mh-process-commands mh-folder-buffer))
406: (throw 'exit nil))
407:
408:
409: (defun mh-rescan-folder (&optional arg)
410: "Optionally process commands in current folder and (re)scan it."
411: (interactive "P")
412: (pop-to-buffer mh-folder-buffer)
413: (if (and (or mh-delete-list mh-move-list)
414: (y-or-n-p "Process commands? "))
415: (mh-process-commands mh-folder-buffer))
416: (mh-regenerate-headers (if arg (read-string "Range? ") "all"))
417: (setq mode-line-format (mh-make-mode-line))
418: (mh-unmark-all-headers nil)
419: (mh-position-to-current))
420:
421:
422: (defun mh-redistribute (to cc)
423: "Redistribute a letter."
424: (interactive "sTo: \nsCc: ")
425: (save-window-excursion
426: (let ((msg-filename (mh-msg-filename))
427: (msg (mh-get-msg-num t)))
428: (mh-read-file (concat mh-user-path "draft") "draft")
429: (delete-other-windows)
430: (when (or (zerop (buffer-size))
431: (not (y-or-n-p "The file 'draft' exists. Redistribute? ")))
432: (erase-buffer)
433: (insert-file-contents msg-filename)
434: (goto-char (dot-min))
435: (insert "Resent-To: " to "\n")
436: (if (not (string= cc ""))
437: (insert "Resent-cc: " cc "\n")))
438:
439: (if (mh-compose-and-send-mail "-dist")
440: (mh-annotate "F" mh-folder-buffer msg
441: "-component" "Distributed-to:"
442: "-text" (concat to
443: (if (string= cc "")
444: ""
445: (concat ", " cc))))))))
446:
447:
448: (defun mh-re-move ()
449: "Move specified message to same folder as last move."
450: (interactive)
451: (if (null mh-last-destination)
452: (error "No previous move")
453: (mh-refile (mh-get-msg-num t) mh-last-destination)))
454:
455:
456: (defun mh-search-folder ()
457: "Search folder for letters matching a pattern."
458: (interactive)
459: (let* ((range "all")
460: (seq (mh-new-seq))
461: (pattern nil))
462: (mh-get-pick-pattern " *pattern*")
463: (while (setq pattern (mh-next-pick-field " *pattern*"))
464: (setq msgs
465: (mh-seq-from-command seq
466: (nconc (cons "pick" pattern)
467: (list (concat "+" mh-current-folder)
468: range
469: "-sequence" seq "-list"))))
470: (setq range seq))
471: (mh-apply-to-seq seq 'mh-notate (mh-seq-to-notation seq) 0)))
472:
473:
474: (defun mh-send (to subject cc)
475: "Compose and send a letter."
476: (interactive "sTo: \nsSubject: \nsCc: ")
477: (message "Composing a message...")
478: (save-window-excursion
479: (mh-read-file (concat mh-user-path "draft") "draft")
480: (delete-other-windows)
481: (when (or (zerop (buffer-size))
482: (not (y-or-n-p "The file 'draft' exists. Use it? ")))
483: (erase-buffer)
484: (if (file-exists-p (concat mh-user-path "components"))
485: (insert-file-contents (concat mh-user-path "components"))
486: (if (file-exists-p (concat mh-lib "components"))
487: (insert-file-contents (concat mh-lib "components"))
488: (error "Can't find components")))
489: (mh-insert-fields "To:" to "Subject:" subject "Cc:" cc)
490: (goto-char (dot-max))
491: (message "Composing a message...done"))
492: (mh-compose-and-send-mail "")))
493:
494:
495: (defun mh-show ()
496: "Show message indicated by cursor in scan buffer."
497: (interactive)
498: (setq mh-summarize nil)
499: (pop-to-buffer mh-folder-buffer)
500: (let ((msgn (mh-get-msg-num t))
501: (msg-filename (mh-msg-filename))
502: (folder mh-current-folder))
503: (if (not (file-exists-p msg-filename))
504: (error "Message %d does not exist." msgn))
505: (push msgn mh-shown-msgs)
506: (switch-to-buffer mh-show-buffer)
507: (erase-buffer)
508: (insert-file-contents msg-filename)
509: (setq buffer-file-name msg-filename)
510: (mh-letter-mode)
511: (cond (mh-clean-message-header
512: (mh-clean-message-header)
513: (goto-char (dot-min)))
514: (t
515: (let ((case-fold-search nil))
516: (re-search-forward "^To:\\|^From:\\|^Subject:" nil t)
517: (beginning-of-line)
518: (recenter 0))))
519: (set-buffer-modified-p nil)
520: (setq mode-line-format
521: (concat "{%b} %[%p of +" folder "/" msgn "%] %M"))
522: ;; These contortions are to force the summary line to be the top window.
523: (pop-to-buffer mh-folder-buffer)
524: (delete-other-windows)
525: (pop-to-buffer mh-show-buffer)
526: (pop-to-buffer mh-folder-buffer)
527: (shrink-window (- (window-height) mh-summary-height))
528: (recenter 1)))
529:
530:
531: (defun mh-summary ()
532: "Show a summary of mh-e commands."
533: (interactive)
534: (message
535: "Next Prev Go Del ^ ! Copy Undo . Toggle Ans Forw Redist Send List Quit Exit")
536: (sit-for 5))
537:
538:
539: (defun mh-toggle-summarize ()
540: "Turn the summary mode of displaying messages on or off."
541: (interactive)
542: (setq mh-summarize (not mh-summarize))
543: (if (not mh-summarize)
544: (mh-show)
545: (delete-other-windows)))
546:
547:
548: (defun mh-undo (&optional arg)
549: "Undo the deletion or move of the specified message(s)."
550: (interactive "P")
551: (cond ((looking-at "^....D")
552: (let ((msgs (if arg (mh-read-seq "undelete") (mh-get-msg-num t))))
553: (setq mh-delete-list (delq msgs mh-delete-list))
554: (if arg
555: (mh-notate-seq msgs ? mh-cmd-note)
556: (mh-notate ? mh-cmd-note))))
557:
558: ((looking-at "^....^")
559: (let ((msgs (if arg (mh-read-seq "unmove") (mh-get-msg-num t))))
560: (mapcar
561: (function (lambda (move) (setcdr msgs (delq msgs (cdr move)))))
562: mh-move-list)
563: (if arg
564: (mh-notate-seq msgs ? mh-cmd-note)
565: (mh-notate ? mh-cmd-note))))
566:
567: (t nil)))
568:
569:
570: (defun mh-visit-folder (&optional arg)
571: "Visit a new folder."
572: (interactive "p")
573: (let* (mh-current-folder
574: mh-folder-buffer
575: (folder (mh-get-folder-name "visit" "" t))
576: (mh-show-buffer (concat "show-" folder)))
577: (save-window-excursion
578: (switch-to-buffer (concat "+" folder))
579: (if (> (buffer-size) 0)
580: (error "folder +%s is open. close it before revisiting." folder))
581: (mh-scan-folder folder (if arg (read-string "range? ") "all"))
582: (pop-to-buffer mh-show-buffer)
583: (unwind-protect
584: (mh-command-loop)
585: (kill-buffer mh-show-buffer)
586: (kill-buffer mh-folder-buffer)))))
587:
588:
589:
590: ;;; Support routines.
591:
592: (defun mh-command-loop ()
593: "Read and execute mh commands."
594: (pop-to-buffer mh-folder-buffer)
595: (delete-other-windows)
596: (recursive-edit))
597:
598:
599: (defun mh-refile (msgs destination)
600: "Refile the msgs in the folder called destination."
601: (let ((others (assoc destination mh-move-list)))
602: (if others
603: (setcdr others (cons msgs (cdr others)))
604: (push (cons destination (list msgs)) mh-move-list))
605: (if (integerp msgs)
606: (mh-notate ?^ mh-cmd-note)
607: (mh-notate-seq msgs ?^ mh-cmd-note))))
608:
609:
610: (defun mh-clean-message-header ()
611: "Flush extraneous lines in a message header. The variable
612: mh-invisible-headers contains a regular expression specifying these lines."
613: (save-restriction
614: (goto-char (dot-min))
615: (search-forward "\n\n" nil t)
616: (narrow-to-region (dot-min) (dot))
617: (goto-char (dot-min))
618: (while (re-search-forward mh-invisible-headers nil t)
619: (beginning-of-line)
620: (kill-line 1)
621: (while (looking-at "^[ \t]+")
622: (beginning-of-line)
623: (kill-line 1)))))
624:
625:
626: (defun mh-read-file (file-name buffer-name)
627: "Read file FILE-NAME into buffer BUFFER-NAME. No errors if disk file
628: has been modified."
629: (pop-to-buffer buffer-name)
630: (kill-buffer buffer-name)
631: (pop-to-buffer buffer-name)
632: (if (file-exists-p file-name)
633: (insert-file-contents file-name t)
634: (setq buffer-file-name file-name))
635: (set-buffer-modified-p nil))
636:
637:
638:
639: ;;; The folder data abstraction.
640:
641: (defun mh-make-folder (name)
642: "Create and initialize a new mail folder called NAME and make
643: it the current folder."
644: (setq mh-current-folder name)
645: (setq mh-folder-buffer (concat "+" name))
646: (switch-to-buffer mh-folder-buffer)
647: (kill-all-local-variables)
648: (setq buffer-read-only nil)
649: (mh-folder-mode)
650: (erase-buffer)
651: (make-variable-buffer-local 'mh-folder-filename)
652: ;; "e.g./usr/foldbar/Mail/inbox/"
653: (setq mh-folder-filename (concat mh-user-path name "/"))
654: (make-variable-buffer-local 'mh-summarize) ; Show scan list only?
655: (setq mh-summarize t)
656: (make-variable-buffer-local 'mh-next-seq-num) ; Index of free sequence id
657: (setq mh-next-seq-num 0)
658: (make-variable-buffer-local 'mh-delete-list) ; List of msgs nums to delete
659: (setq mh-delete-list nil)
660: (make-variable-buffer-local 'mh-move-list) ; Alist of dest . msgs nums
661: (setq mh-move-list nil)
662: (make-variable-buffer-local 'mh-seq-list) ; Alist of seq . msgs nums
663: (setq mh-seq-list nil)
664: (make-variable-buffer-local 'mh-shown-msgs) ; List of msgs shown
665: (setq mh-shown-msgs nil)
666: (setq buffer-read-only t))
667:
668:
669: (defun mh-folder-mode ()
670: " \\[mh-next-line]: next message \\[mh-previous-line]: p
671: revious message
672: \\[mh-delete-msg]: delete (mark for deletion) \\[mh-move-msg]: put (m
673: ark for moving)
674: \\[mh-undo]: undo last delete or mark \\[mh-re-move]: repeat
675: last ^ command
676: \\[mh-copy-msg]: copy message to another folder
677: \\[mh-show]: type message \\[mh-toggle-summarize]: toggle
678: summarize mode
679: \\[scroll-other-window]: page message \\[mh-previous-p
680: age]: page message backwards
681: \\[mh-print-msg]: print message \\[mh-goto]: goto a mes
682: sage
683: \\[mh-exit]: exit \\[mh-quit]: quit
684: \\[mh-send]: send a message \\[mh-redistribute]: redistribu
685: te a message
686: \\[mh-answer]: answer a message \\[mh-forward]: forward a messa
687: ge
688: \\[mh-visit-folder]: visit folder \\[mh-inc-folder]: inc ma
689: il
690: \\[mh-close-folder]: close folder \\[mh-kill-folder]: kill
691: folder
692: \\[mh-list-folders]: list folders \\[mh-renumber-folder]: p
693: ack folder
694: \\[mh-rescan-folder]: rescan folder \\[mh-search-folder]: sea
695: rch folder
696: Edit the scan list, marking messages.
697: When you are done, type 'e'. The messages marked for deletion will be
698: deleted and messages marked for moving will be moved.
699: In any of the submodes, such as editing a message or composing a message,
700: exit with \\[exit-emacs]."
701: (auto-save-mode -1)
702: (use-local-map mh-keymap)
703: (setq major-mode 'mh-folder-mode)
704: (setq mode-name "mh-folder")
705: (if (and (boundp 'mh-folder-mode-hook) mh-folder-mode-hook)
706: (funcall mh-folder-mode-hook)))
707:
708:
709: (defun mh-scan-folder (folder range)
710: "Scan the folder FOLDER over the range RANGE."
711: (mh-make-folder folder)
712: (mh-regenerate-headers range)
713: (when (looking-at "scan: no messages ")
714: (let ((buffer-read-only nil))
715: (erase-buffer))
716: (if (string= range "all")
717: (message "Folder +%s is empty" folder)
718: (message "No messages in +%s, range %s" folder range))
719: (sit-for 5))
720: (setq mode-line-format (mh-make-mode-line))
721: (mh-unmark-all-headers nil)
722: (mh-position-to-current))
723:
724:
725: (defun mh-regenerate-headers (range)
726: "Replace buffer with scan of its contents over range RANGE."
727: (let ((buffer-read-only nil))
728: (message (format "scanning %s..." (buffer-name)))
729: (delete-other-windows)
730: (erase-buffer)
731: (mh-exec-cmd-output "scan" (buffer-name) range)
732: (goto-char (dot-min))
733: (message (format "scanning %s...done" (buffer-name)))
734: ))
735:
736:
737: (defun mh-get-new-mail ()
738: "Read new mail into the current buffer."
739: (let ((buffer-read-only nil))
740: (message (format "inc %s..." (buffer-name)))
741: (goto-char (dot-max))
742: (set-mark (dot))
743: (mh-exec-cmd-output "inc")
744: (message (format "inc %s...done" (buffer-name)))
745: (goto-char (mark))
746: (cond ((looking-at "inc: no mail")
747: (kill-line 1)
748: (message "No new mail")
749: (sit-for 5))
750: (t
751: (kill-line 2))))
752: (setq mode-line-format (mh-make-mode-line)))
753:
754:
755: (defun mh-make-mode-line ()
756: "Returns a string for mode-line-format."
757: (save-excursion
758: (goto-char (dot-min))
759: (set-mark (dot))
760: (goto-char (dot-max))
761: (let ((lines (count-lines (dot) (mark))))
762: (goto-char (dot-min))
763: (let ((first (mh-get-msg-num nil))
764: (case-fold-search nil))
765: (re-search-forward "[ ]*[0-9]*\\+" nil t)
766: (let ((current (mh-get-msg-num nil)))
767: (goto-char (dot-max))
768: (previous-line 1)
769: (let ((last (mh-get-msg-num nil)))
770: (concat "{%b} %[" lines " messages"
771: (if (> lines 0)
772: (concat " (" first " - " last ")")
773: "")
774: (if current
775: (concat " cur = " current)
776: "")
777: "%] ")))))))
778:
779:
780: (defun mh-unmark-all-headers (remove-all-flags)
781: "This function removes all + flags from the headers, and if called
782: with a non-nil argument, removes all D and ^ flags too."
783: (switch-to-buffer mh-folder-buffer)
784: (let ((buffer-read-only nil)
785: (case-fold-search nil))
786: (goto-char (dot-min))
787: (while (if remove-all-flags
788: (re-search-forward "^....\\+" nil t)
789: (re-search-forward "^\\D\\|^\\^\\|^....\\+" nil t))
790: (delete-backward-char 1)
791: (insert " "))))
792:
793:
794: (defun mh-position-to-current ()
795: "Position the cursor at the current message."
796: (let ((curmsg (mh-get-cur-msg mh-folder-filename)))
797: (when (or (zerop curmsg) (mh-goto curmsg t))
798: (message "No message %s" curmsg)
799: (goto-char (dot-max))
800: (forward-line -1))
801: (when (looking-at "[ ]+[0-9]+")
802: (mh-notate ?+ 0)
803: (recenter 0))))
804:
805:
806: (defun mh-pack-folder ()
807: "Closes and packs the current folder."
808: (let ((buffer-read-only nil))
809: (message "closing folder...")
810: (mh-process-commands mh-folder-buffer)
811: (message "packing folder...")
812: (mh-exec-cmd "folder" mh-folder-buffer "-pack")
813: (mh-regenerate-headers "all")
814: (message "packing done"))
815: (setq mode-line-format (mh-make-mode-line)))
816:
817:
818: (defun mh-apply-to-message-list (func list)
819: "Apply function FUNC to each item in a message-list LIST,
820: passing the name and list of messages as arguments."
821: (mapcar (function (lambda (l) (apply func (list (car l) (cdr l))))) list))
822:
823:
824: (defun mh-process-commands (buffer)
825: "Process outstanding commands for the buffer BUFFER."
826: (message "Processing deletes and moves...")
827: (switch-to-buffer buffer)
828: (let ((buffer-read-only nil))
829: ;; Sequences must be first
830: (mh-process-seq-commands mh-seq-list)
831:
832: ;; Then refile messages
833: (mh-apply-to-message-list
834: (function (lambda (dest msgs)
835: (apply 'mh-exec-cmd
836: (nconc (cons "refile" msgs)
837: (list "-src" (format "%s" buffer)
838: (format "+%s" dest))))))
839: mh-move-list)
840:
841: ;; Now delete messages
842: (if mh-delete-list
843: (apply 'mh-exec-cmd
844: (nconc (list "rmm" (format "%s" buffer)) mh-delete-list)))
845:
846: ;; Finally update unseen sequence
847: (if mh-shown-msgs
848: (apply 'mh-exec-cmd-no-wait
849: (nconc (list "show" (format "%s" buffer))
850: mh-shown-msgs
851: (list "-noformat"))))
852:
853: (setq mh-delete-list nil
854: mh-move-list nil
855: mh-seq-list nil
856: mh-shown-msgs nil))
857: (message "Processing deletes and moves...done"))
858:
859:
860:
861: ;;; Routines for editing a message.
862:
863: (defun mh-letter-mode ()
864: "Mode for composing letters in mh.
865: ^N and ^P work normally in the body of a letter but hug the end of field names
866: in the header.
867: ^X^C exits and sends a letter."
868: (text-mode)
869: (if mh-auto-fill-letters
870: (auto-fill-mode 1))
871: (setq paragraph-separate "^[- \t\^L]*$")
872: (setq paragraph-start "^$\\|^\^L\\|^-+$")
873: (when (not mh-letter-mode-map)
874: (setq mh-letter-mode-map (copy-sequence text-mode-map))
875: (define-key mh-letter-mode-map "\^N" 'mh-header-next)
876: (define-key mh-letter-mode-map "\^P" 'mh-header-previous))
877: (use-local-map mh-letter-mode-map)
878: (setq major-mode 'mh-letter-mode)
879: (setq mode-name "mh-letter")
880: (if (and (boundp 'mh-letter-mode-hook) mh-letter-mode-hook)
881: (funcall mh-letter-mode-hook)))
882:
883:
884: (defun mh-header-next (&optional arg)
885: "Modified ^N command that skips to end of header field names."
886: (interactive "p")
887: (next-line (if arg arg 1))
888: (mh-header-line-position))
889:
890:
891: (defun mh-header-previous (&optional arg)
892: "Modified ^P command that skips to end of header field names."
893: (interactive "p")
894: (previous-line (if arg arg 1))
895: (mh-header-line-position))
896:
897:
898: (defun mh-dot-in-header ()
899: "t iff cursor in message header."
900: (save-excursion
901: (let ((wasdot (dot))
902: (case-fold-search nil))
903: (goto-char (dot-min))
904: (re-search-forward "^-*$" nil t)
905: (beginning-of-line)
906: (backward-char 1)
907: (>= (dot) wasdot))))
908:
909:
910: (defun mh-header-line-position ()
911: "Position cursor at end of field name when in header."
912: (if (mh-dot-in-header)
913: (when (save-excursion (beginning-of-line) (not (looking-at " \\|\t")))
914: (beginning-of-line)
915: (search-forward ":" nil t)
916: (if (eolp)
917: (insert " ")
918: (while (looking-at "[ \t]") (forward-char 1))))))
919:
920:
921:
922: ;;; Routines to make a search pattern and search for a message.
923:
924: (defun mh-get-pick-pattern (buffer)
925: "Prompt the user for a pattern to search for in messages. Upon return,
926: current buffer contains the filled-in template."
927: (save-window-excursion
928: (pop-to-buffer buffer)
929: (if (or (zerop (buffer-size))
930: (not (y-or-n-p "Reuse pattern? ")))
931: (mh-pick-template)
932: (message ""))
933: (local-set-key "\^X\^C" 'mh-make-pick-pattern)
934: (let ((mode-line-format "{%b}\tPick Pattern\t^X^C to exit and search"))
935: (catch 'mh-pattern (recursive-edit)))))
936:
937:
938: (defun mh-make-pick-pattern ()
939: (interactive)
940: (goto-char (dot-min))
941: (throw 'mh-pattern nil))
942:
943:
944: (defun mh-pick-template ()
945: (erase-buffer)
946: (insert "From: \n"
947: "To: \n"
948: "Cc: \n"
949: "Date: \n"
950: "Subject: \n"
951: "---------\n")
952: (goto-char (dot-min))
953: (end-of-line)
954: (mh-letter-mode))
955:
956:
957: (defun mh-next-pick-field (buffer)
958: "Return a pattern to search for messages containing the next field, or NIL
959: if no fields remain."
960: (save-excursion
961: (pop-to-buffer buffer)
962: (let ((pat ())
963: (case-fold-search t))
964: (cond ((re-search-forward "^\\([a-z].*\\):[ \t]*\\([a-z0-9].*\\)$" nil t)
965: (region-around-match 1)
966: (let ((component (concat "-" (downcase (region-to-string)))))
967: (region-around-match 2)
968: (setq pat (nconc (list component (region-to-string)) pat)))
969: (forward-line 1)
970: pat)
971: ((re-search-forward "^-*$" nil t)
972: (forward-char 1)
973: (set-mark (dot))
974: (goto-char (dot-max))
975: (let ((body (region-to-string)))
976: (if (> (length body) 0)
977: (list "-search" body)
978: nil)))
979: (t
980: nil)))))
981:
982:
983:
984: ;;; Routines compose and send a letter.
985:
986: (defun mh-compose-and-send-mail (send-args)
987: "Edit a draft message and send or save it. SEND-ARGS is passed to the
988: send command. Returns t if mail is being sent."
989: (save-window-excursion
990: (pop-to-buffer "draft")
991: (mh-letter-mode)
992: (local-set-key "\^X\^C" 'mh-send-letter)
993: (local-set-key "\^X\^Y" 'mh-insert-letter)
994: (mh-header-line-position)
995: (let ((mode-line-format
996: "{%b} %[Mail/draft%] (%p - %m) (^X^C to finish ^X^Y to yank msg)
997: %M"))
998: (catch 'mail-send (recursive-edit)))))
999:
1000:
1001: (defun mh-send-letter ()
1002: "Prompt the user as to the disposition of the just-composed letter."
1003: (interactive)
1004: (save-buffer)
1005: (let ((mode-line-format "{%b} %[Mail/draft%] (%p - %m) %M")
1006: (action (mh-get-response
1007: "Ready to send. Action (s, q, e, ?): "
1008: '(?s ?b ?q ?e ?\^C)
1009: "S-end, Q-uit, E-dit ")))
1010: (cond ((equal action ?s)
1011: (message "Sending...")
1012: (mh-exec-cmd-no-wait "send" "-push" "-unique" send-args
1013: (buffer-file-name))
1014: (message "Sending...done")
1015: (throw 'mail-send t))
1016:
1017: ((equal action ?q)
1018: (message "Not sent... Message remains in buffer draft")
1019: (throw 'mail-send nil)))))
1020:
1021:
1022: (defun mh-insert-letter ()
1023: "Insert a message in the current letter."
1024: (interactive)
1025: (let ((folder (mh-get-folder-name "Message from" mh-current-folder nil))
1026: (message (string-to-int (read-input "Message number: " ""))))
1027: (insert-file-contents (concat mh-user-path folder "/" message))))
1028:
1029:
1030:
1031: ;;; Commands to manipulate sequences.
1032:
1033: (defmacro mh-seq-name (pair)
1034: (list 'car pair))
1035:
1036: (defmacro mh-seq-msgs (pair)
1037: (list 'cdr pair))
1038:
1039:
1040: (defun mh-seq-to-msgs (seq)
1041: "Returns the list of messages in sequence SEQ."
1042: (mh-seq-msgs (assoc seq mh-seq-list)))
1043:
1044:
1045: (defun mh-read-seq (prompt)
1046: "Prompt the user with PROMPT and read a sequence name."
1047: (mh-letter-to-seq
1048: (string-to-char (read-string (format "%s %s" prompt "sequence: ")))))
1049:
1050:
1051: (defun mh-seq-from-command (seq command)
1052: "Make a sequence called SEQ by executing the form COMMAND."
1053: (let ((msgs ())
1054: (case-fold-search t))
1055: (save-window-excursion
1056: (apply 'mh-exec-cmd-quiet command)
1057: (pop-to-buffer " *mh-temp*")
1058: (goto-char (dot-min))
1059: (while (re-search-forward "\\([0-9]+\\)" nil t)
1060: (region-around-match 0) ; BUG in GNU EMACS: should be 1!
1061: (let ((num (string-to-int (region-to-string))))
1062: (if (not (zerop num))
1063: (push num msgs)))))
1064:
1065: (push (cons seq msgs) mh-seq-list)
1066: msgs))
1067:
1068:
1069: (defun mh-remove-seq (seq)
1070: "Delete the sequence SEQ."
1071: (let ((entry (assoc seq mh-seq-list)))
1072: (setq mh-seq-list (delq (car entry) mh-seq-list))
1073: (mh-apply-to-seq (mh-seq-msgs (car entry)) 'mh-notate ? 0)))
1074:
1075:
1076: (defun mh-remove-msg-from-seq (msg-num seq)
1077: "Remove a message MSG-NUM from the sequence SEQ."
1078: (let ((seq (assoc seq mh-seq-list)))
1079: (setcdr (car seq) (delq msg-num (mh-seq-msgs (car seq)))))
1080: (mh-notate ? mh-cmd-note))
1081:
1082:
1083: (defun mh-add-msg-to-seq (msg-num seq)
1084: "Add a message MSG-NUM to a sequence SEQ."
1085: (let ((seq-list (assoc seq mh-seq-list)))
1086: (mh-notate (mh-seq-to-notation seq) 0)
1087: (if (null seq-list)
1088: (push (cons seq (list msg-num)) mh-seq-list)
1089: (setcdr seq-list (cons msg-num (cdr seq-list))))))
1090:
1091:
1092:
1093: (defun mh-new-seq ()
1094: "Return a new sequence name."
1095: (save-excursion
1096: (switch-to-buffer mh-folder-buffer)
1097: (if (= mh-next-seq-num 10)
1098: (error "No more sequences"))
1099: (setq mh-next-seq-num (+ mh-next-seq-num 1))
1100: (mh-letter-to-seq (+ (1- mh-next-seq-num) ?a))))
1101:
1102:
1103: (defun mh-letter-to-seq (letter)
1104: "Given a LETTER, return a string that is a valid sequence name."
1105: (cond ((and (>= letter ?0) (< letter ?9))
1106: (intern (concat "mhe" (char-to-string letter))))
1107: ((and (>= letter ?a) (< letter ?z))
1108: (intern (concat "mhe" (char-to-string letter))))
1109: (t
1110: (error "A sequence is named 0...9"))))
1111:
1112:
1113: (defun mh-seq-to-notation (seq)
1114: "Return the string used to indicate sequence SEQ in a scan listing."
1115: (string-to-char (substring (symbol-name seq) 3 4)))
1116:
1117:
1118: (defun mh-notate-seq (seq notation offset)
1119: "Mark all messages in the sequence SEQ with the NOTATION at character
1120: OFFSET."
1121: (mh-apply-to-seq seq 'mh-notate notation offset))
1122:
1123:
1124: (defun mh-apply-to-seq (seq function &rest args)
1125: "For each message in sequence SEQ, evaluate the FUNCTION with ARGS."
1126: (mapcar (function (lambda (msg) (mh-goto msg) (apply function args)))
1127: (mh-seq-to-msgs seq)))
1128:
1129:
1130: (defun mh-process-seq-commands (seq-list)
1131: "Process outstanding sequence commands for the sequences in SEQ-LIST."
1132: (mh-apply-to-message-list
1133: (function (lambda (seq msgs)
1134: (apply 'mh-exec-cmd-quiet
1135: (nconc (list "mark" "-zero" "-seq" (format "%s" seq)
1136: "-add")
1137: msgs))))
1138: seq-list))
1139:
1140:
1141:
1142: ;;; Issue commands to mh.
1143:
1144: (defun mh-exec-cmd (command &rest args)
1145: "Execute MH command COMMAND with ARGS. Any output is shown to the user."
1146: (save-excursion
1147: (pop-to-buffer " *mh-temp*")
1148: (erase-buffer)
1149: (set-mark (dot))
1150: (apply 'call-process (nconc (list (concat mh-progs command) nil t nil)
1151: (mh-list-to-string args)))
1152: (when (> (buffer-size) 0)
1153: (message "%s" (region-to-string))
1154: (sit-for 5))))
1155:
1156:
1157: (defun mh-exec-cmd-quiet (command &rest args)
1158: "Execute MH command COMMAND with ARGS. Output is collected, but not shown
1159: to the user."
1160: (pop-to-buffer " *mh-temp*")
1161: (erase-buffer)
1162: (set-mark (dot))
1163: (apply 'call-process (nconc (list (concat mh-progs command) nil t nil)
1164: (mh-list-to-string args))))
1165:
1166:
1167: (defun mh-exec-cmd-output (command &rest args)
1168: "Execute MH command COMMAND with ARGS putting the output into the current
1169: buffer."
1170: (apply 'call-process (nconc (list (concat mh-progs command) nil t nil)
1171: (mh-list-to-string args))))
1172:
1173:
1174: (defun mh-exec-cmd-no-wait (command &rest args)
1175: "Execute MH command COMMAND with ARGS and do not wait until it finishes."
1176: (apply 'call-process (nconc (list (concat mh-progs command) nil 0 nil)
1177: (mh-list-to-string args))))
1178:
1179:
1180: (defun mh-list-to-string (l)
1181: "Flattens the list L and makes every element a string."
1182: (let ((new-list nil))
1183: (while l
1184: (cond ((symbolp (car l)) (push (format "%s" (car l)) new-list))
1185: ((numberp (car l)) (push (format "%d" (car l)) new-list))
1186: ((string= (car l) ""))
1187: ((stringp (car l)) (push (car l) new-list))
1188: ((null (car l)))
1189: ((listp (car l)) (setq new-list
1190: (nconc (mh-list-to-string (car l))
1191: new-list)))
1192: (t (error "Bad argument %s" (car l))))
1193: (setq l (cdr l)))
1194: (nreverse new-list)))
1195:
1196:
1197:
1198: ;;; Commands to annotate a message.
1199:
1200: (defun mh-annotate (note &rest args)
1201: "Mark the current message with the character NOTE and annotate the message
1202: with ARGS."
1203: (apply 'mh-exec-cmd-no-wait (cons "anno" args))
1204: (mh-notate note 5))
1205:
1206:
1207: (defun mh-notate (notation offset)
1208: "Marks the current message with the character NOTATION at position OFFSET."
1209: (save-excursion
1210: (pop-to-buffer mh-folder-buffer)
1211: (let ((buffer-read-only nil))
1212: (beginning-of-line)
1213: (goto-char (+ (dot) offset))
1214: (delete-char 1)
1215: (insert notation)
1216: (beginning-of-line))))
1217:
1218:
1219:
1220: ;;; User prompting commands.
1221:
1222: (defun mh-get-folder-name (prompt default can-create)
1223: "Prompt for a folder name with PROMPT. DEFAULT is the default folder.
1224: If the CAN-CREATE flag is t, then the folder can be made if it does not exist."
1225: (let ((exists nil)
1226: (prompt (concat prompt " folder"
1227: (if (string= "" default)
1228: "? "
1229: (concat " [" default "]? "))))
1230: (file-name))
1231: (let ((name))
1232: (while (not exists)
1233: (setq name (read-string prompt))
1234: (if (string= name "")
1235: (setq name default))
1236: (if (string= (substring name 0 1) "+")
1237: (setq name (substring name 1)))
1238: (if (not (string= (substring name 0 1) "/"))
1239: (setq file-name (concat mh-user-path name))
1240: (setq file-name name))
1241: (setq exists (file-exists-p file-name))
1242: (if (not exists)
1243: (cond ((and can-create
1244: (y-or-n-p (concat "Folder +" name
1245: " does not exist. Create it? ")))
1246: (message "Creating %s" name)
1247: (call-process "mkdir" nil nil nil file-name)
1248: (message "Creating %s...done" name)
1249: (setq exists t))
1250:
1251: (can-create
1252: (error ""))
1253:
1254: (t
1255: (setq prompt (concat "Sorry, no such folder as `" name
1256: "' Folder name? "))))))
1257: name)))
1258:
1259:
1260: (defun mh-get-response (prompt possibilities help)
1261: "Prints PROMPT, reads a character, and checks it against the list
1262: of POSSIBILITIES. Returns the character if it is legal. The HELP string
1263: is displayed if the character is not legal."
1264: (let ((ok nil)
1265: (first-char))
1266: (while (not ok)
1267: (let ((pos possibilities))
1268: (message prompt)
1269: (setq first-char (read-char))
1270: (while (and (not ok) pos)
1271: (if (equal first-char (car pos))
1272: (setq ok t))
1273: (setq pos (cdr pos)))
1274:
1275: (cond ((equal first-char ??)
1276: (message help)
1277: (sit-for 5))
1278: ((not ok)
1279: (message "Illegal response '%c'" first-char)
1280: (sit-for 5)))))
1281: first-char))
1282:
1283:
1284:
1285: ;;; Misc. functions.
1286:
1287: (defun mh-get-msg-num (error-if-no-message)
1288: "Returns the message number of the current message. If the argument
1289: ERROR-IF-NO-MESSAGE is t, then complain if the cursor is not pointing to a
1290: message."
1291: (save-excursion
1292: (switch-to-buffer mh-folder-buffer)
1293: (beginning-of-line)
1294: (cond ((looking-at "^\+?\\([0-9]+\\)")
1295: (region-around-match 1)
1296: (string-to-int (region-to-string)))
1297: ((looking-at "^\+?[0-9a-z]?[ ]+\\([0-9]+\\)")
1298: (region-around-match 1)
1299: (string-to-int (region-to-string)))
1300: (error-if-no-message
1301: (error "Cursor not pointing to message"))
1302: (t nil))))
1303:
1304:
1305: (defun mh-msg-filename ()
1306: "Returns a string containing the pathname for a message."
1307: (save-excursion
1308: (switch-to-buffer mh-folder-buffer)
1309: (concat mh-folder-filename (mh-get-msg-num t))))
1310:
1311:
1312: (defun mh-msg-filenames (msgs folder)
1313: "Returns an arglist for ls specifying the messages MSGS in folder FOLDER."
1314: (if msgs
1315: (let ((args (concat folder "{")))
1316: (while (cdr msgs)
1317: (setq args (concat args (car msgs) ","))
1318: (setq msgs (cdr msgs)))
1319: (concat args (car msgs) "}"))
1320: ""))
1321:
1322:
1323: (defun mh-find-path ()
1324: "Set mh_path from ~/.mh_profile."
1325: (save-window-excursion
1326: (if (not (file-exists-p "~/.mh_profile"))
1327: (error "Can find .mh_profile file."))
1328: (switch-to-buffer " *mh_profile*")
1329: (erase-buffer)
1330: (insert-file-contents "~/.mh_profile")
1331: (if (string= (setq mh-user-path (mh-get-field "Path:")) "")
1332: (setq mh-user-path "Mail/")
1333: (setq mh-user-path (concat mh-user-path "/")))
1334: (if (not (string= (substring mh-user-path 0 1) "/"))
1335: (setq mh-user-path (concat (getenv "HOME") "/" mh-user-path)))))
1336:
1337:
1338: (defun mh-get-cur-msg (folder)
1339: "Returns the cur message from FOLDER."
1340: (let ((seq-filename (concat folder ".mh_sequences")))
1341: (save-window-excursion
1342: (cond ((file-exists-p seq-filename)
1343: (switch-to-buffer " *mh_sequences*")
1344: (erase-buffer)
1345: (insert-file-contents seq-filename)
1346: (string-to-int (mh-get-field "cur: ")))
1347: (t 0)))))
1348:
1349:
1350: (defun mh-get-field (field)
1351: "Get the value of field FIELD in the current buffer."
1352: (let ((case-fold-search t))
1353: (goto-char (dot-min))
1354: (cond ((not (search-forward field nil t)) "")
1355: (t
1356: (re-search-forward "[\t ]*\\([a-zA-z0-9/].*\\)$" nil t)
1357: (region-around-match 1)
1358: (let ((field (region-to-string)))
1359: (set-mark (dot))
1360: (forward-line)
1361: (while (looking-at "[ \t]") (forward-line 1))
1362: (backward-char 1)
1363: (concat field (region-to-string)))))))
1364:
1365:
1366: (defun mh-insert-fields (&rest name-values)
1367: "Insert the NAME-VALUE pairs in the current buffer."
1368: (let ((case-fold-search t))
1369: (while name-values
1370: (let ((field-name (car name-values))
1371: (value (cadr name-values)))
1372: (goto-char (dot-min))
1373: (cond ((not (search-forward (concat "\n" field-name) nil t))
1374: (search-forward "---")
1375: (beginning-of-line)
1376: (insert field-name " " value "\n"))
1377: (t
1378: (end-of-line)
1379: (insert " " value)))
1380: (setq name-values (cddr name-values))))))
1381:
1382:
1383:
1384: ;;; Build the keymap for mh:
1385:
1386: (defvar mh-keymap (make-sparse-keymap))
1387:
1388: (define-key mh-keymap "?" 'mh-summary)
1389: (define-key mh-keymap "c" 'mh-copy-msg)
1390: (define-key mh-keymap "d" 'mh-delete-msg)
1391: (define-key mh-keymap "^" 'mh-move-msg)
1392: (define-key mh-keymap "!" 'mh-re-move)
1393: (define-key mh-keymap "u" 'mh-undo)
1394: (define-key mh-keymap "l" 'mh-print-msg)
1395: (define-key mh-keymap "p" 'mh-previous-line)
1396: (define-key mh-keymap "n" 'mh-next-line)
1397: (define-key mh-keymap "g" 'mh-goto)
1398: (define-key mh-keymap " " 'scroll-other-window)
1399: (define-key mh-keymap "\e " 'mh-page-digest)
1400: (define-key mh-keymap "\^H" 'mh-previous-page)
1401: (define-key mh-keymap "t" 'mh-toggle-summarize)
1402: (define-key mh-keymap "." 'mh-show)
1403: (define-key mh-keymap "a" 'mh-answer)
1404: (define-key mh-keymap "f" 'mh-forward)
1405: (define-key mh-keymap "r" 'mh-redistribute)
1406: (define-key mh-keymap "s" 'mh-send)
1407: (define-key mh-keymap "\^X\^C" 'mh-quit)
1408: (define-key mh-keymap "q" 'mh-quit)
1409: (define-key mh-keymap "e" 'mh-exit)
1410: (define-key mh-keymap "0" 'mh-indicate-seq)
1411: (define-key mh-keymap "1" 'mh-indicate-seq)
1412: (define-key mh-keymap "2" 'mh-indicate-seq)
1413: (define-key mh-keymap "3" 'mh-indicate-seq)
1414: (define-key mh-keymap "4" 'mh-indicate-seq)
1415: (define-key mh-keymap "5" 'mh-indicate-seq)
1416: (define-key mh-keymap "6" 'mh-indicate-seq)
1417: (define-key mh-keymap "7" 'mh-indicate-seq)
1418: (define-key mh-keymap "8" 'mh-indicate-seq)
1419: (define-key mh-keymap "9" 'mh-indicate-seq)
1420: (define-key mh-keymap "\ef" 'mh-visit-folder)
1421: (define-key mh-keymap "\ei" 'mh-inc-folder)
1422: (define-key mh-keymap "\ec" 'mh-close-folder)
1423: (define-key mh-keymap "\ek" 'mh-kill-folder)
1424: (define-key mh-keymap "\el" 'mh-list-folders)
1425: (define-key mh-keymap "\ep" 'mh-renumber-folder)
1426: (define-key mh-keymap "\er" 'mh-rescan-folder)
1427: (define-key mh-keymap "\es" 'mh-search-folder)
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.