Mercurial > hg > xemacs-beta
diff lisp/vm/vm-folder.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | ac2d302a0011 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/vm/vm-folder.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,2889 @@ +;;; VM folder related functions +;;; Copyright (C) 1989, 1990, 1991, 1993, 1994, 1995, 1996 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. + +(provide 'vm-folder) + +(defun vm-number-messages (&optional start-point end-point) + "Set the number-of and padded-number-of slots of messages +in vm-message-list. + +If non-nil, START-POINT should point to a cons cell in +vm-message-list and the numbering will begin there, else the +numbering will begin at the head of vm-message-list. If +START-POINT is non-nil the reverse-link-of slot of the message in +the cons must be valid and the message pointed to (if any) must +have a non-nil number-of slot, because it is used to determine +what the starting message number should be. + +If non-nil, END-POINT should point to a cons cell in +vm-message-list and the numbering will end with the message just +before this cell. A nil value means numbering will be done until +the end of vm-message-list is reached." + (let ((n 1) (message-list (or start-point vm-message-list))) + (if (and start-point (vm-reverse-link-of (car start-point))) + (setq n (1+ (string-to-int + (vm-number-of + (car + (vm-reverse-link-of + (car start-point)))))))) + (while (not (eq message-list end-point)) + (vm-set-number-of (car message-list) (int-to-string n)) + (vm-set-padded-number-of (car message-list) (format "%3d" n)) + (setq n (1+ n) message-list (cdr message-list))) + (or end-point (setq vm-ml-highest-message-number (int-to-string (1- n)))) + (if vm-summary-buffer + (vm-copy-local-variables vm-summary-buffer + 'vm-ml-highest-message-number)))) + +(defun vm-set-numbering-redo-start-point (start-point) + "Set vm-numbering-redo-start-point to START-POINT if appropriate. +Also mark the current buffer as needing a display update. + +START-POINT should be a cons in vm-message-list or just t. + (t means start from the beginning of vm-message-list.) +If START-POINT is closer to the head of vm-message-list than +vm-numbering-redo-start-point or is equal to t, then +vm-numbering-redo-start-point is set to match it." + (intern (buffer-name) vm-buffers-needing-display-update) + (if (and (consp start-point) (consp vm-numbering-redo-start-point) + (not (eq vm-numbering-redo-start-point t))) + (let ((mp vm-message-list)) + (while (and mp (not (or (eq mp start-point) + (eq mp vm-numbering-redo-start-point)))) + (setq mp (cdr mp))) + (if (null mp) + (error "Something is wrong in vm-set-numbering-redo-start-point")) + (if (eq mp start-point) + (setq vm-numbering-redo-start-point start-point))) + (setq vm-numbering-redo-start-point start-point))) + +(defun vm-set-numbering-redo-end-point (end-point) + "Set vm-numbering-redo-end-point to END-POINT if appropriate. +Also mark the current buffer as needing a display update. + +END-POINT should be a cons in vm-message-list or just t. + (t means number all the way to the end of vm-message-list.) +If END-POINT is closer to the end of vm-message-list or is equal +to t, then vm-numbering-redo-start-point is set to match it. +The number-of slot is used to determine proximity to the end of +vm-message-list, so this slot must be valid in END-POINT's message +and the message in the cons pointed to by vm-numbering-redo-end-point." + (intern (buffer-name) vm-buffers-needing-display-update) + (cond ((eq end-point t) + (setq vm-numbering-redo-end-point t)) + ((and (consp end-point) + (> (string-to-int + (vm-number-of + (car end-point))) + (string-to-int + (vm-number-of + (car vm-numbering-redo-end-point))))) + (setq vm-numbering-redo-end-point end-point)) + ((null end-point) + (setq vm-numbering-redo-end-point end-point)))) + +(defun vm-do-needed-renumbering () + "Number messages in vm-message-list as specified by +vm-numbering-redo-start-point and vm-numbering-redo-end-point. + +vm-numbering-redo-start-point = t means start at the head +of vm-message-list. +vm-numbering-redo-end-point = t means number all the way to the +end of vm-message-list. + +Otherwise the variables' values should be conses in vm-message-list +or nil." + (if vm-numbering-redo-start-point + (progn + (vm-number-messages (and (consp vm-numbering-redo-start-point) + vm-numbering-redo-start-point) + vm-numbering-redo-end-point) + (setq vm-numbering-redo-start-point nil + vm-numbering-redo-end-point nil)))) + +(defun vm-set-summary-redo-start-point (start-point) + "Set vm-summary-redo-start-point to START-POINT if appropriate. +Also mark the current buffer as needing a display update. + +START-POINT should be a cons in vm-message-list or just t. + (t means start from the beginning of vm-message-list.) +If START-POINT is closer to the head of vm-message-list than +vm-numbering-redo-start-point or is equal to t, then +vm-numbering-redo-start-point is set to match it." + (intern (buffer-name) vm-buffers-needing-display-update) + (if (and (consp start-point) (consp vm-summary-redo-start-point) + (not (eq vm-summary-redo-start-point t))) + (let ((mp vm-message-list)) + (while (and mp (not (or (eq mp start-point) + (eq mp vm-summary-redo-start-point)))) + (setq mp (cdr mp))) + (if (null mp) + (error "Something is wrong in vm-set-summary-redo-start-point")) + (if (eq mp start-point) + (setq vm-summary-redo-start-point start-point))) + (setq vm-summary-redo-start-point start-point))) + +(defun vm-mark-for-summary-update (m &optional dont-kill-cache) + "Mark message M for a summary update. +Also mark M's buffer as needing a display update. Any virtual +messages of M and their buffers are similarly marked for update. +If M is a virtual message and virtual mirroring is in effect for +M (i.e. attribute-of eq attributes-of M's real message), M's real +message and its buffer are scheduled for an update. + +Optional arg DONT-KILL-CACHE non-nil means don't invalidate the +summary-of slot for any messages marked for update. This is +meant to be used by functions that update message information +that is not cached in the summary-of slot, e.g. message numbers +and thread indentation." + (cond ((eq m (vm-real-message-of m)) + ;; this is a real message. + ;; its summary and modeline need to be updated. + (if (not dont-kill-cache) + ;; toss the cache. this also tosses the cache of any + ;; virtual messages mirroring this message. the summary + ;; entry cache must be cleared when an attribute of a + ;; message that could appear in the summary has changed. + (vm-set-summary-of m nil)) + (if (vm-su-start-of m) + (setq vm-messages-needing-summary-update + (cons m vm-messages-needing-summary-update))) + (intern (buffer-name (vm-buffer-of m)) + vm-buffers-needing-display-update) + ;; find the virtual messages of this real message that + ;; need a summary update. + (let ((m-list (vm-virtual-messages-of m))) + (while m-list + (if (eq (vm-attributes-of m) (vm-attributes-of (car m-list))) + (progn + (and (vm-su-start-of (car m-list)) + (setq vm-messages-needing-summary-update + (cons (car m-list) + vm-messages-needing-summary-update))) + (intern (buffer-name (vm-buffer-of (car m-list))) + vm-buffers-needing-display-update))) + (setq m-list (cdr m-list))))) + (t + ;; this is a virtual message. + ;; + ;; if this message has virtual messages then we need to + ;; schedule updates for all the virtual messages that + ;; share a cache with this message and we need to + ;; schedule an update for the underlying real message + ;; since we are mirroring it. + ;; + ;; if there are no virtual messages, then this virtual + ;; message is not mirroring its real message so we need + ;; only take care of this one message. + (if (vm-virtual-messages-of m) + (let ((m-list (vm-virtual-messages-of m))) + ;; schedule updates for all the virtual message who share + ;; the same cache as this message. + (while m-list + (if (eq (vm-attributes-of m) (vm-attributes-of (car m-list))) + (progn + (and (vm-su-start-of (car m-list)) + (setq vm-messages-needing-summary-update + (cons (car m-list) + vm-messages-needing-summary-update))) + (intern (buffer-name (vm-buffer-of (car m-list))) + vm-buffers-needing-display-update))) + (setq m-list (cdr m-list))) + ;; now take care of the real message + (if (not dont-kill-cache) + ;; toss the cache. this also tosses the cache of + ;; any virtual messages sharing the same cache as + ;; this message. + (vm-set-summary-of m nil)) + (and (vm-su-start-of (vm-real-message-of m)) + (setq vm-messages-needing-summary-update + (cons (vm-real-message-of m) + vm-messages-needing-summary-update))) + (intern (buffer-name (vm-buffer-of (vm-real-message-of m))) + vm-buffers-needing-display-update)) + (if (not dont-kill-cache) + (vm-set-virtual-summary-of m nil)) + (and (vm-su-start-of m) + (setq vm-messages-needing-summary-update + (cons m vm-messages-needing-summary-update))) + (intern (buffer-name (vm-buffer-of m)) + vm-buffers-needing-display-update))))) + +(defun vm-force-mode-line-update () + "Force a mode line update in all frames." + (if (fboundp 'force-mode-line-update) + (force-mode-line-update t) + (save-excursion + (set-buffer (other-buffer)) + (set-buffer-modified-p (buffer-modified-p))))) + +(defun vm-do-needed-mode-line-update () + "Do a modeline update for the current folder buffer. +This means setting up all the various vm-ml attribute variables +in the folder buffer and copying necessary variables to the +folder buffer's summary buffer, and then forcing Emacs to update +all modelines. + +Also if a virtual folder being updated has no messages, +erase-buffer is called on its buffer." + ;; XXX This last bit should probably should be moved to + ;; XXX vm-expunge-folder. + + (if (null vm-message-pointer) + ;; erase the leftover message if the folder is really empty. + (if (eq major-mode 'vm-virtual-mode) + (let ((buffer-read-only nil) + (omodified (buffer-modified-p))) + (unwind-protect + (erase-buffer) + (set-buffer-modified-p omodified)))) + ;; try to avoid calling vm-su-labels if possible so as to + ;; avoid loading vm-summary.el. + (if (vm-labels-of (car vm-message-pointer)) + (setq vm-ml-labels (vm-su-labels (car vm-message-pointer))) + (setq vm-ml-labels nil)) + (setq vm-ml-message-number (vm-number-of (car vm-message-pointer))) + (setq vm-ml-message-new (vm-new-flag (car vm-message-pointer))) + (setq vm-ml-message-unread (vm-unread-flag (car vm-message-pointer))) + (setq vm-ml-message-read + (and (not (vm-new-flag (car vm-message-pointer))) + (not (vm-unread-flag (car vm-message-pointer))))) + (setq vm-ml-message-edited (vm-edited-flag (car vm-message-pointer))) + (setq vm-ml-message-filed (vm-filed-flag (car vm-message-pointer))) + (setq vm-ml-message-written (vm-written-flag (car vm-message-pointer))) + (setq vm-ml-message-replied (vm-replied-flag (car vm-message-pointer))) + (setq vm-ml-message-forwarded (vm-forwarded-flag (car vm-message-pointer))) + (setq vm-ml-message-redistributed (vm-redistributed-flag (car vm-message-pointer))) + (setq vm-ml-message-deleted (vm-deleted-flag (car vm-message-pointer))) + (setq vm-ml-message-marked (vm-mark-of (car vm-message-pointer)))) + (if vm-summary-buffer + (let ((modified (buffer-modified-p))) + (save-excursion + (vm-copy-local-variables vm-summary-buffer + 'vm-ml-message-new + 'vm-ml-message-unread + 'vm-ml-message-read + 'vm-ml-message-edited + 'vm-ml-message-replied + 'vm-ml-message-forwarded + 'vm-ml-message-filed + 'vm-ml-message-written + 'vm-ml-message-deleted + 'vm-ml-message-marked + 'vm-ml-message-number + 'vm-ml-highest-message-number + 'vm-folder-read-only + 'vm-folder-type + 'vm-virtual-folder-definition + 'vm-virtual-mirror + 'vm-ml-sort-keys + 'vm-ml-labels + 'vm-message-list) + (set-buffer vm-summary-buffer) + (set-buffer-modified-p modified)))) + (vm-force-mode-line-update)) + +(defun vm-update-summary-and-mode-line () + "Update summary and mode line for all VM folder and summary buffers. +Really this updates all the visible status indicators. + +Message lists are renumbered. +Summary entries are wiped and regenerated. +Mode lines are updated. +Toolbars are updated." + (save-excursion + (mapatoms (function + (lambda (b) + (setq b (get-buffer (symbol-name b))) + (if b + (progn + (set-buffer b) + (vm-check-for-killed-summary) + (and vm-use-toolbar + (vm-toolbar-support-possible-p) + (vm-toolbar-update-toolbar)) + (vm-do-needed-renumbering) + (if vm-summary-buffer + (vm-do-needed-summary-rebuild)) + (vm-do-needed-mode-line-update))))) + vm-buffers-needing-display-update) + (fillarray vm-buffers-needing-display-update 0)) + (if vm-messages-needing-summary-update + (progn + (mapcar (function vm-update-message-summary) + vm-messages-needing-summary-update) + (setq vm-messages-needing-summary-update nil))) + (vm-force-mode-line-update)) + +(defun vm-reverse-link-messages () + "Set reverse links for all messages in vm-message-list." + (let ((mp vm-message-list) + (prev nil)) + (while mp + (vm-set-reverse-link-of (car mp) prev) + (setq prev mp mp (cdr mp))))) + +(defun vm-match-ordered-header (alist) + "Try to match a header in ALIST and return the matching cell. +This is used by header ordering code. + +ALIST looks like this ((\"From\") (\"To\")). This function returns +the alist element whose car matches the header starting at point. +The header ordering code uses the cdr of the element +returned to hold headers to be output later." + (let ((case-fold-search t)) + (catch 'match + (while alist + (if (looking-at (car (car alist))) + (throw 'match (car alist))) + (setq alist (cdr alist))) + nil))) + +(defun vm-match-header (&optional header-name) + "Match a header and save some state information about the matched header. +Optional first arg HEADER-NAME means match the header only +if it matches HEADER-NAME. HEADER-NAME should be a string +containing a header name. The string should end with a colon if just +that name should be matched. A string that does not end in a colon +will match all headers that begin with that string. + +State information is stored in vm-matched-header-vector bound to a vector +of this form. + + [ header-start header-end + header-name-start header-name-end + header-contents-start header-contents-end ] + +Elements are integers. +There are functions to access and use this info." + (let ((case-fold-search t) + (header-name-regexp "\\([^ \t\n:]+\\):")) + (if (if header-name + (and (looking-at header-name) (looking-at header-name-regexp)) + (looking-at header-name-regexp)) + (save-excursion + (aset vm-matched-header-vector 0 (point)) + (aset vm-matched-header-vector 2 (point)) + (aset vm-matched-header-vector 3 (match-end 1)) + (goto-char (match-end 0)) + ;; skip leading whitespace + (skip-chars-forward " \t") + (aset vm-matched-header-vector 4 (point)) + (forward-line 1) + (while (looking-at "[ \t]") + (forward-line 1)) + (aset vm-matched-header-vector 1 (point)) + ;; drop the trailing newline + (aset vm-matched-header-vector 5 (1- (point))))))) + +(defun vm-matched-header () + "Returns the header last matched by vm-match-header. +Trailing newline is included." + (vm-buffer-substring-no-properties (aref vm-matched-header-vector 0) + (aref vm-matched-header-vector 1))) + +(defun vm-matched-header-name () + "Returns the name of the header last matched by vm-match-header." + (vm-buffer-substring-no-properties (aref vm-matched-header-vector 2) + (aref vm-matched-header-vector 3))) + +(defun vm-matched-header-contents () + "Returns the contents of the header last matched by vm-match-header. +Trailing newline is not included." + (vm-buffer-substring-no-properties (aref vm-matched-header-vector 4) + (aref vm-matched-header-vector 5))) + +(defun vm-matched-header-start () + "Returns the start position of the header last matched by vm-match-header." + (aref vm-matched-header-vector 0)) + +(defun vm-matched-header-end () + "Returns the end position of the header last matched by vm-match-header." + (aref vm-matched-header-vector 1)) + +(defun vm-matched-header-name-start () + "Returns the start position of the name of the header last matched +by vm-match-header." + (aref vm-matched-header-vector 2)) + +(defun vm-matched-header-name-end () + "Returns the end position of the name of the header last matched +by vm-match-header." + (aref vm-matched-header-vector 3)) + +(defun vm-matched-header-contents-start () + "Returns the start position of the contents of the header last matched +by vm-match-header." + (aref vm-matched-header-vector 4)) + +(defun vm-matched-header-contents-end () + "Returns the end position of the contents of the header last matched +by vm-match-header." + (aref vm-matched-header-vector 5)) + +(defun vm-get-folder-type (&optional file start end) + "Return a symbol indicating the folder type of the current buffer. +This function works by examining the beginning of a folder. +If optional arg FILE is present the type of FILE is returned instead. +If optional second and third arg START and END are provided, +vm-get-folder-type will examine the the text between those buffer +positions. START and END default to 1 and (buffer-size) + 1. + +Returns + nil if folder has no type (empty) + unknown if the type is not known to VM + mmdf for MMDF folders + babyl for BABYL folders + From_ for UNIX From_ folders + +If vm-trust-From_-with-Content-Length is non-nil, +From_-with-Content-Length is returned if the first message in the +folder has a Content-Length header and the folder otherwise looks +like a From_ folder." + (let ((temp-buffer nil) + b + (case-fold-search nil)) + (unwind-protect + (save-excursion + (if file + (progn + (setq b (vm-get-file-buffer file)) + (if b + (set-buffer b) + (setq temp-buffer (generate-new-buffer "*vm-work*")) + (set-buffer temp-buffer) + (if (file-readable-p file) + (condition-case nil + (insert-file-contents file nil 0 4096) + (wrong-number-of-arguments + (call-process "sed" file temp-buffer nil + "-n" "1,/^$/p"))))))) + (save-excursion + (save-restriction + (or start (setq start 1)) + (or end (setq end (1+ (buffer-size)))) + (widen) + (narrow-to-region start end) + (goto-char (point-min)) + (cond ((zerop (buffer-size)) nil) + ((looking-at "\n*From ") + (if (not vm-trust-From_-with-Content-Length) + 'From_ + (let ((case-fold-search t)) + (re-search-forward vm-content-length-search-regexp + nil t)) + (cond ((match-beginning 1) + 'From_) + ((match-beginning 0) + 'From_-with-Content-Length) + (t 'From_)))) + ((looking-at "\001\001\001\001\n") 'mmdf) + ((looking-at "BABYL OPTIONS:") 'babyl) + (t 'unknown))))) + (and temp-buffer (kill-buffer temp-buffer))))) + +(defun vm-convert-folder-type (old-type new-type) + "Convert buffer from OLD-TYPE to NEW-TYPE. +OLD-TYPE and NEW-TYPE should be symbols returned from vm-get-folder-type. +This should be called on non-live buffers like crash boxes. +This will confuse VM if called on a folder buffer in vm-mode." + (let ((vm-folder-type old-type) + (pos-list nil) + beg end) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (while (vm-find-leading-message-separator) + (setq pos-list (cons (point-marker) pos-list)) + (vm-skip-past-leading-message-separator) + (setq pos-list (cons (point-marker) pos-list)) + (vm-find-trailing-message-separator) + (setq pos-list (cons (point-marker) pos-list)) + (vm-skip-past-trailing-message-separator) + (setq pos-list (cons (point-marker) pos-list))) + (setq pos-list (nreverse pos-list)) + (goto-char (point-min)) + (vm-convert-folder-header old-type new-type) + (while pos-list + (setq beg (car pos-list)) + (goto-char (car pos-list)) + (insert-before-markers (vm-leading-message-separator new-type)) + (delete-region (car pos-list) (car (cdr pos-list))) + (vm-convert-folder-type-headers old-type new-type) + (setq pos-list (cdr (cdr pos-list))) + (setq end (marker-position (car pos-list))) + (goto-char (car pos-list)) + (insert-before-markers (vm-trailing-message-separator new-type)) + (delete-region (car pos-list) (car (cdr pos-list))) + (goto-char beg) + (vm-munge-message-separators new-type beg end) + (setq pos-list (cdr (cdr pos-list)))))) + +(defun vm-convert-folder-header (old-type new-type) + "Convert the folder header form OLD-TYPE to NEW-TYPE. +The folder header is the text at the beginning of a folder that +is a legal part of the folder but is not part of the first +message. This is for dealing with BABYL files." + (if (eq old-type 'babyl) + (save-excursion + (let ((beg (point)) + (case-fold-search t)) + (cond ((and (looking-at "BABYL OPTIONS:") + (search-forward "\037" nil t)) + (delete-region beg (point))))))) + (if (eq new-type 'babyl) + ;; insert before markers so that message location markers + ;; for the first message get moved forward. + (insert-before-markers "BABYL OPTIONS:\nVersion: 5\n\037"))) + +(defun vm-skip-past-folder-header () + "Move point past the folder header. +The folder header is the text at the beginning of a folder that +is a legal part of the folder but is not part of the first +message. This is for dealing with BABYL files." + (cond ((eq vm-folder-type 'babyl) + (search-forward "\037" nil 0)))) + +(defun vm-convert-folder-type-headers (old-type new-type) + "Convert headers in the message around point from OLD-TYPE to NEW-TYPE. +This means to add/delete Content-Length and any other +headers related to folder-type as needed for folder type +conversions. This function expects point to be at the beginning +of the header section of a message, and it only deals with that +message." + (let (length) + ;; get the length now before the content-length headers are + ;; removed. + (if (eq new-type 'From_-with-Content-Length) + (let (start) + (save-excursion + (save-excursion + (search-forward "\n\n" nil 0) + (setq start (point))) + (let ((vm-folder-type old-type)) + (vm-find-trailing-message-separator)) + (setq length (- (point) start))))) + ;; chop out content-length header if new format doesn't need + ;; it or if the new format computed his own copy. + (if (or (eq old-type 'From_-with-Content-Length) + (eq new-type 'From_-with-Content-Length)) + (save-excursion + (while (and (let ((case-fold-search t)) + (re-search-forward vm-content-length-search-regexp + nil t)) + (null (match-beginning 1)) + (progn (goto-char (match-beginning 0)) + (vm-match-header vm-content-length-header))) + (delete-region (vm-matched-header-start) + (vm-matched-header-end))))) + ;; insert the content-length header if needed + (if (eq new-type 'From_-with-Content-Length) + (save-excursion + (insert vm-content-length-header " " (int-to-string length) "\n"))))) + +(defun vm-munge-message-separators (folder-type start end) + "Munge message separators of FOLDER-TYPE found between START and END. +This function is used to eliminate message separators for a particular +folder type that happen to occur in a message. \">\" is prepended to such +separators." + (save-excursion + (let ((vm-folder-type folder-type)) + (cond ((memq folder-type '(From_ From_-with-Content-Length mmdf babyl)) + (setq end (vm-marker end)) + (goto-char start) + (while (and (vm-find-leading-message-separator) + (< (point) end)) + (insert ">")) + (set-marker end nil)))))) + +(defun vm-compatible-folder-p (file) + "Return non-nil if FILE is a compatible folder with the current buffer. +The current folder must have vm-folder-type initialized. +FILE is compatible if + - it is empty + - the current folder is empty + - the two folder types are equal" + (let ((type (vm-get-folder-type file))) + (or (not (and vm-folder-type type)) + (eq vm-folder-type type)))) + +(defun vm-leading-message-separator (&optional folder-type message + for-other-folder) + "Returns a leading message separator for the current folder. +Defaults to returning a separator for the current folder type. + +Optional first arg FOLDER-TYPE means return a separator for that +folder type instead. + +Optional second arg MESSAGE should be a message struct. This is used +generating BABYL separators, because they contain message attributes +and labels that must must be copied from the message. + +Optional third arg FOR-OTHER-FOLDER non-nil means that this separator will +be used a `foreign' folder. This means that the `deleted' +attributes should not be copied for BABYL folders." + (let ((type (or folder-type vm-folder-type))) + (cond ((memq type '(From_ From_-with-Content-Length)) + (concat "From VM " (current-time-string) "\n")) + ((eq type 'mmdf) + "\001\001\001\001\n") + ((eq type 'babyl) + (cond (message + (concat "\014\n0," + (vm-babyl-attributes-string message for-other-folder) + ",\n*** EOOH ***\n")) + (t "\014\n0, recent, unseen,,\n*** EOOH ***\n")))))) + +(defun vm-trailing-message-separator (&optional folder-type) + "Returns a leading message separator for the current folder. +Defaults to returning a separator for the current folder type. + +Optional first arg FOLDER-TYPE means return a separator for that +folder type instead." + (let ((type (or folder-type vm-folder-type))) + (cond ((eq type 'From_) "\n") + ((eq type 'From_-with-Content-Length) "") + ((eq type 'mmdf) "\001\001\001\001\n") + ((eq type 'babyl) "\037")))) + +(defun vm-folder-header (&optional folder-type label-obarray) + "Returns a folder header for the current folder. +Defaults to returning a folder header for the current folder type. + +Optional first arg FOLDER-TYPE means return a folder header for that +folder type instead. + +Optional second arg LABEL-OBARRAY should be an obarray of labels +that have been used in this folder. This is used for BABYL folders." + (let ((type (or folder-type vm-folder-type))) + (cond ((eq type 'babyl) + (let ((list nil)) + (if label-obarray + (mapatoms (function + (lambda (sym) + (setq list (cons sym list)))) + label-obarray)) + (if list + (format "BABYL OPTIONS:\nVersion: 5\nLabels: %s\n\037" + (mapconcat (function symbol-name) list ", ")) + "BABYL OPTIONS:\nVersion: 5\n\037"))) + (t "")))) + +(defun vm-find-leading-message-separator () + "Find the next leading message separator in a folder. +Returns non-nil if the separator is found, nil otherwise." + (cond + ((eq vm-folder-type 'From_) + (let ((reg1 "^From ") + (reg2 "^>From ") + (case-fold-search nil)) + (catch 'done + (while (re-search-forward reg1 nil 'no-error) + (goto-char (match-beginning 0)) + ;; remove the requirement that there be two + ;; consecutive newlines (or the beginning of the + ;; buffer) before "From ". Hopefully this will not + ;; break more than it fixes. (18 August 1995) + (if ;; (and (or (< (point) 3) + ;; (equal (char-after (- (point) 2)) ?\n)) + (save-excursion + (and (= 0 (forward-line 1)) + (or (vm-match-header) + (looking-at reg2)))) + ;; ) + (throw 'done t) + (forward-char 1))) + nil ))) + ((eq vm-folder-type 'From_-with-Content-Length) + (let ((reg1 "\\(^\\|\n+\\)From ") + (case-fold-search nil)) + (if (re-search-forward reg1 nil 'no-error) + (progn (goto-char (match-end 1)) t) + nil ))) + ((eq vm-folder-type 'mmdf) + (let ((reg1 "^\001\001\001\001") + (case-fold-search nil)) + (if (re-search-forward reg1 nil 'no-error) + (progn + (goto-char (match-beginning 0)) + t ) + nil ))) + ((eq vm-folder-type 'babyl) + (let ((reg1 "\014\n[01],") + (case-fold-search nil)) + (catch 'done + (while (re-search-forward reg1 nil 'no-error) + (goto-char (match-beginning 0)) + (if (and (not (bobp)) (= (preceding-char) ?\037)) + (throw 'done t) + (forward-char 1))) + nil ))))) + +(defun vm-find-trailing-message-separator () + "Find the next trailing message separator in a folder." + (cond + ((eq vm-folder-type 'From_) + (vm-find-leading-message-separator) + (forward-char -1)) + ((eq vm-folder-type 'From_-with-Content-Length) + (let ((reg1 "^From ") + content-length + (start-point (point)) + (case-fold-search nil)) + (if (and (let ((case-fold-search t)) + (re-search-forward vm-content-length-search-regexp nil t)) + (null (match-beginning 1)) + (progn (goto-char (match-beginning 0)) + (vm-match-header vm-content-length-header))) + (progn + (setq content-length + (string-to-int (vm-matched-header-contents))) + ;; if search fails, we'll be at point-max + ;; if specified content-length is too long, go to point-max + (if (search-forward "\n\n" nil 0) + (if (>= (- (point-max) (point)) content-length) + (forward-char content-length) + (goto-char (point-max)))) + ;; Some systems seem to add a trailing newline that's + ;; not counted in the Content-Length header. Allow + ;; any number of them to avoid trouble. + (skip-chars-forward "\n"))) + (if (or (eobp) (looking-at reg1)) + nil + (goto-char start-point) + (if (re-search-forward reg1 nil 0) + (forward-char -5))))) + ((eq vm-folder-type 'mmdf) + (vm-find-leading-message-separator)) + ((eq vm-folder-type 'babyl) + (vm-find-leading-message-separator) + (forward-char -1)))) + +(defun vm-skip-past-leading-message-separator () + "Move point past a leading message separator at point." + (cond + ((memq vm-folder-type '(From_ From_-with-Content-Length)) + (let ((reg1 "^>From ") + (case-fold-search nil)) + (forward-line 1) + (while (looking-at reg1) + (forward-line 1)))) + ((eq vm-folder-type 'mmdf) + (forward-char 5) + ;; skip >From. Either SCO's MMDF implementation leaves this + ;; stuff in the message, or many sysadmins have screwed up + ;; their mail configuration. Either way I'm tired of getting + ;; bug reports about it. + (let ((reg1 "^>From ") + (case-fold-search nil)) + (while (looking-at reg1) + (forward-line 1)))) + ((eq vm-folder-type 'babyl) + (search-forward "\n*** EOOH ***\n" nil 0)))) + +(defun vm-skip-past-trailing-message-separator () + "Move point past a trailing message separator at point." + (cond + ((eq vm-folder-type 'From_) + (forward-char 1)) + ((eq vm-folder-type 'From_-with-Content-Length)) + ((eq vm-folder-type 'mmdf) + (forward-char 5)) + ((eq vm-folder-type 'babyl) + (forward-char 1)))) + +(defun vm-build-message-list () + "Build a chain of message structures, stored them in vm-message-list. +Finds the start and end of each message and fills in the relevant +fields in the message structures. + +Also finds the beginning of the header section and the end of the +text section and fills in these fields in the message structures. + +vm-text-of and vm-vheaders-of field don't get filled until they +are needed. + +If vm-message-list already contained messages, the end of the last +known message is found and then the parsing of new messages begins +there and the message are appended to vm-message-list. + +vm-folder-type is initialized here." + (setq vm-folder-type (vm-get-folder-type)) + (save-excursion + (let ((tail-cons nil) + (n 0) + ;; Just for yucks, make the update interval vary. + (modulus (+ (% (vm-abs (random)) 11) 25)) + message last-end) + (if vm-message-list + ;; there are already messages, therefore we're supposed + ;; to add to this list. + (let ((mp vm-message-list) + (end (point-min))) + ;; first we have to find physical end of the folder + ;; prior to the new messages that just came in. + (while mp + (if (< end (vm-end-of (car mp))) + (setq end (vm-end-of (car mp)))) + (if (not (consp (cdr mp))) + (setq tail-cons mp)) + (setq mp (cdr mp))) + (goto-char end)) + ;; there are no messages so we're building the whole list. + ;; start from the beginning of the folder. + (goto-char (point-min)) + ;; whine about newlines at the beginning of the folder. + ;; technically I think this is corruption, but there are + ;; too many busted mail-do-fcc's installed out there to + ;; do more than whine. + (if (and (memq vm-folder-type '(From_ From_-with-Content-Length)) + (= (following-char) ?\n)) + (progn + (message "Warning: newline found at beginning of folder, %s" + (or buffer-file-name (buffer-name))) + (sleep-for 2))) + (vm-skip-past-folder-header)) + (setq last-end (point)) + ;; parse the messages, set the markers that specify where + ;; things are. + (while (vm-find-leading-message-separator) + (setq message (vm-make-message)) + (vm-set-message-type-of message vm-folder-type) + (vm-set-start-of message (vm-marker (point))) + (vm-skip-past-leading-message-separator) + (vm-set-headers-of message (vm-marker (point))) + (vm-find-trailing-message-separator) + (vm-set-text-end-of message (vm-marker (point))) + (vm-skip-past-trailing-message-separator) + (setq last-end (point)) + (vm-set-end-of message (vm-marker (point))) + (vm-set-reverse-link-of message tail-cons) + (if (null tail-cons) + (setq vm-message-list (list message) + tail-cons vm-message-list) + (setcdr tail-cons (list message)) + (setq tail-cons (cdr tail-cons))) + (vm-increment n) + (if (zerop (% n modulus)) + (vm-unsaved-message "Parsing messages... %d" n))) + (if (>= n modulus) + (vm-unsaved-message "Parsing messages... done")) + (if (and (not (= last-end (point-max))) + (not (eq vm-folder-type 'unknown))) + (progn + (message "Warning: garbage found at end of folder, %s" + (or buffer-file-name (buffer-name))) + (sleep-for 2)))))) + +(defun vm-build-header-order-alist (vheaders) + (let ((order-alist (cons nil nil)) + list) + (setq list order-alist) + (while vheaders + (setcdr list (cons (cons (car vheaders) nil) nil)) + (setq list (cdr list) vheaders (cdr vheaders))) + (cdr order-alist))) + +;; Reorder the headers in a message. +;; +;; If a message struct is passed into this function, then we're +;; operating on a message in a folder buffer. Headers are +;; grouped so that the headers that the user wants to see are at +;; the end of the headers section so we can narrow to them. This +;; is done according to the preferences specified in +;; vm-visible-header and vm-invisible-header-regexp. The +;; vheaders field of the message struct is also set. This +;; function is called on demand whenever a vheaders field is +;; discovered to be nil for a particular message. +;; +;; If the message argument is nil, then we are operating on a +;; freestanding message that is not part of a folder buffer. The +;; keep-list and discard-regexp parameters are used in this case. +;; Headers not matched by the keep list or matched by the discard +;; list are stripped from the message. The remaining headers +;; are ordered according to the order of the keep list. + +(defun vm-reorder-message-headers (message keep-list discard-regexp) + (save-excursion + (if message + (progn + (set-buffer (vm-buffer-of message)) + (setq keep-list vm-visible-headers + discard-regexp vm-invisible-header-regexp))) + (save-excursion + (save-restriction + (widen) + ;; if there is a cached regexp that points to the already + ;; ordered headers then use it and avoid a lot of work. + (if (and message (vm-vheaders-regexp-of message)) + (save-excursion + (goto-char (vm-headers-of message)) + (let ((case-fold-search t)) + (re-search-forward (vm-vheaders-regexp-of message) + (vm-text-of message) t)) + (vm-set-vheaders-of message (vm-marker (match-beginning 0)))) + ;; oh well, we gotta do it the hard way. + ;; + ;; header-alist will contain an assoc list version of + ;; keep-list. For messages associated with a folder + ;; buffer: when a matching header is found, the header + ;; is stuffed into its corresponding assoc cell and the + ;; header text is deleted from the buffer. After all + ;; the visible headers have been collected, they are + ;; inserted into the buffer in a clump at the end of + ;; the header section. Unmatched headers are skipped over. + ;; + ;; For free standing messages, unmatched headers are + ;; stripped from the message. + (vm-save-restriction + (let ((header-alist (vm-build-header-order-alist keep-list)) + (buffer-read-only nil) + (work-buffer nil) + (extras nil) + list end-of-header vheader-offset + (folder-buffer (current-buffer)) + ;; This prevents file locking from occuring. Disabling + ;; locking can speed things noticeably if the lock directory + ;; is on a slow device. We don't need locking here because + ;; in a mail context reordering headers is harmless. + (buffer-file-name nil) + (case-fold-search t) + (old-buffer-modified-p (buffer-modified-p))) + (unwind-protect + (progn + (if message + (progn + ;; for babyl folders, keep an untouched + ;; copy of the headers between the + ;; attributes line and the *** EOOH *** + ;; line. + (if (and (eq vm-folder-type 'babyl) + (null (vm-babyl-frob-flag-of message))) + (progn + (goto-char (vm-start-of message)) + (forward-line 2) + (vm-set-babyl-frob-flag-of message t) + (insert-buffer-substring + (current-buffer) + (vm-headers-of message) + (1- (vm-text-of message))))) + (setq work-buffer (generate-new-buffer "*vm-work*")) + (set-buffer work-buffer) + (insert-buffer-substring + folder-buffer + (vm-headers-of message) + (vm-text-of message)) + (goto-char (point-min)))) + (while (and (not (= (following-char) ?\n)) + (vm-match-header)) + (setq end-of-header (vm-matched-header-end) + list (vm-match-ordered-header header-alist)) + ;; don't display/keep this header if + ;; keep-list not matched + ;; and discard-regexp is nil + ;; or + ;; discard-regexp is matched + (if (or (and (null list) (null discard-regexp)) + (and discard-regexp (looking-at discard-regexp))) + ;; skip the unwanted header if doing + ;; work for a folder buffer, otherwise + ;; discard the header. + (if message + (goto-char end-of-header) + (delete-region (point) end-of-header)) + ;; got a match + ;; stuff the header into the cdr of the + ;; returned alist element + (if list + (if (cdr list) + (setcdr list + (concat + (cdr list) + (buffer-substring (point) + end-of-header))) + (setcdr list (buffer-substring (point) + end-of-header))) + (setq extras + (cons (buffer-substring (point) end-of-header) + extras))) + (delete-region (point) end-of-header))) + ;; remember the offset of where the visible + ;; header start so we can initialize the + ;; vm-vheaders-of field later. + (if message + (setq vheader-offset (1- (point)))) + ;; now dump out the headers we saved. + ;; the keep-list headers go first. + (setq list header-alist) + (while list + (if (cdr (car list)) + (progn + (insert (cdr (car list))) + (setcdr (car list) nil))) + (setq list (cdr list))) + ;; now the headers that were not explicitly + ;; undesirable, if any. + (if extras + (progn + (setq extras (nreverse extras)) + (while extras + (insert (car extras)) + (setq extras (cdr extras))))) + ;; update the folder buffer if we're supposed to. + ;; lock out interrupts. + (if message + (let ((inhibit-quit t)) + (set-buffer (vm-buffer-of message)) + (goto-char (vm-headers-of message)) + (insert-buffer-substring work-buffer) + (delete-region (point) (vm-text-of message)) + (set-buffer-modified-p old-buffer-modified-p)))) + (and work-buffer (kill-buffer work-buffer))) + (if message + (progn + (vm-set-vheaders-of message + (vm-marker (+ (vm-headers-of message) + vheader-offset))) + ;; cache a regular expression that can be used to + ;; find the start of the reordered header the next + ;; time this folder is visited. + (goto-char (vm-vheaders-of message)) + (if (vm-match-header) + (vm-set-vheaders-regexp-of + message + (concat "^" (vm-matched-header-name) ":")))))))))))) + +;; Reads the message attributes and cached header information from the +;; header portion of the each message, if our X-VM- attributes header is +;; present. If the header is not present, assume the message is new, +;; unless we are being compatible with Berkeley Mail in which case we +;; also check for a Status header. +;; +;; If a message already has attributes don't bother checking the +;; headers. +;; +;; This function also discovers and stores the position where the +;; message text begins. +;; +;; Totals are gathered for use by vm-emit-totals-blurb. +;; +;; Supports version 4 format of attribute storage, for backward compatibility. + +(defun vm-read-attributes (message-list) + (save-excursion + (let ((mp (or message-list vm-message-list)) + (vm-new-count 0) + (vm-unread-count 0) + (vm-deleted-count 0) + (vm-total-count 0) + (modulus (+ (% (vm-abs (random)) 11) 25)) + (case-fold-search t) + data) + (while mp + (vm-increment vm-total-count) + (if (vm-attributes-of (car mp)) + () + (goto-char (vm-headers-of (car mp))) + ;; find start of text section and save it + (search-forward "\n\n" (vm-text-end-of (car mp)) 0) + (vm-set-text-of (car mp) (point-marker)) + ;; now look for our header + (goto-char (vm-headers-of (car mp))) + (cond + ((re-search-forward vm-attributes-header-regexp + (vm-text-of (car mp)) t) + (goto-char (match-beginning 2)) + (condition-case () + (setq data (read (current-buffer))) + (error (setq data + (list + (make-vector vm-attributes-vector-length nil) + (make-vector vm-cache-vector-length nil) + nil)) + ;; In lieu of a valid attributes header + ;; assume the message is new. avoid + ;; vm-set-new-flag because it asks for a + ;; summary update. + (vm-set-new-flag-in-vector (car data) t))) + ;; support version 4 format + (cond ((vectorp data) + (setq data (vm-convert-v4-attributes data)) + ;; tink the message modflag so that if the + ;; user saves we get rid of the old v4 + ;; attributes header. otherwise we could be + ;; dealing with these things for all eternity. + (vm-set-modflag-of (car mp) t)) + (t + ;; extend vectors if necessary to accomodate + ;; more caching and attributes without alienating + ;; other version 5 folders. + (cond ((< (length (car data)) + vm-attributes-vector-length) + ;; tink the message modflag so that if + ;; the user saves we get rid of the old + ;; short vector. otherwise we could be + ;; dealing with these things for all + ;; eternity. + (vm-set-modflag-of (car mp) t) + (setcar data (vm-extend-vector + (car data) + vm-attributes-vector-length)))) + (cond ((< (length (car (cdr data))) + vm-cache-vector-length) + ;; tink the message modflag so that if + ;; the user saves we get rid of the old + ;; short vector. otherwise we could be + ;; dealing with these things for all + ;; eternity. + (vm-set-modflag-of (car mp) t) + (setcar (cdr data) + (vm-extend-vector + (car (cdr data)) + vm-cache-vector-length)))))) + (vm-set-labels-of (car mp) (nth 2 data)) + (vm-set-cache-of (car mp) (car (cdr data))) + (vm-set-attributes-of (car mp) (car data))) + ((and vm-berkeley-mail-compatibility + (re-search-forward vm-berkeley-mail-status-header-regexp + (vm-text-of (car mp)) t)) + (vm-set-cache-of (car mp) (make-vector vm-cache-vector-length + nil)) + (goto-char (match-beginning 1)) + (vm-set-attributes-of + (car mp) + (make-vector vm-attributes-vector-length nil)) + (vm-set-unread-flag (car mp) (not (looking-at ".*R.*")) t)) + (t + (vm-set-cache-of (car mp) (make-vector vm-cache-vector-length + nil)) + (vm-set-attributes-of + (car mp) + (make-vector vm-attributes-vector-length nil)) + ;; In lieu of a valid attributes header + ;; assume the message is new. avoid + ;; vm-set-new-flag because it asks for a + ;; summary update. + (vm-set-new-flag-of (car mp) t))) + ;; let babyl attributes override the normal VM + ;; attributes header. + (cond ((eq vm-folder-type 'babyl) + (vm-read-babyl-attributes (car mp))))) + (cond ((vm-deleted-flag (car mp)) + (vm-increment vm-deleted-count)) + ((vm-new-flag (car mp)) + (vm-increment vm-new-count)) + ((vm-unread-flag (car mp)) + (vm-increment vm-unread-count))) + (if (zerop (% vm-total-count modulus)) + (vm-unsaved-message "Reading attributes... %d" vm-total-count)) + (setq mp (cdr mp))) + (if (>= vm-total-count modulus) + (vm-unsaved-message "Reading attributes... done")) + (if (null message-list) + (setq vm-totals (list vm-modification-counter + vm-total-count + vm-new-count + vm-unread-count + vm-deleted-count)))))) + +(defun vm-read-babyl-attributes (message) + (let ((case-fold-search t) + (labels nil) + (vect (make-vector vm-attributes-vector-length nil))) + (vm-set-attributes-of message vect) + (save-excursion + (goto-char (vm-start-of message)) + ;; skip past ^L\n + (forward-char 2) + (vm-set-babyl-frob-flag-of message (if (= (following-char) ?1) t nil)) + ;; skip past 0, + (forward-char 2) + ;; loop, noting attributes as we go. + (while (and (not (eobp)) (not (looking-at ","))) + (cond ((looking-at " unseen,") + (vm-set-unread-flag-of message t)) + ((looking-at " recent,") + (vm-set-new-flag-of message t)) + ((looking-at " deleted,") + (vm-set-deleted-flag-of message t)) + ((looking-at " answered,") + (vm-set-replied-flag-of message t)) + ((looking-at " forwarded,") + (vm-set-forwarded-flag-of message t)) + ((looking-at " filed,") + (vm-set-filed-flag-of message t)) + ((looking-at " redistributed,") + (vm-set-redistributed-flag-of message t)) + ;; only VM knows about these, as far as I know. + ((looking-at " edited,") + (vm-set-forwarded-flag-of message t)) + ((looking-at " written,") + (vm-set-forwarded-flag-of message t))) + (skip-chars-forward "^,") + (and (not (eobp)) (forward-char 1))) + (and (not (eobp)) (forward-char 1)) + (while (looking-at " \\([^\000-\040,\177-\377]+\\),") + (setq labels (cons (vm-buffer-substring-no-properties + (match-beginning 1) + (match-end 1)) + labels)) + (goto-char (match-end 0))) + (vm-set-labels-of message labels)))) + +(defun vm-set-default-attributes (message-list) + (let ((mp (or message-list vm-message-list)) attr cache) + (while mp + (setq attr (make-vector vm-attributes-vector-length nil) + cache (make-vector vm-cache-vector-length nil)) + (vm-set-cache-of (car mp) cache) + (vm-set-attributes-of (car mp) attr) + ;; make message be new by default, but avoid vm-set-new-flag + ;; because it asks for a summary update for the message. + (vm-set-new-flag-of (car mp) t) + ;; since this function is usually called in lieu of reading + ;; attributes from the buffer, the attributes may be + ;; untrustworthy. tink the message modflag to force the + ;; new attributes out if the user saves. + (vm-set-modflag-of (car mp) t) + (setq mp (cdr mp))))) + +(defun vm-emit-totals-blurb () + (save-excursion + (vm-select-folder-buffer) + (if (not (equal (nth 0 vm-totals) vm-modification-counter)) + (let ((mp vm-message-list) + (vm-new-count 0) + (vm-unread-count 0) + (vm-deleted-count 0) + (vm-total-count 0)) + (while mp + (vm-increment vm-total-count) + (cond ((vm-deleted-flag (car mp)) + (vm-increment vm-deleted-count)) + ((vm-new-flag (car mp)) + (vm-increment vm-new-count)) + ((vm-unread-flag (car mp)) + (vm-increment vm-unread-count))) + (setq mp (cdr mp))) + (setq vm-totals (list vm-modification-counter + vm-total-count + vm-new-count + vm-unread-count + vm-deleted-count)))) + (if (equal (nth 1 vm-totals) 0) + (message "No messages.") + (message "%d message%s, %d new, %d unread, %d deleted" + (nth 1 vm-totals) (if (= (nth 1 vm-totals) 1) "" "s") + (nth 2 vm-totals) + (nth 3 vm-totals) + (nth 4 vm-totals))))) + +(defun vm-convert-v4-attributes (data) + (list (apply 'vector + (nconc (vm-vector-to-list data) + (make-list (- vm-attributes-vector-length + (length data)) + nil))) + (make-vector vm-cache-vector-length nil))) + +(defun vm-gobble-labels () + (let ((case-fold-search t) + lim) + (save-excursion + (vm-save-restriction + (widen) + (if (eq vm-folder-type 'babyl) + (progn + (goto-char (point-min)) + (vm-skip-past-folder-header) + (setq lim (point)) + (goto-char (point-min)) + (if (re-search-forward "^Labels:" lim t) + (let (string list) + (setq string (buffer-substring + (point) + (progn (end-of-line) (point))) + list (vm-parse string +"[\000-\040,\177-\377]*\\([^\000-\040,\177-\377]+\\)[\000-\040,\177-\377]*")) + (mapcar (function + (lambda (s) + (intern (downcase s) vm-label-obarray))) + list)))) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (if (re-search-forward vm-labels-header-regexp lim t) + (let (list) + (setq list (read (current-buffer))) + (mapcar (function + (lambda (s) + (intern s vm-label-obarray))) + list)))))) + t )) + +;; Go to the message specified in a bookmark and eat the bookmark. +;; Returns non-nil if successful, nil otherwise. +(defun vm-gobble-bookmark () + (let ((case-fold-search t) + n lim) + (save-excursion + (vm-save-restriction + (widen) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (if (re-search-forward vm-bookmark-header-regexp lim t) + (setq n (read (current-buffer)))))) + (if n + (vm-record-and-change-message-pointer + vm-message-pointer + (nthcdr (1- n) vm-message-list))) + t )) + +(defun vm-gobble-visible-header-variables () + (save-excursion + (vm-save-restriction + (let ((case-fold-search t) + lim) + (widen) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (if (re-search-forward vm-vheader-header-regexp lim t) + (let (vis invis (got nil)) + (condition-case () + (setq vis (read (current-buffer)) + invis (read (current-buffer)) + got t) + (error nil)) + ;; if the variables don't match the values stored when this + ;; folder was saved, then we have to discard any cached + ;; vheader info so the user will see the right headers. + (and got (or (not (equal vis vm-visible-headers)) + (not (equal invis vm-invisible-header-regexp))) + (let ((mp vm-message-list)) + (vm-unsaved-message "Discarding visible header info...") + (while mp + (vm-set-vheaders-regexp-of (car mp) nil) + (vm-set-vheaders-of (car mp) nil) + (setq mp (cdr mp))))))))))) + +;; Read and delete the header that gives the folder's desired +;; message order. +(defun vm-gobble-message-order () + (let ((case-fold-search t) + lim v order + (mp vm-message-list) + list-length) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (if (re-search-forward vm-message-order-header-regexp lim t) + (progn + (vm-unsaved-message "Reordering messages...") + (setq order (read (current-buffer)) + list-length (length vm-message-list) + v (make-vector (max list-length (length order)) nil)) + (while (and order mp) + (aset v (1- (car order)) (car mp)) + (setq order (cdr order) mp (cdr mp))) + ;; lock out interrupts while the message list is in + ;; an inconsistent state. + (let ((inhibit-quit t)) + (setq vm-message-list (delq nil (append v mp)) + vm-message-order-changed nil + vm-message-order-header-present t + vm-message-pointer (memq (car vm-message-pointer) + vm-message-list)) + (vm-set-numbering-redo-start-point t) + (vm-reverse-link-messages)) + (vm-unsaved-message "Reordering messages... done"))))))) + +;; Read the header that gives the folder's cached summary format +;; If the current summary format is different, then the cached +;; summary lines are discarded. +(defun vm-gobble-summary () + (let ((case-fold-search t) + summary lim + (mp vm-message-list)) + (save-excursion + (vm-save-restriction + (widen) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (if (re-search-forward vm-summary-header-regexp lim t) + (progn + (setq summary (read (current-buffer))) + (if (not (equal summary vm-summary-format)) + (while mp + (vm-set-summary-of (car mp) nil) + ;; force restuffing of cache to clear old + ;; summary entry cache. + (vm-set-modflag-of (car mp) t) + (setq mp (cdr mp)))))))))) + +;; Stuff the message attributes back into the message as headers. +(defun vm-stuff-attributes (m &optional for-other-folder) + (save-excursion + (vm-save-restriction + (widen) + (let ((old-buffer-modified-p (buffer-modified-p)) + attributes cache + (case-fold-search t) + (buffer-read-only nil) + opoint + ;; This prevents file locking from occuring. Disabling + ;; locking can speed things noticeably if the lock + ;; directory is on a slow device. We don't need locking + ;; here because the user shouldn't care about VM stuffing + ;; its own status headers. + (buffer-file-name nil) + (delflag (vm-deleted-flag m))) + (unwind-protect + (progn + ;; don't put this folder's summary entry into another folder. + (if for-other-folder + (vm-set-summary-of m nil) + (if (vm-su-start-of m) + ;; fill the summary cache if it's not done already. + (vm-su-summary m))) + (setq attributes (vm-attributes-of m) + cache (vm-cache-of m)) + (and delflag for-other-folder + (vm-set-deleted-flag-in-vector + (setq attributes (copy-sequence attributes)) nil)) + (if (eq vm-folder-type 'babyl) + (vm-stuff-babyl-attributes m for-other-folder)) + (goto-char (vm-headers-of m)) + (while (re-search-forward vm-attributes-header-regexp + (vm-text-of m) t) + (delete-region (match-beginning 0) (match-end 0))) + (goto-char (vm-headers-of m)) + (setq opoint (point)) + (insert-before-markers + vm-attributes-header " (" + (let ((print-escape-newlines t)) + (prin1-to-string attributes)) + "\n\t" + (let ((print-escape-newlines t)) + (prin1-to-string cache)) + "\n\t" + (let ((print-escape-newlines t)) + (prin1-to-string (vm-labels-of m))) + ")\n") + (set-marker (vm-headers-of m) opoint) + (cond ((and (eq vm-folder-type 'From_) + vm-berkeley-mail-compatibility) + (goto-char (vm-headers-of m)) + (while (re-search-forward + vm-berkeley-mail-status-header-regexp + (vm-text-of m) t) + (delete-region (match-beginning 0) (match-end 0))) + (goto-char (vm-headers-of m)) + (cond ((not (vm-new-flag m)) + (insert-before-markers + vm-berkeley-mail-status-header + (if (vm-unread-flag m) "" "R") + "O\n") + (set-marker (vm-headers-of m) opoint))))) + (vm-set-modflag-of m nil)) + (set-buffer-modified-p old-buffer-modified-p)))))) + +;; we can be a bit lazy in this function since it's only called +;; from within vm-stuff-attributes. we don't worry about +;; restoring the modified flag, setting buffer-read-only, or +;; about not moving point. +(defun vm-stuff-babyl-attributes (m for-other-folder) + (goto-char (vm-start-of m)) + (forward-char 2) + (if (vm-babyl-frob-flag-of m) + (insert "1") + (insert "0")) + (delete-char 1) + (forward-char 1) + (if (looking-at "\\( [^\000-\040,\177-\377]+,\\)+") + (delete-region (match-beginning 0) (match-end 0))) + (if (vm-new-flag m) + (insert " recent, unseen,") + (if (vm-unread-flag m) + (insert " unseen,"))) + (if (and (not for-other-folder) (vm-deleted-flag m)) + (insert " deleted,")) + (if (vm-replied-flag m) + (insert " answered,")) + (if (vm-forwarded-flag m) + (insert " forwarded,")) + (if (vm-redistributed-flag m) + (insert " redistributed,")) + (if (vm-filed-flag m) + (insert " filed,")) + (if (vm-edited-flag m) + (insert " edited,")) + (if (vm-written-flag m) + (insert " written,")) + (forward-char 1) + (if (looking-at "\\( [^\000-\040,\177-\377]+,\\)+") + (delete-region (match-beginning 0) (match-end 0))) + (mapcar (function (lambda (label) (insert " " label ","))) + (vm-labels-of m))) + +(defun vm-babyl-attributes-string (m for-other-folder) + (concat + (if (vm-new-flag m) + " recent, unseen," + (if (vm-unread-flag m) + " unseen,")) + (if (and (not for-other-folder) (vm-deleted-flag m)) + " deleted,") + (if (vm-replied-flag m) + " answered,") + (if (vm-forwarded-flag m) + " forwarded,") + (if (vm-redistributed-flag m) + " redistributed,") + (if (vm-filed-flag m) + " filed,") + (if (vm-edited-flag m) + " edited,") + (if (vm-written-flag m) + " written,"))) + +(defun vm-babyl-labels-string (m) + (let ((list nil) + (labels (vm-labels-of m))) + (while labels + (setq list (cons "," (cons (car labels) (cons " " list))) + labels (cdr labels))) + (apply 'concat (nreverse list)))) + +(defun vm-stuff-virtual-attributes (message) + (let ((virtual (vm-virtual-message-p message))) + (if (or (not virtual) (and virtual (vm-virtual-messages-of message))) + (save-excursion + (set-buffer + (vm-buffer-of + (vm-real-message-of message))) + (vm-stuff-attributes (vm-real-message-of message)))))) + +(defun vm-stuff-labels () + (if vm-message-pointer + (save-excursion + (vm-save-restriction + (widen) + (let ((old-buffer-modified-p (buffer-modified-p)) + (case-fold-search t) + ;; This prevents file locking from occuring. Disabling + ;; locking can speed things noticeably if the lock + ;; directory is on a slow device. We don't need locking + ;; here because the user shouldn't care about VM stuffing + ;; its own status headers. + (buffer-file-name nil) + (buffer-read-only nil) + lim) + (if (eq vm-folder-type 'babyl) + (progn + (goto-char (point-min)) + (vm-skip-past-folder-header) + (delete-region (point) (point-min)) + (insert-before-markers (vm-folder-header vm-folder-type + vm-label-obarray)))) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-find-leading-message-separator) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-find-leading-message-separator) + (vm-skip-past-leading-message-separator) + (while (re-search-forward vm-labels-header-regexp lim t) + (progn (goto-char (match-beginning 0)) + (if (vm-match-header vm-labels-header) + (delete-region (vm-matched-header-start) + (vm-matched-header-end))))) + ;; To insert or to insert-before-markers, that is the question. + ;; + ;; If we insert-before-markers we push a header behind + ;; vm-headers-of, which is clearly undesirable. So we + ;; just insert. This will cause the summary header + ;; to be visible if there are no non-visible headers, + ;; oh well, no way around this. + (insert vm-labels-header " " + (let ((print-escape-newlines t) + (list nil)) + (mapatoms (function + (lambda (sym) + (setq list (cons (symbol-name sym) list)))) + vm-label-obarray) + (prin1-to-string list)) + "\n") + (set-buffer-modified-p old-buffer-modified-p)))))) + +;; Insert a bookmark into the first message in the folder. +(defun vm-stuff-bookmark () + (if vm-message-pointer + (save-excursion + (vm-save-restriction + (widen) + (let ((old-buffer-modified-p (buffer-modified-p)) + (case-fold-search t) + ;; This prevents file locking from occuring. Disabling + ;; locking can speed things noticeably if the lock + ;; directory is on a slow device. We don't need locking + ;; here because the user shouldn't care about VM stuffing + ;; its own status headers. + (buffer-file-name nil) + (buffer-read-only nil) + lim) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-find-leading-message-separator) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-find-leading-message-separator) + (vm-skip-past-leading-message-separator) + (if (re-search-forward vm-bookmark-header-regexp lim t) + (progn (goto-char (match-beginning 0)) + (if (vm-match-header vm-bookmark-header) + (delete-region (vm-matched-header-start) + (vm-matched-header-end))))) + ;; To insert or to insert-before-markers, that is the question. + ;; + ;; If we insert-before-markers we push a header behind + ;; vm-headers-of, which is clearly undesirable. So we + ;; just insert. This will cause the bookmark header + ;; to be visible if there are no non-visible headers, + ;; oh well, no way around this. + (insert vm-bookmark-header " " + (vm-number-of (car vm-message-pointer)) + "\n") + (set-buffer-modified-p old-buffer-modified-p)))))) + +;; Insert the summary format variable header into the first message. +(defun vm-stuff-summary () + (if vm-message-pointer + (save-excursion + (vm-save-restriction + (widen) + (let ((old-buffer-modified-p (buffer-modified-p)) + (case-fold-search t) + ;; This prevents file locking from occuring. Disabling + ;; locking can speed things noticeably if the lock + ;; directory is on a slow device. We don't need locking + ;; here because the user shouldn't care about VM stuffing + ;; its own status headers. + (buffer-file-name nil) + (buffer-read-only nil) + lim) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-find-leading-message-separator) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-find-leading-message-separator) + (vm-skip-past-leading-message-separator) + (while (re-search-forward vm-summary-header-regexp lim t) + (progn (goto-char (match-beginning 0)) + (if (vm-match-header vm-summary-header) + (delete-region (vm-matched-header-start) + (vm-matched-header-end))))) + ;; To insert or to insert-before-markers, that is the question. + ;; + ;; If we insert-before-markers we push a header behind + ;; vm-headers-of, which is clearly undesirable. So we + ;; just insert. This will cause the summary header + ;; to be visible if there are no non-visible headers, + ;; oh well, no way around this. + (insert vm-summary-header " " + (let ((print-escape-newlines t)) + (prin1-to-string vm-summary-format)) + "\n") + (set-buffer-modified-p old-buffer-modified-p)))))) + +;; stuff the current values of the header variables for future messages. +(defun vm-stuff-header-variables () + (if vm-message-pointer + (save-excursion + (vm-save-restriction + (widen) + (let ((old-buffer-modified-p (buffer-modified-p)) + (case-fold-search t) + (print-escape-newlines t) + lim + (buffer-read-only nil) + ;; This prevents file locking from occuring. Disabling + ;; locking can speed things noticeably if the lock + ;; directory is on a slow device. We don't need locking + ;; here because the user shouldn't care about VM stuffing + ;; its own status headers. + (buffer-file-name nil)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-find-leading-message-separator) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-find-leading-message-separator) + (vm-skip-past-leading-message-separator) + (while (re-search-forward vm-vheader-header-regexp lim t) + (progn (goto-char (match-beginning 0)) + (if (vm-match-header vm-vheader-header) + (delete-region (vm-matched-header-start) + (vm-matched-header-end))))) + ;; To insert or to insert-before-markers, that is the question. + ;; + ;; If we insert-before-markers we push a header behind + ;; vm-headers-of, which is clearly undesirable. So we + ;; just insert. This header will be visible if there + ;; are no non-visible headers, oh well, no way around this. + (insert vm-vheader-header " " + (prin1-to-string vm-visible-headers) " " + (prin1-to-string vm-invisible-header-regexp) + "\n") + (set-buffer-modified-p old-buffer-modified-p)))))) + +;; Insert a header into the first message of the folder that lists +;; the folder's message order. +(defun vm-stuff-message-order () + (if (cdr vm-message-list) + (save-excursion + (vm-save-restriction + (widen) + (let ((old-buffer-modified-p (buffer-modified-p)) + (case-fold-search t) + ;; This prevents file locking from occuring. Disabling + ;; locking can speed things noticeably if the lock + ;; directory is on a slow device. We don't need locking + ;; here because the user shouldn't care about VM stuffing + ;; its own status headers. + (buffer-file-name nil) + lim n + (buffer-read-only nil) + (mp (copy-sequence vm-message-list))) + (setq mp + (sort mp + (function + (lambda (p q) + (< (vm-start-of p) (vm-start-of q)))))) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-find-leading-message-separator) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-find-leading-message-separator) + (vm-skip-past-leading-message-separator) + (while (re-search-forward vm-message-order-header-regexp lim t) + (progn (goto-char (match-beginning 0)) + (if (vm-match-header vm-message-order-header) + (delete-region (vm-matched-header-start) + (vm-matched-header-end))))) + ;; To insert or to insert-before-markers, that is the question. + ;; + ;; If we insert-before-markers we push a header behind + ;; vm-headers-of, which is clearly undesirable. So we + ;; just insert. This header will be visible if there + ;; are no non-visible headers, oh well, no way around this. + (insert vm-message-order-header "\n\t(") + (setq n 0) + (while mp + (insert (vm-number-of (car mp))) + (setq n (1+ n) mp (cdr mp)) + (and mp (insert + (if (zerop (% n 15)) + "\n\t " + " ")))) + (insert ")\n") + (setq vm-message-order-changed nil + vm-message-order-header-present t) + (set-buffer-modified-p old-buffer-modified-p)))))) + +;; Remove the message order header. +(defun vm-remove-message-order () + (if (cdr vm-message-list) + (save-excursion + (vm-save-restriction + (widen) + (let ((old-buffer-modified-p (buffer-modified-p)) + (case-fold-search t) + lim + ;; This prevents file locking from occuring. Disabling + ;; locking can speed things noticeably if the lock + ;; directory is on a slow device. We don't need locking + ;; here because the user shouldn't care about VM stuffing + ;; its own status headers. + (buffer-file-name nil) + (buffer-read-only nil)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (search-forward "\n\n" nil t) + (setq lim (point)) + (goto-char (point-min)) + (vm-skip-past-folder-header) + (vm-skip-past-leading-message-separator) + (while (re-search-forward vm-message-order-header-regexp lim t) + (progn (goto-char (match-beginning 0)) + (if (vm-match-header vm-message-order-header) + (delete-region (vm-matched-header-start) + (vm-matched-header-end))))) + (setq vm-message-order-header-present nil) + (set-buffer-modified-p old-buffer-modified-p)))))) + +(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-unread-message (&optional count) + "Set the `unread' attribute for the current message. If the message is +already new or unread, then it is left unchanged. + +Numeric prefix argument N means to unread the current message plus the +next N-1 messages. A negative N means unread the current message and +the previous N-1 messages. + +When invoked on marked messages (via vm-next-command-uses-marks), +all marked messages are affected, other messages are ignored." + (interactive "p") + (or count (setq count 1)) + (vm-follow-summary-cursor) + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-folder-empty) + (let ((mlist (vm-select-marked-or-prefixed-messages count))) + (while mlist + (if (and (not (vm-unread-flag (car mlist))) + (not (vm-new-flag (car mlist)))) + (vm-set-unread-flag (car mlist) t)) + (setq mlist (cdr mlist)))) + (vm-display nil nil '(vm-unread-message) '(vm-unread-message)) + (vm-update-summary-and-mode-line)) + +(defun vm-quit-just-bury () + "Bury the current VM folder and summary buffers. +The folder is not altered and Emacs is still visiting it. You +can switch back to it with switch-to-buffer or by using the +Buffer Menu." + (interactive) + (vm-select-folder-buffer) + (if (not (memq major-mode '(vm-mode vm-virtual-mode))) + (error "%s must be invoked from a VM buffer." this-command)) + (vm-check-for-killed-summary) + + (run-hooks 'vm-quit-hook) + + (vm-display nil nil '(vm-quit-just-bury) + '(vm-quit-just-bury quitting)) + (if vm-summary-buffer + (vm-display vm-summary-buffer nil nil nil)) + (if vm-summary-buffer + (vm-bury-buffer vm-summary-buffer)) + (vm-display (current-buffer) nil nil nil) + (vm-bury-buffer (current-buffer))) + +(defun vm-quit-just-iconify () + "Iconify the frame and bury the current VM folder and summary buffers. +The folder is not altered and Emacs is still visiting it." + (interactive) + (vm-select-folder-buffer) + (if (not (memq major-mode '(vm-mode vm-virtual-mode))) + (error "%s must be invoked from a VM buffer." this-command)) + (vm-check-for-killed-summary) + + (run-hooks 'vm-quit-hook) + + (vm-display nil nil '(vm-quit-just-iconify) + '(vm-quit-just-iconify quitting)) + (vm-bury-buffer (current-buffer)) + (if vm-summary-buffer + (vm-bury-buffer vm-summary-buffer)) + (vm-iconify-frame)) + +(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. Deleted messages are not expunged." + (interactive) + (vm-select-folder-buffer) + (if (not (memq major-mode '(vm-mode vm-virtual-mode))) + (error "%s must be invoked from a VM buffer." this-command)) + (vm-check-for-killed-summary) + (vm-display nil nil '(vm-quit vm-quit-no-change) + (list this-command 'quitting)) + (let ((virtual (eq major-mode 'vm-virtual-mode))) + (cond + ((and (not virtual) no-change (buffer-modified-p) + (not (zerop vm-messages-not-on-disk)) + ;; Folder may have been saved with C-x C-s and attributes 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 'vm-set-buffer-modified-p vm-undo-record-list)) + (not + (y-or-n-p + (format + "%d message%s have not been saved to disk, quit anyway? " + vm-messages-not-on-disk + (if (= 1 vm-messages-not-on-disk) "" "s"))))) + (error "Aborted")) + ((and (not virtual) + no-change (buffer-modified-p) vm-confirm-quit + (not (y-or-n-p "There are unsaved changes, quit anyway? "))) + (error "Aborted")) + ((and (eq vm-confirm-quit t) + (not (y-or-n-p "Do you really want to quit? "))) + (error "Aborted"))) + + (run-hooks 'vm-quit-hook) + + (vm-virtual-quit) + (if (and (not no-change) (not virtual)) + (progn + ;; this could take a while, so give the user some feedback + (vm-unsaved-message "Quitting...") + (or vm-folder-read-only (eq major-mode 'vm-virtual-mode) + (vm-change-all-new-to-unread)))) + (if (and (buffer-modified-p) (not no-change) (not virtual)) + (vm-save-folder)) + (vm-unsaved-message "") + (let ((summary-buffer vm-summary-buffer) + (mail-buffer (current-buffer))) + (if summary-buffer + (progn + (vm-display vm-summary-buffer nil nil nil) + (kill-buffer summary-buffer))) + (set-buffer mail-buffer) + (vm-display mail-buffer nil nil nil) + ;; vm-display is not supposed to change the current buffer. + ;; still better to be safe here. + (set-buffer mail-buffer) + (set-buffer-modified-p nil) + (kill-buffer (current-buffer))) + (vm-update-summary-and-mode-line))) + +(defun vm-start-itimers-if-needed () + (if (or (natnump vm-flush-interval) + (natnump vm-auto-get-new-mail)) + (progn + (if (null + (condition-case data + (progn (require 'itimer) t) + (error nil))) + (setq vm-flush-interval t + vm-auto-get-new-mail t) + (and (natnump vm-flush-interval) (not (get-itimer "vm-flush")) + (start-itimer "vm-flush" 'vm-flush-itimer-function + vm-flush-interval nil)) + (and (natnump vm-auto-get-new-mail) (not (get-itimer "vm-get-mail")) + (start-itimer "vm-get-mail" 'vm-get-mail-itimer-function + vm-auto-get-new-mail nil)))))) + +;; support for numeric vm-auto-get-new-mail +(defun vm-get-mail-itimer-function () + (if (integerp vm-auto-get-new-mail) + (set-itimer-restart current-itimer vm-auto-get-new-mail)) + (let ((b-list (buffer-list))) + (while (and (not (input-pending-p)) b-list) + (save-excursion + (set-buffer (car b-list)) + (if (and (eq major-mode 'vm-mode) + (not (and (not (buffer-modified-p)) + buffer-file-name + (file-newer-than-file-p + (make-auto-save-file-name) + buffer-file-name))) + (not vm-block-new-mail) + (not vm-folder-read-only) + (vm-get-spooled-mail) + (vm-assimilate-new-messages t)) + (progn + ;; don't move the message pointer unless the folder + ;; was empty. + (if (and (null vm-message-pointer) + (vm-thoughtfully-select-message)) + (vm-preview-current-message) + (vm-update-summary-and-mode-line))))) + (setq b-list (cdr b-list))))) + +;; support for numeric vm-flush-interval +(defun vm-flush-itimer-function () + (if (integerp vm-flush-interval) + (set-itimer-restart current-itimer vm-flush-interval)) + ;; if no vm-mode buffers are found, we might as well shut down the + ;; flush itimer. + (if (not (vm-flush-cached-data)) + (set-itimer-restart current-itimer nil))) + +;; flush cached data in all vm-mode buffers. +;; returns non-nil if any vm-mode buffers were found. +(defun vm-flush-cached-data () + (save-excursion + (let ((buf-list (buffer-list)) + (found-one nil)) + (while (and buf-list (not (input-pending-p))) + (set-buffer (car buf-list)) + (cond ((and (eq major-mode 'vm-mode) vm-message-list) + (setq found-one t) + (if (not (eq vm-modification-counter + vm-flushed-modification-counter)) + (let ((mp vm-message-list)) + (vm-stuff-summary) + (vm-stuff-labels) + (and vm-message-order-changed + (vm-stuff-message-order)) + (while (and mp (not (input-pending-p))) + (if (vm-modflag-of (car mp)) + (vm-stuff-attributes (car mp))) + (setq mp (cdr mp))) + (and (null mp) + (setq vm-flushed-modification-counter + vm-modification-counter)))))) + (setq buf-list (cdr buf-list))) + ;; if we haven't checked them all return non-nil so + ;; the flusher won't give up trying. + (or buf-list found-one) ))) + +;; 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 (and (eq major-mode 'vm-mode) (not vm-inhibit-write-file-hook)) + ;; The vm-save-restriction isn't really necessary here, since + ;; the stuff routines clean up after themselves, but should remain + ;; as a safeguard against the time when other stuff is added here. + (vm-save-restriction + (let ((mp vm-message-list) + (buffer-read-only)) + (while mp + (if (vm-modflag-of (car mp)) + (vm-stuff-attributes (car mp))) + (setq mp (cdr mp))) + (if vm-message-list + (progn + ;; get summary cache up-to-date + (vm-update-summary-and-mode-line) + (vm-stuff-bookmark) + (vm-stuff-header-variables) + (vm-stuff-labels) + (vm-stuff-summary) + (and vm-message-order-changed + (vm-stuff-message-order)))) + nil )))) + +(defun vm-save-buffer (prefix) + (interactive "P") + (vm-select-folder-buffer) + (vm-error-if-virtual-folder) + (save-buffer prefix) + (intern (buffer-name) vm-buffers-needing-display-update) + (setq vm-block-new-mail nil) + (vm-display nil nil '(vm-save-buffer) '(vm-save-buffer)) + (vm-update-summary-and-mode-line)) + +(defun vm-write-file () + (interactive) + (vm-select-folder-buffer) + (vm-error-if-virtual-folder) + (call-interactively 'write-file) + (intern (buffer-name) vm-buffers-needing-display-update) + (setq vm-block-new-mail nil) + (vm-display nil nil '(vm-write-file) '(vm-write-file)) + (vm-update-summary-and-mode-line)) + +(defun vm-save-folder (&optional prefix) + "Save current folder to disk. +Deleted messages are not expunged. +Prefix arg is handled the same as for the command save-buffer. + +When applied to a virtual folder, this command runs itself on +each of the underlying real folders associated with the virtual +folder." + (interactive (list current-prefix-arg)) + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-display nil nil '(vm-save-folder) '(vm-save-folder)) + (if (eq major-mode 'vm-virtual-mode) + (vm-virtual-save-folder prefix) + (if (buffer-modified-p) + (let (mp) + ;; stuff the attributes of messages that need it. + (vm-unsaved-message "Stuffing attributes...") + (setq mp vm-message-list) + (while mp + (if (vm-modflag-of (car mp)) + (vm-stuff-attributes (car mp))) + (setq mp (cdr mp))) + ;; stuff bookmark and header variable values + (if vm-message-list + (progn + ;; get summary cache up-to-date + (vm-update-summary-and-mode-line) + (vm-stuff-bookmark) + (vm-stuff-header-variables) + (vm-stuff-labels) + (vm-stuff-summary) + (and vm-message-order-changed + (vm-stuff-message-order)))) + (vm-unsaved-message "Saving...") + (let ((vm-inhibit-write-file-hook t)) + (save-buffer prefix)) + (vm-set-buffer-modified-p nil) + (vm-clear-modification-flag-undos) + (setq vm-messages-not-on-disk 0) + (setq vm-block-new-mail nil) + (and (zerop (buffer-size)) + vm-delete-empty-folders + buffer-file-name + (or (eq vm-delete-empty-folders t) + (y-or-n-p (format "%s is empty, remove it? " + (or buffer-file-name (buffer-name))))) + (condition-case () + (progn + (delete-file buffer-file-name) + (message "%s removed" buffer-file-name)) + ;; no can do, oh well. + (error nil))) + (vm-update-summary-and-mode-line)) + (message "No changes need to be saved")))) + +(defun vm-save-and-expunge-folder (&optional prefix) + "Expunge folder, then save it to disk. +Prefix arg is handled the same as for the command save-buffer. +Expunge won't be done if folder is read-only. + +When applied to a virtual folder, this command works as if you had +run vm-expunge-folder followed by vm-save-folder." + (interactive (list current-prefix-arg)) + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-display nil nil '(vm-save-and-expunge-folder) + '(vm-save-and-expunge-folder)) + (if (not vm-folder-read-only) + (progn + (vm-unsaved-message "Expunging...") + (vm-expunge-folder t))) + (vm-save-folder prefix)) + +(defun vm-handle-file-recovery-or-reversion (recovery) + (if (and vm-summary-buffer (buffer-name vm-summary-buffer)) + (kill-buffer vm-summary-buffer)) + (vm-virtual-quit) + ;; reset major mode, this will cause vm to start from scratch. + (setq major-mode 'fundamental-mode) + ;; If this is a recovery, we can't allow the user to get new + ;; mail until a real save is performed. Until then the buffer + ;; and the disk don't match. + (if recovery + (setq vm-block-new-mail t)) + (vm buffer-file-name)) + +;; detect if a recover-file is being performed +;; and handle things properly. +(defun vm-handle-file-recovery () + (if (and (buffer-modified-p) + (eq major-mode 'vm-mode) + vm-message-list + (= (vm-end-of (car vm-message-list)) 1)) + (vm-handle-file-recovery-or-reversion t))) + +;; detect if a revert-buffer is being performed +;; and handle things properly. +(defun vm-handle-file-reversion () + (if (and (not (buffer-modified-p)) + (eq major-mode 'vm-mode) + vm-message-list + (= (vm-end-of (car vm-message-list)) 1)) + (vm-handle-file-recovery-or-reversion nil))) + +;; FSF v19.23 revert-buffer doesn't mash all the markers together +;; like v18 and prior v19 versions, so the check in +;; vm-handle-file-reversion doesn't work. However v19.23 has a +;; hook we can use, after-revert-hook. +(defun vm-after-revert-buffer-hook () + (if (eq major-mode 'vm-mode) + (vm-handle-file-recovery-or-reversion nil))) + +(defun vm-help () + "Display help for various VM activities." + (interactive) + (if (eq major-mode 'vm-summary-mode) + (vm-select-folder-buffer)) + (let ((pop-up-windows (and pop-up-windows (eq vm-mutable-windows t))) + (pop-up-frames vm-mutable-frames)) + (cond + ((eq last-command 'vm-help) + (describe-function major-mode)) + ((eq vm-system-state 'previewing) + (message "Type SPC to read message, n previews next message (? gives more help)")) + ((memq vm-system-state '(showing reading)) + (message "SPC and b scroll, (d)elete, (s)ave, (n)ext, (r)eply (? gives more help)")) + ((eq vm-system-state 'editing) + (message + (substitute-command-keys + "Type \\[vm-edit-message-end] to end edit, \\[vm-edit-message-abort] to abort with no change."))) + ((eq major-mode 'mail-mode) + (message + (substitute-command-keys + "Type \\[vm-mail-send-and-exit] to send message, \\[kill-buffer] to discard this message"))) + (t (describe-mode))))) + +(defun vm-spool-move-mail (source destination) + (let ((handler (and (fboundp 'find-file-name-handler) + (condition-case () + (find-file-name-handler source 'vm-spool-move-mail) + (wrong-number-of-arguments + (find-file-name-handler source))))) + status error-buffer) + (if handler + (funcall handler 'vm-spool-move-mail source destination) + (setq error-buffer + (get-buffer-create + (format "*output of %s %s %s*" + vm-movemail-program source destination))) + (save-excursion + (set-buffer error-buffer) + (erase-buffer)) + (setq status + (call-process vm-movemail-program nil error-buffer t + source destination)) + (save-excursion + (set-buffer error-buffer) + (if (and (numberp status) (not (= 0 status))) + (insert (format "\n%s exited with code %s\n" + vm-movemail-program status))) + (if (> (buffer-size) 0) + (progn + (vm-display-buffer error-buffer) + (if (and (numberp status) (not (= 0 status))) + (error "Failed getting new mail from %s" source) + (message "Warning: unexpected output from %s" + vm-movemail-program) + (sleep-for 2))) + ;; nag, nag, nag. + (kill-buffer error-buffer)) + t )))) + +(defun vm-gobble-crash-box (crash-box) + (save-excursion + (vm-save-restriction + (widen) + (let ((opoint-max (point-max)) crash-buf + (buffer-read-only nil) + (inbox-buffer-file buffer-file-name) + (inbox-folder-type vm-folder-type) + (inbox-empty (zerop (buffer-size))) + got-mail crash-folder-type + (old-buffer-modified-p (buffer-modified-p))) + (setq crash-buf + ;; crash box could contain a letter bomb... + ;; force user notification of file variables for v18 Emacses + ;; enable-local-variables == nil disables them for newer Emacses + (let ((inhibit-local-variables t) + (enable-local-variables nil)) + (find-file-noselect crash-box))) + (save-excursion + (set-buffer crash-buf) + (setq crash-folder-type (vm-get-folder-type)) + (if (and crash-folder-type vm-check-folder-types) + (cond ((eq crash-folder-type 'unknown) + (error "crash box %s's type is unrecognized" crash-box)) + ((eq inbox-folder-type 'unknown) + (error "inbox %s's type is unrecognized" + inbox-buffer-file)) + ((null inbox-folder-type) + (if vm-default-folder-type + (if (not (eq vm-default-folder-type + crash-folder-type)) + (if vm-convert-folder-types + (progn + (vm-convert-folder-type + crash-folder-type + vm-default-folder-type) + ;; so that kill-buffer won't ask a + ;; question later... + (set-buffer-modified-p nil)) + (error "crash box %s mismatches vm-default-folder-type: %s, %s" + crash-box crash-folder-type + vm-default-folder-type))))) + ((not (eq inbox-folder-type crash-folder-type)) + (if vm-convert-folder-types + (progn + (vm-convert-folder-type crash-folder-type + inbox-folder-type) + ;; so that kill-buffer won't ask a + ;; question later... + (set-buffer-modified-p nil)) + (error "crash box %s mismatches %s's folder type: %s, %s" + crash-box inbox-buffer-file + crash-folder-type inbox-folder-type))))) + ;; toss the folder header if the inbox is not empty + (goto-char (point-min)) + (if (not inbox-empty) + (progn + (vm-convert-folder-header (or inbox-folder-type + vm-default-folder-type) + nil) + (set-buffer-modified-p nil)))) + (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) + (vm-increment vm-modification-counter) + (setq got-mail (/= opoint-max (point-max))) + (set-buffer-modified-p old-buffer-modified-p) + (kill-buffer crash-buf) + (if (not (stringp vm-keep-crash-boxes)) + (vm-error-free-call 'delete-file crash-box) + (rename-file crash-box + (concat (expand-file-name vm-keep-crash-boxes) + (if (not + (= (aref vm-keep-crash-boxes + (1- (length vm-keep-crash-boxes))) + ?/)) + "/" + "") + "Z" + (substring + (timezone-make-date-sortable + (current-time-string)) + 4))) + ;; guarantee that each new saved crashbox will have a + ;; different name, assuming time doesn't reverse. + (sleep-for 1)) + got-mail )))) + +(defun vm-get-spooled-mail () + (if vm-block-new-mail + (error "Can't get new mail until you save this folder.")) + (let ((triples nil) + ;; since we could accept-process-output here (POP code), + ;; a timer process might try to start retrieving mail + ;; before we finish. block these attempts. + (vm-block-new-mail t) + crash in maildrop popdrop + (got-mail nil)) + (cond ((null (vm-spool-files)) + (setq triples (list + (list vm-primary-inbox + (concat vm-spool-directory (user-login-name)) + vm-crash-box)))) + ((stringp (car (vm-spool-files))) + (setq triples + (mapcar (function + (lambda (s) (list vm-primary-inbox s vm-crash-box))) + (vm-spool-files)))) + ((consp (car (vm-spool-files))) + (setq triples (vm-spool-files)))) + (while triples + (setq in (expand-file-name (nth 0 (car triples)) vm-folder-directory) + maildrop (nth 1 (car triples)) + crash (nth 2 (car triples))) + (if (eq (current-buffer) (vm-get-file-buffer in)) + (progn + (if (file-exists-p crash) + (progn + (message "Recovering messages from %s..." crash) + (setq got-mail (or (vm-gobble-crash-box crash) got-mail)) + (message "Recovering messages from %s... done" crash))) + (setq popdrop (and vm-recognize-pop-maildrops + (string-match vm-recognize-pop-maildrops + maildrop) + ;; maildrop with password clipped + (vm-safe-popdrop-string maildrop))) + (if (or popdrop + (and (not (equal 0 (nth 7 (file-attributes maildrop)))) + (file-readable-p maildrop))) + (progn + (setq crash (expand-file-name crash vm-folder-directory)) + (if (not popdrop) + (setq maildrop (expand-file-name maildrop))) + (if (if popdrop + (vm-pop-move-mail maildrop crash) + (vm-spool-move-mail maildrop crash)) + (if (vm-gobble-crash-box crash) + (progn + (setq got-mail t) + (message "Got mail from %s." + (or popdrop maildrop))))))))) + (setq triples (cdr triples))) + (if got-mail + (run-hooks 'vm-retrieved-spooled-mail-hook)) + got-mail )) + +(defun vm-safe-popdrop-string (drop) + (or (and (string-match "^\\([^:]+\\):[^:]+:[^:]+:\\([^:]+\\):[^:]+" drop) + (concat (substring drop (match-beginning 2) (match-end 2)) + "@" + (substring drop (match-beginning 1) (match-end 1)))) + "???")) + +(defun vm-get-new-mail (&optional arg) + "Move any new mail that has arrived in any of the spool files for the +current folder into the folder. New mail is appended to the disk +and buffer copies of the folder. + +Prefix arg means to gather mail from a user specified folder, instead of +the usual spool files. The file name will be read from the minibuffer. +Unlike when getting mail from a spool file, the source file is left +undisturbed after its messages have been copied. + +When applied to a virtual folder, this command runs itself on +each of the underlying real folders associated with this virtual folder. +A prefix argument has no effect; mail is always gathered from the +spool files." + (interactive "P") + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-folder-read-only) + (cond ((eq major-mode 'vm-virtual-mode) + (vm-virtual-get-new-mail)) + ((null arg) + (if (not (eq major-mode 'vm-mode)) + (vm-mode)) + (if (consp (car (vm-spool-files))) + (vm-unsaved-message "Checking for new mail for %s..." + (or buffer-file-name (buffer-name))) + (vm-unsaved-message "Checking for new mail...")) + (let (totals-blurb) + (if (and (vm-get-spooled-mail) (vm-assimilate-new-messages t)) + (progn + ;; say this NOW, before the non-previewers read + ;; a message, alter the new message count and + ;; confuse themselves. + (setq totals-blurb (vm-emit-totals-blurb)) + (vm-display nil nil '(vm-get-new-mail) '(vm-get-new-mail)) + (if (vm-thoughtfully-select-message) + (vm-preview-current-message) + (vm-update-summary-and-mode-line)) + (message totals-blurb)) + (if (consp (car (vm-spool-files))) + (message "No new mail for %s" + (or buffer-file-name (buffer-name))) + (message "No new mail.")) + (and (interactive-p) (sit-for 4) (vm-unsaved-message ""))))) + (t + (let ((buffer-read-only nil) + folder mcount totals-blurb) + (setq folder (read-file-name "Gather mail from folder: " + vm-folder-directory t)) + (if (and vm-check-folder-types + (not (vm-compatible-folder-p folder))) + (error "Folder %s is not the same format as this folder." + folder)) + (save-excursion + (vm-save-restriction + (widen) + (goto-char (point-max)) + (insert-file-contents folder))) + (setq mcount (length vm-message-list)) + (if (vm-assimilate-new-messages) + (progn + ;; say this NOW, before the non-previewers read + ;; a message, alter the new message count and + ;; confuse themselves. + (setq totals-blurb (vm-emit-totals-blurb)) + (vm-display nil nil '(vm-get-new-mail) '(vm-get-new-mail)) + (if (vm-thoughtfully-select-message) + (vm-preview-current-message) + (vm-update-summary-and-mode-line)) + (message totals-blurb) + ;; The gathered messages are actually still on disk + ;; unless the user deletes the folder himself. + ;; However, users may not understand what happened if + ;; the messages go away after a "quit, no save". + (setq vm-messages-not-on-disk + (+ vm-messages-not-on-disk + (- (length vm-message-list) + mcount)))) + (message "No messages gathered.")))))) + +;; returns non-nil if there were any new messages +(defun vm-assimilate-new-messages (&optional dont-read-attributes gobble-order) + (let ((tail-cons (vm-last vm-message-list)) + b-list new-messages) + (save-excursion + (vm-save-restriction + (widen) + (vm-build-message-list) + (if (or (null tail-cons) (cdr tail-cons)) + (progn + (setq vm-ml-sort-keys nil) + (if dont-read-attributes + (vm-set-default-attributes (cdr tail-cons)) + (vm-read-attributes (cdr tail-cons))) + ;; Yuck. This has to be done here instead of in the + ;; vm function because this needs to be done before + ;; any initial thread sort (so that if the thread + ;; sort matches the saved order the folder won't be + ;; modified) but after the message list is created. + ;; Since thread sorting is done here this has to be + ;; done here too. + (if gobble-order + (vm-gobble-message-order)) + (if vm-thread-obarray + (vm-build-threads (cdr tail-cons)))))) + (setq new-messages (if tail-cons (cdr tail-cons) vm-message-list)) + (vm-set-numbering-redo-start-point new-messages) + (vm-set-summary-redo-start-point new-messages)) + ;; copy the new-messages list because sorting might scramble + ;; it. Also something the user does when + ;; vm-arrived-message-hook is run might affect it. + ;; vm-assimilate-new-messages returns this value so it must + ;; not be mangled. + (setq new-messages (copy-sequence new-messages)) + (if vm-summary-show-threads + (progn + ;; get numbering and summary of new messages done now + ;; so that the sort code only has to worry about the + ;; changes it needs to make. + (vm-update-summary-and-mode-line) + (vm-sort-messages "thread"))) + (if (and vm-arrived-message-hook + new-messages + ;; tail-cons == nil means vm-message-list was empty. + ;; Thus new-messages == vm-message-list. In this + ;; case, run the hooks only if this is not the first + ;; time vm-assimilate-new-messages has been called + ;; in this folder. gobble-order non-nil is a good + ;; indicator that this is the first time because the + ;; order is gobbled only once per visit and always + ;; the first time vm-assimilate-new-messages is + ;; called. + (or tail-cons (null gobble-order))) + (let ((new-messages new-messages)) + ;; seems wise to do this so that if the user runs VM + ;; command here they start with as much of a clean + ;; slate as we can provide, given we're currently deep + ;; in the guts of VM. + (vm-update-summary-and-mode-line) + (while new-messages + (vm-run-message-hook (car new-messages) 'vm-arrived-message-hook) + (setq new-messages (cdr new-messages))))) + (vm-update-summary-and-mode-line) + (run-hooks 'vm-arrived-messages-hook) + (if (and new-messages vm-virtual-buffers) + (save-excursion + (setq b-list vm-virtual-buffers) + (while b-list + ;; buffer might be dead + (if (buffer-name (car b-list)) + (let (tail-cons) + (set-buffer (car b-list)) + (setq tail-cons (vm-last vm-message-list)) + (vm-build-virtual-message-list new-messages) + (if (or (null tail-cons) (cdr tail-cons)) + (progn + (setq vm-ml-sort-keys nil) + (if vm-thread-obarray + (vm-build-threads (cdr tail-cons))) + (vm-set-summary-redo-start-point + (or (cdr tail-cons) vm-message-list)) + (vm-set-numbering-redo-start-point + (or (cdr tail-cons) vm-message-list)) + (if (null vm-message-pointer) + (progn (setq vm-message-pointer vm-message-list + vm-need-summary-pointer-update t) + (if vm-message-pointer + (vm-preview-current-message)))) + (if vm-summary-show-threads + (progn + (vm-update-summary-and-mode-line) + (vm-sort-messages "thread"))))))) + (setq b-list (cdr b-list))))) + new-messages )) + +;; return a list of all marked messages or the messages indicated by a +;; prefix argument. +(defun vm-select-marked-or-prefixed-messages (prefix) + (let (mlist) + (if (eq last-command 'vm-next-command-uses-marks) + (setq mlist (vm-marked-messages)) + (let ((direction (if (< prefix 0) 'backward 'forward)) + (count (vm-abs prefix)) + (vm-message-pointer vm-message-pointer)) + (if (not (eq vm-circular-folders t)) + (vm-check-count prefix)) + (while (not (zerop count)) + (setq mlist (cons (car vm-message-pointer) mlist)) + (vm-decrement count) + (if (not (zerop count)) + (vm-move-message-pointer direction)))) + (nreverse mlist)))) + +(defun vm-display-startup-message () + (if (sit-for 5) + (let ((lines vm-startup-message-lines)) + (message "VM %s, Copyright (C) 1996 Kyle E. Jones; type ? for help" + vm-version) + (setq vm-startup-message-displayed t) + (while (and (sit-for 4) lines) + (message (substitute-command-keys (car lines))) + (setq lines (cdr lines))))) + (vm-unsaved-message "")) + +(defun vm-load-init-file (&optional interactive) + (interactive "p") + (if (or (not vm-init-file-loaded) interactive) + (progn + (and vm-init-file + (load vm-init-file (not interactive) (not interactive) t)) + (and vm-options-file (load vm-options-file t t t)))) + (setq vm-init-file-loaded t) + (vm-display nil nil '(vm-load-init-file) '(vm-load-init-file))) + +(defun vm-session-initialization () + ;; If this is the first time VM has been run in this Emacs session, + ;; do some necessary preparations. + (if (or (not (boundp 'vm-session-beginning)) + vm-session-beginning) + (progn + (random t) + (vm-load-init-file) + (if (not vm-window-configuration-file) + (setq vm-window-configurations vm-default-window-configuration) + (or (vm-load-window-configurations vm-window-configuration-file) + (setq vm-window-configurations vm-default-window-configuration))) + (setq vm-buffers-needing-display-update (make-vector 29 0)) + (setq vm-session-beginning nil)))) + +(defun vm-toggle-read-only () + (interactive) + (vm-select-folder-buffer) + (setq vm-folder-read-only (not vm-folder-read-only)) + (intern (buffer-name) vm-buffers-needing-display-update) + (message "Folder is now %s" + (if vm-folder-read-only "read-only" "modifiable")) + (vm-display nil nil '(vm-toggle-read-only) '(vm-toggle-read-only)) + (vm-update-summary-and-mode-line)) + +;; this does the real major mode scutwork. +(defun vm-mode-internal () + (widen) + (make-local-variable 'require-final-newline) + ;; don't kill local variables, as there is some state we'd like to + ;; keep. rather than non-portably marking the variables we + ;; want to keep, just avoid calling kill-local-variables and + ;; reset everything that needs to be reset. + (setq + major-mode 'vm-mode + mode-line-format vm-mode-line-format + mode-name "VM" + ;; must come after the setting of major-mode + mode-popup-menu (and vm-use-menus + (vm-menu-support-possible-p) + (vm-menu-mode-menu)) + buffer-read-only t + require-final-newline nil + vm-thread-obarray nil + vm-thread-subject-obarray nil + vm-label-obarray (make-vector 29 0) + vm-last-message-pointer nil + vm-modification-counter 0 + vm-message-list nil + vm-message-pointer nil + vm-message-order-changed nil + vm-message-order-header-present nil + vm-summary-buffer nil + vm-system-state nil + vm-undo-record-list nil + vm-undo-record-pointer nil + vm-virtual-buffers (vm-link-to-virtual-buffers) + vm-folder-type (vm-get-folder-type)) + (use-local-map vm-mode-map) + (and (vm-menu-support-possible-p) + (vm-menu-install-menus)) + (run-hooks 'vm-mode-hook) + ;; compatibility + (run-hooks 'vm-mode-hooks)) + +(defun vm-link-to-virtual-buffers () + (let ((b-list (buffer-list)) + (vbuffers nil) + (folder-buffer (current-buffer)) + folders clauses) + (save-excursion + (while b-list + (set-buffer (car b-list)) + (cond ((eq major-mode 'vm-virtual-mode) + (setq clauses (cdr vm-virtual-folder-definition)) + (while clauses + (setq folders (car (car clauses))) + (while folders + (if (eq folder-buffer (vm-get-file-buffer + (expand-file-name + (car folders) + vm-folder-directory))) + (setq vbuffers (cons (car b-list) vbuffers) + vm-real-buffers (cons folder-buffer + vm-real-buffers) + folders nil + clauses nil)) + (setq folders (cdr folders))) + (setq clauses (cdr clauses))))) + (setq b-list (cdr b-list))) + vbuffers ))) + +(defun vm-change-folder-type (type) + "Change folder type to TYPE. +TYPE may be one of the following symbol values: + + From_ + From_-with-Content-Length + mmdf + babyl + +Interactively TYPE will be read from the minibuffer." + (interactive + (let ((this-command this-command) + (last-command last-command) + (types vm-supported-folder-types)) + (vm-select-folder-buffer) + (vm-error-if-virtual-folder) + (setq types (vm-delqual (symbol-name vm-folder-type) + (copy-sequence types))) + (list (intern (vm-read-string "Change folder to type: " types))))) + (vm-select-folder-buffer) + (vm-check-for-killed-summary) + (vm-error-if-virtual-folder) + (vm-error-if-folder-empty) + (if (not (memq type '(From_ From_-with-Content-Length mmdf babyl))) + (error "Unknown folder type: %s" type)) + (if (or (null vm-folder-type) + (eq vm-folder-type 'unknown)) + (error "Current folder's type is unknown, can't change it.")) + (let ((mp vm-message-list) + (buffer-read-only nil) + (old-type vm-folder-type) + ;; no interruptions + (inhibit-quit t) + (n 0) + ;; Just for laughs, make the update interval vary. + (modulus (+ (% (vm-abs (random)) 11) 5)) + text-end opoint) + (save-excursion + (vm-save-restriction + (widen) + (setq vm-folder-type type) + (goto-char (point-min)) + (vm-convert-folder-header old-type type) + (while mp + (goto-char (vm-start-of (car mp))) + (setq opoint (point)) + (insert (vm-leading-message-separator type (car mp))) + (if (> (vm-headers-of (car mp)) (vm-start-of (car mp))) + (delete-region (point) (vm-headers-of (car mp))) + (set-marker (vm-headers-of (car mp)) (point)) + ;; if headers-of == start-of then so could vheaders-of + ;; and text-of. clear them to force a recompute. + (vm-set-vheaders-of (car mp) nil) + (vm-set-text-of (car mp) nil)) + (vm-convert-folder-type-headers old-type type) + (goto-char (vm-text-end-of (car mp))) + (setq text-end (point)) + (insert-before-markers (vm-trailing-message-separator type)) + (delete-region (vm-text-end-of (car mp)) (vm-end-of (car mp))) + (set-marker (vm-text-end-of (car mp)) text-end) + (goto-char (vm-headers-of (car mp))) + (vm-munge-message-separators type (vm-headers-of (car mp)) + (vm-text-end-of (car mp))) + (vm-set-byte-count-of (car mp) nil) + (vm-set-babyl-frob-flag-of (car mp) nil) + (vm-set-message-type-of (car mp) type) + ;; Technically we should mark each message for a + ;; summary update since the message byte counts might + ;; have changed. But I don't think anyone cares that + ;; much and the summary regeneration would make this + ;; process slower. + (setq mp (cdr mp) n (1+ n)) + (if (zerop (% n modulus)) + (vm-unsaved-message "Converting... %d" n)))))) + (vm-clear-modification-flag-undos) + (intern (buffer-name) vm-buffers-needing-display-update) + (vm-update-summary-and-mode-line) + (message "Conversion complete.") + ;; message separator strings may have leaked into view + (if (> (point-max) (vm-text-end-of (car vm-message-pointer))) + (narrow-to-region (point-min) (vm-text-end-of (car vm-message-pointer)))) + (vm-display nil nil '(vm-change-folder-type) '(vm-change-folder-type))) + +(if (not (memq 'vm-write-file-hook write-file-hooks)) + (setq write-file-hooks + (cons 'vm-write-file-hook write-file-hooks))) + +(if (not (memq 'vm-handle-file-recovery find-file-hooks)) + (setq find-file-hooks + (nconc find-file-hooks + '(vm-handle-file-recovery + vm-handle-file-reversion)))) + +;; after-revert-hook is new to FSF v19.23 +(defvar after-revert-hook) +(if (boundp 'after-revert-hook) + (setq after-revert-hook + (cons 'vm-after-revert-buffer-hook after-revert-hook)) + (setq after-revert-hook (list 'vm-after-revert-buffer-hook)))