[comp.emacs] mh-e

kpc00@JUTS.ccc.amdahl.com (kpc) (08/10/90)

Can anybody describe some of the relative advantages and disadvantages
of mh-e wrt rmail?  (mh-e is an mh front end.  It appears that you
must have mh to use mh-e.  Will this be true in v19?)

P.S.  Here is a new question to add to the FAQ list: "What is the
difference between comp.emacs and gnu.emacs?  Which should I post to?"

--
Neither representing any company nor, necessarily, myself.
--
Neither representing any company nor, necessarily, myself.

jayavant@hpfcdj.HP.COM (Rajeev Jayavant) (08/14/90)

/ hpfcdj:comp.emacs / kpc00@JUTS.ccc.amdahl.com (kpc) /  9:35 pm  Aug  9, 1990 /

>> Can anybody describe some of the relative advantages and disadvantages
>> of mh-e wrt rmail?  (mh-e is an mh front end.  It appears that you
>> must have mh to use mh-e.  Will this be true in v19?)

I have used both in the past.  Initially I used mh-e because I was
already using mh outside of emacs and wanted to be able to access my
existing mail messages.  When I migrated to a machine that didn't have
mh installed, I switched to rmail.  The two interfaces are pretty
similar, though a couple of notable differences are mentioned below.

rmail has the advantage of consolidating all of the messages in a
"folder" into a single file.  mh creates a separate directory for each
folder with a separate file for each message.  While rmail tended to
keep my directories cleaner, I liked the way header information is
managed in the mh-e mode much better, and I have gone back to using
mh-e.

Unless rmail has been updated, it only had an option to generate a
list of message headers which you couldn't really mainpulate.  In
mh-e, there is a 3 line window containing only headers which you can
browse through.  Keystrokes can be used in the header window to view,
delete, answer,etc. the message corresponding to the selected header.

							Rajeev
------------------------------------------------------------------------------
Rajeev Jayavant (rajeev@hpfcla.hp.com)	      "Excuse me, I've lost my marbles"
Hewlett Packard - Graphics Technology Division        - P. Opus, [Bloom County]

pierson@encore.com (Dan L. Pierson) (08/15/90)

In article <16940001@hpfcdj.HP.COM> jayavant@hpfcdj.HP.COM (Rajeev Jayavant) writes:

   / hpfcdj:comp.emacs / kpc00@JUTS.ccc.amdahl.com (kpc) /  9:35 pm  Aug  9, 1990 /

   >> Can anybody describe some of the relative advantages and disadvantages
   >> of mh-e wrt rmail?  (mh-e is an mh front end.  It appears that you
   >> must have mh to use mh-e.  Will this be true in v19?)

<Rajeev's message heavily butchered for brevity>
   I have used both in the past. ...
                                       The two interfaces are pretty
   similar, though a couple of notable differences are mentioned below.

   rmail has the advantage of consolidating all of the messages in a
   "folder" into a single file.  mh creates a separate directory for each
   folder with a separate file for each message.

   Unless rmail has been updated, it only had an option to generate a
   list of message headers which you couldn't really mainpulate.  In
   mh-e, there is a 3 line window containing only headers which you can
   browse through.  Keystrokes can be used in the header window to view,
   delete, answer,etc. the message corresponding to the selected header.

I started out with rmail, installed mh so that I could use mh-e
(mostly because of the lack of rmail support for summary (header)
mode).  Eventually the slowness of mh-e got to me.  It is *much*
slower than rmail for common operations such as viewing the next
message in a large folder.

The Kyle Jones wrote VM.  One way to look at VM is as a mail mode that
combines all the single file mail folder advantages of rmail with the
summary mode support of mh-e.  But there's more! :-)

Unlike either rmail or mh-e, VM uses standard Unix format mail files
so that you can access your mail either from the shell or from Emacs.
It has active support, a bugs mailing list, and a lot of good
features.  There are a few additional things I'd like (better handling
of compressed mail folders, appending to "open" folders, ...) but I'm
glad I switched.
--

                                            dan

In real life: Dan Pierson, Encore Computer Corporation, Research
UUCP: {talcott,linus,necis,decvax}!encore!pierson
Internet: pierson@encore.com

beckmann@endor.harvard.edu (Gary Beckmann) (08/16/90)

Dan Pierson writes

> The Kyle Jones wrote VM.  One way to look at VM is as a mail mode that
> combines all the single file mail folder advantages of rmail with the

I'll bite.  Where does find VM, or where is Kyle Jones so that I can
ask him for a copy.  Thanks!
--
					Gary Beckmann

narten@cs.albany.edu (Thomas Narten) (08/16/90)

Many moons ago, in converting from gosmacs to GNU emacs, I heavily
hacked GNU's mh-e to make it more like the mh-e I was used to.  I (and
several others) have been using it for several years, and it has only
one significant problem that I know of.  Features:

- allows one to edit multiple drafts at same time 
- caches contents of folders in a file.  Thus, looking at folders does
  not usually require actually issuing an MH "scan" (a BIG performance
  win when folders contain several hundred messages).
- one can mark messages (e.g. delete them) but they are not actually
  deleted until you close the folder.  If you exit emacs before
  closing the folder, the marked messages are remembered for the next
  invocation (via the folder caching mechanism).
- Fast compose.  Drafts are composed directly with lisp; "comp" is not
  called.
- no notion of "current" message in folder.  Whatever line the
  cursor is at is the "current" message, regardless of how you got there
- Messages are not automatically displayed when you move the cursor
  via "n" or "p". To display a messge, one types "t" explicitlyp
- probably others :-).  I can't remember all the things I didn't like
  about the distributed mh-e.

Some of lisp code is ugly, as I wasn't much of a elisp programmer when
I did the work.  There is also one bug that I haven't tracked down,
because it only strikes rarely (once every few months) and hasn't
bothered me enough to track down.  I believe it has to do with
aborting certain operations with ^G, which has the side effect of
leaving certain data structures out of whack.  When it strikes, mh
looses track of which messages you have marked, and one is forced to
rescan the folder and go back and remark all of its messages.

The lisp code can be retrieved from karp.albany.edu in
pub/narten/mh-e.el.Z.  For those without ftp access, here it is.

;;;  mh-e.el	(Version: 3.2 for GNU Emacs Version 17)

;;;  Copyright (C) 1985 Free Software Foundation
;;;     Original Author James Larus, larus@berkeley.arpa, ucbvax!larus
;;;     Heavily modified by Thomas Narten narten@cs.purdue.edu purdue!narten
;;;	Please send suggestions and corrections to the above address.
;;;
;;;  This file contains mh-e, a GNU Emacs front end to the MH mail system.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but without any warranty.  No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing.

;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; document "GNU Emacs copying permission notice".   An exact copy
;; of the document is supposed to have been given to you along with
;; GNU Emacs so that you can know how you may redistribute it all.
;; It should be in a file named COPYING.  Among other things, the
;; copyright notice and this notice must be preserved on all copies.


;;;  Original version for Gosling emacs by Brian Reid, Stanford, 1982.
;;;  Modified by James Larus, BBN, July 1984 and UCB, 1984 & 1985.
;;;  Rewritten for GNU Emacs, James Larus 1985.


;;;  NB MH must have been compiled with the MHE compiler flag or several
;;;  features necessary to this program will be missing.



;;; Constants:

;;; Set for local environment:
;;;* These are now in paths.el.
;(defvar mh-progs "/usr/new/mh/"     "Directory containing MH commands")
;(defvar mh-lib   "/usr/new/lib/mh/" "Directory of MH library")
(defvar mh-reply-to-sender-only nil 
  "non null means send reply to sender only")


;;; Mode hooks:

(defvar mh-folder-mode-hook nil
  "*Invoked in mh-folder-mode on a new folder.")
(defvar mh-letter-mode-hook nil
  "*Invoked in mh-letter-mode on a new letter.")
