home *** CD-ROM | disk | FTP | other *** search
Text File | 1989-09-11 | 64.3 KB | 1,731 lines |
- ;;; UNIX style mail reader for GNU Emacs
- ;;; Copyright (C) 1989 Kyle E. Jones
- ;;;
- ;;; This program is free software; you can redistribute it and/or modify
- ;;; it under the terms of the GNU General Public License as published by
- ;;; the Free Software Foundation; either version 1, or (at your option)
- ;;; any later version.
- ;;;
- ;;; This program is distributed in the hope that it will be useful,
- ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;;; GNU General Public License for more details.
- ;;;
- ;;; You should have received a copy of the GNU General Public License
- ;;; along with this program; if not, write to the Free Software
- ;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- ;; This is a set of Emacs-Lisp commands and support functions for
- ;; reading mail. While a mail reader (RMAIL) is distributed with GNU
- ;; Emacs it converts a user's mailbox to BABYL format, a behavior I
- ;; find quite unpalatable.
- ;;
- ;; VM is similar to RMAIL in that it scoops mail from the system mailbox
- ;; into a primary inbox for reading, but the similarity ends there.
- ;; VM does not reformat the mailbox beyond reordering the headers
- ;; according to user preference, and adding a header used internally to
- ;; store message attributes.
- ;;
- ;; Entry points to VM are the commands vm and vm-visit-folder.
- ;;
- ;; If autoloading then the lines:
- ;; (autoload 'vm "vm" nil t)
- ;; (autoload 'vm-visit-folder "vm" nil t)
- ;; should appear in a user's .emacs or in default.el in the lisp
- ;; directory of the Emacs distribution.
- ;;
- ;; VM requires Emacs' etc/movemail to work on your system.
-
- (provide 'vm)
-
- (defvar vm-primary-inbox "~/INBOX"
- "*Mail is moved from the system mailbox to this file for reading.")
-
- (defvar vm-crash-box "~/INBOX.CRASH"
- "*File in which to store mail temporarily while it is transferrred from
- the system mailbox to the primary inbox. If the something happens
- during this mail transfer, any missing mail will be found in this file.
- VM will do crash recovery from this file automatically at startup, as
- necessary.")
-
- (defvar vm-spool-files nil
- "*If non-nil this variable's value should be a list of strings naming files
- that VM will check for incoming mail instead of the where VM thinks your
- system mailbox is. This variable can be used to specify multiple spool files
- or to point VM in the right direction if its notion of your system mailbox is
- incorrect.")
-
- (defvar vm-visible-headers
- '("From:" "Sender:" "To:" "Apparently-To:" "Cc:" "Subject:" "Date:")
- "*List of headers that should be visible when VM first displays a message.
- These should be listed in the order you wish them presented.
- Regular expressions are allowed.")
-
- (defvar vm-highlighted-header-regexp nil
- "*Regular expression that matches the beginnings of headers that should
- be highlighted when a message is first presented. For exmaple setting
- this variable to \"^From\\\\|^Subject\" causes the From: and Subject:
- headers to be highlighted.")
-
- (defvar vm-preview-lines 0
- "*Non-nil value N causes VM to display the visible headers + N lines of text
- from a message when it is first presented. The message is not actually marked
- as read until the message is exposed in its entirety. Nil causes VM not to
- preview a message at all; it is displayed in its entirety when first
- presented and is marked as read.")
-
- (defvar vm-preview-read-messages t
- "*Non-nil value means to preview messages, even if they've already been read.
- A nil value causes VM to preview messages only if new or unread.")
-
- (defvar vm-folder-type nil
- "*Value specifies the type of mail folder VM should expect to read and
- write. Nil means expect the UNIX style folders characterized by the
- \"\\n\\nFrom \" message separators. The only other supported value for
- this variable is the symbol `mmdf' which causes VM to use
- \"^A^A^A^A\\n\" MMDF style leaders and trailers.")
-
- (defvar vm-folder-directory nil
- "*Directory where folders of mail are kept.")
-
- (defvar vm-confirm-new-folders nil
- "*Non-nil value causes interactive calls to vm-save-message
- to ask for confirmation before creating a new folder.")
-
- (defvar vm-delete-empty-folders t
- "*Non-nil value causes VM to remove empty (zero length) folder files
- after saving them.")
-
- (defvar vm-included-text-prefix " > "
- "*String used to prefix included text in replies.")
-
- (defvar vm-auto-folder-alist nil
- "*Non-nil value should be an alist that VM will use to choose a default
- folder name when messages are saved. The alist should be of the form
- \((HEADER-NAME
- (REGEXP . FOLDER-NAME) ...
- ...))
- where HEADER-NAME and REGEXP are strings, and FOLDER-NAME is a string or an s-expression that evaluates to a string.
-
- If any part of the contents of the message header named by HEADER-NAME
- is matched by the regular expression REGEXP, VM will evaluate the
- corresponding FOLDER-NAME and use the result as the default when
- prompting for a folder to save the message in. If trhe resulting folder
- name is a relative pathname, then it will resolve to the directory named by
- vm-folder-directory, or the default-directory of the currently visited
- folder if vm-folder-directory is nil.
-
- When FOLDER-NAME is evaluated, the current buffer will contain only the
- contents of the header named by HEADER-NAME. It is safe to modify this
- buffer. You can use the match data from any \\( ... \\) grouping
- constructs in REGEXP along with the function buffer-substring to build a
- folder name based on the header information.
-
- Matching is case sensitive.")
-
- (defvar vm-visit-when-saving nil
- "*Non-nil causes VM to visit folders when saving messages. This means
- VM will read the folder into Emacs and append the message to the buffer
- instead of appending to the folder file directly. This behavior is
- ideal when folders are encrypted or compressed since appending plaintext
- to such files is a ghastly mistake.
-
- Note the setting of this variable does not affect how the primary inbox
- is accessed, i.e. the primary inbox must be a plaintext file.")
-
- (defvar vm-in-reply-to-format "%i"
- "*String which specifies the format of the contents of the In-Reply-To
- header that is generated for replies. See the documentation for the
- variable vm-summary-format for information on what this string may
- contain. The format should *not* end with a newline.
- Nil means don't put an In-Reply-To: header in replies.")
-
- (defvar vm-included-text-attribution-format "%F writes:\n"
- "*String which specifies the format of the attribution that precedes the
- included text from a message in a reply. See the documentation for the
- variable vm-summary-format for information on what this string may contain.
- Nil means don't attribute included text in replies.")
-
- (defvar vm-forwarding-subject-format "forwarded message from %F"
- "*String which specifies the format of the contents of the Subject
- header that is generated for a forwarded message. See the documentation
- for the variable vm-summary-format for information on what this string
- may contain. The format should *not* end with a newline. Nil means
- leave the Subject header empty when forwarding.")
-
- (defvar vm-summary-format "%2n %a %-17.17F %3m %2d %3l/%-5c \"%s\"\n"
- "*String which specifies the message summary line format.
- The string may contain the printf-like `%' conversion specifiers which
- substitute information about the message into the final summary line.
-
- Recognized specifiers are:
- a - attribute indicators (always three characters wide)
- The first char is `D', `N', `U' or ` ' for deleted, new, unread
- and read message respectively.
- The second char is `F' for filed (saved) messages.
- The third char is `R' if the message has been replied to.
- c - number of characters in message (ignoring headers)
- d - date of month message sent
- f - author's address
- F - author's full name (same as f if full name not found)
- h - hour message sent
- i - message ID
- l - number of lines in message (ignoring headers)
- m - month message sent
- n - message number
- s - message subject
- w - day of the week message sent
- y - year message sent
- z - timezone of date when the message was sent
-
- Use %% to get a single %.
-
- A numeric field width may be specified between the `%' and the specifier;
- this causes right justification of the substituted string. A negative field
- width causes left justification.
-
- The field width may be followed by a `.' and a number specifying the maximum
- allowed length of the substituted string. If the string is longer than this
- value it is truncated.
-
- The summary format need not be one line per message but it must end with
- a newline, otherwise the message pointer will not be displayed correctly
- in the summary window.")
-
- (defvar vm-mail-window-percentage 75
- "*Percentage of the screen that should be used to show mail messages.
- The rest of the screen will be used by the summary buffer, if displayed.")
-
- (defvar vm-mutable-windows t
- "*This variable's value controls VM's window usage.
-
- A value of t gives VM free run of the Emacs display; it will commandeer
- the entire screen for its purposes.
-
- A value of nil restricts VM's window usage to the window from which
- it was invoked. VM will not create, delete, or use any other windows,
- nor will it resize it's own window.
-
- A value that is neither t nor nil allows VM to use other windows, but it
- will not create new ones, or resize or delete the current ones.")
-
- (defvar vm-startup-with-summary nil
- "*Value tells VM what to display when a folder is visited.
- Nil means display folder only, t means display the summary only. A
- value that is neither t not nil means to display both folder and summary.
- The latter only works if the variable pop-up-windows's value is non-nil.
- See the documentation for vm-mail-window-percentage to see how to change how
- the screen is apportioned between the folder and summary windows.")
-
- (defvar vm-follow-summary-cursor nil
- "*Non-nil value causes VM to select the message under the cursor in the
- summary window before executing commands that operate on the current message.
- This occurs only when the summary buffer window is the selected window.")
-
- (defvar vm-group-by nil
- "*Non-nil value tells VM how to group message presentation.
- Currently, the valid non-nil values for this variable are
- \"subject\", which causes messages with the same subject (ignoring
- Re:'s) to be presented together,
- \"author\", which causes messages with the same author to be presented
- together, and
- \"date-sent\", which causes message sent on the same day to be
- presented together.
- \"arrival-time\" which appears only for completeness, this is the
- default behavior and is the same as nil.
-
- The ordering of the messages in the folder itself is not altered, messages
- are simply numbered and ordered differently internally.")
-
- (defvar vm-skip-deleted-messages t
- "*Non-nil value causes VM's `n' and 'p' commands to skip over
- deleted messages. If all messages are marked deleted then this variable
- is, of course, ignored.")
-
- (defvar vm-skip-read-messages nil
- "*Non-nil value causes VM's `n' and `p' commands to skip over
- message that have already been read in favor of new or unread messages.
- If there are no unread message then this variable is, of course, ignored.")
-
- (defvar vm-move-after-deleting nil
- "*Non-nil value causes VM's `d' command to automatically invoke
- vm-next-message or vm-previous-message after deleting, to move
- past the deleted messages.")
-
- (defvar vm-delete-after-saving nil
- "*Non-nil value causes VM automatically to mark messages for deletion
- after successfully saving them to a folder.")
-
- (defvar vm-circular-folders 0
- "*Value determines whether VM folders will be considered circular by
- various commands. `Circular' means VM will wrap from the end of the folder
- to the start and vice versa when moving the message pointer or deleting,
- undeleting or saving messages before or after the current message.
-
- A value of t causes all VM commands to consider folders circular.
-
- A value of nil causes all of VM commands to signal an error if the start
- or end of the folder would have to be passed to complete the command.
- For movement commands, this occurs after the message pointer has been
- moved as far it can go. For other commands the error occurs before any
- part of the command has been executed, i.e. no moves, saves, etc. will
- be done unless they can be done in their entirety.
-
- A value that is not nil and not t causes only VM's movement commands to
- consider folders circular. Saves, deletes and undeleted command will
- behave the same as if the value is nil.")
-
- (defvar vm-search-using-regexps nil
- "*Non-nil value causes VM's search command will interpret user input as a
- regular expression instead of as a literal string.")
-
- (defvar vm-mode-hooks nil
- "*List of hook functions to run when a buffer enters vm-mode.
- These hook functions should generally be used to set key bindings
- and local variables. Mucking about in the folder buffer is certainly
- possible but it is not encouraged.")
-
- (defvar vm-berkeley-mail-compatibility
- (memq system-type '(berkeley-unix))
- "*Non-nil means to read and write BSD Mail(1) style Status: headers.
- This makes sense if you plan to use VM to read mail archives created by
- Mail.")
-
- (defvar vm-gargle-uucp nil
- "*Non-nil value means to use a crufty regular expression that does
- surprisingly well at beautifying UUCP addresses that are substitued for
- %f as part of summary and attribution formats.")
-
- (defvar vm-strip-reply-headers nil
- "*Non-nil value causes VM to strip away all comments and extraneous text
- from the headers generated in reply messages. If you use the \"fakemail\"
- program as distributed with Emacs, you probably want to set this variable to
- to t, because as of Emacs v18.52 \"fakemail\" could not handle unstripped
- headers.")
-
- (defvar vm-rfc934-forwarding t
- "*Non-nil value causes VM to use char stuffing as described in RFC 934
- when packaging a message to be forwarded. This will allow the recipient
- to use a standard bursting agent on the message and act upon it as if it
- were sent directly.")
-
- (defvar vm-inhibit-startup-message nil
- "*Non-nil causes VM not to display its copyright notice, disclaimers
- etc. when started in the usual way.")
-
- (defvar mail-yank-hooks nil
- "*List of hooks functions called after yanking a message into a *mail*
- buffer.")
-
- (defvar vm-mode-map nil
- "Keymap for VM mode and VM Summary mode.")
-
- (defconst vm-version "4.41"
- "Version number of VM.")
-
- ;; internal vars
- (defvar vm-message-list nil)
- (make-variable-buffer-local 'vm-message-list)
- (defvar vm-message-pointer nil)
- (make-variable-buffer-local 'vm-message-pointer)
- (defvar vm-last-message-pointer nil)
- (make-variable-buffer-local 'vm-last-message-pointer)
- (defvar vm-primary-inbox-p nil)
- (make-variable-buffer-local 'vm-primary-inbox-p)
- (defvar vm-visible-header-alist nil)
- (make-variable-buffer-local 'vm-visible-header-alist)
- (defvar vm-mail-buffer nil)
- (make-variable-buffer-local 'vm-mail-buffer)
- (defvar vm-summary-buffer nil)
- (make-variable-buffer-local 'vm-summary-buffer)
- (defvar vm-system-state nil)
- (make-variable-buffer-local 'vm-system-state)
- (defvar vm-undo-record-list nil)
- (make-variable-buffer-local 'vm-undo-record-list)
- (defvar vm-undo-record-pointer nil)
- (make-variable-buffer-local 'vm-undo-record-pointer)
- (defvar vm-messages-needing-display-update nil)
- (make-variable-buffer-local 'vm-messages-needing-display-update)
- (defvar vm-current-grouping nil)
- (make-variable-buffer-local 'vm-current-grouping)
- (defvar vm-last-save-folder nil)
- (make-variable-buffer-local 'vm-last-save-folder)
- (defvar vm-last-pipe-command nil)
- (make-variable-buffer-local 'vm-last-pipe-command)
- (defvar vm-messages-not-on-disk 0)
- (make-variable-buffer-local 'vm-messages-not-on-disk)
- (defvar vm-inhibit-write-file-hook nil)
- (defvar vm-session-beginning t)
- (defconst vm-spool-directory
- (or (and (boundp 'rmail-spool-directory) rmail-spool-directory)
- "/usr/spool/mail"))
- (defconst vm-attributes-header-regexp
- "^X-VM-Attributes:\\(.*\n\\([ \t]+.*\n\\)*\\)")
- (defconst vm-attributes-header "X-VM-Attributes:")
- (defconst vm-berkeley-mail-status-header "Status: ")
- (defconst vm-berkeley-mail-status-header-regexp "^Status: ..?\n")
- (defconst vm-generic-header-regexp "^[^:\n]+:\\(.*\n\\([ \t]+.*\n\\)*\\)")
- (defconst vm-header-regexp-format "^%s:[ \t]*\\(.*\\(\n[ \t]+.*\\)*\\)")
- (defconst vm-supported-groupings-alist
- '(("arrival-time") ("subject") ("author") ("date-sent")))
- (defconst vm-total-count 0)
- (defconst vm-new-count 0)
- (defconst vm-unread-count 0)
- ;; for the mode line
- (defvar vm-ml-message-number nil)
- (make-variable-buffer-local 'vm-ml-message-number)
- (defvar vm-ml-highest-message-number nil)
- (make-variable-buffer-local 'vm-ml-highest-message-number)
- (defvar vm-ml-attributes-string nil)
- (make-variable-buffer-local 'vm-ml-attributes-string)
-
- ;; general purpose macros and functions
- (defmacro vm-marker (pos &optional buffer)
- (list 'set-marker '(make-marker) pos buffer))
-
- (defmacro vm-increment (variable)
- (list 'setq variable (list '1+ variable)))
-
- (defmacro vm-decrement (variable)
- (list 'setq variable (list '1- variable)))
-
- (defun vm-abs (n) (if (< n 0) (- n) n))
-
- ;; save-restriction flubs restoring the clipping region if you
- ;; (widen) and modify text outside the old region.
- ;; This should do it right.
- (defmacro vm-save-restriction (&rest forms)
- (let ((vm-sr-clip (make-symbol "vm-sr-clip"))
- (vm-sr-min (make-symbol "vm-sr-min"))
- (vm-sr-max (make-symbol "vm-sr-max")))
- (list 'let (list (list vm-sr-clip '(> (buffer-size)
- (- (point-max) (point-min)))))
- (list 'and vm-sr-clip
- (list 'setq vm-sr-min '(set-marker (make-marker) (point-min)))
- (list 'setq vm-sr-max '(set-marker (make-marker) (point-max))))
- (list 'unwind-protect (cons 'progn forms)
- '(widen)
- (list 'and vm-sr-clip
- (list 'progn
- (list 'narrow-to-region vm-sr-min vm-sr-max)
- (list 'set-marker vm-sr-min nil)
- (list 'set-marker vm-sr-max nil)))))))
-
- ;; macros and functions dealing with accessing messages struct fields
- (defun vm-make-message () (make-vector 20 nil))
-
- ;; where message begins (From_ line)
- (defmacro vm-start-of (message) (list 'aref message 0))
- ;; where visible headers start
- (defun vm-vheaders-of (message)
- (or (aref message 1)
- (progn (vm-reorder-message-headers message)
- (aref message 1))))
- ;; where text section starts
- (defmacro vm-text-of (message) (list 'aref message 2))
- ;; where message ends
- (defmacro vm-end-of (message) (list 'aref message 3))
- ;; message number
- (defmacro vm-number-of (message) (list 'aref message 4))
- ;; message attribute vector
- (defmacro vm-attributes-of (message) (list 'aref message 5))
- (defmacro vm-new-flag (message) (list 'aref (list 'aref message 5) 0))
- (defmacro vm-unread-flag (message) (list 'aref (list 'aref message 5) 1))
- (defmacro vm-deleted-flag (message) (list 'aref (list 'aref message 5) 2))
- (defmacro vm-filed-flag (message) (list 'aref (list 'aref message 5) 3))
- (defmacro vm-replied-flag (message) (list 'aref (list 'aref message 5) 4))
- ;; message size in bytes (as a string)
- (defmacro vm-byte-count-of (message) (list 'aref message 6))
- ;; weekday sent
- (defmacro vm-weekday-of (message) (list 'aref message 7))
- ;; month day
- (defmacro vm-monthday-of (message) (list 'aref message 8))
- ;; month sent
- (defmacro vm-month-of (message) (list 'aref message 9))
- ;; year sent
- (defmacro vm-year-of (message) (list 'aref message 10))
- ;; hour sent
- (defmacro vm-hour-of (message) (list 'aref message 11))
- ;; timezone
- (defmacro vm-zone-of (message) (list 'aref message 12))
- ;; message author's full name (Full-Name: or gouged from From:)
- (defmacro vm-full-name-of (message) (list 'aref message 13))
- ;; message author address (gouged from From:)
- (defmacro vm-from-of (message) (list 'aref message 14))
- ;; message ID (Message-Id:)
- (defmacro vm-message-id-of (message) (list 'aref message 15))
- ;; number of lines in message (as a string)
- (defmacro vm-line-count-of (message) (list 'aref message 16))
- ;; message subject (Subject:)
- (defmacro vm-subject-of (message) (list 'aref message 17))
- (defmacro vm-su-start-of (message) (list 'aref message 18))
- (defmacro vm-su-end-of (message) (list 'aref message 19))
-
- (defmacro vm-set-start-of (message start) (list 'aset message 0 start))
- (defmacro vm-set-vheaders-of (message vh) (list 'aset message 1 vh))
- (defmacro vm-set-text-of (message text) (list 'aset message 2 text))
- (defmacro vm-set-end-of (message end) (list 'aset message 3 end))
- (defmacro vm-set-number-of (message n) (list 'aset message 4 n))
- (defmacro vm-set-attributes-of (message attrs) (list 'aset message 5 attrs))
- (defmacro vm-set-byte-count-of (message count) (list 'aset message 6 count))
- (defmacro vm-set-weekday-of (message val) (list 'aset message 7 val))
- (defmacro vm-set-monthday-of (message val) (list 'aset message 8 val))
- (defmacro vm-set-month-of (message val) (list 'aset message 9 val))
- (defmacro vm-set-year-of (message val) (list 'aset message 10 val))
- (defmacro vm-set-hour-of (message val) (list 'aset message 11 val))
- (defmacro vm-set-zone-of (message val) (list 'aset message 12 val))
- (defmacro vm-set-full-name-of (message author) (list 'aset message 13 author))
- (defmacro vm-set-from-of (message author) (list 'aset message 14 author))
- (defmacro vm-set-message-id-of (message id) (list 'aset message 15 id))
- (defmacro vm-set-line-count-of (message count) (list 'aset message 16 count))
- (defmacro vm-set-subject-of (message subject) (list 'aset message 17 subject))
- (defmacro vm-set-su-start-of (message start) (list 'aset message 18 start))
- (defmacro vm-set-su-end-of (message end) (list 'aset message 19 end))
-
- (defun vm-text-end-of (message)
- (- (vm-end-of message)
- (cond ((eq vm-folder-type 'mmdf) 5)
- (t 1))))
-
- ;; The remaining routines in this group are part of the undo system.
-
- ;; init
- (if vm-mode-map
- ()
- (setq vm-mode-map (make-keymap))
- (suppress-keymap vm-mode-map)
- (define-key vm-mode-map "h" 'vm-summarize)
- (define-key vm-mode-map "\M-n" 'vm-next-unread-message)
- (define-key vm-mode-map "\M-p" 'vm-previous-unread-message)
- (define-key vm-mode-map "n" 'vm-next-message)
- (define-key vm-mode-map "p" 'vm-previous-message)
- (define-key vm-mode-map "N" 'vm-Next-message)
- (define-key vm-mode-map "P" 'vm-Previous-message)
- (define-key vm-mode-map "\t" 'vm-goto-message-last-seen)
- (define-key vm-mode-map "\r" 'vm-goto-message)
- (define-key vm-mode-map "t" 'vm-expose-hidden-headers)
- (define-key vm-mode-map " " 'vm-scroll-forward)
- (define-key vm-mode-map "b" 'vm-scroll-backward)
- (define-key vm-mode-map "\C-?" 'vm-scroll-backward)
- (define-key vm-mode-map "d" 'vm-delete-message)
- (define-key vm-mode-map "u" 'vm-undelete-message)
- (define-key vm-mode-map "k" 'vm-kill-subject)
- (define-key vm-mode-map "f" 'vm-followup)
- (define-key vm-mode-map "F" 'vm-followup-include-text)
- (define-key vm-mode-map "r" 'vm-reply)
- (define-key vm-mode-map "R" 'vm-reply-include-text)
- (define-key vm-mode-map "z" 'vm-forward-message)
- (define-key vm-mode-map "@" 'vm-send-digest)
- (define-key vm-mode-map "*" 'vm-burst-digest)
- (define-key vm-mode-map "m" 'vm-mail)
- (define-key vm-mode-map "g" 'vm-get-new-mail)
- (define-key vm-mode-map "G" 'vm-group-messages)
- (define-key vm-mode-map "v" 'vm-visit-folder)
- (define-key vm-mode-map "s" 'vm-save-message)
- (define-key vm-mode-map "w" 'vm-save-message-sans-headers)
- (define-key vm-mode-map "A" 'vm-auto-archive-messages)
- (define-key vm-mode-map "S" 'vm-save-folder)
- (define-key vm-mode-map "|" 'vm-pipe-message-to-command)
- (define-key vm-mode-map "#" 'vm-expunge-folder)
- (define-key vm-mode-map "q" 'vm-quit)
- (define-key vm-mode-map "x" 'vm-quit-no-change)
- (define-key vm-mode-map "?" 'vm-help)
- (define-key vm-mode-map "\C-_" 'vm-undo)
- (define-key vm-mode-map "\C-xu" 'vm-undo)
- (define-key vm-mode-map "!" 'shell-command)
- (define-key vm-mode-map "<" 'beginning-of-buffer)
- (define-key vm-mode-map ">" 'vm-end-of-message)
- (define-key vm-mode-map "\M-s" 'vm-isearch-forward)
- (define-key vm-mode-map "=" 'vm-summarize)
- (define-key vm-mode-map "\M-C" 'vm-show-copying-restrictions)
- (define-key vm-mode-map "\M-W" 'vm-show-no-warranty))
-
- (defun vm-mark-for-display-update (message)
- (if (not (memq message vm-messages-needing-display-update))
- (setq vm-messages-needing-display-update
- (cons message vm-messages-needing-display-update))))
-
- (defun vm-last (list) (while (cdr-safe list) (setq list (cdr list))) list)
-
- (put 'folder-empty 'error-conditions '(folder-empty error))
- (put 'folder-empty 'error-message "Folder is empty")
-
- (defun vm-error-if-folder-empty ()
- (while (null vm-message-list)
- (signal 'folder-empty nil)))
-
- (defun vm-proportion-windows ()
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (if (not (one-window-p t))
- (let ((mail-w (get-buffer-window (current-buffer)))
- (n (- (window-height (get-buffer-window (current-buffer)))
- (/ (* vm-mail-window-percentage
- (- (screen-height)
- (window-height (minibuffer-window))))
- 100)))
- (old-w (selected-window)))
- (if mail-w
- (save-excursion
- (select-window mail-w)
- (shrink-window n)
- (select-window old-w))))))
-
- (defun vm-number-messages ()
- (let ((n 1) (message-list vm-message-list))
- (while message-list
- (vm-set-number-of (car message-list) (int-to-string n))
- (setq n (1+ n) message-list (cdr message-list)))
- (setq vm-ml-highest-message-number (int-to-string (1- n)))))
-
- (defun vm-match-visible-header (alist)
- (catch 'match
- (while alist
- (if (looking-at (car (car alist)))
- (throw 'match (car alist)))
- (setq alist (cdr alist)))
- nil))
-
- (defun vm-delete-header ()
- (if (looking-at vm-generic-header-regexp)
- (delete-region (match-beginning 0) (match-end 0))))
-
- ;; Build a chain of message structures.
- ;; Find the start and end of each message and fill end the relevant
- ;; fields in the message structures.
-
- (defun vm-build-message-list ()
- (save-excursion
- (vm-build-visible-header-alist)
- (let (tail-cons message prev-message case-fold-search marker
- start-regexp sep-pattern trailer-length)
- (if (eq vm-folder-type 'mmdf)
- (setq start-regexp "^\001\001\001\001\n"
- separator-string "\n\001\001\001\001\n\001\001\001\001"
- trailer-length 6)
- (setq start-regexp "^From "
- separator-string "\n\nFrom "
- trailer-length 2))
- (if vm-message-list
- (let ((mp vm-message-list)
- (end (point-min)))
- (while mp
- (if (< end (vm-end-of (car mp)))
- (setq end (vm-end-of (car mp))))
- (setq mp (cdr mp)))
- ;; move back past trailer so separator-string will match below
- (goto-char (- end trailer-length))
- (setq tail-cons (vm-last vm-message-list)))
- (goto-char (point-min))
- (if (looking-at start-regexp)
- (progn
- (setq message (vm-make-message) prev-message message)
- (vm-set-start-of message (vm-marker (match-beginning 0)))
- (setq vm-message-list (list message)
- tail-cons vm-message-list))))
- (while (search-forward separator-string nil t)
- (setq marker (vm-marker (+ trailer-length (match-beginning 0)))
- message (vm-make-message))
- (vm-set-start-of message marker)
- (if prev-message
- (vm-set-end-of prev-message marker))
- (if tail-cons
- (progn
- (setcdr tail-cons (list message))
- (setq tail-cons (cdr tail-cons)
- prev-message message))
- (setq vm-message-list (list message)
- tail-cons vm-message-list
- prev-message message)))
- (if prev-message
- (vm-set-end-of prev-message (vm-marker (point-max)))))))
-
- (defun vm-build-visible-header-alist ()
- (let ((header-alist (cons nil nil))
- (vheaders vm-visible-headers)
- list)
- (setq list header-alist)
- (while vheaders
- (setcdr list (cons (cons (car vheaders) nil) nil))
- (setq list (cdr list) vheaders (cdr vheaders)))
- (setq vm-visible-header-alist (cdr header-alist))))
-
- ;; Group the headers that the user wants to see at the end of the headers
- ;; section so we can narrow to them. The vheaders field of the
- ;; message struct is set. This function is called on demand whenever
- ;; a vheaders field is discovered to be nil for a particular message.
-
- (defun vm-reorder-message-headers (message)
- (save-excursion
- (vm-save-restriction
- (let ((header-alist vm-visible-header-alist)
- list buffer-read-only match-end-0
- (inhibit-quit t)
- (old-buffer-modified-p (buffer-modified-p)))
- (goto-char (vm-start-of message))
- (forward-line)
- (while (and (not (= (following-char) ?\n))
- (looking-at vm-generic-header-regexp))
- (setq match-end-0 (match-end 0)
- list (vm-match-visible-header header-alist))
- (if (null list)
- (goto-char match-end-0)
- (if (cdr list)
- (setcdr list
- (concat
- (cdr list)
- (buffer-substring (point) match-end-0)))
- (setcdr list (buffer-substring (point) match-end-0)))
- (delete-region (point) match-end-0)))
- (vm-set-vheaders-of message (point-marker))
- (setq list header-alist)
- (while list
- (if (cdr (car list))
- (progn
- (insert (cdr (car list)))
- (setcdr (car list) nil)))
- (setq list (cdr list)))
- (set-buffer-modified-p old-buffer-modified-p)))))
-
- ;; Read the attribute headers from the messages and store their contents
- ;; in attributes fields of the message structures. If a message has no
- ;; attributes header assume it is new. If a message already has
- ;; attributes don't bother checking the headers.
- ;;
- ;; Stores the position where the message text begins in the message struct.
-
- (defun vm-read-attributes ()
- (save-excursion
- (let ((mp vm-message-list))
- (setq vm-new-count 0
- vm-unread-count 0
- vm-total-count 0)
- (while mp
- (vm-increment vm-total-count)
- (if (vm-attributes-of (car mp))
- ()
- (goto-char (vm-start-of (car mp)))
- (search-forward "\n\n" (vm-text-end-of (car mp)) 0)
- (vm-set-text-of (car mp) (point-marker))
- (goto-char (vm-start-of (car mp)))
- (cond ((re-search-forward vm-attributes-header-regexp
- (vm-text-of (car mp)) t)
- (goto-char (match-beginning 1))
- (vm-set-attributes-of (car mp)
- (condition-case ()
- (read (current-buffer))
- (error (vector t nil nil nil nil))))
- ;; If attributes are unrecogniable just assume the
- ;; message is new.
- (cond ((or (not (vectorp (vm-attributes-of (car mp))))
- (not (= (length (vm-attributes-of (car mp)))
- 5)))
- (vm-set-attributes-of (car mp)
- (vector t nil nil nil nil)))))
- ((and vm-berkeley-mail-compatibility
- (re-search-forward vm-berkeley-mail-status-header-regexp
- (vm-text-of (car mp)) t))
- (vm-set-attributes-of (car mp) (vector nil (looking-at "R")
- nil nil nil)))
- (t
- (vm-set-attributes-of (car mp) (vector t nil nil nil nil)))))
- (cond ((vm-deleted-flag (car mp))) ; don't count deleted messages
- ((vm-new-flag (car mp))
- (vm-increment vm-new-count))
- ((vm-unread-flag (car mp))
- (vm-increment vm-unread-count)))
- (setq mp (cdr mp))))))
-
- ;; Stuff the messages attributes back into the messages as headers.
- (defun vm-stuff-attributes ()
- (save-excursion
- (vm-save-restriction
- (widen)
- (let ((mp vm-message-list) attributes buffer-read-only
- (old-buffer-modified-p (buffer-modified-p)))
- (while mp
- (setq attributes (vm-attributes-of (car mp)))
- (goto-char (vm-start-of (car mp)))
- (if (re-search-forward vm-attributes-header-regexp
- (vm-text-of (car mp)) t)
- (delete-region (match-beginning 0) (match-end 0)))
- (cond (vm-berkeley-mail-compatibility
- (goto-char (vm-start-of (car mp)))
- (if (re-search-forward vm-berkeley-mail-status-header-regexp
- (vm-text-of (car mp)) t)
- (delete-region (match-beginning 0) (match-end 0)))
- (cond ((not (vm-new-flag (car mp)))
- (goto-char (vm-start-of (car mp)))
- (forward-line)
- (insert-before-markers
- vm-berkeley-mail-status-header
- (if (vm-unread-flag (car mp)) "" "R")
- "O\n")))))
- (goto-char (vm-start-of (car mp)))
- (forward-line)
- (insert-before-markers vm-attributes-header " "
- (prin1-to-string attributes) "\n")
- (setq mp (cdr mp)))
- (set-buffer-modified-p old-buffer-modified-p)))))
-
- ;; Remove any message marked for deletion from the buffer and the
- ;; message list.
- (defun vm-gobble-deleted-messages ()
- (save-excursion
- (vm-save-restriction
- (widen)
- (let ((mp vm-message-list) prev buffer-read-only did-gobble)
- (while mp
- (if (not (vm-deleted-flag (car mp)))
- (setq prev mp)
- (setq did-gobble t)
- (delete-region (vm-start-of (car mp))
- (vm-end-of (car mp)))
- (if (null prev)
- (setq vm-message-list (cdr vm-message-list))
- (setcdr prev (cdr mp))))
- (setq mp (cdr mp)))
- (if did-gobble
- (progn
- (vm-clear-expunge-invalidated-undos)
- (if (null vm-message-list)
- (setq overlay-arrow-position nil))
- (cond ((and vm-last-message-pointer
- (vm-deleted-flag (car vm-last-message-pointer)))
- (setq vm-last-message-pointer nil)))
- (cond ((and vm-message-pointer
- (vm-deleted-flag (car vm-message-pointer)))
- (setq vm-system-state nil)
- (setq mp (cdr vm-message-pointer))
- (while (and mp (vm-deleted-flag (car mp)))
- (setq mp (cdr mp)))
- (setq vm-message-pointer
- (or mp (vm-last vm-message-list)))))
- did-gobble ))))))
-
- (defun vm-change-all-new-to-unread ()
- (let ((mp vm-message-list))
- (while mp
- (if (vm-new-flag (car mp))
- (progn
- (vm-set-new-flag (car mp) nil)
- (vm-set-unread-flag (car mp) t)))
- (setq mp (cdr mp)))))
-
- (defun vm-update-summary-and-mode-line ()
- (setq vm-ml-message-number (vm-number-of (car vm-message-pointer)))
- (cond ((vm-new-flag (car vm-message-pointer))
- (setq vm-ml-attributes-string "new"))
- ((vm-unread-flag (car vm-message-pointer))
- (setq vm-ml-attributes-string "unread"))
- (t (setq vm-ml-attributes-string "read")))
- (cond ((vm-filed-flag (car vm-message-pointer))
- (setq vm-ml-attributes-string
- (concat vm-ml-attributes-string " filed"))))
- (cond ((vm-replied-flag (car vm-message-pointer))
- (setq vm-ml-attributes-string
- (concat vm-ml-attributes-string " replied"))))
- (cond ((vm-deleted-flag (car vm-message-pointer))
- (setq vm-ml-attributes-string
- (concat vm-ml-attributes-string " deleted"))))
- (while vm-messages-needing-display-update
- (vm-update-message-summary vm-messages-needing-display-update)
- (setq vm-messages-needing-display-update
- (cdr vm-messages-needing-display-update)))
- (save-excursion
- (set-buffer (other-buffer))
- (set-buffer-modified-p (buffer-modified-p))))
-
- (defun vm-goto-message (n)
- "Go to the message numbered N.
- Interactively N is the prefix argument. If no prefix arg is provided
- N is prompted for in the minibuffer."
- (interactive "NGo to message: ")
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (vm-error-if-folder-empty)
- (let ((cons (nthcdr (1- n) vm-message-list)))
- (if (null cons)
- (error "No such message."))
- (if (eq vm-message-pointer cons)
- (vm-preview-current-message)
- (setq vm-last-message-pointer vm-message-pointer
- vm-message-pointer cons)
- (vm-set-summary-pointer (car vm-message-pointer))
- (vm-preview-current-message))))
-
- (defun vm-goto-message-last-seen ()
- "Go to the message last previewed."
- (interactive)
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (vm-error-if-folder-empty)
- (if vm-last-message-pointer
- (let (tmp)
- (setq tmp vm-message-pointer
- vm-message-pointer vm-last-message-pointer
- vm-last-message-pointer tmp)
- (vm-set-summary-pointer (car vm-message-pointer))
- (vm-preview-current-message))))
-
- (put 'beginning-of-folder 'error-conditions '(beginning-of-folder error))
- (put 'beginning-of-folder 'error-message "Beginning of folder")
- (put 'end-of-folder 'error-conditions '(end-of-folder error))
- (put 'end-of-folder 'error-message "End of folder")
-
- (defun vm-check-count (count)
- (if (>= count 0)
- (if (< (length vm-message-pointer) count)
- (signal 'end-of-folder nil))
- (if (< (1+ (- (length vm-message-list) (length vm-message-pointer)))
- (vm-abs count))
- (signal 'beginning-of-folder nil))))
-
- (defun vm-move-message-pointer (direction)
- (let ((mp vm-message-pointer))
- (if (eq direction 'forward)
- (progn
- (setq mp (cdr mp))
- (if (null mp)
- (if vm-circular-folders
- (setq mp vm-message-list)
- (signal 'end-of-folder nil))))
- (if (eq mp vm-message-list)
- (if vm-circular-folders
- (setq mp (vm-last vm-message-list))
- (signal 'beginning-of-folder nil))
- (setq mp (let ((curr vm-message-list))
- (while (not (eq (cdr curr) mp))
- (setq curr (cdr curr)))
- curr))))
- (setq vm-message-pointer mp)))
-
- (defun vm-should-skip-message (mp)
- (or (and vm-skip-deleted-messages
- (vm-deleted-flag (car mp)))
- (and vm-skip-read-messages
- (or (vm-deleted-flag (car mp))
- (not (or (vm-new-flag (car mp))
- (vm-unread-flag (car mp))))))))
-
- (defun vm-next-message (&optional count retry)
- "Go forward one message and preview it.
- With prefix arg COUNT, go forward COUNT messages. A negative COUNT
- means go backward. If the absolute value of COUNT > 1 the values of the
- variables vm-skip-deleted-messages and vm-skip-read-messages are
- ignored."
- (interactive "p\np")
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (vm-error-if-folder-empty)
- (or count (setq count 1))
- (let ((oldmp vm-message-pointer)
- (error)
- (direction (if (> count 0) 'forward 'backward))
- (count (vm-abs count)))
- (cond
- ((null vm-message-pointer)
- (setq vm-message-pointer vm-message-list))
- ((/= count 1)
- (condition-case ()
- (while (not (zerop count))
- (vm-move-message-pointer direction)
- (vm-decrement count))
- (beginning-of-folder (setq error 'beginning-of-folder))
- (end-of-folder (setq error 'end-of-folder))))
- (t
- (condition-case ()
- (progn
- (vm-move-message-pointer direction)
- (while (and (not (eq oldmp vm-message-pointer))
- (vm-should-skip-message vm-message-pointer))
- (vm-move-message-pointer direction))
- ;; Retry the move if we've gone a complete circle and we should
- ;; skip the current message and there are other messages.
- (and (eq vm-message-pointer oldmp) retry (cdr vm-message-list)
- (vm-should-skip-message vm-message-pointer)
- (vm-move-message-pointer direction)))
- (beginning-of-folder
- (setq vm-message-pointer oldmp)
- (if retry
- (vm-move-message-pointer direction)
- (setq error 'beginning-of-folder)))
- (end-of-folder
- (setq vm-message-pointer oldmp)
- (if retry
- (vm-move-message-pointer direction)
- (setq error 'end-of-folder))))))
- (if (not (eq vm-message-pointer oldmp))
- (progn
- (setq vm-last-message-pointer oldmp)
- (vm-set-summary-pointer (car vm-message-pointer))
- (vm-preview-current-message)))
- (if error
- (signal error nil))))
-
- (defun vm-previous-message (&optional count retry)
- "Go back one message and preview it.
- With prefix arg COUNT, go backward COUNT messages. A negative COUNT
- means go forward. If the absolute value of COUNT > 1 the values of the
- variables vm-skip-deleted-messages and vm-skip-read-messages are
- ignored."
- (interactive "p\np")
- (or count (setq count 1))
- (vm-next-message (- count) retry))
-
- (defun vm-Next-message (&optional count)
- "Like vm-next-message but will not skip messages."
- (interactive "p")
- (let (vm-skip-deleted-messages vm-skip-read-messages)
- (vm-next-message count)))
-
- (defun vm-Previous-message (&optional count)
- "Like vm-previous-message but will not skip messages."
- (interactive "p")
- (let (vm-skip-deleted-messages vm-skip-read-messages)
- (vm-previous-message count)))
-
- (defun vm-next-unread-message ()
- "Move forward to the nearest new or unread message, if there is one."
- (interactive)
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (condition-case ()
- (let ((vm-skip-read-messages t)
- (oldmp vm-message-pointer))
- (vm-next-message)
- ;; in case vm-circular-folder is non-nil
- (and (eq vm-message-pointer oldmp) (signal 'end-of-folder nil)))
- (end-of-folder (error "No next unread message"))))
-
- (defun vm-previous-unread-message ()
- "Move backward to the nearest new or unread message, if there is one."
- (interactive)
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (condition-case ()
- (let ((vm-skip-read-messages t)
- (oldmp vm-message-pointer))
- (vm-previous-message)
- ;; in case vm-circular-folder is non-nil
- (and (eq vm-message-pointer oldmp) (signal 'beginning-of-folder nil)))
- (beginning-of-folder (error "No previous unread message"))))
-
- (defun vm-preview-current-message ()
- (setq vm-system-state 'previewing)
- (widen)
- (narrow-to-region
- (vm-vheaders-of (car vm-message-pointer))
- (if vm-preview-lines
- (min
- (vm-text-end-of (car vm-message-pointer))
- (save-excursion
- (goto-char (vm-text-of (car vm-message-pointer)))
- (forward-line (if (natnump vm-preview-lines) vm-preview-lines 0))
- (point)))
- (vm-text-of (car vm-message-pointer))))
- (let ((w (get-buffer-window (current-buffer))))
- (and w (progn (set-window-start w (point-min))
- (set-window-point w (point-max))))
- (and w vm-highlighted-header-regexp
- (progn
- (save-restriction
- (narrow-to-region (point) (point))
- (sit-for 0))
- (goto-char (point-min))
- (while (re-search-forward vm-highlighted-header-regexp nil t)
- (save-restriction
- (goto-char (match-beginning 0))
- (looking-at vm-generic-header-regexp)
- (goto-char (match-beginning 1))
- (narrow-to-region (point-min) (point))
- (sit-for 0)
- (setq inverse-video t)
- (widen)
- (narrow-to-region (point-min) (match-end 1))
- (sit-for 0)
- (setq inverse-video nil)
- (goto-char (match-end 0)))))))
- (goto-char (point-max))
- ;; De Morgan's Theorems could clear away most of the following negations,
- ;; but the resulting code would be horribly obfuscated.
- (if (or (null vm-preview-lines)
- (and (not vm-preview-read-messages)
- (not (vm-new-flag (car vm-message-pointer)))
- (not (vm-unread-flag (car vm-message-pointer)))))
- ;; Don't sit and howl unless the mail buffer is visible.
- (vm-show-current-message (get-buffer-window (current-buffer)))
- (vm-update-summary-and-mode-line)))
-
- (defun vm-show-current-message (&optional sit-and-howl)
- (setq vm-system-state 'reading)
- (save-excursion
- (goto-char (point-min))
- (widen)
- (narrow-to-region (point) (vm-text-end-of (car vm-message-pointer))))
- (cond ((vm-new-flag (car vm-message-pointer))
- (vm-set-new-flag (car vm-message-pointer) nil))
- ((vm-unread-flag (car vm-message-pointer))
- (vm-set-unread-flag (car vm-message-pointer) nil)))
- (vm-update-summary-and-mode-line)
- (cond (sit-and-howl
- (sit-for 0)
- (vm-howl-if-eom-visible))))
-
- (defun vm-expose-hidden-headers ()
- "Expose headers omitted from vm-visible-headers."
- (interactive)
- (vm-follow-summary-cursor)
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (vm-error-if-folder-empty)
- (save-excursion
- (goto-char (point-max))
- (widen)
- (narrow-to-region (point) (vm-start-of (car vm-message-pointer)))
- (let (w)
- (and (setq w (get-buffer-window (current-buffer)))
- (= (window-start w) (vm-vheaders-of (car vm-message-pointer)))
- (set-window-start w (vm-start-of (car vm-message-pointer)))))))
-
- (defun vm-howl-if-eom-visible ()
- (let ((w (get-buffer-window (current-buffer))))
- (and w (pos-visible-in-window-p (point-max) w)
- (message "End of message %s from %s"
- (vm-number-of (car vm-message-pointer))
- (vm-su-full-name (car vm-message-pointer))))))
-
- ;; message-changed is an old-fashoined local variable.
- (defun vm-scroll-forward (&optional arg message-changed)
- "Scroll forward a screenful of text.
- If the current message is being previewed, the message body is revealed.
- If at the end of the current message, move to the next message."
- (interactive "P")
- (setq message-changed (vm-follow-summary-cursor))
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (vm-error-if-folder-empty)
- (if (null (get-buffer-window (current-buffer)))
- (progn
- (if vm-mutable-windows
- (let ((pop-up-windows
- (and pop-up-windows (eq vm-mutable-windows t))))
- (display-buffer (current-buffer)))
- (switch-to-buffer (current-buffer)))
- (if (and vm-summary-buffer (get-buffer-window vm-summary-buffer)
- (eq vm-mutable-windows t))
- (vm-proportion-windows))
- (if (eq vm-system-state 'previewing)
- (vm-show-current-message t)
- (vm-howl-if-eom-visible)))
- (if (eq vm-system-state 'previewing)
- (vm-show-current-message t)
- (if message-changed
- (vm-howl-if-eom-visible)
- (let ((w (get-buffer-window (current-buffer)))
- (old-w (selected-window)))
- (unwind-protect
- (progn
- (select-window w)
- (if (not (eq (condition-case () (scroll-up arg)
- (end-of-buffer (if (null arg)
- (progn
- (vm-next-message)
- 'next-message))))
- 'next-message))
- (vm-howl-if-eom-visible)))
- (select-window old-w)))))))
-
- (defun vm-scroll-backward (&optional arg)
- "Scroll backward a screenful of text."
- (interactive "P")
- (vm-follow-summary-cursor)
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (vm-error-if-folder-empty)
- (if (null (get-buffer-window (current-buffer)))
- (progn
- (if vm-mutable-windows
- (let ((pop-up-windows
- (and pop-up-windows (eq vm-mutable-windows t))))
- (display-buffer (current-buffer)))
- (switch-to-buffer (current-buffer)))
- (if (and vm-summary-buffer (get-buffer-window vm-summary-buffer)
- (eq vm-mutable-windows t))
- (vm-proportion-windows)))
- (let ((w (get-buffer-window (current-buffer)))
- (old-w (selected-window)))
- (unwind-protect
- (progn
- (select-window w)
- (scroll-down arg))
- (select-window old-w)))))
-
- (defun vm-end-of-message ()
- "Displays the end of the current message, exposing and marking it read
- as necessary."
- (interactive)
- (vm-follow-summary-cursor)
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (vm-error-if-folder-empty)
- (if (eq vm-system-state 'previewing)
- (vm-show-current-message))
- (goto-char (point-max))
- (vm-howl-if-eom-visible))
-
- (defun vm-quit-no-change ()
- "Exit VM without saving changes made to the folder."
- (interactive)
- (vm-quit t))
-
- (defun vm-quit (&optional no-change)
- "Quit VM, saving changes and expunging messages marked for deletion.
- New messages are changed to unread."
- (interactive)
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (and no-change (buffer-modified-p)
- (not (zerop vm-messages-not-on-disk))
- ;; Folder may have been saved with C-x C-s and atriutes may have
- ;; been changed after that; in that case vm-messages-not-on-disk
- ;; would not have been zeroed. However, all modification flag
- ;; undos are cleared if VM actually modifies the folder buffer
- ;; (as opposed to the folder's attributes), so this can be used
- ;; to verify that there are indeed unsaved messages.
- (null (assq 'set-buffer-modified-p vm-undo-record-list))
- (not (y-or-n-p
- (format "%d message%s have not been saved to disk, exit anyway? "
- vm-messages-not-on-disk
- (if (= 1 vm-messages-not-on-disk) "" "s"))))
- (error "Aborted"))
- (let ((inhibit-quit t))
- (if (not no-change)
- (vm-change-all-new-to-unread))
- (if (and (buffer-modified-p) (not no-change))
- (vm-save-folder t))
- (let ((summary-buffer vm-summary-buffer)
- (mail-buffer (current-buffer)))
- (if summary-buffer
- (progn
- (setq overlay-arrow-position nil)
- (if (eq vm-mutable-windows t)
- (delete-windows-on vm-summary-buffer))
- (kill-buffer summary-buffer)))
- (set-buffer mail-buffer)
- (set-buffer-modified-p nil)
- (kill-buffer (current-buffer)))
- ;; Make sure we are now dealing with the buffer and window that
- ;; would be selected were we to give up control now.
- (set-buffer (window-buffer (selected-window)))
- ;; If we land on a buffer that VM knows about
- ;; do some nice things for the user, if we're allowed.
- (cond ((and (eq major-mode 'vm-mode) (eq vm-mutable-windows t))
- (if (null vm-startup-with-summary)
- (delete-other-windows)
- (condition-case () (vm-summarize t) (error nil))
- (and (not (eq major-mode 'vm-summary-mode))
- (eq vm-startup-with-summary t)
- (not (one-window-p t))
- vm-summary-buffer
- (get-buffer-window vm-summary-buffer)
- (progn
- (select-window (get-buffer-window vm-summary-buffer))
- (delete-other-windows)))))
- ((eq major-mode 'vm-summary-mode)
- (cond ((eq vm-startup-with-summary nil)
- (switch-to-buffer vm-mail-buffer)
- (and (not (one-window-p t)) (eq vm-mutable-windows t)
- (delete-other-windows)))
- ((not (eq vm-startup-with-summary t))
- (let ((pop-up-windows
- (and pop-up-windows (eq vm-mutable-windows t))))
- (display-buffer vm-mail-buffer))
- (if (eq vm-mutable-windows t)
- (if (eq major-mode 'vm-summary-mode)
- (vm-proportion-windows)
- (switch-to-buffer vm-summary-buffer))))
- ((eq vm-mutable-windows t)
- (delete-other-windows)))))))
-
- ;; This allows C-x C-s to do the right thing for VM mail buffers.
- ;; Note that deleted messages are not expunged.
- (defun vm-write-file-hook ()
- (if (not (eq major-mode 'vm-mode))
- ()
- (if vm-inhibit-write-file-hook
- ()
- ;; The vm-save-restriction isn't really necessary here (since
- ;; vm-stuff-atributes cleans up after itself) but should remain
- ;; as a safeguard against the time when other stuff is added here.
- (vm-save-restriction
- (let ((inhibit-quit t)
- (buffer-read-only))
- (vm-stuff-attributes)
- nil )))))
-
- (defun vm-save-folder (&optional quitting)
- "Save current folder to disk."
- (interactive)
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (if (buffer-modified-p)
- (let ((inhibit-quit t))
- ;; may get error if folder is emptied by the expunge.
- (condition-case ()
- (vm-expunge-folder quitting t)
- (error nil))
- (vm-stuff-attributes)
- (let ((vm-inhibit-write-file-hook t))
- (save-buffer))
- (setq vm-messages-not-on-disk 0)
- (and (zerop (buffer-size)) vm-delete-empty-folders
- (condition-case ()
- (progn
- (delete-file buffer-file-name)
- (message "%s removed" buffer-file-name))
- (error nil)))
- (if (not quitting)
- (if vm-message-pointer
- (vm-update-summary-and-mode-line)
- (vm-next-message))))))
-
- (defun vm-visit-folder (folder)
- "Visit a mail file.
- VM will parse and present its messages to you in the usual way."
- (interactive
- (list (read-file-name
- "Visit folder: " (if vm-folder-directory
- (expand-file-name vm-folder-directory)
- default-directory) nil t)))
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (vm folder))
-
- (defun vm-help ()
- "Display VM command and variable information."
- (interactive)
- (if (and vm-mail-buffer (get-buffer-window vm-mail-buffer))
- (set-buffer vm-mail-buffer))
- (let ((pop-up-windows (and pop-up-windows (eq vm-mutable-windows t))))
- (cond
- ((eq last-command 'vm-help)
- (describe-mode))
- ((eq vm-system-state 'previewing)
- (message "Type SPC to read message, n previews next message (? gives more help)"))
- ((eq vm-system-state 'reading)
- (message "SPC and b scroll, (d)elete, (s)ave, (n)ext, (r)eply (? gives more help)"))
- (t (describe-mode)))))
-
- (defun vm-move-mail (source destination)
- (call-process "movemail" nil nil nil (expand-file-name source)
- (expand-file-name destination)))
-
- (defun vm-gobble-crash-box ()
- (save-excursion
- (vm-save-restriction
- (widen)
- (let ((opoint-max (point-max)) crash-buf buffer-read-only
- (old-buffer-modified-p (buffer-modified-p))
- ;; crash box could contain a letter bomb...
- ;; force user notification of file variables.
- (inhibit-local-variables t))
- (setq crash-buf (find-file-noselect vm-crash-box))
- (goto-char (point-max))
- (insert-buffer-substring crash-buf
- 1 (1+ (save-excursion
- (set-buffer crash-buf)
- (widen)
- (buffer-size))))
- (write-region opoint-max (point-max) buffer-file-name t t)
- (backup-buffer)
- ;; make sure primary inbox is private. 384 = octal 600
- (condition-case () (set-file-modes buffer-file-name 384) (error nil))
- (set-buffer-modified-p old-buffer-modified-p)
- (kill-buffer crash-buf)
- (condition-case () (delete-file vm-crash-box)
- (error nil))))))
-
- (defun vm-get-spooled-mail ()
- (let ((spool-files (or vm-spool-files
- (list (concat vm-spool-directory (user-login-name)))))
- (inhibit-quit t)
- (got-mail))
- (if (file-exists-p vm-crash-box)
- (progn
- (message "Recovering messages from crash box...")
- (vm-gobble-crash-box)
- (message "Recovering messages from crash box... done")
- (setq got-mail t)))
- (while spool-files
- (if (file-readable-p (car spool-files))
- (progn
- (message "Getting new mail from %s..." (car spool-files))
- (vm-move-mail (car spool-files) vm-crash-box)
- (vm-gobble-crash-box)
- (message "Getting new mail from %s... done" (car spool-files))
- (setq got-mail t)))
- (setq spool-files (cdr spool-files)))
- got-mail ))
-
- (defun vm-get-new-mail ()
- "Move any new mail that has arrived in the system mailbox into the
- primary inbox. New mail is appended to the disk and buffer copies of
- the primary inbox.
-
- This command is valid only from the primary inbox buffer."
- (interactive)
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- (if (not vm-primary-inbox-p)
- (error "This is not your primary inbox."))
- (if (not (and (vm-get-spooled-mail) (vm-assimilate-new-messages)))
- (message "No new mail.")
- (vm-emit-totals-blurb)
- ;; If there's a current grouping, then the summary has already
- ;; been redone in vm-group-messages.
- (if (and vm-summary-buffer (not vm-current-grouping))
- (progn
- (vm-do-summary)
- (vm-emit-totals-blurb)))
- (vm-thoughtfully-select-message)
- (if vm-summary-buffer
- (vm-set-summary-pointer (car vm-message-pointer)))))
-
- (defun vm-emit-totals-blurb ()
- (message "%d message%s, %d new, %d unread."
- vm-total-count (if (= vm-total-count 1) "" "s")
- vm-new-count vm-unread-count))
-
- (defun vm-find-first-unread-message ()
- (let (mp unread-mp)
- (setq mp vm-message-list)
- (while mp
- (if (and (vm-new-flag (car mp)) (not (vm-deleted-flag (car mp))))
- (setq unread-mp mp mp nil)
- (setq mp (cdr mp))))
- (if (null unread-mp)
- (progn
- (setq mp vm-message-list)
- (while mp
- (if (and (vm-unread-flag (car mp))
- (not (vm-deleted-flag (car mp))))
- (setq unread-mp mp mp nil)
- (setq mp (cdr mp))))))
- unread-mp))
-
- ;; returns non-nil if there were any new messages
- (defun vm-assimilate-new-messages ()
- (let ((tail-cons (vm-last vm-message-list))
- (new-messages-p (null vm-message-pointer)))
- (vm-save-restriction
- (widen)
- (vm-build-message-list)
- (vm-read-attributes)
- (setq new-messages-p (or new-messages-p (cdr tail-cons)))
- (if (and vm-current-grouping new-messages-p)
- (condition-case data
- (vm-group-messages vm-current-grouping)
- ;; presumably an unsupported grouping
- (error (message (car (cdr data)))
- (sleep-for 2)
- (vm-number-messages)))
- (vm-number-messages)))
- new-messages-p ))
-
- (defun vm-thoughtfully-select-message ()
- ;; This is called after new messages have been assimilated in a folder.
- ;; We move to a new message only if the user is not "reading" the current
- ;; message, or if there is no current message.
- ;;
- ;; Most of the complications in the `if' test below are due to the presence
- ;; of the variables vm-preview-lines and vm-preview-read-messages.
- ;; These can cause previewing never to be done, or not be done for
- ;; specific messages. In these cases VM assumes a user is "reading"
- ;; an exposed message if the top of the message is not visible in the
- ;; folder buffer window.
- (if (or (null vm-message-pointer)
- (not (eq vm-system-state 'reading))
- (and (or (null vm-preview-lines)
- (and (not vm-preview-read-messages)
- (not (vm-new-flag (car vm-message-pointer)))
- (not (vm-unread-flag (car vm-message-pointer)))))
- (let ((w (get-buffer-window (current-buffer))))
- (and w (pos-visible-in-window-p (point-min) w)))))
- (let ((mp (vm-find-first-unread-message)))
- (if mp
- (progn
- (if vm-message-pointer
- (setq vm-last-message-pointer vm-message-pointer
- vm-message-pointer mp)
- (setq vm-message-pointer mp))
- (vm-preview-current-message))
- (if (null vm-message-pointer)
- (vm-Next-message))))))
-
- (defun vm-display-startup-message ()
- (if (sit-for 5)
- (let ((lines
- '(
- "You may give out copies of VM. Type \\[vm-show-copying-restrictions] to see the conditions"
- "VM comes with ABSOLUTELY NO WARRANTY; type \\[vm-show-no-warranty] for full details"
- )))
- (message "VM %s, Copyright (C) 1989 Kyle E. Jones; type ? for help"
- vm-version)
- (while (and (sit-for 4) lines)
- (message (substitute-command-keys (car lines)))
- (setq lines (cdr lines)))))
- (message ""))
-
- (defun vm (&optional folder)
- "Read mail under Emacs.
- Optional first arg FOLDER specifies the folder to visit. It defaults
- to the value of vm-primary-inbox. The folder buffer is put into VM
- mode, a major mode for reading mail.
-
- Visiting the primary inbox causes any contents of the system mailbox to
- be moved and appended to the resulting buffer.
-
- All the messages can be read by repeatedly pressing SPC. Messages are
- marked for deletion with `d', and saved to a folder with `s'. Quitting
- VM with `q' expunges messages marked for deletion and saves the buffered
- folder to disk.
-
- See the documentation for vm-mode for more information."
- (interactive)
- (if vm-session-beginning
- (progn
- (random t)
- (load "vm-undo")
- (load "vm-summary")))
- (if vm-mail-buffer
- (set-buffer vm-mail-buffer))
- ;; set inhibit-local-varaibles non-nil to protect
- ;; against letter bombs.
- (let ((inhibit-local-variables t))
- (setq mail-buffer (find-file-noselect
- (or folder (expand-file-name vm-primary-inbox))))
- (set-buffer mail-buffer)
- (or (buffer-modified-p) (setq vm-messages-not-on-disk 0))
- (let ((first-time (not (eq major-mode 'vm-mode)))
- (inhibit-quit t))
- (if first-time
- (progn
- (buffer-flush-undo (current-buffer))
- (abbrev-mode 0)
- (auto-fill-mode 0)
- (vm-mode)))
- (if (or (and vm-primary-inbox-p (vm-get-spooled-mail)) first-time)
- (progn
- (vm-assimilate-new-messages)
- ;; Can't allow a folder-empty error here because execution
- ;; would abort before the session startup code below.
- (if (null vm-message-list)
- (message "Folder is empty.")
- (vm-emit-totals-blurb)
- ;; If there's a current grouping, then the summary has already
- ;; been redone in vm-group-messages.
- (if (and vm-summary-buffer (not vm-current-grouping))
- (progn
- (vm-do-summary)
- ;; The summary update messages erased this info
- ;; from the echo area.
- (vm-emit-totals-blurb)))
- (save-window-excursion
- ;; Make sure the mail buffer is not visible. This is
- ;; needed to insure that if vm-preview-lines is nil, the
- ;; mail window won't be momentarily displayed and then
- ;; disappear behind the summary window, if
- ;; vm-startup-with-summary is t.
- (if (get-buffer-window mail-buffer)
- (if (one-window-p)
- (switch-to-buffer (other-buffer))
- (delete-windows-on mail-buffer)))
- (set-buffer mail-buffer)
- (vm-thoughtfully-select-message))
- (if vm-summary-buffer
- (vm-set-summary-pointer (car vm-message-pointer))))))
- (switch-to-buffer mail-buffer)
- (if (and vm-message-list vm-startup-with-summary)
- (progn
- (vm-summarize t)
- (vm-emit-totals-blurb)
- (and (eq vm-startup-with-summary t)
- (eq vm-mutable-windows t)
- (if (eq major-mode 'vm-summary-mode)
- (delete-other-windows)
- (select-window (get-buffer-window vm-summary-buffer))
- (delete-other-windows))))
- (if (eq vm-mutable-windows t)
- (delete-other-windows)))
- (if vm-session-beginning
- (progn
- (setq vm-session-beginning nil)
- (or vm-inhibit-startup-message folder
- (vm-display-startup-message))
- (if (and vm-message-list (not (input-pending-p)))
- (vm-emit-totals-blurb)))))))
-
- (defun vm-mode ()
- "Major mode for reading mail.
-
- Commands:
- h - summarize folder contents
-
- n - go to next message
- p - go to previous message
- N - like `n' but ignores skip-variable settings
- P - like `p' but ignores skip-variable settings
- M-n - go to next unread message
- M-p - go to previous unread message
- RET - go to numbered message (uses prefix arg or prompts in minibuffer)
- TAB - go to last message seen
- M-s - incremental search through the folder
-
- t - display hidden headers
- SPC - scroll forward a page (if at end of message, then display next message)
- b - scroll backward a page
- > - go to end of current message
-
- d - delete current message (mark as deleted)
- u - undelete
- k - mark for deletion all messages with same subject as the current message
-
- r - reply (only to the sender of the message)
- R - reply with included text for current message
- f - followup (reply to all recipients of message)
- F - followup with included text from the current message
- z - forward the current message
- m - send a message
-
- @ - digestify and mail entire folder contents (the folder is not modified)
- * - burst a digest into indivdual messages, and append and assimilate these
- message into the current folder.
-
- G - group messages according to some criteria
-
- g - get any new mail that has arrived in the system mailbox
- (new mail is appended to the disk and buffer copies of the
- primary inbox.)
- v - visit another mail folder
-
- s - save current message in a folder (appends if folder already exists)
- w - write current message to a file without its headers (appends if exists)
- S - save entire folder to disk, expunging deleted messages
- A - save unfiled messages to their vm-auto-folder-alist specified folders
- # - expunge deleted messages (without saving folder)
- q - quit VM, deleted messages are expunged, folder saved to disk
- x - exit VM with no change to the folder
-
- C-_ - undo, special undo that retracts the most recent
- changes in message attributes. Expunges and saves
- cannot be undone.
-
- ? - help
-
- ! - run a shell command
- | - run a shell command with the current message as input
-
- M-c - view conditions under which youmay redistribute of VM
- M-w - view the details of VM's lack of a warranty
-
- Variables:
- vm-auto-folder-alist
- vm-berkeley-mail-compatibility
- vm-circular-folders
- vm-confirm-new-folders
- vm-crash-box
- vm-delete-after-saving
- vm-delete-empty-folders
- vm-folder-directory
- vm-folder-type
- vm-follow-summary-cursor
- vm-forwarding-subject-format
- vm-gargle-uucp
- vm-group-by
- vm-highlighted-header-regexp
- vm-in-reply-to-format
- vm-included-text-attribution-format
- vm-included-text-prefix
- vm-inhibit-startup-message
- vm-mail-window-percentage
- vm-mode-hooks
- vm-move-after-deleting
- vm-mutable-windows
- vm-preview-lines
- vm-preview-read-messages
- vm-primary-inbox
- vm-rfc934-forwarding
- vm-search-using-regexps
- vm-skip-deleted-messages
- vm-skip-read-messages
- vm-spool-files
- vm-startup-with-summary
- vm-strip-reply-headers
- vm-summary-format
- vm-visible-headers
- vm-visit-when-saving"
- (widen)
- (make-local-variable 'require-final-newline)
- (make-local-variable 'file-precious-flag)
- (setq
- buffer-read-only nil
- case-fold-search t
- file-precious-flag t
- major-mode 'vm-mode
- mode-line-format
- '("" mode-line-modified mode-line-buffer-identification " "
- global-mode-string
- (vm-message-list
- (" %[(" vm-ml-attributes-string ")%]----")
- (" %[()%]----"))
- (-3 . "%p") "-%-")
- mode-line-buffer-identification
- '("VM " vm-version ": %b"
- (vm-message-list
- (" " vm-ml-message-number
- " (of " vm-ml-highest-message-number ")")
- " (no messages)"))
- mode-name "VM"
- require-final-newline nil
- vm-current-grouping vm-group-by
- vm-primary-inbox-p (equal buffer-file-name
- (expand-file-name vm-primary-inbox)))
- (use-local-map vm-mode-map)
- (run-hooks 'vm-mode-hooks))
-
- (put 'vm-mode 'mode-class 'special)
-
- (autoload 'vm-group-messages "vm-group" nil t)
-
- (autoload 'vm-reply "vm-reply" nil t)
- (autoload 'vm-reply-include-text "vm-reply" nil t)
- (autoload 'vm-followup "vm-reply" nil t)
- (autoload 'vm-followup-include-text "vm-reply" nil t)
- (autoload 'vm-mail "vm-reply" nil t)
- (autoload 'vm-forward-message "vm-reply" nil t)
- (autoload 'vm-send-digest "vm-reply" nil t)
-
- (autoload 'vm-isearch-forward "vm-search" nil t)
-
- (autoload 'vm-burst-digest "vm-digest" nil t)
- (autoload 'vm-rfc934-char-stuff-region "vm-digest")
- (autoload 'vm-digestify-region "vm-digest")
-
- (autoload 'vm-show-no-warranty "vm-license" nil t)
- (autoload 'vm-show-copying-restrictions "vm-license" nil t)
-
- (autoload 'vm-auto-archive-messages "vm-save" nil t)
- (autoload 'vm-save-message "vm-save" nil t)
- (autoload 'vm-save-message-sans-headers "vm-save" nil t)
- (autoload 'vm-pipe-message-to-command "vm-save" nil t)
-
- (autoload 'vm-delete-message "vm-delete" nil t)
- (autoload 'vm-undelete-message "vm-delete" nil t)
- (autoload 'vm-kill-subject "vm-delete" nil t)
- (autoload 'vm-expunge-folder "vm-delete" nil t)
-
- (if (not (memq 'vm-write-file-hook write-file-hooks))
- (setq write-file-hooks
- (cons 'vm-write-file-hook write-file-hooks)))
-