diff lisp/vm/vm-summary.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children 859a2309aef8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/vm/vm-summary.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,992 @@
+;;; Summary gathering and formatting routines for VM
+;;; Copyright (C) 1989, 1990, 1993, 1994, 1995 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-summary)
+
+(defun vm-summary-mode-internal ()
+  (setq mode-name "VM Summary"
+	major-mode 'vm-summary-mode
+	mode-line-format vm-mode-line-format
+	;; 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
+	vm-summary-pointer nil
+	vm-summary-=> (if (stringp vm-summary-arrow) vm-summary-arrow "")
+	vm-summary-no-=> (make-string (length vm-summary-=>) ? )
+	truncate-lines t)
+  ;; horizontal scrollbar off by default
+  ;; user can turn it on in summary hook if desired.
+  (and (fboundp 'set-specifier)
+       scrollbar-height
+       (set-specifier scrollbar-height (cons (current-buffer) 0)))
+  (use-local-map vm-summary-mode-map)
+  (and (vm-menu-support-possible-p)
+       (vm-menu-install-menus))
+  (and (vm-mouse-support-possible-p)
+       (vm-mouse-xemacs-mouse-p)
+       (add-hook 'mode-motion-hook 'mode-motion-highlight-line))
+  (if (or vm-frame-per-folder vm-frame-per-summary)
+      (vm-set-hooks-for-frame-deletion))
+  (run-hooks 'vm-summary-mode-hook)
+  ;; Lucid Emacs apparently used this name
+  (run-hooks 'vm-summary-mode-hooks))
+
+(fset 'vm-summary-mode 'vm-mode)
+(put 'vm-summary-mode 'mode-class 'special)
+
+(defun vm-summarize (&optional display)
+  "Summarize the contents of the folder in a summary buffer. 
+The format is as described by the variable vm-summary-format.  Generally
+one line per message is most pleasing to the eye but this is not
+mandatory."
+  (interactive "p")
+  (vm-select-folder-buffer)
+  (vm-check-for-killed-summary)
+  (if (null vm-summary-buffer)
+      (let ((b (current-buffer))
+	    (read-only vm-folder-read-only))
+	(setq vm-summary-buffer
+	      (get-buffer-create (format "%s Summary" (buffer-name))))
+	(save-excursion
+	  (set-buffer vm-summary-buffer)
+	  (abbrev-mode 0)
+	  (auto-fill-mode 0)
+	  (if (fboundp 'buffer-disable-undo)
+	      (buffer-disable-undo (current-buffer))
+	    ;; obfuscation to make the v19 compiler not whine
+	    ;; about obsolete functions.
+	    (let ((x 'buffer-flush-undo))
+	      (funcall x (current-buffer))))
+	  (setq vm-mail-buffer b
+		vm-folder-read-only read-only)
+	  (vm-summary-mode-internal))
+	(vm-set-summary-redo-start-point t)))
+  (if display
+      (save-excursion
+	(if vm-frame-per-summary
+	    (let ((w (vm-get-buffer-window vm-summary-buffer)))
+	      (if (null w)
+		  (progn
+		    (vm-goto-new-frame 'summary)
+		    (vm-set-hooks-for-frame-deletion))
+		(save-excursion
+		  (select-window w)
+		  (and vm-warp-mouse-to-new-frame
+		       (vm-warp-mouse-to-frame-maybe (vm-window-frame w)))))))
+	(vm-display vm-summary-buffer t
+		    '(vm-summarize
+		      vm-summarize-other-frame)
+		    (list this-command))
+	;; need to do this after any frame creation because the
+	;; toolbar sets frame-specific height and width specifiers.
+	(set-buffer vm-summary-buffer)
+	(and (vm-toolbar-support-possible-p) vm-use-toolbar
+	     (vm-toolbar-install-toolbar)))
+    (vm-display nil nil '(vm-summarize vm-summarize-other-frame)
+		(list this-command)))
+  (vm-update-summary-and-mode-line))
+
+(defun vm-summarize-other-frame (&optional display)
+  "Like vm-summarize, but run in a newly created frame."
+  (interactive "p")
+  (if (vm-multiple-frames-possible-p)
+      (vm-goto-new-frame 'summary))
+  (vm-summarize display)
+  (if (vm-multiple-frames-possible-p)
+      (vm-set-hooks-for-frame-deletion)))
+
+(defun vm-do-summary (&optional start-point)
+  (let ((m-list (or start-point vm-message-list))
+	mp
+	(n 0)
+	;; Just for laughs, make the update interval vary.
+	(modulus (+ (% (vm-abs (random)) 11) 10))
+	(mouse-track-func
+	    (and (vm-mouse-support-possible-p)
+		 (vm-mouse-fsfemacs-mouse-p)
+		 (function vm-mouse-set-mouse-track-highlight)))
+	summary)
+    (setq mp m-list)
+    (save-excursion
+      (set-buffer vm-summary-buffer)
+      (let ((buffer-read-only nil)
+	    (modified (buffer-modified-p)))
+	(unwind-protect
+	    (progn
+	      (if start-point
+		  (if (vm-su-start-of (car mp))
+		      (progn
+			(goto-char (vm-su-start-of (car mp)))
+			(delete-region (point) (point-max)))
+		    (goto-char (point-max)))
+		(erase-buffer)
+		(setq vm-summary-pointer nil))
+	      ;; avoid doing long runs down the marker chain while
+	      ;; building the summary.  use integers to store positions
+	      ;; and then convert them to markers after all the
+	      ;; insertions are done.
+	      (while mp
+		(setq summary (vm-su-summary (car mp)))
+		(vm-set-su-start-of (car mp) (point))
+		(insert vm-summary-no-=>)
+		(vm-tokenized-summary-insert (car mp) (vm-su-summary (car mp)))
+		(vm-set-su-end-of (car mp) (point))
+		(setq mp (cdr mp) n (1+ n))
+		(if (zerop (% n modulus))
+		    (vm-unsaved-message "Generating summary... %d" n)))
+	      ;; now convert the ints to markers.
+	      (if (>= n modulus)
+		  (vm-unsaved-message "Generating summary markers... "))
+	      (setq mp m-list)
+	      (while mp
+		(and mouse-track-func (funcall mouse-track-func
+					       (vm-su-start-of (car mp))
+					       (vm-su-end-of (car mp))))
+		(vm-set-su-start-of (car mp) (vm-marker (vm-su-start-of (car mp))))
+		(vm-set-su-end-of (car mp) (vm-marker (vm-su-end-of (car mp))))
+		(setq mp (cdr mp))))
+	  (set-buffer-modified-p modified))
+	(run-hooks 'vm-summary-redo-hook)))
+    (if (>= n modulus)
+	(vm-unsaved-message "Generating summary... done"))))
+
+(defun vm-do-needed-summary-rebuild ()
+  (if (and vm-summary-redo-start-point vm-summary-buffer)
+      (progn
+	(vm-copy-local-variables vm-summary-buffer 'vm-summary-show-threads)
+	(vm-do-summary (and (consp vm-summary-redo-start-point)
+			    vm-summary-redo-start-point))
+	(setq vm-summary-redo-start-point nil)
+	(and vm-message-pointer
+	     (vm-set-summary-pointer (car vm-message-pointer)))
+	(setq vm-need-summary-pointer-update nil))
+    (and vm-need-summary-pointer-update
+	 vm-summary-buffer
+	 vm-message-pointer
+	 (progn
+	   (vm-set-summary-pointer (car vm-message-pointer))
+	   (setq vm-need-summary-pointer-update nil)))))
+
+(defun vm-update-message-summary (m)
+  (if (and (vm-su-start-of m)
+	   (marker-buffer (vm-su-start-of m)))
+      (let ((modified (buffer-modified-p))
+	    (mouse-track-func
+	     (and (vm-mouse-support-possible-p)
+		  (vm-mouse-fsfemacs-mouse-p)
+		  (function vm-mouse-set-mouse-track-highlight)))
+	    summary)
+	(save-excursion
+	  (setq summary (vm-su-summary m))
+	  (set-buffer (marker-buffer (vm-su-start-of m)))
+	  (let ((buffer-read-only nil)
+		(selected nil)
+		(modified (buffer-modified-p)))
+	    (unwind-protect
+		(save-excursion
+		  (goto-char (vm-su-start-of m))
+		  (setq selected (not (looking-at vm-summary-no-=>)))
+		  ;; We do a little dance to update the text in
+		  ;; order to make the markets in the text do
+		  ;; what we want.
+		  ;;
+		  ;; 1. We need to avoid having the su-start-of
+		  ;;    and su-end-of market clumping together at
+		  ;;    the start position.
+		  ;;
+		  ;; 2. We want the window point market (w->pointm
+		  ;;    in the Emacs display code) to move to the
+		  ;;    start of the summary entry if it is
+		  ;;    anywhere within the su-start-of to
+		  ;;    su-end-of region.
+		  ;;
+		  ;; We achieve (2) by deleting before inserting.
+		  ;; Reversing the order of insertion/deletion
+		  ;; pushes the point marker into the next
+		  ;; summary entry. We achieve (1) by inserting a
+		  ;; placeholder character at the end of the
+		  ;; summary entry before deleting the region.
+		  (goto-char (vm-su-end-of m))
+		  (insert-before-markers "z")
+		  (goto-char (vm-su-start-of m))
+		  (delete-region (point) (1- (vm-su-end-of m)))
+		  (if (not selected)
+		      (insert vm-summary-no-=>)
+		    (insert vm-summary-=>))
+		  (vm-tokenized-summary-insert m (vm-su-summary m))
+		  (delete-char 1)
+		  (run-hooks 'vm-summary-update-hook)
+		  (and mouse-track-func (funcall mouse-track-func
+						 (vm-su-start-of m)
+						 (vm-su-end-of m)))
+		  (if (and selected vm-summary-highlight-face)
+		      (vm-summary-highlight-region (vm-su-start-of m) (point)
+						   vm-summary-highlight-face)))
+	      (set-buffer-modified-p modified)))))))
+
+(defun vm-set-summary-pointer (m)
+  (if vm-summary-buffer
+      (let ((w (vm-get-visible-buffer-window vm-summary-buffer))
+	    (mouse-track-func
+	       (and (vm-mouse-support-possible-p)
+		    (vm-mouse-fsfemacs-mouse-p)
+		    (function vm-mouse-set-mouse-track-highlight)))
+	    (old-window nil))
+	(vm-save-buffer-excursion
+	  (unwind-protect
+	      (progn
+		(set-buffer vm-summary-buffer)
+		(if w
+		    (progn
+		      (setq old-window (selected-window))
+		      (select-window w)))
+		(let ((buffer-read-only nil))
+		  (if (and vm-summary-pointer
+			   (vm-su-start-of vm-summary-pointer))
+		      (progn
+			(goto-char (vm-su-start-of vm-summary-pointer))
+			(insert vm-summary-no-=>)
+			(delete-char (length vm-summary-=>))
+			(and mouse-track-func
+			     (funcall mouse-track-func
+				      (- (point) (length vm-summary-=>))
+					 (point)))))
+		  (setq vm-summary-pointer m)
+		  (goto-char (vm-su-start-of m))
+		  (let ((modified (buffer-modified-p)))
+		    (unwind-protect
+			(progn
+			  (insert vm-summary-=>)
+			  (delete-char (length vm-summary-=>))
+			  (and mouse-track-func
+			       (funcall mouse-track-func
+					(- (point) (length vm-summary-=>))
+					   (point))))
+		      (set-buffer-modified-p modified)))
+		  (forward-char (- (length vm-summary-=>)))
+		  (if vm-summary-highlight-face
+		      (vm-summary-highlight-region
+		       (vm-su-start-of m) (vm-su-end-of m)
+		       vm-summary-highlight-face))
+		  (and w vm-auto-center-summary (vm-auto-center-summary))
+		  (run-hooks 'vm-summary-pointer-update-hook)))
+	    (and old-window (select-window old-window)))))))
+
+(defun vm-summary-highlight-region (start end face)
+  (cond ((fboundp 'make-overlay)
+	 (if (and vm-summary-overlay (overlay-buffer vm-summary-overlay))
+	     (move-overlay vm-summary-overlay start end)
+	   (setq vm-summary-overlay (make-overlay start end))
+	   (overlay-put vm-summary-overlay 'evaporate nil)
+	   (overlay-put vm-summary-overlay 'face face)))
+	((fboundp 'make-extent)
+	 (if (and vm-summary-overlay (extent-end-position vm-summary-overlay))
+	     (set-extent-endpoints vm-summary-overlay start end)
+	   (setq vm-summary-overlay (make-extent start end))
+	   (set-extent-property vm-summary-overlay 'detachable nil)
+	   (set-extent-property vm-summary-overlay 'face face)))))
+
+(defun vm-auto-center-summary ()
+  (if vm-auto-center-summary
+      (if (or (eq vm-auto-center-summary t) (not (one-window-p t)))
+	  (recenter '(4)))))
+
+(defun vm-sprintf (format-variable message &optional tokenize)
+  ;; compile the format into an eval'able s-expression
+  ;; if it hasn't been compiled already.
+  (if (not (eq (get format-variable 'vm-compiled-format)
+	       (symbol-value format-variable)))
+      (vm-compile-format format-variable tokenize))
+  ;; The local variable name `vm-su-message' is mandatory here for
+  ;; the format s-expression to work.
+  (let ((vm-su-message message))
+    (eval (get format-variable 'vm-format-sexp))))
+
+(defun vm-tokenized-summary-insert (message tokens)
+  (if (stringp tokens)
+      (insert tokens)
+    (let (token)
+      (while tokens
+	(setq token (car tokens))
+	(cond ((stringp token)
+	       (insert token))
+	      ((eq token 'number)
+	       (insert (vm-padded-number-of message)))
+	      ((eq token 'mark)
+	       (insert (vm-su-mark message)))
+	      ((eq token 'thread-indent)
+	       (if (and vm-summary-show-threads
+			(natnump vm-summary-thread-indent-level))
+		   (insert-char ?\ (* vm-summary-thread-indent-level
+				      (vm-th-thread-indentation message))))))
+	(setq tokens (cdr tokens))))))
+
+(defun vm-compile-format (format-variable &optional tokenize)
+  (let ((format (symbol-value format-variable))
+	(case-fold-search nil)
+	(done nil)
+	(list nil)
+	(sexp nil)
+	(sexp-fmt nil)
+	(last-match-end 0)
+	token conv-spec)
+    (store-match-data nil)
+    (while (not done)
+      (setq token nil)
+      (while
+	  (and (not token)
+	       (string-match
+		"%\\(-\\)?\\([0-9]+\\)?\\(\\.\\(-?[0-9]+\\)\\)?\\([aAcdfFhHiIlLmMnstTwyz*%]\\|U[A-Za-z]\\)"
+		format (match-end 0)))
+	(setq conv-spec (aref format (match-beginning 5)))
+	(if (memq conv-spec '(?a ?A ?c ?d ?f ?F ?h ?H ?i ?L ?I ?l ?M
+				 ?m ?n ?s ?t ?T ?U ?w ?y ?z ?* ))
+	    (progn
+	      (cond ((= conv-spec ?a)
+		     (setq sexp (cons (list 'vm-su-attribute-indicators
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?A)
+		     (setq sexp (cons (list 'vm-su-attribute-indicators-long
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?c)
+		     (setq sexp (cons (list 'vm-su-byte-count
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?d)
+		     (setq sexp (cons (list 'vm-su-monthday
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?f)
+		     (setq sexp (cons (list 'vm-su-interesting-from
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?F)
+		     (setq sexp (cons (list 'vm-su-interesting-full-name
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?h)
+		     (setq sexp (cons (list 'vm-su-hour
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?H)
+		     (setq sexp (cons (list 'vm-su-hour-short
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?i)
+		     (setq sexp (cons (list 'vm-su-message-id
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?I)
+		     (if tokenize
+			 (setq token ''thread-indent)
+		       (setq sexp (cons (list 'vm-su-thread-indent
+					      'vm-su-message) sexp))))
+		    ((= conv-spec ?l)
+		     (setq sexp (cons (list 'vm-su-line-count
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?L)
+		     (setq sexp (cons (list 'vm-su-labels
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?m)
+		     (setq sexp (cons (list 'vm-su-month
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?M)
+		     (setq sexp (cons (list 'vm-su-month-number
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?n)
+		     (if tokenize
+			 (setq token ''number)
+		       (setq sexp (cons (list 'vm-padded-number-of
+					      'vm-su-message) sexp))))
+		    ((= conv-spec ?s)
+		     (setq sexp (cons (list 'vm-su-subject
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?T)
+		     (setq sexp (cons (list 'vm-su-to-names
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?t)
+		     (setq sexp (cons (list 'vm-su-to
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?U)
+		     (setq sexp
+			   (cons (list 'vm-run-user-summary-function
+				       (list 'quote
+					     (intern
+					      (concat
+					       "vm-summary-function-"
+					       (substring
+						format
+						(1+ (match-beginning 5))
+						(+ 2 (match-beginning 5))))))
+				       'vm-su-message) sexp)))
+		    ((= conv-spec ?w)
+		     (setq sexp (cons (list 'vm-su-weekday
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?y)
+		     (setq sexp (cons (list 'vm-su-year
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?z)
+		     (setq sexp (cons (list 'vm-su-zone
+					    'vm-su-message) sexp)))
+		    ((= conv-spec ?*)
+		     (if tokenize
+			 (setq token ''mark)
+		       (setq sexp (cons (list 'vm-su-mark
+					      'vm-su-message) sexp)))))
+	      (cond ((and (not token) (match-beginning 1))
+		     (setcar sexp
+			     (list 'vm-left-justify-string (car sexp)
+				   (string-to-int
+				    (substring format
+					       (match-beginning 2)
+					       (match-end 2))))))
+		    ((and (not token) (match-beginning 2))
+		     (setcar sexp
+			     (list 'vm-right-justify-string (car sexp)
+				   (string-to-int
+				    (substring format
+					       (match-beginning 2)
+					       (match-end 2)))))))
+	      (cond ((and (not token) (match-beginning 3))
+		     (setcar sexp
+			     (list 'vm-truncate-string (car sexp)
+				   (string-to-int
+				    (substring format
+					       (match-beginning 4)
+					       (match-end 4)))))))
+	      (setq sexp-fmt
+		    (cons (if token "" "%s")
+			  (cons (substring format
+					   last-match-end
+					   (match-beginning 0))
+				sexp-fmt))))
+	  (setq sexp-fmt
+		(cons "%%"
+		      (cons (substring format
+				       (or last-match-end 0)
+				       (match-beginning 0))
+			    sexp-fmt))))
+	  (setq last-match-end (match-end 0)))
+      (if (not token)
+	  (setq sexp-fmt
+		(cons (substring format last-match-end (length format))
+		      sexp-fmt)
+		done t))
+      (setq sexp-fmt (apply 'concat (nreverse sexp-fmt)))
+      (if sexp
+	  (setq sexp (cons 'format (cons sexp-fmt (nreverse sexp))))
+	(setq sexp sexp-fmt))
+      (if tokenize
+	  (setq list (nconc list (if (equal sexp "") nil (list sexp))
+			    (and token (list token)))
+		sexp nil
+		sexp-fmt nil)))
+    (put format-variable 'vm-compiled-format format)
+    (put format-variable 'vm-format-sexp (if list (cons 'list list) sexp))))
+
+(defun vm-get-header-contents (message header-name-regexp)
+  (let ((contents nil)
+	regexp)
+    (setq regexp (concat "^\\(" header-name-regexp "\\)")
+	  message (vm-real-message-of message))
+    (save-excursion
+      (set-buffer (vm-buffer-of (vm-real-message-of message)))
+      (save-restriction
+	(widen)
+	(goto-char (vm-headers-of message))
+	(let ((case-fold-search t))
+	  (while (and (re-search-forward regexp (vm-text-of message) t)
+		      (save-excursion (goto-char (match-beginning 0))
+				      (vm-match-header)))
+	    (if contents
+		(setq contents
+		      (concat contents ", " (vm-matched-header-contents)))
+	      (setq contents (vm-matched-header-contents))))))
+      contents )))
+
+(defun vm-left-justify-string (string width)
+  (if (>= (length string) width)
+      string
+    (concat string (make-string (- width (length string)) ?\ ))))
+
+(defun vm-right-justify-string (string width)
+  (if (>= (length string) width)
+      string
+    (concat (make-string (- width (length string)) ?\ ) string)))
+
+(defun vm-truncate-string (string width)
+  (cond ((<= (length string) width)
+	 string)
+	((< width 0)
+	 (substring string width))
+	(t
+	 (substring string 0 width))))
+
+(defun vm-su-attribute-indicators (m)
+  (concat
+   (cond ((vm-deleted-flag m) "D")
+	 ((vm-new-flag m) "N")
+	 ((vm-unread-flag m) "U")
+	 (t " "))
+   (cond ((vm-filed-flag m) "F")
+	 ((vm-written-flag m) "W")
+	 (t " "))
+   (cond ((vm-replied-flag m) "R")
+	 ((vm-forwarded-flag m) "Z")
+	 ((vm-redistributed-flag m) "B")
+	 (t " "))
+   (cond ((vm-edited-flag m) "E")
+	 (t " "))))
+
+(defun vm-su-attribute-indicators-long (m)
+  (concat
+   (cond ((vm-deleted-flag m) "D")
+	 ((vm-new-flag m) "N")
+	 ((vm-unread-flag m) "U")
+	 (t " "))
+   (if (vm-replied-flag m) "r" " ")
+   (if (vm-forwarded-flag m) "z" " ")
+   (if (vm-redistributed-flag m) "b" " ")
+   (if (vm-filed-flag m) "f" " ")
+   (if (vm-written-flag m) "w" " ")
+   (if (vm-edited-flag m) "e" " ")))
+
+(defun vm-su-byte-count (m)
+  (or (vm-byte-count-of m)
+      (vm-set-byte-count-of
+       m
+       (int-to-string
+	(- (vm-text-end-of (vm-real-message-of m))
+	   (vm-text-of (vm-real-message-of m)))))))
+
+(defun vm-su-weekday (m)
+  (or (vm-weekday-of m)
+      (progn (vm-su-do-date m) (vm-weekday-of m))))
+
+(defun vm-su-monthday (m)
+  (or (vm-monthday-of m)
+      (progn (vm-su-do-date m) (vm-monthday-of m))))
+
+(defun vm-su-month (m)
+  (or (vm-month-of m)
+      (progn (vm-su-do-date m) (vm-month-of m))))
+
+(defun vm-su-month-number (m)
+  (or (vm-month-number-of m)
+      (progn (vm-su-do-date m) (vm-month-number-of m))))
+
+(defun vm-su-year (m)
+  (or (vm-year-of m)
+      (progn (vm-su-do-date m) (vm-year-of m))))
+
+(defun vm-su-hour-short (m)
+  (let ((string (vm-su-hour m)))
+    (if (> (length string) 5)
+	(substring string 0 5)
+      string)))
+
+(defun vm-su-hour (m)
+  (or (vm-hour-of m)
+      (progn (vm-su-do-date m) (vm-hour-of m))))
+
+(defun vm-su-zone (m)
+  (or (vm-zone-of m)
+      (progn (vm-su-do-date m) (vm-zone-of m))))
+
+(defun vm-su-mark (m) (if (vm-mark-of m) "*" " "))
+
+;; Some yogurt-headed delivery agents don't provide a Date: header.
+(defun vm-grok-From_-date (message)
+  ;; This works only on the From_ types, obviously
+  (if (not (memq (vm-message-type-of message)
+		 '(From_ From_-with-Content-Length)))
+      nil
+    (save-excursion
+      (set-buffer (vm-buffer-of (vm-real-message-of message)))
+      (save-restriction
+	(widen)
+	(goto-char (vm-start-of message))
+	(let ((case-fold-search nil))
+	  (if (or (looking-at
+		   ;; special case this so that the "remote from blah"
+		   ;; isn't included.
+		   "From [^ \t\n]*[ \t]+\\([^ \t\n].*\\) remote from .*")
+		  (looking-at "From [^ \t\n]*[ \t]+\\([^ \t\n].*\\)"))
+	      (vm-buffer-substring-no-properties
+	       (match-beginning 1)
+	       (match-end 1))))))))
+
+(defun vm-parse-date (date)
+  (let ((weekday "")
+	(monthday "")
+	(month "")
+	(year "")
+	(hour "")
+	(timezone "")
+	(start nil)
+	string
+	(case-fold-search t))
+    (if (string-match "sun\\|mon\\|tue\\|wed\\|thu\\|fri\\|sat" date)
+	(setq weekday (substring date (match-beginning 0) (match-end 0))))
+    (if (string-match "jan\\|feb\\|mar\\|apr\\|may\\|jun\\|jul\\|aug\\|sep\\|oct\\|nov\\|dec" date)
+	(setq month (substring date (match-beginning 0) (match-end 0))))
+    (if (string-match "[0-9]?[0-9]:[0-9][0-9]\\(:[0-9][0-9]\\)?" date)
+	(setq hour (substring date (match-beginning 0) (match-end 0))))
+    (if (or (string-match "[^a-z][+---][0-9][0-9][0-9][0-9]" date)
+	    (string-match "e[ds]t\\|c[ds]t\\|p[ds]t\\|m[ds]t" date)
+	    (string-match "ast\\|nst\\|met\\|eet\\|jst\\|bst\\|ut" date)
+	    (string-match "gmt\\([+---][0-9]+\\)?" date))
+	(setq timezone (substring date (match-beginning 0) (match-end 0))))
+    (while (string-match "\\(\\`\\|[^:+---0-9]\\|[a-z]-\\)[0-9]+\\(\\'\\|[^:]\\)"
+			 date start)
+      (setq string (substring date (match-end 1) (match-beginning 2))
+	    start (match-end 0))
+      (cond ((string-match "\\`[4-9]." string)
+	     ;; Assume that any two digits less than 40 are a date and not
+	     ;; a year.  The world will surely end soon.
+	     (setq year (concat "19" string)))
+	    ((< (length string) 3)
+	     (setq monthday string))
+	    (t (setq year string))))
+    
+    (aset vm-parse-date-workspace 0 weekday)
+    (aset vm-parse-date-workspace 1 monthday)
+    (aset vm-parse-date-workspace 2 month)
+    (aset vm-parse-date-workspace 3 year)
+    (aset vm-parse-date-workspace 4 hour)
+    (aset vm-parse-date-workspace 5 timezone)
+    vm-parse-date-workspace))
+
+(defun vm-su-do-date (m)
+  (let ((case-fold-search t)
+	vector date)
+    (setq date (or (vm-get-header-contents m "Date:") (vm-grok-From_-date m)))
+    (cond
+     ((null date)
+      (vm-set-weekday-of m "")
+      (vm-set-monthday-of m "")
+      (vm-set-month-of m "")
+      (vm-set-month-number-of m "")
+      (vm-set-year-of m "")
+      (vm-set-hour-of m "")
+      (vm-set-zone-of m ""))
+     ((string-match
+;; The date format recognized here is the one specified in RFC 822.
+;; Some slop is allowed e.g. dashes between the monthday, month and year
+;; because such malformed headers have been observed.
+"\\(\\([a-z][a-z][a-z]\\),\\)?[ \t\n]*\\([0-9][0-9]?\\)[ \t\n---]*\\([a-z][a-z][a-z]\\)[ \t\n---]*\\([0-9]*[0-9][0-9]\\)[ \t\n]*\\([0-9:]+\\)[ \t\n]*\\([a-z][a-z]?[a-z]?\\|[---+][0-9][0-9][0-9][0-9]\\)"
+       date)
+      (if (match-beginning 2)
+	  (vm-set-weekday-of m (substring date (match-beginning 2)
+					  (match-end 2)))
+	(vm-set-weekday-of m ""))
+      (vm-set-monthday-of m (substring date (match-beginning 3) (match-end 3)))
+      (vm-su-do-month m (substring date (match-beginning 4) (match-end 4)))
+      (vm-set-year-of m (substring date (match-beginning 5) (match-end 5)))
+      (if (= 2 (length (vm-year-of m)))
+	  (vm-set-year-of m (concat "19" (vm-year-of m))))
+      (vm-set-hour-of m (substring date (match-beginning 6) (match-end 6)))
+      (vm-set-zone-of m (substring date (match-beginning 7) (match-end 7))))
+     ((string-match
+;; UNIX ctime(3) format, with slop allowed in the whitespace, and we allow for
+;; the possibility of a timezone at the end.
+"\\([a-z][a-z][a-z]\\)[ \t\n]*\\([a-z][a-z][a-z]\\)[ \t\n]*\\([0-9][0-9]?\\)[ \t\n]*\\([0-9:]+\\)[ \t\n]*\\([0-9][0-9][0-9][0-9]\\)[ \t\n]*\\([a-z][a-z]?[a-z]?\\|[---+][0-9][0-9][0-9][0-9]\\)?"
+       date)
+      (vm-set-weekday-of m (substring date (match-beginning 1) (match-end 1)))
+      (vm-su-do-month m (substring date (match-beginning 2) (match-end 2)))
+      (vm-set-monthday-of m (substring date (match-beginning 3) (match-end 3)))
+      (vm-set-hour-of m (substring date (match-beginning 4) (match-end 4)))
+      (vm-set-year-of m (substring date (match-beginning 5) (match-end 5)))
+      (if (match-beginning 6)
+	  (vm-set-zone-of m (substring date (match-beginning 6)
+				       (match-end 6)))))
+     (t
+      (setq vector (vm-parse-date date))
+      (vm-set-weekday-of m (elt vector 0))
+      (vm-set-monthday-of m (elt vector 1))
+      (vm-su-do-month m (elt vector 2))
+      (vm-set-year-of m (elt vector 3))
+      (vm-set-hour-of m (elt vector 4))
+      (vm-set-zone-of m (elt vector 5)))))
+
+  ;; Normalize all hour and date specifications to avoid jagged margins.
+  ;; If the hour is " 3:..." or "3:...", turn it into "03:...".
+  ;; If the date is "03", turn it into " 3".
+  (cond ((null (vm-hour-of m)) nil)
+	((string-match "\\`[0-9]:" (vm-hour-of m))
+	 (vm-set-hour-of m (concat "0" (vm-hour-of m)))))
+  (cond ((null (vm-monthday-of m)) nil)
+	((string-match "\\`0[0-9]\\'" (vm-monthday-of m))
+	 (vm-set-monthday-of m (substring (vm-monthday-of m) 1 2))))
+  )
+
+(defun vm-su-do-month (m month-abbrev)
+  (let ((val (assoc (downcase month-abbrev) vm-month-alist)))
+    (if val
+	(progn (vm-set-month-of m (nth 1 val))
+	       (vm-set-month-number-of m (nth 2 val)))
+      (vm-set-month-of m "")
+      (vm-set-month-number-of m ""))))
+
+(defun vm-run-user-summary-function (function message)
+  (let ((message (vm-real-message-of message)))
+    (save-excursion
+      (set-buffer (vm-buffer-of message))
+      (save-restriction
+	(widen)
+	(save-excursion
+	  (narrow-to-region (vm-headers-of message) (vm-text-end-of message))
+	  (funcall function message))))))
+
+(defun vm-su-full-name (m)
+  (or (vm-full-name-of m)
+      (progn (vm-su-do-author m) (vm-full-name-of m))))
+
+(defun vm-su-interesting-full-name (m)
+  (if vm-summary-uninteresting-senders
+      (let ((case-fold-search nil))
+	(if (string-match vm-summary-uninteresting-senders (vm-su-from m))
+	    (concat vm-summary-uninteresting-senders-arrow (vm-su-to-names m))
+	  (vm-su-full-name m)))
+    (vm-su-full-name m)))
+
+(defun vm-su-from (m)
+  (or (vm-from-of m)
+      (progn (vm-su-do-author m) (vm-from-of m))))
+
+(defun vm-su-interesting-from (m)
+  (if vm-summary-uninteresting-senders
+      (let ((case-fold-search nil))
+	(if (string-match vm-summary-uninteresting-senders (vm-su-from m))
+	    (concat vm-summary-uninteresting-senders-arrow (vm-su-to m))
+	  (vm-su-from m)))
+    (vm-su-from m)))
+
+;; Some yogurt-headed delivery agents don't even provide a From: header.
+(defun vm-grok-From_-author (message)
+  ;; This works only on the From_ types, obviously
+  (if (not (memq (vm-message-type-of message)
+		 '(From_ From_-with-Content-Length)))
+      nil
+    (save-excursion
+      (set-buffer (vm-buffer-of message))
+      (save-restriction
+	(widen)
+	(goto-char (vm-start-of message))
+	(let ((case-fold-search nil))
+	  (if (looking-at "From \\([^ \t\n]+\\)")
+	      (vm-buffer-substring-no-properties
+	       (match-beginning 1)
+	       (match-end 1))))))))
+
+(defun vm-su-do-author (m)
+  (let ((full-name (vm-get-header-contents m "Full-Name:"))
+	(from (or (vm-get-header-contents m "From:")
+		  (vm-grok-From_-author m)))
+	pair)
+    (if (and full-name (string-match "^[ \t]*$" full-name))
+	(setq full-name nil))
+    (if (null from)
+	(progn
+	  (setq from "???")
+	  (if (null full-name)
+	      (setq full-name "???")))
+      (setq pair (funcall vm-chop-full-name-function from)
+	    from (or (nth 1 pair) from)
+	    full-name (or full-name (nth 0 pair) from)))
+    (if (string-match "\\`\"\\([^\"]+\\)\"\\'" full-name)
+ 	(setq full-name
+ 	      (substring full-name (match-beginning 1) (match-end 1))))
+    (vm-set-full-name-of m full-name)
+    (vm-set-from-of m from)))
+
+(defun vm-default-chop-full-name (address)
+  (let ((from address)
+	(full-name nil))
+    (cond ((string-match
+"\\`[ \t\n]*\\([^< \t\n]+\\([ \t\n]+[^< \t\n]+\\)*\\)?[ \t\n]*<\\([^>]+\\)>[ \t\n]*\\'"
+			 address)
+	   (if (match-beginning 1)
+	       (setq full-name
+		     (substring address (match-beginning 1) (match-end 1))))
+	   (setq from
+		 (substring address (match-beginning 3) (match-end 3))))
+	  ((string-match
+"\\`[ \t\n]*\\(\\(\"[^\"]+\"\\|[^\"( \t\n]\\)+\\)[ \t\n]*(\\([^ \t\n]+\\([ \t\n]+[^ \t\n]+\\)*\\)?)[ \t\n]*\\'"
+			 address)
+	   (if (match-beginning 3)
+	       (setq full-name
+		     (substring address (match-beginning 3) (match-end 3))))
+	   (setq from
+		 (substring address (match-beginning 1) (match-end 1)))))
+    (list full-name from)))
+
+;; test for existence and functionality of mail-extract-address-components
+;; there are versions out there that don't work right, so we run
+;; some test data through it to see if we can trust it.
+(defun vm-choose-chop-full-name-function (address)
+  (let ((test-data '(("kyle@uunet.uu.net" .
+		      (nil "kyle@uunet.uu.net"))
+		     ("c++std=lib@inet.research.att.com" .
+		      (nil "c++std=lib@inet.research.att.com"))
+		     ("\"Piet.Rypens\" <rypens@reks.uia.ac.be>" .
+		      ("Piet Rypens" "rypens@reks.uia.ac.be"))
+		     ("makke@wins.uia.ac.be (Marc.Gemis)" .
+		      ("Marc Gemis" "makke@wins.uia.ac.be"))
+		     ("" . (nil nil))))
+	(failed nil)
+	result)
+    (while test-data
+      (setq result (condition-case nil
+		       (mail-extract-address-components (car (car test-data)))
+		     (error nil)))
+      (if (not (equal result (cdr (car test-data))))
+	  ;; failed test, use default
+	  (setq failed t
+		test-data nil)
+	(setq test-data (cdr test-data))))
+    (if failed
+	;; it failed, use default
+	(setq vm-chop-full-name-function 'vm-default-chop-full-name)
+      ;; it passed the tests
+      (setq vm-chop-full-name-function 'mail-extract-address-components))
+    (funcall vm-chop-full-name-function address)))
+
+(defun vm-su-do-recipients (m)
+  (let ((mail-use-rfc822 t) names addresses to cc all list)
+    (setq to (or (vm-get-header-contents m "To:")
+		 (vm-get-header-contents m "Apparently-To:")
+		 ;; desperation....
+		 (user-login-name))
+	  cc (vm-get-header-contents m "Cc:")
+	  all to
+	  all (if all (concat all ", " cc) cc)
+	  addresses (rfc822-addresses all))
+    (setq list (vm-parse-addresses all))
+    (while list
+      (cond ((string= (car list) ""))
+	    ((string-match "^\\(\"?\\([^<]+[^ \t\n\"]\\)\"?[ \t\n]+\\)?<\\([^>]+\\)>"
+			   (car list))
+	     (if (match-beginning 2)
+		 (setq names
+		       (cons
+			(substring (car list) (match-beginning 2)
+				   (match-end 2))
+			names))
+	       (setq names
+		     (cons
+		      (substring (car list) (match-beginning 3)
+				 (match-end 3))
+		      names))))
+	    ((string-match "[\000-\177]*(\\([^)]+\\))[\000-\177]*" (car list))
+	     (setq names
+		   (cons (substring (car list) (match-beginning 1)
+				    (match-end 1))
+			 names)))
+	    (t (setq names (cons (car list) names))))
+      (setq list (cdr list)))
+    (setq names (nreverse names)) ; added by jwz for fixed vm-parse-addresses
+    (vm-set-to-of m (mapconcat 'identity addresses ", "))
+    (vm-set-to-names-of m (mapconcat 'identity names ", "))))
+
+(defun vm-su-to (m)
+  (or (vm-to-of m) (progn (vm-su-do-recipients m) (vm-to-of m))))
+
+(defun vm-su-to-names (m)
+  (or (vm-to-names-of m) (progn (vm-su-do-recipients m) (vm-to-names-of m))))
+				  
+(defun vm-su-message-id (m)
+  (or (vm-message-id-of m)
+      (vm-set-message-id-of
+       m
+       (or (vm-get-header-contents m "Message-Id:")
+	   ;; try running md5 on the message body to produce an ID
+	   ;; better than nothing.
+	   (save-excursion
+	     (set-buffer (vm-buffer-of (vm-real-message-of m)))
+	     (save-restriction
+	       (widen)
+	       (condition-case nil
+		   (concat "<fake-VM-id."
+			   (vm-pop-md5-string
+			    (buffer-substring
+			     (vm-text-of (vm-real-message-of m))
+			     (vm-text-end-of (vm-real-message-of m))))
+			   "@talos.iv>")
+		 (error nil))))
+	   (concat "<" (int-to-string (vm-abs (random))) "@toto.iv>")))))
+
+(defun vm-su-line-count (m)
+  (or (vm-line-count-of m)
+      (vm-set-line-count-of
+       m
+       (save-excursion
+	 (set-buffer (vm-buffer-of (vm-real-message-of m)))
+	 (save-restriction
+	   (widen)
+	   (int-to-string
+	    (count-lines (vm-text-of (vm-real-message-of m))
+			 (vm-text-end-of (vm-real-message-of m)))))))))
+
+(defun vm-su-subject (m)
+  (or (vm-subject-of m)
+      (vm-set-subject-of
+       m
+       (let ((subject (or (vm-get-header-contents m "Subject:") ""))
+	     (i nil))
+	 (if vm-summary-subject-no-newlines
+	     (while (setq i (string-match "\n" subject i))
+	       (aset subject i ?\ )))
+	 subject ))))
+
+(defun vm-su-summary (m)
+  (if (and (vm-virtual-message-p m) (not (vm-virtual-messages-of m)))
+      (or (vm-virtual-summary-of m)
+	  (save-excursion
+	    (vm-select-folder-buffer)
+	    (vm-set-virtual-summary-of m (vm-sprintf 'vm-summary-format m t))
+	    (vm-virtual-summary-of m)))
+    (or (vm-summary-of m)
+	(save-excursion
+	  (vm-select-folder-buffer)
+	  (vm-set-summary-of m (vm-sprintf 'vm-summary-format m t))
+	  (vm-summary-of m)))))
+
+(defun vm-fix-my-summary!!! ()
+  (interactive)
+  (vm-select-folder-buffer)
+  (vm-check-for-killed-summary)
+  (vm-error-if-folder-empty)
+  (vm-unsaved-message "Fixing your summary...")
+  (let ((mp vm-message-list))
+    (while mp
+      (vm-set-summary-of (car mp) nil)
+      (vm-mark-for-summary-update (car mp))
+      (vm-stuff-attributes (car mp))
+      (setq mp (cdr mp)))
+    (set-buffer-modified-p t)
+    (vm-update-summary-and-mode-line))
+  (vm-unsaved-message "Fixing your summary... done"))
+
+(defun vm-su-thread-indent (m)
+  (if (natnump vm-summary-thread-indent-level)
+      (make-string (* (vm-th-thread-indentation m)
+		      vm-summary-thread-indent-level)
+		   ?\ )
+    "" ))
+
+(defun vm-su-labels (m)
+  (or (vm-label-string-of m)
+      (vm-set-label-string-of
+       m
+       (mapconcat 'identity (vm-labels-of m) ","))
+      (vm-label-string-of m)))