(defvar mh-compose-letter-hook nil
  "*Invoked in mh-compose-and-send-mail on an outgoing letter.  It is passed
three arguments: TO recipients, SUBJECT, and CC recipients.")


;;; Personal preferences:

(defvar mh-auto-fill-letters t
  "*Non-nil means invoke auto-fill-mode in draft messages.")
(defvar mh-clean-message-header nil
  "*Non-nil means remove invisible header lines in messages.")
(defvar mh-lpr-command-format "lpr -p -J '%s'"
  "*Format for Unix command line to print a message. The format should be
a unix command line, with the string \"%s\" where the folder and message
number should appear.")
(defvar mh-summary-height 4
  "*Number of lines in summary window.")
(defvar mh-ins-buf-prefix ">"
  "*String to put before each non-blank line of the the current message
as it is inserted in an outgoing letter.")


;;; Real constants:

(defvar mh-cmd-note 4		       "Offset to insert notation")
(defvar mh-invisible-headers
  "^Received:\\|^Message-Id:\\|^Remailed-\\|^Via:\\|^Mail-from:\\|^Return-Path\\|^In-Reply-To:\\|^Resent-"
  "Regexp specifying headers that are not to be shown.")
(defvar mh-rejected-letter-start "^   ----- Unsent message follows -----$"
  "Regexp specifying the beginning of the wrapper around a letter returned
by the mail system.")

(defvar mh-auto-display t " t = auto display messages, nil means don't")


;;; Global variables:

(defvar mh-current-show-message nil "filename of current message in show window
e.g. +inbox/1")
(defvar mh-draft-number  1	     "next draft number to use.")
(defvar mh-send-window-size  7	     "enlarge window to this size drafts")
(defvar mh-user-path  ""	     "User's mail folder.")
(defvar mh-last-destination nil	     "Destination of last "move" command.")
(defvar mh-folder-mode-map (make-sparse-keymap) "Keymap for MH folders.")
(defvar mh-letter-mode-map (make-sparse-keymap) "Keymap for composing mail.")
(defvar mh-pick-mode-map (make-sparse-keymap) "Keymap for searching folder.")
(defvar mh-folder-list nil	     "List of folder names for completion.")

;;; Macros:

(defmacro push (v l)
  (list 'setq l (list 'cons v l)))

(defmacro caar (l)
  (list 'car (list 'car l)))

(defmacro cadr (l)
  (list 'car (list 'cdr l)))

(defmacro cdar (l)
  (list 'cdr (list 'car l)))

(defmacro cddr (l)
  (list 'cdr (list 'cdr l)))

