Mercurial > hg > xemacs-beta
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:376386a54a3c |
---|---|
1 ;;; Summary gathering and formatting routines for VM | |
2 ;;; Copyright (C) 1989, 1990, 1993, 1994, 1995 Kyle E. Jones | |
3 ;;; | |
4 ;;; This program is free software; you can redistribute it and/or modify | |
5 ;;; it under the terms of the GNU General Public License as published by | |
6 ;;; the Free Software Foundation; either version 1, or (at your option) | |
7 ;;; any later version. | |
8 ;;; | |
9 ;;; This program is distributed in the hope that it will be useful, | |
10 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 ;;; GNU General Public License for more details. | |
13 ;;; | |
14 ;;; You should have received a copy of the GNU General Public License | |
15 ;;; along with this program; if not, write to the Free Software | |
16 ;;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
17 | |
18 (provide 'vm-summary) | |
19 | |
20 (defun vm-summary-mode-internal () | |
21 (setq mode-name "VM Summary" | |
22 major-mode 'vm-summary-mode | |
23 mode-line-format vm-mode-line-format | |
24 ;; must come after the setting of major-mode | |
25 mode-popup-menu (and vm-use-menus | |
26 (vm-menu-support-possible-p) | |
27 (vm-menu-mode-menu)) | |
28 buffer-read-only t | |
29 vm-summary-pointer nil | |
30 vm-summary-=> (if (stringp vm-summary-arrow) vm-summary-arrow "") | |
31 vm-summary-no-=> (make-string (length vm-summary-=>) ? ) | |
32 truncate-lines t) | |
33 ;; horizontal scrollbar off by default | |
34 ;; user can turn it on in summary hook if desired. | |
35 (and (fboundp 'set-specifier) | |
36 scrollbar-height | |
37 (set-specifier scrollbar-height (cons (current-buffer) 0))) | |
38 (use-local-map vm-summary-mode-map) | |
39 (and (vm-menu-support-possible-p) | |
40 (vm-menu-install-menus)) | |
41 (and (vm-mouse-support-possible-p) | |
42 (vm-mouse-xemacs-mouse-p) | |
43 (add-hook 'mode-motion-hook 'mode-motion-highlight-line)) | |
44 (if (or vm-frame-per-folder vm-frame-per-summary) | |
45 (vm-set-hooks-for-frame-deletion)) | |
46 (run-hooks 'vm-summary-mode-hook) | |
47 ;; Lucid Emacs apparently used this name | |
48 (run-hooks 'vm-summary-mode-hooks)) | |
49 | |
50 (fset 'vm-summary-mode 'vm-mode) | |
51 (put 'vm-summary-mode 'mode-class 'special) | |
52 | |
53 (defun vm-summarize (&optional display) | |
54 "Summarize the contents of the folder in a summary buffer. | |
55 The format is as described by the variable vm-summary-format. Generally | |
56 one line per message is most pleasing to the eye but this is not | |
57 mandatory." | |
58 (interactive "p") | |
59 (vm-select-folder-buffer) | |
60 (vm-check-for-killed-summary) | |
61 (if (null vm-summary-buffer) | |
62 (let ((b (current-buffer)) | |
63 (read-only vm-folder-read-only)) | |
64 (setq vm-summary-buffer | |
65 (get-buffer-create (format "%s Summary" (buffer-name)))) | |
66 (save-excursion | |
67 (set-buffer vm-summary-buffer) | |
68 (abbrev-mode 0) | |
69 (auto-fill-mode 0) | |
70 (if (fboundp 'buffer-disable-undo) | |
71 (buffer-disable-undo (current-buffer)) | |
72 ;; obfuscation to make the v19 compiler not whine | |
73 ;; about obsolete functions. | |
74 (let ((x 'buffer-flush-undo)) | |
75 (funcall x (current-buffer)))) | |
76 (setq vm-mail-buffer b | |
77 vm-folder-read-only read-only) | |
78 (vm-summary-mode-internal)) | |
79 (vm-set-summary-redo-start-point t))) | |
80 (if display | |
81 (save-excursion | |
82 (if vm-frame-per-summary | |
83 (let ((w (vm-get-buffer-window vm-summary-buffer))) | |
84 (if (null w) | |
85 (progn | |
86 (vm-goto-new-frame 'summary) | |
87 (vm-set-hooks-for-frame-deletion)) | |
88 (save-excursion | |
89 (select-window w) | |
90 (and vm-warp-mouse-to-new-frame | |
91 (vm-warp-mouse-to-frame-maybe (vm-window-frame w))))))) | |
92 (vm-display vm-summary-buffer t | |
93 '(vm-summarize | |
94 vm-summarize-other-frame) | |
95 (list this-command)) | |
96 ;; need to do this after any frame creation because the | |
97 ;; toolbar sets frame-specific height and width specifiers. | |
98 (set-buffer vm-summary-buffer) | |
99 (and (vm-toolbar-support-possible-p) vm-use-toolbar | |
100 (vm-toolbar-install-toolbar))) | |
101 (vm-display nil nil '(vm-summarize vm-summarize-other-frame) | |
102 (list this-command))) | |
103 (vm-update-summary-and-mode-line)) | |
104 | |
105 (defun vm-summarize-other-frame (&optional display) | |
106 "Like vm-summarize, but run in a newly created frame." | |
107 (interactive "p") | |
108 (if (vm-multiple-frames-possible-p) | |
109 (vm-goto-new-frame 'summary)) | |
110 (vm-summarize display) | |
111 (if (vm-multiple-frames-possible-p) | |
112 (vm-set-hooks-for-frame-deletion))) | |
113 | |
114 (defun vm-do-summary (&optional start-point) | |
115 (let ((m-list (or start-point vm-message-list)) | |
116 mp | |
117 (n 0) | |
118 ;; Just for laughs, make the update interval vary. | |
119 (modulus (+ (% (vm-abs (random)) 11) 10)) | |
120 (mouse-track-func | |
121 (and (vm-mouse-support-possible-p) | |
122 (vm-mouse-fsfemacs-mouse-p) | |
123 (function vm-mouse-set-mouse-track-highlight))) | |
124 summary) | |
125 (setq mp m-list) | |
126 (save-excursion | |
127 (set-buffer vm-summary-buffer) | |
128 (let ((buffer-read-only nil) | |
129 (modified (buffer-modified-p))) | |
130 (unwind-protect | |
131 (progn | |
132 (if start-point | |
133 (if (vm-su-start-of (car mp)) | |
134 (progn | |
135 (goto-char (vm-su-start-of (car mp))) | |
136 (delete-region (point) (point-max))) | |
137 (goto-char (point-max))) | |
138 (erase-buffer) | |
139 (setq vm-summary-pointer nil)) | |
140 ;; avoid doing long runs down the marker chain while | |
141 ;; building the summary. use integers to store positions | |
142 ;; and then convert them to markers after all the | |
143 ;; insertions are done. | |
144 (while mp | |
145 (setq summary (vm-su-summary (car mp))) | |
146 (vm-set-su-start-of (car mp) (point)) | |
147 (insert vm-summary-no-=>) | |
148 (vm-tokenized-summary-insert (car mp) (vm-su-summary (car mp))) | |
149 (vm-set-su-end-of (car mp) (point)) | |
150 (setq mp (cdr mp) n (1+ n)) | |
151 (if (zerop (% n modulus)) | |
152 (vm-unsaved-message "Generating summary... %d" n))) | |
153 ;; now convert the ints to markers. | |
154 (if (>= n modulus) | |
155 (vm-unsaved-message "Generating summary markers... ")) | |
156 (setq mp m-list) | |
157 (while mp | |
158 (and mouse-track-func (funcall mouse-track-func | |
159 (vm-su-start-of (car mp)) | |
160 (vm-su-end-of (car mp)))) | |
161 (vm-set-su-start-of (car mp) (vm-marker (vm-su-start-of (car mp)))) | |
162 (vm-set-su-end-of (car mp) (vm-marker (vm-su-end-of (car mp)))) | |
163 (setq mp (cdr mp)))) | |
164 (set-buffer-modified-p modified)) | |
165 (run-hooks 'vm-summary-redo-hook))) | |
166 (if (>= n modulus) | |
167 (vm-unsaved-message "Generating summary... done")))) | |
168 | |
169 (defun vm-do-needed-summary-rebuild () | |
170 (if (and vm-summary-redo-start-point vm-summary-buffer) | |
171 (progn | |
172 (vm-copy-local-variables vm-summary-buffer 'vm-summary-show-threads) | |
173 (vm-do-summary (and (consp vm-summary-redo-start-point) | |
174 vm-summary-redo-start-point)) | |
175 (setq vm-summary-redo-start-point nil) | |
176 (and vm-message-pointer | |
177 (vm-set-summary-pointer (car vm-message-pointer))) | |
178 (setq vm-need-summary-pointer-update nil)) | |
179 (and vm-need-summary-pointer-update | |
180 vm-summary-buffer | |
181 vm-message-pointer | |
182 (progn | |
183 (vm-set-summary-pointer (car vm-message-pointer)) | |
184 (setq vm-need-summary-pointer-update nil))))) | |
185 | |
186 (defun vm-update-message-summary (m) | |
187 (if (and (vm-su-start-of m) | |
188 (marker-buffer (vm-su-start-of m))) | |
189 (let ((modified (buffer-modified-p)) | |
190 (mouse-track-func | |
191 (and (vm-mouse-support-possible-p) | |
192 (vm-mouse-fsfemacs-mouse-p) | |
193 (function vm-mouse-set-mouse-track-highlight))) | |
194 summary) | |
195 (save-excursion | |
196 (setq summary (vm-su-summary m)) | |
197 (set-buffer (marker-buffer (vm-su-start-of m))) | |
198 (let ((buffer-read-only nil) | |
199 (selected nil) | |
200 (modified (buffer-modified-p))) | |
201 (unwind-protect | |
202 (save-excursion | |
203 (goto-char (vm-su-start-of m)) | |
204 (setq selected (not (looking-at vm-summary-no-=>))) | |
205 ;; We do a little dance to update the text in | |
206 ;; order to make the markets in the text do | |
207 ;; what we want. | |
208 ;; | |
209 ;; 1. We need to avoid having the su-start-of | |
210 ;; and su-end-of market clumping together at | |
211 ;; the start position. | |
212 ;; | |
213 ;; 2. We want the window point market (w->pointm | |
214 ;; in the Emacs display code) to move to the | |
215 ;; start of the summary entry if it is | |
216 ;; anywhere within the su-start-of to | |
217 ;; su-end-of region. | |
218 ;; | |
219 ;; We achieve (2) by deleting before inserting. | |
220 ;; Reversing the order of insertion/deletion | |
221 ;; pushes the point marker into the next | |
222 ;; summary entry. We achieve (1) by inserting a | |
223 ;; placeholder character at the end of the | |
224 ;; summary entry before deleting the region. | |
225 (goto-char (vm-su-end-of m)) | |
226 (insert-before-markers "z") | |
227 (goto-char (vm-su-start-of m)) | |
228 (delete-region (point) (1- (vm-su-end-of m))) | |
229 (if (not selected) | |
230 (insert vm-summary-no-=>) | |
231 (insert vm-summary-=>)) | |
232 (vm-tokenized-summary-insert m (vm-su-summary m)) | |
233 (delete-char 1) | |
234 (run-hooks 'vm-summary-update-hook) | |
235 (and mouse-track-func (funcall mouse-track-func | |
236 (vm-su-start-of m) | |
237 (vm-su-end-of m))) | |
238 (if (and selected vm-summary-highlight-face) | |
239 (vm-summary-highlight-region (vm-su-start-of m) (point) | |
240 vm-summary-highlight-face))) | |
241 (set-buffer-modified-p modified))))))) | |
242 | |
243 (defun vm-set-summary-pointer (m) | |
244 (if vm-summary-buffer | |
245 (let ((w (vm-get-visible-buffer-window vm-summary-buffer)) | |
246 (mouse-track-func | |
247 (and (vm-mouse-support-possible-p) | |
248 (vm-mouse-fsfemacs-mouse-p) | |
249 (function vm-mouse-set-mouse-track-highlight))) | |
250 (old-window nil)) | |
251 (vm-save-buffer-excursion | |
252 (unwind-protect | |
253 (progn | |
254 (set-buffer vm-summary-buffer) | |
255 (if w | |
256 (progn | |
257 (setq old-window (selected-window)) | |
258 (select-window w))) | |
259 (let ((buffer-read-only nil)) | |
260 (if (and vm-summary-pointer | |
261 (vm-su-start-of vm-summary-pointer)) | |
262 (progn | |
263 (goto-char (vm-su-start-of vm-summary-pointer)) | |
264 (insert vm-summary-no-=>) | |
265 (delete-char (length vm-summary-=>)) | |
266 (and mouse-track-func | |
267 (funcall mouse-track-func | |
268 (- (point) (length vm-summary-=>)) | |
269 (point))))) | |
270 (setq vm-summary-pointer m) | |
271 (goto-char (vm-su-start-of m)) | |
272 (let ((modified (buffer-modified-p))) | |
273 (unwind-protect | |
274 (progn | |
275 (insert vm-summary-=>) | |
276 (delete-char (length vm-summary-=>)) | |
277 (and mouse-track-func | |
278 (funcall mouse-track-func | |
279 (- (point) (length vm-summary-=>)) | |
280 (point)))) | |
281 (set-buffer-modified-p modified))) | |
282 (forward-char (- (length vm-summary-=>))) | |
283 (if vm-summary-highlight-face | |
284 (vm-summary-highlight-region | |
285 (vm-su-start-of m) (vm-su-end-of m) | |
286 vm-summary-highlight-face)) | |
287 (and w vm-auto-center-summary (vm-auto-center-summary)) | |
288 (run-hooks 'vm-summary-pointer-update-hook))) | |
289 (and old-window (select-window old-window))))))) | |
290 | |
291 (defun vm-summary-highlight-region (start end face) | |
292 (cond ((fboundp 'make-overlay) | |
293 (if (and vm-summary-overlay (overlay-buffer vm-summary-overlay)) | |
294 (move-overlay vm-summary-overlay start end) | |
295 (setq vm-summary-overlay (make-overlay start end)) | |
296 (overlay-put vm-summary-overlay 'evaporate nil) | |
297 (overlay-put vm-summary-overlay 'face face))) | |
298 ((fboundp 'make-extent) | |
299 (if (and vm-summary-overlay (extent-end-position vm-summary-overlay)) | |
300 (set-extent-endpoints vm-summary-overlay start end) | |
301 (setq vm-summary-overlay (make-extent start end)) | |
302 (set-extent-property vm-summary-overlay 'detachable nil) | |
303 (set-extent-property vm-summary-overlay 'face face))))) | |
304 | |
305 (defun vm-auto-center-summary () | |
306 (if vm-auto-center-summary | |
307 (if (or (eq vm-auto-center-summary t) (not (one-window-p t))) | |
308 (recenter '(4))))) | |
309 | |
310 (defun vm-sprintf (format-variable message &optional tokenize) | |
311 ;; compile the format into an eval'able s-expression | |
312 ;; if it hasn't been compiled already. | |
313 (if (not (eq (get format-variable 'vm-compiled-format) | |
314 (symbol-value format-variable))) | |
315 (vm-compile-format format-variable tokenize)) | |
316 ;; The local variable name `vm-su-message' is mandatory here for | |
317 ;; the format s-expression to work. | |
318 (let ((vm-su-message message)) | |
319 (eval (get format-variable 'vm-format-sexp)))) | |
320 | |
321 (defun vm-tokenized-summary-insert (message tokens) | |
322 (if (stringp tokens) | |
323 (insert tokens) | |
324 (let (token) | |
325 (while tokens | |
326 (setq token (car tokens)) | |
327 (cond ((stringp token) | |
328 (insert token)) | |
329 ((eq token 'number) | |
330 (insert (vm-padded-number-of message))) | |
331 ((eq token 'mark) | |
332 (insert (vm-su-mark message))) | |
333 ((eq token 'thread-indent) | |
334 (if (and vm-summary-show-threads | |
335 (natnump vm-summary-thread-indent-level)) | |
336 (insert-char ?\ (* vm-summary-thread-indent-level | |
337 (vm-th-thread-indentation message)))))) | |
338 (setq tokens (cdr tokens)))))) | |
339 | |
340 (defun vm-compile-format (format-variable &optional tokenize) | |
341 (let ((format (symbol-value format-variable)) | |
342 (case-fold-search nil) | |
343 (done nil) | |
344 (list nil) | |
345 (sexp nil) | |
346 (sexp-fmt nil) | |
347 (last-match-end 0) | |
348 token conv-spec) | |
349 (store-match-data nil) | |
350 (while (not done) | |
351 (setq token nil) | |
352 (while | |
353 (and (not token) | |
354 (string-match | |
355 "%\\(-\\)?\\([0-9]+\\)?\\(\\.\\(-?[0-9]+\\)\\)?\\([aAcdfFhHiIlLmMnstTwyz*%]\\|U[A-Za-z]\\)" | |
356 format (match-end 0))) | |
357 (setq conv-spec (aref format (match-beginning 5))) | |
358 (if (memq conv-spec '(?a ?A ?c ?d ?f ?F ?h ?H ?i ?L ?I ?l ?M | |
359 ?m ?n ?s ?t ?T ?U ?w ?y ?z ?* )) | |
360 (progn | |
361 (cond ((= conv-spec ?a) | |
362 (setq sexp (cons (list 'vm-su-attribute-indicators | |
363 'vm-su-message) sexp))) | |
364 ((= conv-spec ?A) | |
365 (setq sexp (cons (list 'vm-su-attribute-indicators-long | |
366 'vm-su-message) sexp))) | |
367 ((= conv-spec ?c) | |
368 (setq sexp (cons (list 'vm-su-byte-count | |
369 'vm-su-message) sexp))) | |
370 ((= conv-spec ?d) | |
371 (setq sexp (cons (list 'vm-su-monthday | |
372 'vm-su-message) sexp))) | |
373 ((= conv-spec ?f) | |
374 (setq sexp (cons (list 'vm-su-interesting-from | |
375 'vm-su-message) sexp))) | |
376 ((= conv-spec ?F) | |
377 (setq sexp (cons (list 'vm-su-interesting-full-name | |
378 'vm-su-message) sexp))) | |
379 ((= conv-spec ?h) | |
380 (setq sexp (cons (list 'vm-su-hour | |
381 'vm-su-message) sexp))) | |
382 ((= conv-spec ?H) | |
383 (setq sexp (cons (list 'vm-su-hour-short | |
384 'vm-su-message) sexp))) | |
385 ((= conv-spec ?i) | |
386 (setq sexp (cons (list 'vm-su-message-id | |
387 'vm-su-message) sexp))) | |
388 ((= conv-spec ?I) | |
389 (if tokenize | |
390 (setq token ''thread-indent) | |
391 (setq sexp (cons (list 'vm-su-thread-indent | |
392 'vm-su-message) sexp)))) | |
393 ((= conv-spec ?l) | |
394 (setq sexp (cons (list 'vm-su-line-count | |
395 'vm-su-message) sexp))) | |
396 ((= conv-spec ?L) | |
397 (setq sexp (cons (list 'vm-su-labels | |
398 'vm-su-message) sexp))) | |
399 ((= conv-spec ?m) | |
400 (setq sexp (cons (list 'vm-su-month | |
401 'vm-su-message) sexp))) | |
402 ((= conv-spec ?M) | |
403 (setq sexp (cons (list 'vm-su-month-number | |
404 'vm-su-message) sexp))) | |
405 ((= conv-spec ?n) | |
406 (if tokenize | |
407 (setq token ''number) | |
408 (setq sexp (cons (list 'vm-padded-number-of | |
409 'vm-su-message) sexp)))) | |
410 ((= conv-spec ?s) | |
411 (setq sexp (cons (list 'vm-su-subject | |
412 'vm-su-message) sexp))) | |
413 ((= conv-spec ?T) | |
414 (setq sexp (cons (list 'vm-su-to-names | |
415 'vm-su-message) sexp))) | |
416 ((= conv-spec ?t) | |
417 (setq sexp (cons (list 'vm-su-to | |
418 'vm-su-message) sexp))) | |
419 ((= conv-spec ?U) | |
420 (setq sexp | |
421 (cons (list 'vm-run-user-summary-function | |
422 (list 'quote | |
423 (intern | |
424 (concat | |
425 "vm-summary-function-" | |
426 (substring | |
427 format | |
428 (1+ (match-beginning 5)) | |
429 (+ 2 (match-beginning 5)))))) | |
430 'vm-su-message) sexp))) | |
431 ((= conv-spec ?w) | |
432 (setq sexp (cons (list 'vm-su-weekday | |
433 'vm-su-message) sexp))) | |
434 ((= conv-spec ?y) | |
435 (setq sexp (cons (list 'vm-su-year | |
436 'vm-su-message) sexp))) | |
437 ((= conv-spec ?z) | |
438 (setq sexp (cons (list 'vm-su-zone | |
439 'vm-su-message) sexp))) | |
440 ((= conv-spec ?*) | |
441 (if tokenize | |
442 (setq token ''mark) | |
443 (setq sexp (cons (list 'vm-su-mark | |
444 'vm-su-message) sexp))))) | |
445 (cond ((and (not token) (match-beginning 1)) | |
446 (setcar sexp | |
447 (list 'vm-left-justify-string (car sexp) | |
448 (string-to-int | |
449 (substring format | |
450 (match-beginning 2) | |
451 (match-end 2)))))) | |
452 ((and (not token) (match-beginning 2)) | |
453 (setcar sexp | |
454 (list 'vm-right-justify-string (car sexp) | |
455 (string-to-int | |
456 (substring format | |
457 (match-beginning 2) | |
458 (match-end 2))))))) | |
459 (cond ((and (not token) (match-beginning 3)) | |
460 (setcar sexp | |
461 (list 'vm-truncate-string (car sexp) | |
462 (string-to-int | |
463 (substring format | |
464 (match-beginning 4) | |
465 (match-end 4))))))) | |
466 (setq sexp-fmt | |
467 (cons (if token "" "%s") | |
468 (cons (substring format | |
469 last-match-end | |
470 (match-beginning 0)) | |
471 sexp-fmt)))) | |
472 (setq sexp-fmt | |
473 (cons "%%" | |
474 (cons (substring format | |
475 (or last-match-end 0) | |
476 (match-beginning 0)) | |
477 sexp-fmt)))) | |
478 (setq last-match-end (match-end 0))) | |
479 (if (not token) | |
480 (setq sexp-fmt | |
481 (cons (substring format last-match-end (length format)) | |
482 sexp-fmt) | |
483 done t)) | |
484 (setq sexp-fmt (apply 'concat (nreverse sexp-fmt))) | |
485 (if sexp | |
486 (setq sexp (cons 'format (cons sexp-fmt (nreverse sexp)))) | |
487 (setq sexp sexp-fmt)) | |
488 (if tokenize | |
489 (setq list (nconc list (if (equal sexp "") nil (list sexp)) | |
490 (and token (list token))) | |
491 sexp nil | |
492 sexp-fmt nil))) | |
493 (put format-variable 'vm-compiled-format format) | |
494 (put format-variable 'vm-format-sexp (if list (cons 'list list) sexp)))) | |
495 | |
496 (defun vm-get-header-contents (message header-name-regexp) | |
497 (let ((contents nil) | |
498 regexp) | |
499 (setq regexp (concat "^\\(" header-name-regexp "\\)") | |
500 message (vm-real-message-of message)) | |
501 (save-excursion | |
502 (set-buffer (vm-buffer-of (vm-real-message-of message))) | |
503 (save-restriction | |
504 (widen) | |
505 (goto-char (vm-headers-of message)) | |
506 (let ((case-fold-search t)) | |
507 (while (and (re-search-forward regexp (vm-text-of message) t) | |
508 (save-excursion (goto-char (match-beginning 0)) | |
509 (vm-match-header))) | |
510 (if contents | |
511 (setq contents | |
512 (concat contents ", " (vm-matched-header-contents))) | |
513 (setq contents (vm-matched-header-contents)))))) | |
514 contents ))) | |
515 | |
516 (defun vm-left-justify-string (string width) | |
517 (if (>= (length string) width) | |
518 string | |
519 (concat string (make-string (- width (length string)) ?\ )))) | |
520 | |
521 (defun vm-right-justify-string (string width) | |
522 (if (>= (length string) width) | |
523 string | |
524 (concat (make-string (- width (length string)) ?\ ) string))) | |
525 | |
526 (defun vm-truncate-string (string width) | |
527 (cond ((<= (length string) width) | |
528 string) | |
529 ((< width 0) | |
530 (substring string width)) | |
531 (t | |
532 (substring string 0 width)))) | |
533 | |
534 (defun vm-su-attribute-indicators (m) | |
535 (concat | |
536 (cond ((vm-deleted-flag m) "D") | |
537 ((vm-new-flag m) "N") | |
538 ((vm-unread-flag m) "U") | |
539 (t " ")) | |
540 (cond ((vm-filed-flag m) "F") | |
541 ((vm-written-flag m) "W") | |
542 (t " ")) | |
543 (cond ((vm-replied-flag m) "R") | |
544 ((vm-forwarded-flag m) "Z") | |
545 ((vm-redistributed-flag m) "B") | |
546 (t " ")) | |
547 (cond ((vm-edited-flag m) "E") | |
548 (t " ")))) | |
549 | |
550 (defun vm-su-attribute-indicators-long (m) | |
551 (concat | |
552 (cond ((vm-deleted-flag m) "D") | |
553 ((vm-new-flag m) "N") | |
554 ((vm-unread-flag m) "U") | |
555 (t " ")) | |
556 (if (vm-replied-flag m) "r" " ") | |
557 (if (vm-forwarded-flag m) "z" " ") | |
558 (if (vm-redistributed-flag m) "b" " ") | |
559 (if (vm-filed-flag m) "f" " ") | |
560 (if (vm-written-flag m) "w" " ") | |
561 (if (vm-edited-flag m) "e" " "))) | |
562 | |
563 (defun vm-su-byte-count (m) | |
564 (or (vm-byte-count-of m) | |
565 (vm-set-byte-count-of | |
566 m | |
567 (int-to-string | |
568 (- (vm-text-end-of (vm-real-message-of m)) | |
569 (vm-text-of (vm-real-message-of m))))))) | |
570 | |
571 (defun vm-su-weekday (m) | |
572 (or (vm-weekday-of m) | |
573 (progn (vm-su-do-date m) (vm-weekday-of m)))) | |
574 | |
575 (defun vm-su-monthday (m) | |
576 (or (vm-monthday-of m) | |
577 (progn (vm-su-do-date m) (vm-monthday-of m)))) | |
578 | |
579 (defun vm-su-month (m) | |
580 (or (vm-month-of m) | |
581 (progn (vm-su-do-date m) (vm-month-of m)))) | |
582 | |
583 (defun vm-su-month-number (m) | |
584 (or (vm-month-number-of m) | |
585 (progn (vm-su-do-date m) (vm-month-number-of m)))) | |
586 | |
587 (defun vm-su-year (m) | |
588 (or (vm-year-of m) | |
589 (progn (vm-su-do-date m) (vm-year-of m)))) | |
590 | |
591 (defun vm-su-hour-short (m) | |
592 (let ((string (vm-su-hour m))) | |
593 (if (> (length string) 5) | |
594 (substring string 0 5) | |
595 string))) | |
596 | |
597 (defun vm-su-hour (m) | |
598 (or (vm-hour-of m) | |
599 (progn (vm-su-do-date m) (vm-hour-of m)))) | |
600 | |
601 (defun vm-su-zone (m) | |
602 (or (vm-zone-of m) | |
603 (progn (vm-su-do-date m) (vm-zone-of m)))) | |
604 | |
605 (defun vm-su-mark (m) (if (vm-mark-of m) "*" " ")) | |
606 | |
607 ;; Some yogurt-headed delivery agents don't provide a Date: header. | |
608 (defun vm-grok-From_-date (message) | |
609 ;; This works only on the From_ types, obviously | |
610 (if (not (memq (vm-message-type-of message) | |
611 '(From_ From_-with-Content-Length))) | |
612 nil | |
613 (save-excursion | |
614 (set-buffer (vm-buffer-of (vm-real-message-of message))) | |
615 (save-restriction | |
616 (widen) | |
617 (goto-char (vm-start-of message)) | |
618 (let ((case-fold-search nil)) | |
619 (if (or (looking-at | |
620 ;; special case this so that the "remote from blah" | |
621 ;; isn't included. | |
622 "From [^ \t\n]*[ \t]+\\([^ \t\n].*\\) remote from .*") | |
623 (looking-at "From [^ \t\n]*[ \t]+\\([^ \t\n].*\\)")) | |
624 (vm-buffer-substring-no-properties | |
625 (match-beginning 1) | |
626 (match-end 1)))))))) | |
627 | |
628 (defun vm-parse-date (date) | |
629 (let ((weekday "") | |
630 (monthday "") | |
631 (month "") | |
632 (year "") | |
633 (hour "") | |
634 (timezone "") | |
635 (start nil) | |
636 string | |
637 (case-fold-search t)) | |
638 (if (string-match "sun\\|mon\\|tue\\|wed\\|thu\\|fri\\|sat" date) | |
639 (setq weekday (substring date (match-beginning 0) (match-end 0)))) | |
640 (if (string-match "jan\\|feb\\|mar\\|apr\\|may\\|jun\\|jul\\|aug\\|sep\\|oct\\|nov\\|dec" date) | |
641 (setq month (substring date (match-beginning 0) (match-end 0)))) | |
642 (if (string-match "[0-9]?[0-9]:[0-9][0-9]\\(:[0-9][0-9]\\)?" date) | |
643 (setq hour (substring date (match-beginning 0) (match-end 0)))) | |
644 (if (or (string-match "[^a-z][+---][0-9][0-9][0-9][0-9]" date) | |
645 (string-match "e[ds]t\\|c[ds]t\\|p[ds]t\\|m[ds]t" date) | |
646 (string-match "ast\\|nst\\|met\\|eet\\|jst\\|bst\\|ut" date) | |
647 (string-match "gmt\\([+---][0-9]+\\)?" date)) | |
648 (setq timezone (substring date (match-beginning 0) (match-end 0)))) | |
649 (while (string-match "\\(\\`\\|[^:+---0-9]\\|[a-z]-\\)[0-9]+\\(\\'\\|[^:]\\)" | |
650 date start) | |
651 (setq string (substring date (match-end 1) (match-beginning 2)) | |
652 start (match-end 0)) | |
653 (cond ((string-match "\\`[4-9]." string) | |
654 ;; Assume that any two digits less than 40 are a date and not | |
655 ;; a year. The world will surely end soon. | |
656 (setq year (concat "19" string))) | |
657 ((< (length string) 3) | |
658 (setq monthday string)) | |
659 (t (setq year string)))) | |
660 | |
661 (aset vm-parse-date-workspace 0 weekday) | |
662 (aset vm-parse-date-workspace 1 monthday) | |
663 (aset vm-parse-date-workspace 2 month) | |
664 (aset vm-parse-date-workspace 3 year) | |
665 (aset vm-parse-date-workspace 4 hour) | |
666 (aset vm-parse-date-workspace 5 timezone) | |
667 vm-parse-date-workspace)) | |
668 | |
669 (defun vm-su-do-date (m) | |
670 (let ((case-fold-search t) | |
671 vector date) | |
672 (setq date (or (vm-get-header-contents m "Date:") (vm-grok-From_-date m))) | |
673 (cond | |
674 ((null date) | |
675 (vm-set-weekday-of m "") | |
676 (vm-set-monthday-of m "") | |
677 (vm-set-month-of m "") | |
678 (vm-set-month-number-of m "") | |
679 (vm-set-year-of m "") | |
680 (vm-set-hour-of m "") | |
681 (vm-set-zone-of m "")) | |
682 ((string-match | |
683 ;; The date format recognized here is the one specified in RFC 822. | |
684 ;; Some slop is allowed e.g. dashes between the monthday, month and year | |
685 ;; because such malformed headers have been observed. | |
686 "\\(\\([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]\\)" | |
687 date) | |
688 (if (match-beginning 2) | |
689 (vm-set-weekday-of m (substring date (match-beginning 2) | |
690 (match-end 2))) | |
691 (vm-set-weekday-of m "")) | |
692 (vm-set-monthday-of m (substring date (match-beginning 3) (match-end 3))) | |
693 (vm-su-do-month m (substring date (match-beginning 4) (match-end 4))) | |
694 (vm-set-year-of m (substring date (match-beginning 5) (match-end 5))) | |
695 (if (= 2 (length (vm-year-of m))) | |
696 (vm-set-year-of m (concat "19" (vm-year-of m)))) | |
697 (vm-set-hour-of m (substring date (match-beginning 6) (match-end 6))) | |
698 (vm-set-zone-of m (substring date (match-beginning 7) (match-end 7)))) | |
699 ((string-match | |
700 ;; UNIX ctime(3) format, with slop allowed in the whitespace, and we allow for | |
701 ;; the possibility of a timezone at the end. | |
702 "\\([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]\\)?" | |
703 date) | |
704 (vm-set-weekday-of m (substring date (match-beginning 1) (match-end 1))) | |
705 (vm-su-do-month m (substring date (match-beginning 2) (match-end 2))) | |
706 (vm-set-monthday-of m (substring date (match-beginning 3) (match-end 3))) | |
707 (vm-set-hour-of m (substring date (match-beginning 4) (match-end 4))) | |
708 (vm-set-year-of m (substring date (match-beginning 5) (match-end 5))) | |
709 (if (match-beginning 6) | |
710 (vm-set-zone-of m (substring date (match-beginning 6) | |
711 (match-end 6))))) | |
712 (t | |
713 (setq vector (vm-parse-date date)) | |
714 (vm-set-weekday-of m (elt vector 0)) | |
715 (vm-set-monthday-of m (elt vector 1)) | |
716 (vm-su-do-month m (elt vector 2)) | |
717 (vm-set-year-of m (elt vector 3)) | |
718 (vm-set-hour-of m (elt vector 4)) | |
719 (vm-set-zone-of m (elt vector 5))))) | |
720 | |
721 ;; Normalize all hour and date specifications to avoid jagged margins. | |
722 ;; If the hour is " 3:..." or "3:...", turn it into "03:...". | |
723 ;; If the date is "03", turn it into " 3". | |
724 (cond ((null (vm-hour-of m)) nil) | |
725 ((string-match "\\`[0-9]:" (vm-hour-of m)) | |
726 (vm-set-hour-of m (concat "0" (vm-hour-of m))))) | |
727 (cond ((null (vm-monthday-of m)) nil) | |
728 ((string-match "\\`0[0-9]\\'" (vm-monthday-of m)) | |
729 (vm-set-monthday-of m (substring (vm-monthday-of m) 1 2)))) | |
730 ) | |
731 | |
732 (defun vm-su-do-month (m month-abbrev) | |
733 (let ((val (assoc (downcase month-abbrev) vm-month-alist))) | |
734 (if val | |
735 (progn (vm-set-month-of m (nth 1 val)) | |
736 (vm-set-month-number-of m (nth 2 val))) | |
737 (vm-set-month-of m "") | |
738 (vm-set-month-number-of m "")))) | |
739 | |
740 (defun vm-run-user-summary-function (function message) | |
741 (let ((message (vm-real-message-of message))) | |
742 (save-excursion | |
743 (set-buffer (vm-buffer-of message)) | |
744 (save-restriction | |
745 (widen) | |
746 (save-excursion | |
747 (narrow-to-region (vm-headers-of message) (vm-text-end-of message)) | |
748 (funcall function message)))))) | |
749 | |
750 (defun vm-su-full-name (m) | |
751 (or (vm-full-name-of m) | |
752 (progn (vm-su-do-author m) (vm-full-name-of m)))) | |
753 | |
754 (defun vm-su-interesting-full-name (m) | |
755 (if vm-summary-uninteresting-senders | |
756 (let ((case-fold-search nil)) | |
757 (if (string-match vm-summary-uninteresting-senders (vm-su-from m)) | |
758 (concat vm-summary-uninteresting-senders-arrow (vm-su-to-names m)) | |
759 (vm-su-full-name m))) | |
760 (vm-su-full-name m))) | |
761 | |
762 (defun vm-su-from (m) | |
763 (or (vm-from-of m) | |
764 (progn (vm-su-do-author m) (vm-from-of m)))) | |
765 | |
766 (defun vm-su-interesting-from (m) | |
767 (if vm-summary-uninteresting-senders | |
768 (let ((case-fold-search nil)) | |
769 (if (string-match vm-summary-uninteresting-senders (vm-su-from m)) | |
770 (concat vm-summary-uninteresting-senders-arrow (vm-su-to m)) | |
771 (vm-su-from m))) | |
772 (vm-su-from m))) | |
773 | |
774 ;; Some yogurt-headed delivery agents don't even provide a From: header. | |
775 (defun vm-grok-From_-author (message) | |
776 ;; This works only on the From_ types, obviously | |
777 (if (not (memq (vm-message-type-of message) | |
778 '(From_ From_-with-Content-Length))) | |
779 nil | |
780 (save-excursion | |
781 (set-buffer (vm-buffer-of message)) | |
782 (save-restriction | |
783 (widen) | |
784 (goto-char (vm-start-of message)) | |
785 (let ((case-fold-search nil)) | |
786 (if (looking-at "From \\([^ \t\n]+\\)") | |
787 (vm-buffer-substring-no-properties | |
788 (match-beginning 1) | |
789 (match-end 1)))))))) | |
790 | |
791 (defun vm-su-do-author (m) | |
792 (let ((full-name (vm-get-header-contents m "Full-Name:")) | |
793 (from (or (vm-get-header-contents m "From:") | |
794 (vm-grok-From_-author m))) | |
795 pair) | |
796 (if (and full-name (string-match "^[ \t]*$" full-name)) | |
797 (setq full-name nil)) | |
798 (if (null from) | |
799 (progn | |
800 (setq from "???") | |
801 (if (null full-name) | |
802 (setq full-name "???"))) | |
803 (setq pair (funcall vm-chop-full-name-function from) | |
804 from (or (nth 1 pair) from) | |
805 full-name (or full-name (nth 0 pair) from))) | |
806 (if (string-match "\\`\"\\([^\"]+\\)\"\\'" full-name) | |
807 (setq full-name | |
808 (substring full-name (match-beginning 1) (match-end 1)))) | |
809 (vm-set-full-name-of m full-name) | |
810 (vm-set-from-of m from))) | |
811 | |
812 (defun vm-default-chop-full-name (address) | |
813 (let ((from address) | |
814 (full-name nil)) | |
815 (cond ((string-match | |
816 "\\`[ \t\n]*\\([^< \t\n]+\\([ \t\n]+[^< \t\n]+\\)*\\)?[ \t\n]*<\\([^>]+\\)>[ \t\n]*\\'" | |
817 address) | |
818 (if (match-beginning 1) | |
819 (setq full-name | |
820 (substring address (match-beginning 1) (match-end 1)))) | |
821 (setq from | |
822 (substring address (match-beginning 3) (match-end 3)))) | |
823 ((string-match | |
824 "\\`[ \t\n]*\\(\\(\"[^\"]+\"\\|[^\"( \t\n]\\)+\\)[ \t\n]*(\\([^ \t\n]+\\([ \t\n]+[^ \t\n]+\\)*\\)?)[ \t\n]*\\'" | |
825 address) | |
826 (if (match-beginning 3) | |
827 (setq full-name | |
828 (substring address (match-beginning 3) (match-end 3)))) | |
829 (setq from | |
830 (substring address (match-beginning 1) (match-end 1))))) | |
831 (list full-name from))) | |
832 | |
833 ;; test for existence and functionality of mail-extract-address-components | |
834 ;; there are versions out there that don't work right, so we run | |
835 ;; some test data through it to see if we can trust it. | |
836 (defun vm-choose-chop-full-name-function (address) | |
837 (let ((test-data '(("kyle@uunet.uu.net" . | |
838 (nil "kyle@uunet.uu.net")) | |
839 ("c++std=lib@inet.research.att.com" . | |
840 (nil "c++std=lib@inet.research.att.com")) | |
841 ("\"Piet.Rypens\" <rypens@reks.uia.ac.be>" . | |
842 ("Piet Rypens" "rypens@reks.uia.ac.be")) | |
843 ("makke@wins.uia.ac.be (Marc.Gemis)" . | |
844 ("Marc Gemis" "makke@wins.uia.ac.be")) | |
845 ("" . (nil nil)))) | |
846 (failed nil) | |
847 result) | |
848 (while test-data | |
849 (setq result (condition-case nil | |
850 (mail-extract-address-components (car (car test-data))) | |
851 (error nil))) | |
852 (if (not (equal result (cdr (car test-data)))) | |
853 ;; failed test, use default | |
854 (setq failed t | |
855 test-data nil) | |
856 (setq test-data (cdr test-data)))) | |
857 (if failed | |
858 ;; it failed, use default | |
859 (setq vm-chop-full-name-function 'vm-default-chop-full-name) | |
860 ;; it passed the tests | |
861 (setq vm-chop-full-name-function 'mail-extract-address-components)) | |
862 (funcall vm-chop-full-name-function address))) | |
863 | |
864 (defun vm-su-do-recipients (m) | |
865 (let ((mail-use-rfc822 t) names addresses to cc all list) | |
866 (setq to (or (vm-get-header-contents m "To:") | |
867 (vm-get-header-contents m "Apparently-To:") | |
868 ;; desperation.... | |
869 (user-login-name)) | |
870 cc (vm-get-header-contents m "Cc:") | |
871 all to | |
872 all (if all (concat all ", " cc) cc) | |
873 addresses (rfc822-addresses all)) | |
874 (setq list (vm-parse-addresses all)) | |
875 (while list | |
876 (cond ((string= (car list) "")) | |
877 ((string-match "^\\(\"?\\([^<]+[^ \t\n\"]\\)\"?[ \t\n]+\\)?<\\([^>]+\\)>" | |
878 (car list)) | |
879 (if (match-beginning 2) | |
880 (setq names | |
881 (cons | |
882 (substring (car list) (match-beginning 2) | |
883 (match-end 2)) | |
884 names)) | |
885 (setq names | |
886 (cons | |
887 (substring (car list) (match-beginning 3) | |
888 (match-end 3)) | |
889 names)))) | |
890 ((string-match "[\000-\177]*(\\([^)]+\\))[\000-\177]*" (car list)) | |
891 (setq names | |
892 (cons (substring (car list) (match-beginning 1) | |
893 (match-end 1)) | |
894 names))) | |
895 (t (setq names (cons (car list) names)))) | |
896 (setq list (cdr list))) | |
897 (setq names (nreverse names)) ; added by jwz for fixed vm-parse-addresses | |
898 (vm-set-to-of m (mapconcat 'identity addresses ", ")) | |
899 (vm-set-to-names-of m (mapconcat 'identity names ", ")))) | |
900 | |
901 (defun vm-su-to (m) | |
902 (or (vm-to-of m) (progn (vm-su-do-recipients m) (vm-to-of m)))) | |
903 | |
904 (defun vm-su-to-names (m) | |
905 (or (vm-to-names-of m) (progn (vm-su-do-recipients m) (vm-to-names-of m)))) | |
906 | |
907 (defun vm-su-message-id (m) | |
908 (or (vm-message-id-of m) | |
909 (vm-set-message-id-of | |
910 m | |
911 (or (vm-get-header-contents m "Message-Id:") | |
912 ;; try running md5 on the message body to produce an ID | |
913 ;; better than nothing. | |
914 (save-excursion | |
915 (set-buffer (vm-buffer-of (vm-real-message-of m))) | |
916 (save-restriction | |
917 (widen) | |
918 (condition-case nil | |
919 (concat "<fake-VM-id." | |
920 (vm-pop-md5-string | |
921 (buffer-substring | |
922 (vm-text-of (vm-real-message-of m)) | |
923 (vm-text-end-of (vm-real-message-of m)))) | |
924 "@talos.iv>") | |
925 (error nil)))) | |
926 (concat "<" (int-to-string (vm-abs (random))) "@toto.iv>"))))) | |
927 | |
928 (defun vm-su-line-count (m) | |
929 (or (vm-line-count-of m) | |
930 (vm-set-line-count-of | |
931 m | |
932 (save-excursion | |
933 (set-buffer (vm-buffer-of (vm-real-message-of m))) | |
934 (save-restriction | |
935 (widen) | |
936 (int-to-string | |
937 (count-lines (vm-text-of (vm-real-message-of m)) | |
938 (vm-text-end-of (vm-real-message-of m))))))))) | |
939 | |
940 (defun vm-su-subject (m) | |
941 (or (vm-subject-of m) | |
942 (vm-set-subject-of | |
943 m | |
944 (let ((subject (or (vm-get-header-contents m "Subject:") "")) | |
945 (i nil)) | |
946 (if vm-summary-subject-no-newlines | |
947 (while (setq i (string-match "\n" subject i)) | |
948 (aset subject i ?\ ))) | |
949 subject )))) | |
950 | |
951 (defun vm-su-summary (m) | |
952 (if (and (vm-virtual-message-p m) (not (vm-virtual-messages-of m))) | |
953 (or (vm-virtual-summary-of m) | |
954 (save-excursion | |
955 (vm-select-folder-buffer) | |
956 (vm-set-virtual-summary-of m (vm-sprintf 'vm-summary-format m t)) | |
957 (vm-virtual-summary-of m))) | |
958 (or (vm-summary-of m) | |
959 (save-excursion | |
960 (vm-select-folder-buffer) | |
961 (vm-set-summary-of m (vm-sprintf 'vm-summary-format m t)) | |
962 (vm-summary-of m))))) | |
963 | |
964 (defun vm-fix-my-summary!!! () | |
965 (interactive) | |
966 (vm-select-folder-buffer) | |
967 (vm-check-for-killed-summary) | |
968 (vm-error-if-folder-empty) | |
969 (vm-unsaved-message "Fixing your summary...") | |
970 (let ((mp vm-message-list)) | |
971 (while mp | |
972 (vm-set-summary-of (car mp) nil) | |
973 (vm-mark-for-summary-update (car mp)) | |
974 (vm-stuff-attributes (car mp)) | |
975 (setq mp (cdr mp))) | |
976 (set-buffer-modified-p t) | |
977 (vm-update-summary-and-mode-line)) | |
978 (vm-unsaved-message "Fixing your summary... done")) | |
979 | |
980 (defun vm-su-thread-indent (m) | |
981 (if (natnump vm-summary-thread-indent-level) | |
982 (make-string (* (vm-th-thread-indentation m) | |
983 vm-summary-thread-indent-level) | |
984 ?\ ) | |
985 "" )) | |
986 | |
987 (defun vm-su-labels (m) | |
988 (or (vm-label-string-of m) | |
989 (vm-set-label-string-of | |
990 m | |
991 (mapconcat 'identity (vm-labels-of m) ",")) | |
992 (vm-label-string-of m))) |