(defmacro when (pred &rest body)
  (list 'cond (cons pred body)))



;;; Entry points:


(defun ask-user-about-supersession-threat (&optional a)
  (interactive)
  (message "ask-user-about-supersession-threat called...")
  (sit-for 1)
) 
(defun mh-rmail (&optional arg)
  "Inc(orporate) new mail if optional ARG omitted, or scan a MH mail box
if arg is present.  This front end uses the MH mail system, which uses
different conventions from the usual mail system."
  (interactive "P")
  (mh-find-path)
  (if (null mh-folder-list)
      (progn
	(setq mh-folder-list (mh-make-folder-list))
	(switch-to-buffer "show")
	(make-local-variable 'mh-current-folder)
	(setq buffer-read-only "t")))
    
  (let ((folder (mh-prompt-for-folder "" "+inbox" t)))
    (if (not (equal  (buffer-name)  folder))
	(switch-to-buffer-other-window  folder))
    (if (or (not (boundp 'mh-current-folder)) (null mh-current-folder))
	(mh-make-folder folder))
    (mh-folder-mode)
    (mh-make-folder-mode-line)))





;;; User executable mh-e commands:



(defun mh-reply (&optional arg)
  "Answer a letter.  If given an argument, then include the current message
in the reply."
  (interactive "P")
  (let ((msg-filename (mh-msg-filename))
	(msg (mh-get-msg-num t))
	(folder mh-current-folder))
  (mh-show)
  (mh-switch-to-folder-buffer)
  (apply 'mh-exec-cmd
	 (list "repl" "-build" mh-current-folder msg ))
;	 (list "repl" "-noedit" mh-current-folder msg ))
  (mh-get-newdraft)
  (erase-buffer)
  (insert-file (format "%sreply" mh-user-path))
  (delete-file (format "%sreply" mh-user-path))
  (if (null mh-reply-to-sender-only)
      ()
    (while (not (eobp))
      (if (looking-at "^[Cc]c: ") (progn
				    (kill-line 1)
				    (while (looking-at "^[ \t]+") (kill-line 1)))
      (next-line 1))))
  (set-buffer-modified-p nil)
  (let ((clines (count-lines (point-min) (point-max))))
    (if (<= (window-height) clines)
	(enlarge-window (- (+ clines 2) (window-height))))
  (goto-char (point-min))
  (re-search-forward "^$" (point-max) nil)
  (mh-letter-mode)
  (mh-letter-mode-line))))

  

(defun mh-Reply (&optional arg)
  "Answer a letter (to sender only).  If given an argument, then
 include the current message in the reply."
  (interactive "P")
  (let ((mh-reply-to-sender-only "t"))
    (mh-reply)))
;(defun mh-toggle-auto-display-mode () "toggle the current display mode"
  


(defun mh-copy-msg (&optional arg)
  "Copy specified message(s) to another folder without deleting them."
  (interactive "P")
  (mh-switch-to-folder-buffer)
  (let ((msgs (if arg
		  (mh-seq-to-msgs (mh-read-seq "Copy"))
		  (mh-get-msg-num t))))
    (mh-exec-cmd-no-wait "refile" msgs "-link" "-src"
			 mh-current-folder
			 (mh-prompt-for-folder "Copy to" "" t))
    (if arg
	(mh-notate-seq msgs ?C mh-cmd-note)
	(mh-notate ?C mh-cmd-note))))



(defun mh-delete-msg ()
  "Mark the current message for later deletion."
  (interactive)
  (mh-switch-to-folder-buffer)
  (mh-delete-a-msg)
  (mh-next-msg))

(defun mh-sync-folder-with-show ()
  (interactive)
  (save-excursion
  (set-buffer mh-current-folder)
  (let ((msg (mh-get-msg-num t)))
    (set-buffer "show")
      (if (not (string-equal (concat mh-current-folder "/" msg)
			 mh-current-show-message))
	  (progn
		     (mh-goto-msg (string-to-int
			 (substring mh-current-show-message
				    (progn
				      (string-match "/"
						    mh-current-show-message)
				      (match-end 0))))))
      ))))
(defun mh-execute-commands ()
  "Process outstanding delete and move commands."
  (interactive)
  (save-excursion
    (mh-process-commands mh-current-folder))
  (mh-make-folder-mode-line))



(defun mh-forward ()
  "Forward a letter."
  (interactive)
  (let ((msg-filename (mh-msg-filename))
	(msg (mh-get-msg-num t))
	(folder mh-current-folder))
    (mh-get-newdraft)
    (erase-buffer)
    (if (not (insert-file (format "%scomponents" mh-user-path)))
	(insert-string "To: \nCc: \nSubject: \n\n"))
    (goto-char (point-max))
    (insert-string "\n------- Forwarded Message\n\n")
    (insert-file msg-filename)
    (goto-char (point-max))
    (insert-string "\n------- End of Forwarded Message\n")
    (goto-char (point-min))
    (end-of-line)
    (not-modified)
    (mh-letter-mode)
    (if (< (window-height) mh-send-window-size)
	(enlarge-window (- mh-send-window-size (window-height)))
			())
    (mh-letter-mode-line)))


(defun mh-goto-msg (number &optional no-error-if-no-message)
  "Position the cursor at message NUMBER.  Do not signal an error if optional
ARG is t.  Return non-nil if cursor is at message."
  (interactive "nMessage number? ")
  (save-excursion)
  (set-buffer mh-current-folder)
  (let ((starting-place (point)))
    (goto-char (point-min))
    (cond ((not (re-search-forward (mh-msg-search-pat number) nil t))
	   (goto-char starting-place)
	   (if (not no-error-if-no-message) (error "No message %d " number))
	   nil)
    )))


(defun mh-inc-folder ()
  "inc(orporate) new mail into inbox."
  (interactive)
  (switch-to-buffer "+inbox")
  (if (or (not (boundp 'mh-current-folder)) (null mh-current-folder))
      (mh-make-folder "+inbox"))
  (if (mh-get-new-mail)
      (mh-show))
)


(defun mh-indicate-seq (&optional arg)
  "Add the specified message(s) to a sequence."
  (interactive "P")
  (let ((new-seq (mh-char-to-seq last-input-char))
	(old-seq (if (looking-at "^[0-9a-z]")
		     (mh-char-to-seq (char-after (point))))))
    (if old-seq
	(if arg
	    (mh-remove-seq old-seq)
	    (mh-remove-msg-from-seq (mh-get-msg-num t) old-seq)))
    (if (and (not arg)
	     (or (not old-seq) (not (equal new-seq old-seq))))
	(mh-add-msg-to-seq (mh-get-msg-num t) new-seq)))
    (mh-next-msg))


(defun mh-kill-folder ()
  "Removes the current folder."
  (interactive)
  (if (yes-or-no-p (format "Remove folder %s? " mh-current-folder))
      (let ((buffer mh-current-folder))
	(switch-to-buffer-other-window " *mh-temp*")
	(mh-exec-cmd "rmf" buffer)
	(mh-remove-folder-from-folder-list buffer)
	(message "Folder removed")
	(kill-buffer buffer))
      (message "Folder not removed")))


(defun mh-list-folders ()
  "List mail folders."
  (interactive)
  (message "listing folders...")
  (switch-to-buffer-other-window "*mh-temp*")
  (erase-buffer)
  (mh-exec-cmd-output "folders")
  (goto-char (point-min))
  (message ""))



(defun mh-move-msg (&optional arg)
  "Move specified message(s) to another folder."
  (interactive "P")
  (mh-switch-to-folder-buffer)
  (setq mh-last-destination (mh-prompt-for-folder "Destination" "" t))
  (if arg
      (mh-map-over-seq 'mh-move-a-msg (mh-read-seq "Move"))
      (mh-move-a-msg))
  (mh-next-msg))


(defun mh-next-line (&optional arg)
  "Move to next undeleted message in window and display body if summary
flag set."
  (interactive "p")
  (mh-switch-to-folder-buffer)
  (forward-line (if arg arg 1))
  (setq mh-next-direction 'forward)
  (if (not (re-search-forward "^[^\t^]...[^D^]" nil 0 arg))
      (progn
	(forward-line -1)
	 (error "No more messages"))
    (beginning-of-line)))





(defun mh-renumber-folder ()
  "Renumber messages in folder to be 1..N."
  (interactive)
  (mh-switch-to-folder-buffer)
  (mh-build-commands)
  (message "packing buffer...")
  (mh-pack-folder)
  (mh-unmark-all-headers nil)
  (message ""))


(defun mh-page-digest ()
  "Advance displayed message to next digested message."
  (interactive)
  (mh-switch-to-show-buffer)
;  (goto-char (point-min))
  (let ((case-fold-search nil))
    (when (not (search-forward "\nFrom:" nil t))
	  (error "No more messages")))
  (if (re-search-backward "^\n" 0 t)
	(next-line 1))
  (recenter 0)
  (re-search-forward "^\n")
  (mh-switch-to-folder-buffer))

(defun mh-burst-digest ()
  "Advance displayed message to next digested message."
  (interactive)
  (message "Bursting Digest")
  (let ((msg (mh-get-msg-num t))
	(folder mh-current-folder))
  (apply 'mh-exec-cmd
	 (list "burst" folder msg)))
  (message "scanning folder")
  (mh-rescan-folder)
  (message ""))


(defun mh-page-show-buffer () "Page the message in the show buffer"
  (interactive)
  (mh-switch-to-show-buffer)
  (scroll-up))


(defun mh-previous-line (&optional arg)
  "Move to previous message in window and display body if summary flag set."
  (interactive "p")
  (mh-switch-to-folder-buffer)
  (setq mh-next-direction 'backward)
;  (if (not (re-search-backward "^[ ]*[0-9]+[ +^]" nil t arg))
   (if (not (re-search-backward "^[^\t^]...[^D^]" nil t arg))

	(error "Beginning of messages")))



(defun mh-previous-page ()
  "Page the displayed message backwards."
  (interactive)
  (save-excursion
    (switch-to-buffer-other-window mh-show-buffer)
    (unwind-protect
	(scroll-down nil)
      (other-window -1))))



(defun mh-rescan-folder (&optional arg)
  "Optionally process commands in current folder and (re)scan it."
  (interactive "P")
  (mh-build-commands)
  (if (and (or mh-delete-list mh-move-list)
	   (y-or-n-p "Process commands? "))
      (mh-process-commands mh-current-folder))
  (setq mh-next-direction 'forward)
  (mh-scan-folder mh-current-folder
		  (if arg (read-string "Range [all]? ") "all")))


(defun mh-redistribute (to cc)
  "Redistribute a letter."
  (interactive "sTo: \nsCc: ")
  (let ((msg-filename (mh-msg-filename))
	(msg (mh-get-msg-num t))
	(folder mh-current-folder))
    (mh-get-newdraft)
    (delete-other-windows)
    (when (or (zerop (buffer-size))
	      (not (y-or-n-p
		    "The file 'draft' exists.  Redistribute old version? ")))
      (erase-buffer)
      (insert-file-contents msg-filename)
      (goto-char (point-min))
      (insert "Resent-To: " to "\n")
      (if (not (equal cc ""))
	  (insert "Resent-cc: " cc "\n")))
    (mh-compose-and-send-mail "-dist" folder to (mh-get-field "Subject:") cc
			      "F" "Distributed-to:")))


(defun mh-move-again ()
  "Move specified message to same folder as last move."
  (interactive)
  (if (null mh-last-destination)
      (error "No previous move"))
  (mh-move-a-msg)
  (message "Destination folder: %s" mh-last-destination)
  (mh-next-msg))


(defun mh-write-msg-to-file (file)
  "Append the current message to the end of a file."
  (interactive "FSave message in file: ")
  (let ((msg-filename (mh-msg-filename)))
    (call-process "/bin/csh" nil 0 nil "-c"
		  (format "cat %s >> %s " msg-filename file))))

    
(defun mh-search-folder ()
  "Search the current folder for messages matching a pattern."
  (interactive)
  (let ((folder mh-current-folder))
    (switch-to-buffer-other-window "pick-pattern")
    (if (or (zerop (buffer-size))
	    (not (y-or-n-p "Reuse pattern? ")))
	(mh-make-pick-template)
	(message ""))
    (setq mh-searching-folder folder)))



(defun mh-send ()
  "Compose and send a letter."
  (interactive)
  (let ((folder (if (boundp 'mh-current-folder) mh-current-folder)))
    (mh-get-newdraft)
    (erase-buffer)
    (if (file-exists-p (format "%scomponents" mh-user-path))
	(insert-file-contents (format "%scomponents" mh-user-path))
      (if (file-exists-p (format "%scomponents" mh-lib))
	  (insert-file-contents (format "%scomponents" mh-lib))
	(error "Can't find components")))
      (goto-char (point-min))
      (end-of-line)
      (set-buffer-modified-p nil))
    (mh-letter-mode)
;    (let ((mh-send-window-size (cond ((< mh-send-window-size 
;					(count-lines (point-min) (point-max)))
;				      (count-lines (point-min) (point-max)))
;				     (t mh-send-window-size))))
;    (if (< (window-height) mh-send-window-size)
;	(enlarge-window (- mh-send-window-size (window-height)))
;			()))
    (let ((clines (count-lines (point-min) (point-max))))
      (if (<= (window-height) clines)
	(enlarge-window (- clines (window-height)))))
    (mh-letter-mode-line)
    )
(defun mh-letter-mode-line ()
    (setq mode-line-format "%[%b%] (%p) %M (%m)%-")
)  

(defun mh-resize-folder-and-show-window ()
  "resize windows, putting message in big window and folder in small
 window at top"
  (interactive)
;  (mh-show)
; assume that we are in folder window as that is where mh-show leaves us.
; These contortions are to force the summary line to be the top window.
  (delete-other-windows)
  (switch-to-buffer-other-window mh-show-buffer)
  (switch-to-buffer-other-window mh-current-folder)
  (shrink-window (- (window-height) mh-summary-height))
  (recenter 1)
)
(defun mh-show-all-headers ()
(interactive)
(let ((mh-clean-message-header nil))
  (mh-show)))

(defun mh-show ()
  "Show message indicated by cursor in scan buffer."
  (interactive)
  (mh-switch-to-folder-buffer)
  (let
      ((msgn (mh-get-msg-num t))
       (msg-filename (mh-msg-filename))
       (folder mh-current-folder))
    (if (not (file-exists-p msg-filename))
	(error "Message %d does not exist." msgn))
    (setq mh-current-show-message (concat mh-current-folder "/" msgn))
    (switch-to-buffer-other-window mh-show-buffer)
    (let (buffer-read-only)
      (erase-buffer)
      (set-variable 'mh-current-folder folder)
      (insert-file-contents msg-filename)
;      (setq buffer-file-name msg-filename)
      (if mh-clean-message-header
	  (mh-clean-msg-header (point-min)))
      (goto-char (point-min))
      (set-buffer-modified-p nil)
      (mh-folder-mode)
      (mh-make-show-mode-line-format)
      (unlock-buffer))
    (switch-to-buffer-other-window folder)))


(defun mh-make-show-mode-line-format ()
      (setq mode-line-format
	    (concat "{%b} " folder "/" msgn " %[%p of "
		    (count-lines (point-min) (point-max)) " lines"
		    "%]	%M (%m)%-"))
)
  
  




(defun mh-sort-folder (&optional arg)
  "Sort the messages in the current folder by date."
  (interactive "P")
  (mh-process-commands mh-current-folder)
  (setq mh-next-direction 'forward)
  (message "sorting folder...")
  (mh-exec-cmd "sortm" mh-current-folder)
  (message "")
  (mh-scan-folder mh-current-folder "all"))


(defun mh-summary ()
  "Show a summary of mh-e commands."
  (interactive)
  (message
"Next Prev Go Del ^ ! Copy Undo . Toggle Ans Forw Redist Send List Execute")
  (sit-for 5))


(defun mh-undo (&optional arg)
  "Undo the deletion or move of the specified message(s)."
  (interactive "P")
  (cond ((looking-at "^....D")       (mh-notate ?  mh-cmd-note))
	((looking-at "^....\\^")  
	 (let (buffer-read-only)
	   (mh-notate ? mh-cmd-note)
	   (re-search-forward "^\tmove")
	   (beginning-of-line)
	   (kill-line 1)))))

(defun mh-undo-folder ()
  "Undo all commands in current folder."
  (interactive "")
  (cond ((yes-or-no-p "Undo all commands in folder? ")
	 (setq mh-delete-list nil
	       mh-move-list nil
	       mh-seq-list nil
	       mh-next-direction 'forward)
	 (mh-unmark-all-headers t))
	(t
	 (message "Commands not undone."))))





;;; Support routines.

(defun mh-switch-to-folder-buffer ()
"Switch to buffer containing current folder going to another window if 
the current window is not the one you want"
  (interactive)
  (if (equal (buffer-name) "show")
      (progn
	(switch-to-buffer-other-window mh-current-folder)
	(mh-sync-folder-with-show))))
  

(defun mh-switch-to-show-buffer ()
"Switch to buffer containing current message going to another window if 
the current window is not the one you want"
  (interactive)
  (cond ((equal (buffer-name) "show") t)
  (t (switch-to-buffer-other-window "show"))))


(defun mh-delete-a-msg ()
  "Delete the message pointed to by the cursor."
  (if (looking-at "....^")
      (error "Message %d already moved.  Undo move before deleting."
	     (mh-get-msg-num t)))
  (mh-notate ?D mh-cmd-note))


(defun mh-move-a-msg ()
  "Move the message pointed to by the cursor."
  (if (looking-at "....D")
      (error "Message %d is already deleted.  Undo delete before moving."
	     (mh-get-msg-num nil))
	(mh-notate ?^ mh-cmd-note)
	(let (buffer-read-only)
	  (end-of-line)
	  (insert-string "\n\tmove to " mh-last-destination))))


(defun mh-clean-msg-header (start)
  "Flush extraneous lines in a message header.  The variable
mh-invisible-headers contains a regular expression specifying these lines."
  (save-restriction
    (goto-char start)
    (search-forward "\n\n" nil t)
    (narrow-to-region start (point))
    (goto-char (point-min))
    (while (re-search-forward mh-invisible-headers nil t)
      (progn 
	(beginning-of-line)
	(kill-line 1)
	(while (looking-at "^[ \t]+")
	  (beginning-of-line)
	  (kill-line 1))))
    ))


(defun mh-get-newdraft () "Allocate a new draft file showing it in the 
current window"
  (interactive)
  (setq mh-draft-number (+ mh-draft-number 1))
  (switch-to-buffer "*mh-temp*" t)
  (find-file (format "%sdraft_%d" mh-user-path mh-draft-number)))
  

(defun mh-next-msg ()
  "Move backward or forward to the next message in the buffer."
  (switch-to-buffer mh-current-folder)
  (if (eq mh-next-direction 'forward)
      (mh-next-line 1)
      (mh-previous-line 1)))
;  (if auto-display (mh-show)))




;;; The folder data abstraction.

(defun mh-make-folder (name)
  "Create and initialize a new mail folder called NAME and make
it the current folder."
  (switch-to-buffer name)
  (set-visited-file-name (format "%s%s/%s" mh-user-path (substring name 1) name))
  (if (and (= (buffer-size) 0) 
	   (file-exists-p (format "%s%s/%s" mh-user-path (substring name 1) name)))
    (progn 
;      (message "finding alternate file")
	  (not-modified)
	  (find-alternate-file (format "%s%s/%s" mh-user-path (substring name 1) name)))
    (progn 
;      (message "setting file name we vistited")
      (set-visited-file-name (format "%s%s/%s" mh-user-path
				     (substring name 1) name))))
b;  (find-file (format "%s%s" mh-user-path name))
;  (message "visiting %s" (format "%s%s" mh-user-path name))
  (kill-all-local-variables)
  (setq buffer-read-only nil)
;  (erase-buffer)
  (make-local-variable 'mh-current-folder) ; Name of folder
  (setq mh-current-folder name)
  (make-local-variable 'mh-show-buffer) ; Buffer that displays messages
;  (setq mh-show-buffer (format "show-%s" mh-current-folder))
  (setq mh-show-buffer "show")
  (make-local-variable 'mh-folder-filename) ; e.g. /usr/foobar/Mail/inbox/
  (setq mh-folder-filename (format "%s%s/" mh-user-path (substring name 1)))
  (make-local-variable 'mh-next-seq-num)  ; Index of free sequence id
  (setq mh-next-seq-num 0)
  (make-local-variable 'mh-delete-list)	 ; List of msgs nums to delete
  (setq mh-delete-list nil)
  (make-local-variable 'mh-move-list)	 ; Alist of dest . msgs nums
  (setq mh-move-list nil)
  (make-local-variable 'mh-seq-list)	 ; Alist of seq . msgs nums
  (setq mh-seq-list nil)
  (make-local-variable 'mh-next-direction) ; Direction to move to next message
  (setq mh-next-direction 'forward)
  (mh-folder-mode)
  (setq buffer-read-only "t"))


(defun mh-folder-mode ()
  "Major mode for \"editing\" an MH folder scan listing.
Messages can be marked for refiling and deletion.  However, both actions
are defered until you request execution with \\[mh-execute-commands].
\\{mh-folder-mode-map}
  A prefix argument (\\[universal-argument]) to delete, move, list, or undo applies the action to a message sequence.

Variables controlling mh-e operation are (defaults in parentheses):

 mh-auto-fill-letters (t)
    Non-nil means invoke auto-fill-mode in draft messages.

 mh-clean-message-header (nil)
    Non-nil means remove header lines matching the regular expression
    specified in mh-invisible-headers from messages.


 mh-lpr-command-format (\"lpr -p -J '%s'\")
    Format for command used to print a message on a system printer.

 mh-summary-height (4)
    Number of lines in the summary window.

 mh-ins-buf-prefix (\">> \")
    String to insert before each non-blank line of a message as it is
    inserted in a letter being composed."

  (use-local-map mh-folder-mode-map)
  (setq major-mode 'mh-folder-mode)
  (setq mode-name "mh-folder")
  (if (and (boundp 'mh-folder-mode-hook) mh-folder-mode-hook)
      (funcall mh-folder-mode-hook)))


(defun mh-scan-folder (folder range)
  "Scan the folder FOLDER over the range RANGE.  Return in the folder."
  (if (null (get-buffer folder))
      (mh-make-folder folder)
      (switch-to-buffer-other-window folder))
  (mh-regenerate-headers range)
  (when (looking-at "scan: no messages ")
      (let (buffer-read-only)
	(erase-buffer))
      (if (equal range "all")
	  (message  "Folder %s is empty" folder)
	(message  "No messages in %s, range %s" folder range))
      (sit-for 5))
  (mh-make-folder-mode-line)
  (mh-unmark-all-headers nil))


(defun mh-regenerate-headers (range)
  "Replace buffer with scan of its contents over range RANGE."
  (let (buffer-read-only)
    (message (format "scanning %s..." (buffer-name)))
    (delete-other-windows)
    (erase-buffer)
    (mh-exec-cmd-output "scan" (buffer-name) range)
    (goto-char (point-min))
    (message "")
    ))


(defun mh-get-new-mail ()
  "Read new mail into the current buffer.  Return t if there was new mail,
nil otherwise.  Return in the current buffer."
  (let (buffer-read-only)
    (message (format "inc %s..." (buffer-name)))
    (mh-unmark-all-headers nil)
    (setq mh-next-direction 'forward)
    (goto-char (point-max))
    (let ((start-of-inc (point)))
      (mh-exec-cmd-output "inc")
      (message "")
      (goto-char start-of-inc)
      (cond ((looking-at "inc: no mail")
	     (kill-line 1)
	     (mh-make-folder-mode-line)
	     (previous-line 1)
	     (message "No new mail.")
	     nil)
	    (t
	     (kill-line 2)
	     (mh-make-folder-mode-line)
	     t)))
    (and (boundp 'display-time-string)
	 (stringp display-time-string)
	 (progn
	   (sit-for 0)
	   (setq display-time-string (remove-string " Mail" display-time-string))))
    ))

(defun remove-string (substring string)
  "Remove the first occurrence of SUBSTRING from STRING."
  (let ((loc (string-match substring string)))
    (if loc
	(concat (substring string 0 loc)
		(substring string  (+ (length substring) loc)))
      string)))

(defun how-many (regexp)
  "return number of matches for REGEXP following point."
  (let ((count 0) opoint)
    (save-excursion
     (while (not (eobp))
       (re-search-forward regexp nil "t")
       (if (not (eobp)) (forward-char 1))
       (setq count (1+ count)))
     count)))

(defun mh-make-folder-mode-line ()
  "sets mode line for folder mode"
  (set-buffer mh-current-folder)
  (save-excursion
    (goto-char (point-max))
    (previous-line 1)
    (let  ((case-fold-search nil)
	   (last (mh-get-msg-num nil)))
	   (goto-char (point-min))
	   (re-search-forward "^[ ]*[0-9]+" nil 0 1)
	   (setq mode-line-format
	    (concat "{%b}%% %[" (how-many "^ ")
		    " messages" (format " (%d - %d)" (mh-get-msg-num nil) last)
		    "%] %M (%m)%-")))))


(defun mh-unmark-all-headers (remove-all-flags)
  "This function removes all + flags from the headers, and if called
  with a non-nil argument, removes all D and ^ flags too."
  (let (buffer-read-only
	(case-fold-search nil))
    (goto-char (point-min))
    (while (if remove-all-flags
	       (re-search-forward "^....\\D\\|^....\\^\\|^....\\+" nil t)
	       (re-search-forward "^....\\+" nil t))
      (delete-backward-char 1)
      (insert " "))))


(defun mh-goto-cur-msg ()
  "Position the cursor at the current message."

  (mh-switch-to-folder-buffer)
  (mh-show))


(defun mh-pack-folder ()
  "Closes and packs the current folder."
  (let (buffer-read-only)
    (message "processing commands for %s" mh-current-folder)
    (mh-process-commands mh-current-folder)
    (message "packing folder...")
    (mh-exec-cmd-quiet "folder" mh-current-folder "-pack")
    (mh-regenerate-headers "all")
    (message ""))
  (mh-make-folder-mode-line))
  

(defun mh-map-over-msgs (func list)
  "Apply the function FUNC to each message in message-list LIST, 
passing the name and list of messages as arguments."
  (mapcar (function (lambda (l) (apply func (list (car l) (cdr l))))) list))


(defun mh-build-commands () "Create list messages to be deleted and moved.
       On return, mh-move-list is a list of lists, with each list containing
       folder/message number pairs. mh-delete-list contains all messages
       to be removed."
  (save-excursion
    (goto-char (point-min))
    (setq mh-delete-list nil)
    (setq mh-move-list nil)
    (while (not (eobp)) 
      (progn
	(if (re-search-forward "^[ ]*[0-9]+[D^]" nil t)
	    (progn
	      (backward-char 1)
	      (cond
		((looking-at "D") 
		 (push (mh-get-msg-num t) mh-delete-list))
		((looking-at "\\^")
		 (let ((mh-last-destination (mh-get-dest-folder)))
		   (let ((others (assoc mh-last-destination mh-move-list))
			 (msg (mh-get-msg-num t)))
		     (if others
			 (setcdr others (cons msg (cdr others)))
		       (push (cons mh-last-destination (list msg)) 
			     mh-move-list)))))
		))
	  (goto-char (point-max)))
	(forward-line 1)))
    (goto-char (point-max))))

(defun mh-get-dest-folder ()
  (save-excursion
    (re-search-forward "^\tmove to +")
    (setq mh-last-destination (buffer-substring (dot) 
						(progn 
						  (forward-word 1)
						  (dot))))
    ))
  

  

(defun mh-process-commands (buffer)
  "Process outstanding commands for the buffer BUFFER."
  (message "Processing deletes and moves...")
  (switch-to-buffer buffer)
  (let (buffer-read-only)
    ;; Sequences must be first
    (mh-build-commands)
    (mh-process-seq-commands mh-seq-list)

    ;; Then refile messages
    (mh-map-over-msgs
     (function (lambda (dest msgs)
		 (apply 'mh-exec-cmd
			(nconc (cons "refile" msgs)
			       (list "-src" (format "%s" buffer) dest)))))
     mh-move-list)

    ;; Now delete messages
    (if mh-delete-list
	(apply 'mh-exec-cmd
	       (nconc (list "rmm" (format "%s" buffer)) mh-delete-list)))

    ;; Mark as cur message.
    (if (mh-get-msg-num nil)
	(mh-exec-cmd-no-wait "mark" mh-current-folder (mh-get-msg-num nil)
			     "-seq" "cur" "-add" "-zero" "-nolist")
	(mh-exec-cmd-no-wait "mark" mh-current-folder "-seq" "cur" "-delete"
			     "all" "-nolist"))

    (switch-to-buffer buffer)
    (goto-char (point-min))
    (flush-lines "^....D")
    (goto-char (point-min))
    (flush-lines "^....^")
    (goto-char (point-min))
    (flush-lines "^\tmove to +")
    (setq mh-delete-list nil
	  mh-move-list nil
	  mh-seq-list nil))
  (message ""))



;;; A mode for composing and sending a message.

(defun mh-letter-mode ()
    "Mode for composing letters in mh-e.
\\{mh-letter-mode-map}"
  (text-mode)
  (if mh-auto-fill-letters
      (auto-fill-mode 1))
  (make-local-variable 'paragraph-start)
  (setq paragraph-start (concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-start))
  (make-local-variable 'paragraph-separate)
  (setq paragraph-separate
	(concat "^[ \t]*[-_][-_][-_]+$\\|" paragraph-separate))
  (use-local-map mh-letter-mode-map)
  (setq major-mode 'mh-letter-mode)
  (setq mode-name "mh-letter")
  (if (and (boundp 'mh-letter-mode-hook) mh-letter-mode-hook)
      (funcall mh-letter-mode-hook)))


(defun mh-to-to ()
  "Move point to end of To: field."
  (interactive)
  (expand-abbrev)
  (mh-position-on-field "To:" t))


(defun mh-to-subject ()
  "Move point to end of Subject: field."
  (interactive)
  (expand-abbrev)
  (mh-position-on-field "Subject:" t))


(defun mh-to-cc ()
  "Move point to end of Cc: field.  Creates the field if necessary"
  (interactive)
  (expand-abbrev)
  (when (not (mh-position-on-field "Cc:" t))
    (mh-position-on-field "To:" nil)
    (insert-string "\nCc: ")))


(defun mh-to-bcc ()
  "Move point to end of Bcc: field.  Creates the field if necessary"
  (interactive)
  (expand-abbrev)
  (when (not (mh-position-on-field "Bcc:" t))
    (mh-position-on-field "To:" nil)
    (insert-string "\nBcc: ")))


(defun mh-to-fcc ()
  "Move point to end of Fcc: field.  Creates the field if necessary"
  (interactive)
  (expand-abbrev)
  (when (not (mh-position-on-field "Fcc:" t))
    (mh-position-on-field "To:" nil)
    (insert-string "\nFcc: ")))


(defun mh-check-whom ()
  "List recipients of the current message."
  (interactive)
  (let ((file-name (buffer-file-name)))
    (save-buffer)
    (message "Checking recipients...")
    (switch-to-buffer-other-window "*Mail Recipients*")
    (bury-buffer (current-buffer))
    (erase-buffer)
    (mh-exec-cmd-output "whom" file-name)
    (previous-window)))



;;; Routines to make a search pattern and search for a message.

(defun mh-make-pick-template ()
  "Initialize a buffer with a template for a pick pattern."
  (erase-buffer)
  (kill-all-local-variables)
  (make-local-variable 'mh-searching-folder)
  (insert "From: \n"
	  "To: \n"
	  "Cc: \n"
	  "Date: \n"
	  "Subject: \n"
	  "---------\n")
  (mh-letter-mode)
  (use-local-map mh-pick-mode-map)
  (setq mode-line-format "{%b}\tPick Pattern\t(^C^C to do search)")
  (goto-char (point-min))
  (end-of-line))


(defun mh-do-pick-search ()
  "Search for the messages in the current folder meeting the qualification
in the current buffer and make them into a sequence."
  (interactive)
  (let* ((pattern-buffer (buffer-name))
	 (searching-buffer mh-searching-folder)
	 (range "all")
	 (seq (mh-new-seq mh-searching-folder))
	 (pattern nil))
    (message "Searching...")
    (goto-char (point-min))
    (while (setq pattern (mh-next-pick-field pattern-buffer))
      (setq msgs
	    (mh-seq-from-command searching-buffer
				 seq
				 (nconc (cons "pick" pattern)
					(list searching-buffer
					      range
					      "-sequence" seq "-list"))))
      (setq range seq))
    (message "")
    (switch-to-buffer searching-buffer)
    (mh-notate-seq seq (mh-seq-to-notation seq) 0)))


(defun mh-next-pick-field (buffer)
  "Return the next piece of a pick argument that can be extracted from the
BUFFER.  Returns nil if no pieces remain."
  (switch-to-buffer buffer)
  (let ((case-fold-search t))
    (cond ((eobp)
	   nil)
	  ((re-search-forward "^\\([a-z].*\\):[ \t]*\\([a-z0-9].*\\)$" nil t)
	   (let* ((component
		   (format "-%s"
			   (downcase (buffer-substring (match-beginning 1)
						       (match-end 1)))))
		  (pat (buffer-substring (match-beginning 2) (match-end 2))))
	       (forward-line 1)
	       (list component pat)))
	  ((re-search-forward "^-*$" nil t)
	   (forward-char 1)
	   (let ((body (buffer-substring (point) (point-max))))
	     (if (and (> (length body) 0) (not (equal body "\n")))
		 (list "-search" body)
		 nil)))
	  (t
	   nil))))



;;; Routines compose and send a letter.

(defun mh-compose-and-send-mail (send-args sent-from-folder to subject cc
					   &optional annotate-char
					   annotate-field)
  "Edit and compose a draft message and send or save it.
SENT-FROM-FOLDER is buffer containing summary of current folder, if any.
SEND-ARGS is an optional argument passed to the send command.
The TO, SUBJECT, and CC fields are passed to the mh-compose-letter-hook.
If ANNOTATE-CHAR is non-null, it is used to notate the scan listing of the
current message.  In that case, the ANNOTATE-FIELD is used to build a string
for mh-annotate-msg."
  (let ((sent-from-msg))
    (save-window-excursion
      (when sent-from-folder
	(switch-to-buffer sent-from-folder)
	(setq sent-from-msg (mh-get-msg-num nil))))
    (pop-to-buffer "draft")
    (mh-letter-mode)
    (make-local-variable 'mh-send-args)
    (setq mh-send-args send-args)
    (make-local-variable 'mh-sent-from-folder)
    (setq mh-sent-from-folder sent-from-folder)
    (make-local-variable 'mh-sent-from-msg)
    (setq mh-sent-from-msg sent-from-msg)
    (make-local-variable 'mh-annotate-field)
    (setq mh-annotate-field annotate-field)
    (make-local-variable 'mh-annotate-char)
    (setq mh-annotate-char annotate-char)
    (mh-letter-mode-line)
    (if (and (boundp 'mh-compose-letter-hook) mh-compose-letter-hook)
	(funcall mh-compose-letter-hook to subject cc))))


(defun mh-send-letter ()
  "Send the letter in the current buffer."
  (interactive)
  (save-buffer)
  (mh-exec-cmd-no-wait "send" "-push" "-unique" (buffer-file-name))
  (message "Sent!")
  (kill-buffer (buffer-name)))


(defun mh-insert-letter (&optional arg)
  "Insert a message in the current letter, asking for folder and number.
Removes headers using mh-invisible-headers.
Prefixes each non-blank line with mh-ins-buf-prefix (default \">> \").
Just \\[universal-argument] means do not indent and do not delete any
header fields.  Leaves point before the text and mark after it."
  (interactive "p")
  (let ((folder (mh-prompt-for-folder "Message from" mh-sent-from-folder nil))
	(message (read-input (format "Message number%s: "
				     (if mh-sent-from-msg
					 (format " [%d]" mh-sent-from-msg)
					 ""))))
	(start (point)))
    (if (equal message "") (setq message (format "%d" mh-sent-from-msg)))
    (mh-exec-lib-cmd-output "mhl" "-nobell"
			    (format "%s%s/%s" mh-user-path
				    (substring folder 1) message))
    (when (not (equal arg 4))
      (mh-clean-msg-header start)
      (narrow-to-region start (mark))
      (mh-insert-prefix-string mh-ins-buf-prefix)
      (widen))
    (exchange-point-and-mark)))

(defun mh-insert-cur-msg-with-header ()
  (interactive)
  (mh-insert-cur-msg t))

(defun mh-insert-cur-msg (&optional arg)
  "Inserts the currently visible message into the current buffer.
Prefixes the string mh-ins-buf-prefix to each non-blank line
of the inserted text.  If there is a region set in the
currently visible message's buffer, only the region will be grabbed.
Otherwise, the region from (point) to the end will be grabbed."
  (interactive)
  (let ((to-point (point))
	(to-buffer (current-buffer)))
    (set-buffer "show")
    (goto-char (point-min))
    (if (not arg)
	(search-forward "\n\n" nil t))
    (let  ((mh-ins-str (buffer-substring (point) (point-max))))
      (set-buffer to-buffer)
      (narrow-to-region to-point to-point)
      (insert-string mh-ins-str)
      (mh-insert-prefix-string mh-ins-buf-prefix)
      (widen))))


(defun mh-insert-prefix-string (ins-string)
"Preface each line in the current buffer with STRING."
  (goto-char (point-min))
  (replace-regexp "^." (concat ins-string "\\&") nil)
  (goto-char (point-min)))





;;; Commands to manipulate sequences.

(defmacro mh-seq-name (pair)
  (list 'car pair))

(defmacro mh-seq-msgs (pair)
  (list 'cdr pair))


(defun mh-seq-to-msgs (seq)
  "Returns the list of messages in sequence SEQ."
  (mh-seq-msgs (assoc seq mh-seq-list)))


(defun mh-read-seq (prompt)
  "Prompt the user with PROMPT and read a sequence name."
  (mh-char-to-seq
   (string-to-char (read-string (format "%s %s" prompt "sequence: ")))))


(defun mh-seq-from-command (folder seq command)
  "In FOLDER, make a sequence named SEQ by executing COMMAND."
  (let ((msgs ())
	(case-fold-search t))
    (save-excursion
      (save-window-excursion
	(apply 'mh-exec-cmd-quiet command)
	(switch-to-buffer "*mh-temp*" t) 
	(goto-char (point-min))
	(while (re-search-forward "\\([0-9]+\\)" nil t)
	  (let ((num (string-to-int (buffer-substring (match-beginning 1)
						      (match-end 1)))))
	    (if (not (zerop num))
		(push num msgs)))))
      (switch-to-buffer folder)
      (push (cons seq msgs) mh-seq-list)
      msgs)))


(defun mh-remove-seq (seq)
  "Delete the sequence SEQ."
  (let ((entry (assoc seq mh-seq-list)))
    (setq mh-seq-list (delq (car entry) mh-seq-list))
    (mh-notate-seq (mh-seq-msgs (car entry)) ?  0)))


(defun mh-remove-msg-from-seq (msg-num seq)
  "Remove a message MSG-NUM from the sequence SEQ."
  (let ((seq (assoc seq mh-seq-list)))
    (if seq
	(setcdr seq (delq msg-num (mh-seq-msgs seq)))))
  (mh-notate ? 0))


(defun mh-add-msg-to-seq (msg-num seq)
  "Add a message MSG-NUM to a sequence SEQ."
  (let ((seq-list (assoc seq mh-seq-list)))
    (mh-notate (mh-seq-to-notation seq) 0)
    (if (null seq-list)
	(push (cons seq (list msg-num)) mh-seq-list)
	(setcdr seq-list (cons msg-num (cdr seq-list))))))



(defun mh-new-seq (folder)
  "Return a new sequence name for FOLDER."
  (save-excursion
    (switch-to-buffer folder)
    (if (= mh-next-seq-num 10)
	(error "No more sequences"))
    (setq mh-next-seq-num (+ mh-next-seq-num 1))
    (mh-char-to-seq (+ (1- mh-next-seq-num) ?a))))


(defun mh-char-to-seq (letter)
  "Given a LETTER, return a string that is a valid sequence name."
  (cond ((and (>= letter ?0) (<= letter ?9))
	 (intern (format "mhe%c" letter)))
	((and (>= letter ?a) (<= letter ?z))
	 (intern (format "mhe%c" letter)))
	(t
	 (error "A sequence is named 0...9"))))


(defun mh-seq-to-notation (seq)
  "Return the string used to indicate sequence SEQ in a scan listing."
  (string-to-char (substring (symbol-name seq) 3 4)))


(defun mh-notate-seq (seq notation offset)
  "Mark all messages in the sequence SEQ with the NOTATION at character
OFFSET."
  (mh-map-over-seq 'mh-notate seq notation offset))


(defun mh-map-over-seq (func seq &rest args)
  "Invoke the function FUNC at each message in the sequence SEQ, passing
the remaining ARGS as arguments."
  (mapcar (function (lambda (msg) (mh-goto-msg msg) (apply func args)))
	  (mh-seq-to-msgs seq)))


(defun mh-process-seq-commands (seq-list)
  "Process outstanding sequence commands for the sequences in SEQ-LIST."
  (mh-map-over-msgs
   (function (lambda (seq msgs)
	       (apply 'mh-exec-cmd-quiet
		      (nconc (list "mark" "-zero" "-seq" (format "%s" seq)
				   "-add" "-nolist")
			     msgs))))
   seq-list))



;;; Issue commands to mh.

(defun mh-exec-cmd (command &rest args)
  "Execute MH command COMMAND with ARGS.  Any output is shown to the user."
  (save-excursion
;    (message "%s %s" command (prin1-to-string args))
;    (sit-for 0)
    (apply 'call-process (nconc (list (format "%s%s" mh-progs command)
				      nil t nil)
				(mh-list-to-string args)))
;    (if (> (buffer-size) 0)
;	(progn
;	  (message "sleeping")
;	  (sit-for 5)))
    ))


(defun mh-exec-cmd-quiet (command &rest args)
  "Execute MH command COMMAND with ARGS.  Output is collected, but not shown
 to the user."
  (save-excursion
    (switch-to-buffer "*mh-temp*" t) 
    (erase-buffer)
;    (message "%s %s" command (prin1-to-string args))
;    (sit-for 0)
    (apply 'call-process (nconc (list (format "%s%s" mh-progs command)
				      nil t nil)
				(mh-list-to-string args)))))


(defun mh-exec-cmd-output (command &rest args)
  "Execute MH command COMMAND with ARGS putting the output into buffer after
point.  Set mark after inserted text."
  (set-mark (point))
;  (message "%s %s" command (prin1-to-string args))
;  (sit-for 0)
  (apply 'call-process (nconc (list (format "%s%s" mh-progs command) nil t nil)
			      (mh-list-to-string args)))
  (exchange-point-and-mark))


(defun mh-exec-cmd-no-wait (command &rest args)
  "Execute MH command COMMAND with ARGS and do not wait until it finishes."
;  (message "%s %s" command (prin1-to-string args))
;  (sit-for 0)
  (apply 'call-process (nconc (list (format "%s%s" mh-progs command) nil 0 nil)
			      (mh-list-to-string args))))



(defun mh-exec-lib-cmd-output (command &rest args)
  "Execute MH library command COMMAND with ARGS.  Put the output into
buffer after point.  Set mark after inserted text."
  (set-mark (point))
  (apply 'call-process (nconc (list (format "%s%s" mh-lib command) nil t nil)
			      (mh-list-to-string args)))
  (exchange-point-and-mark))


(defun mh-list-to-string (l)
  "Flattens the list L and makes every element a string."
  (let ((new-list nil))
    (while l
      (cond ((symbolp (car l)) (push (format "%s" (car l)) new-list))
	    ((numberp (car l)) (push (format "%d" (car l)) new-list))
	    ((equal (car l) ""))
	    ((stringp (car l)) (push (car l) new-list))
	    ((null (car l)))
	    ((listp (car l)) (setq new-list
				   (nconc (mh-list-to-string (car l))
					  new-list)))
	    (t (error "Bad argument %s" (car l))))
      (setq l (cdr l)))
    (nreverse new-list)))



;;; Commands to annotate a message.

(defun mh-annotate-msg (msg buffer note &rest args)
  "Mark the MESSAGE in BUFFER listing with the character NOTE and annotate
the saved message with ARGS."
  ;; Wait for annotation to finish, to avoid race condition with reading msg.
  (apply 'mh-exec-cmd (cons "anno" (nconc (list buffer msg) args)))
  (save-excursion
    (switch-to-buffer buffer)
    (if (mh-goto-msg msg t)
	(mh-notate note 5))))


(defun mh-notate (notation offset)
  "Marks the current message with the character NOTATION at position OFFSET."
  (let (buffer-read-only)
    (beginning-of-line)
    (goto-char (+ (point) offset))
    (delete-char 1)
    (insert notation)
    (beginning-of-line)))



;;; User prompting commands.

(defun mh-prompt-for-folder (prompt default can-create)
  "Prompt for a folder name with PROMPT.  Returns the folder's name.
DEFAULT is used if the folder exists and the user types CR.
If the CAN-CREATE flag is t,then a non-existant folder is made."
  (let* ((prompt (format "%s Folder%s" prompt
			 (if (equal "" default)
			     "? "
			     (format " (%s)? " default))))
	 name)
    (while (and (setq name (completing-read prompt mh-folder-list
					    nil (not can-create)))
		(equal name "")
		(equal default "")))
    (cond ((equal name "")
	   (setq name default))
	  ((not (equal (substring name 0 1) "+"))
	   (setq name (format "+%s" name))))
    (let ((new-file-p
	   (not
	    (file-exists-p (format "%s%s" mh-user-path (substring name 1))))))
      (cond ((and new-file-p
		  (y-or-n-p
		   (format "Folder %s does not exist. Create it? " name)))
	     (message "Creating %s" name)
	     (call-process "mkdir" nil nil nil
			   (format "%s%s" mh-user-path (substring name 1)))
	     (message "")
	     (push (list name) mh-folder-list)
	     (push (list (substring name 1 nil)) mh-folder-list))
	    (new-file-p
	     (error ""))
	    (t
	     (when (null (assoc name mh-folder-list))
	       (push (list name) mh-folder-list)
	       (push (list (substring name 1 nil)) mh-folder-list)))))
    name))


(defun mh-make-folder-list ()
  "Returns a list of the user's folders in a form suitable for completing
read."
  (interactive)
  (save-window-excursion
    (mh-exec-cmd-quiet "folders" "-fast" "-norecurse")
    (switch-to-buffer "*mh-temp*" t)
    (goto-char (point-min))
    (let ((list nil))
      (while (not (eobp))
	(let ((start (point)))
	  (search-forward "\n" nil t)
	  (let ((folder (buffer-substring start (- (point) 1))))
	    (push (list folder) list)
	    (push (list (format "+%s" folder)) list))))
      list)))


(defun mh-remove-folder-from-folder-list (folder)
  "Remove FOLDER from the list of folders."
  (setq mh-folder-list
	(delq (assoc (substring folder 1 nil) mh-folder-list)
	      mh-folder-list)))



;;; Misc. functions.

(defun mh-get-msg-num (error-if-no-message)
  "Returns the message number of the current message.  If the argument
ERROR-IF-NO-MESSAGE is t, then complain if the cursor is not pointing to a
message."
  (interactive)
  (set-buffer mh-current-folder)
  (beginning-of-line)
  (cond ((looking-at "^[0-9a-z]?[ ]+\\([0-9]+\\)")
	 (string-to-int (buffer-substring (match-beginning 1)
					  (match-end 1))))
	((looking-at "^\\([0-9]+\\)")
	 (string-to-int (buffer-substring (match-beginning 1)
					  (match-end 1))))
	(error-if-no-message
	  (error "Cursor not pointing to message"))
	(t nil)))


(defun mh-msg-search-pat (n)
  "Returns a search pattern for message N in the scan listing."
  (cond ((< n 10) (format "^...%d" n))
	((< n 100) (format "^..%d" n))
	((< n 1000) (format "^.%d" n))
	(t (format "^%d" n))))


(defun mh-msg-filename ()
  "Returns a string containing the pathname for the file containing the
current message."
  (save-window-excursion
    (mh-switch-to-folder-buffer)
    (format "%s%d" mh-folder-filename (mh-get-msg-num t))))


(defun mh-msg-filenames (msgs folder)
  "Returns a string of filenames for MSGS in FOLDER."
  (mapconcat (function (lambda (msg) (concat folder msg))) msgs " "))


(defun mh-find-path ()
  "Set mh_path from  ~/.mh_profile."
  (save-window-excursion
    (if (not (file-exists-p "~/.mh_profile"))
	(error "Cannot find .mh_profile file."))
    (switch-to-buffer " *mh_temp*")
    (erase-buffer)
    (insert-file-contents "~/.mh_profile")
    (if (equal (setq mh-user-path (mh-get-field "Path:")) "")
	(setq mh-user-path "Mail/")
	(setq mh-user-path (format "%s/" mh-user-path)))
    (if (not (equal (substring mh-user-path 0 1) "/"))
	(setq mh-user-path (format "%s/%s" (getenv "HOME") mh-user-path)))))


(defun mh-get-cur-msg (folder)
  "Returns the number of the 'cur' message in FOLDER."
  (save-excursion
    (switch-to-buffer folder)
    (mh-get-msg-num t)))



(defun mh-get-field (field)
  "Find and return the value of field FIELD in the current buffer.
Returns the empty string if the field is not in the message."
  (let ((case-fold-search t))
    (goto-char (point-min))
    (cond ((not (search-forward field nil t)) "")
	  ((looking-at "[\t ]*$") "")
	  (t
	   (re-search-forward "[\t ]*\\([^\t \n].*\\)$" nil t)
	   (let ((field (buffer-substring (match-beginning 1)
					  (match-end 1)))
		 (end-of-match (point)))
	     (forward-line)
	     (while (looking-at "[ \t]") (forward-line 1))
	     (backward-char 1)
	     (format "%s%s" field (buffer-substring end-of-match (point))))))))


(defun mh-insert-fields (&rest name-values)
  "Insert the NAME-VALUE pairs in the current buffer."
  (let ((case-fold-search t))
    (while name-values
      (let ((field-name (car name-values))
	    (value (cadr name-values)))
	(goto-char (point-min))
	(cond ((not (re-search-forward (format "^%s" field-name) nil t))
	       (re-search-forward "^---\\|^$")
	       (beginning-of-line)
	       (insert field-name " " value "\n"))
	      (t
	       (end-of-line)
	       (insert " " value)))
	(setq name-values (cddr name-values))))))


(defun mh-position-on-field (field set-mark)
  "Set point to the end of the line beginning with FIELD.  Sets the mark
to the point, if SET-MARK is non-nil."
  (if set-mark (set-mark (point)))
  (goto-char (point-min))
  (if (re-search-forward (format "^%s" field) nil t)
      (progn (end-of-line) t)
      nil))
(defun mh-grab-from-field ()
  "return From: field string"
  (save-excursion
    (set-buffer "show")
    (mh-get-field "From:")))

(defun mh-insert-from-field ()
  "grab the from field from the current show buffer and insert at point"
  (interactive)
  (insert (mh-grab-from-field)))
  

(defun ask-user-about-supersession-threat (&optional a)
  (interactive)
)
;;; Build the folder-mode keymap:
(define-key mh-folder-mode-map "\^Ck" 'mh-kill-folder)
(define-key mh-folder-mode-map "\^Cf" 'mh-list-folders)
(define-key mh-folder-mode-map "\^Ci" 'get-sun-spots-index)
(define-key mh-folder-mode-map "\^Cp" 'mh-renumber-folder)
(define-key mh-folder-mode-map "\C-cr" 'mh-rescan-folder)
(define-key mh-folder-mode-map "\C-c\C-s" 'mh-rescan-folder)
(define-key mh-folder-mode-map "t" 'mh-show)
(define-key mh-folder-mode-map "T" 'mh-show-all-headers)
(define-key mh-folder-mode-map "c" 'mh-copy-msg)
(define-key mh-folder-mode-map "?" 'describe-mode)
(define-key mh-folder-mode-map "\C-c\C-c" 'mh-execute-commands)
(define-key mh-folder-mode-map "i" 'mh-inc-folder)
(define-key mh-folder-mode-map "f" 'mh-forward)
(define-key mh-folder-mode-map "c" 'mh-send)
(define-key mh-folder-mode-map "r" 'mh-reply)
(define-key mh-folder-mode-map "R" 'mh-Reply)
(define-key mh-folder-mode-map " " 'mh-page-show-buffer)
(define-key mh-folder-mode-map "." 'mh-resize-folder-and-show-window)
(define-key mh-folder-mode-map "u" 'mh-undo)
(define-key mh-folder-mode-map "!" 'mh-move-again)
(define-key mh-folder-mode-map "^" 'mh-move-msg)
(define-key mh-folder-mode-map "s" 'mh-move-msg)
(define-key mh-folder-mode-map "d" 'mh-delete-msg)
(define-key mh-folder-mode-map "p" 'mh-previous-line)
(define-key mh-folder-mode-map "n" 'mh-next-line)
(define-key mh-folder-mode-map "N" 'mh-page-digest)
(define-key mh-folder-mode-map "b" 'mh-burst-digest)
(define-key mh-folder-mode-map "m" 'mh-toggle-auto-display)
  

;;; Build the letter-mode keymap:

(define-key mh-letter-mode-map "\^Cb" 'mh-to-bcc)
(define-key mh-letter-mode-map "\^Cw" 'mh-check-whom)
(define-key mh-letter-mode-map "\^Cc" 'mh-to-cc)
;(define-key mh-letter-mode-map "\^Cf" 'mh-to-fcc)
(define-key mh-letter-mode-map "\^Cf" 'mh-insert-from-field)
(define-key mh-letter-mode-map "\^Cs" 'mh-to-subject)
(define-key mh-letter-mode-map "\^Ct" 'mh-to-to)
(define-key mh-letter-mode-map "\^Cy" 'mh-insert-cur-msg)
(define-key mh-letter-mode-map "\^C\^Y" 'mh-insert-cur-msg-with-header)
(define-key mh-letter-mode-map "\^C\^C" 'mh-send-letter)

;;; Build the pick-mode keymap:

(define-key mh-pick-mode-map "\^C\^C" 'mh-do-pick-search)
(define-key mh-pick-mode-map "\^Cb" 'mh-to-bcc)
(define-key mh-pick-mode-map "\^Cc" 'mh-to-cc)
(define-key mh-pick-mode-map "\^Cf" 'mh-to-fcc)
(define-key mh-pick-mode-map "\^Cs" 'mh-to-subject)
(define-key mh-pick-mode-map "\^Ct" 'mh-to-to)
(define-key mh-pick-mode-map "\^Cw" 'mh-check-whom)

--
Thomas Narten
narten@cs.albany.edu

larus@primost.cs.wisc.edu (James Larus) (08/17/90)

As the author of mh-e, I should point out that many people's perceptions
of mh-e are based on old, out-of-date version.  The new mh-e is much
faster than the old version and does almost everything that Thomas
Narten's (narten@cs.albany.edu) patches add.  You can ftp the latest
version from primost.cs.wisc.edu in the file ~ftp/pub/mh-e.el.Z

/Jim