Initial Commit
[packages] / xemacs-packages / hyperbole / hvm.el
1 ;;; hvm.el --- Support Hyperbole buttons in mail reader: Vm.
2
3 ;; Copyright (C) 1991-1995, BeOpen.com and the Free Software Foundation, Inc.
4 ;; Developed with support from Motorola Inc.
5
6 ;; Author: Bob Weiner, Brown U.
7 ;; Maintainer: Mats Lidell <matsl@contactor.se>
8 ;; Keywords: hypermedia, mail
9
10 ;; This file is part of GNU Hyperbole.
11
12 ;; GNU Hyperbole is free software; you can redistribute it and/or
13 ;; modify it under the terms of the GNU General Public License as
14 ;; published by the Free Software Foundation; either version 3, or (at
15 ;; your option) any later version.
16
17 ;; GNU Hyperbole is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 ;; General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02110-1301, USA.
26
27 ;;; Commentary:
28 ;;
29 ;;   Automatically configured for use in "hyperbole.el".
30 ;;   If hsite loading fails prior to initializing Hyperbole Vm support,
31 ;;
32 ;;       {M-x Vm-init RET}
33 ;;
34 ;;   will do it.
35 ;;
36
37 ;;; Code:
38
39 ;;;
40 ;;; Other required Elisp libraries
41 ;;;
42
43 (require 'hmail)
44 (load "hsmail")
45 (require 'vm)
46 (or (and (fboundp 'vm-edit-message) (fboundp 'vm-edit-message-end))
47     (load "vm-edit"))
48 (vm-session-initialization)
49
50 ;;;
51 ;;; Public variables
52 ;;;
53
54 ;;; Current versions of VM define this next variable in "vm-vars.el".  We
55 ;;; define it here for earlier VM versions.
56 (defvar vm-edit-message-mode nil
57   "*Major mode to use when editing messages in VM.")
58
59 ;;; "hmail.el" procedures will branch improperly if a regular mode, like VM's
60 ;;; default `text-mode', is used for editing.
61 (setq vm-edit-message-mode 'vm-edit-mode)
62
63 (defun vm-edit-mode ()
64   "Major mode for editing vm mail messages.
65   Special commands:\\{vm-edit-message-map}
66 Turning on vm-edit-mode calls the value of the variable vm-edit-message-hook,
67 if that value is non-nil."
68   (interactive)
69   (kill-all-local-variables)
70 ;;  (use-local-map vm-edit-message-map)
71   (setq mode-name "VM Edit")
72   (setq major-mode 'vm-edit-mode)
73   (setq local-abbrev-table text-mode-abbrev-table)
74   (set-syntax-table text-mode-syntax-table)
75 ;;  (run-hooks 'vm-edit-message-hook)
76   )
77
78 ;;;
79 ;;; Public functions
80 ;;;
81
82 (defun Vm-init ()
83   "Initializes Hyperbole support for Vm mail reading."
84   (interactive)
85   (setq hmail:composer  'mail-mode
86         hmail:lister    'vm-summary-mode
87         hmail:modifier  'vm-edit-mode
88         hmail:reader    'vm-mode)
89   ;;
90   ;; Setup public abstract interface to Hyperbole defined mail
91   ;; reader-specific functions used in "hmail.el".
92   ;;
93   (rmail:init)
94   ;;
95   ;; Setup private abstract interface to mail reader-specific functions
96   ;; used in "hmail.el".
97   ;;
98   (defalias 'rmail:get-new       'vm-get-new-mail)
99   (defalias 'rmail:msg-forward   'vm-forward-message)
100   (defalias 'rmail:summ-msg-to   'vm-follow-summary-cursor)
101   (defalias 'rmail:summ-new      'vm-summarize)
102   (if (interactive-p)
103       (message "Hyperbole VM mail reader support initialized."))
104   )
105
106 (defun Vm-msg-hdrs-full (toggled)
107   "If TOGGLED is non-nil, toggle full/hidden headers, else show full headers."
108   (save-excursion
109     (if (or toggled
110             (let ((exposed (= (point-min)
111                               (vm-start-of (car vm-message-pointer)))))
112               (not exposed)))
113         (progn (vm-expose-hidden-headers)
114                (setq toggled t)))
115     toggled))
116
117 (defun Vm-msg-narrow ()
118   "Narrows mail reader buffer to current message.
119 This includes Hyperbole button data."
120   (save-excursion
121     (vm-select-folder-buffer)
122     (narrow-to-region (point-min) (Vm-msg-end))))
123
124 (defun Vm-msg-next ()           (vm-next-message 1))
125
126 (defun Vm-msg-num ()
127   "Returns number of vm mail message that point is within, in physical message order."
128   (interactive)
129   (let ((count 1)
130         (case-fold-search))
131     (save-excursion
132       (save-restriction
133         (widen)
134         (while (re-search-backward Vm-msg-start-regexp nil t)
135           (setq count (1+ count)))))
136     count))
137
138 (defun Vm-msg-prev ()           (vm-previous-message 1))
139
140 (defun Vm-msg-to-p (mail-msg-id mail-file)
141   "Sets current buffer to start of msg with MAIL-MSG-ID in MAIL-FILE.
142 Returns t if successful, else nil or signals error."
143   (if (not (file-readable-p mail-file))
144       nil
145     (vm-visit-folder mail-file)
146     (widen)
147     (goto-char 1)
148       (if (let ((case-fold-search))
149             (re-search-forward (concat rmail:msg-hdr-prefix
150                                        (regexp-quote mail-msg-id)) nil t))
151           ;; Found matching msg
152           (progn
153             (setq buffer-read-only t)
154             (vm-goto-message-at-point)
155             t))))
156
157 (defun Vm-msg-widen ()
158   "Widens buffer to full current message including Hyperbole button data."
159   (save-excursion
160     (vm-select-folder-buffer)
161     (narrow-to-region (point-min) (Vm-msg-end))))
162
163 (defun Vm-to ()
164   "Sets current buffer to a mail reader buffer."
165   (and (eq major-mode 'vm-summary-mode) (set-buffer vm-mail-buffer)))
166
167 (defun Vm-Summ-delete ()
168   (vm-follow-summary-cursor)
169   (vm-delete-message 1))
170
171 (defalias 'Vm-Summ-expunge          'vm-expunge-folder)
172
173 (defalias 'Vm-Summ-goto             'vm-follow-summary-cursor)
174
175 (defun Vm-Summ-to ()
176   "Sets current buffer to a mail listing buffer."
177   (and (eq major-mode 'vm-mode) (set-buffer vm-summary-buffer)))
178
179 (defun Vm-Summ-undelete-all ()
180   (message
181    "(Vm-Summ-undelete-all: Vm doesn't have an undelete all msgs function."))
182
183 ;;;
184 ;;; Private functions
185 ;;;
186
187 (defun Vm-msg-end ()
188   "Returns end point for current Vm message, including Hyperbole button data.
189 Has side-effect of widening buffer."
190   (save-excursion
191     (goto-char (point-min))
192     (widen)
193     (if (let ((case-fold-search))
194           (re-search-forward Vm-msg-start-regexp nil t))
195         (match-beginning 0)
196       (point-max))))
197
198 ;;; Overlay version of this function from "vm-page.el" to hide any
199 ;;; Hyperbole button data whenever a message is displayed in its entirety.
200 (defun vm-show-current-message ()
201   (and vm-display-using-mime
202        vm-auto-decode-mime-messages
203        (if vm-mail-buffer
204            (not (vm-buffer-variable-value vm-mail-buffer 'vm-mime-decoded))
205          (not vm-mime-decoded))
206        (not (vm-mime-plain-message-p (car vm-message-pointer)))
207        (condition-case data
208            (vm-decode-mime-message)
209          (vm-mime-error (vm-set-mime-layout-of (car vm-message-pointer)
210                                                (car (cdr data)))
211                         (message "%s" (car (cdr data))))))
212   (if (and (natnump vm-fill-paragraphs-containing-long-lines)
213            (vm-mime-plain-message-p (car vm-message-pointer)))
214       (let ((needmsg (> (- (vm-text-end-of (car vm-message-pointer))
215                            (vm-text-of (car vm-message-pointer)))
216                         12000)))
217         (if needmsg
218             (message "Searching for paragraphs to fill..."))
219         (vm-fill-paragraphs-containing-long-lines
220          vm-fill-paragraphs-containing-long-lines
221          (vm-text-of (car vm-message-pointer))
222          (vm-text-end-of (car vm-message-pointer)))
223         (if needmsg
224             (message "Searching for paragraphs to fill... done"))))
225   (vm-save-buffer-excursion
226    (save-excursion
227      (save-excursion
228        (goto-char (point-min))
229        (widen)
230        ; (hmail:msg-narrow (point-min) (Vm-msg-end)))
231        ; (narrow-to-region (point) (vm-text-end-of (car vm-message-pointer))))
232        (hmail:msg-narrow (point) (vm-text-end-of (car vm-message-pointer))))
233      (and vm-honor-page-delimiters
234           (save-excursion
235             (if (search-forward page-delimiter nil t)
236                 (progn
237                   (goto-char (match-beginning 0))
238                   (not (looking-at (regexp-quote hmail:hbdata-sep))))))
239           (progn
240             (if (looking-at page-delimiter)
241                 (forward-page 1))
242             (vm-narrow-to-page))))
243    ;; don't mark the message as read if the user can't see it!
244    (if (vm-get-buffer-window (current-buffer))
245        (progn
246          (save-excursion
247            (setq vm-system-state 'showing)
248            (if vm-mail-buffer
249                (vm-set-buffer-variable vm-mail-buffer 'vm-system-state
250                                        'showing))
251            ;; We could be in the presentation buffer here.  Since
252            ;; the presentation buffer's message pointer and sole
253            ;; message are a mockup, they will cause trouble if
254            ;; passed into the undo/update system.  So we switch
255            ;; into the real message buffer to do attribute
256            ;; updates.
257            (vm-select-folder-buffer)
258            (cond ((vm-new-flag (car vm-message-pointer))
259                   (vm-set-new-flag (car vm-message-pointer) nil)))
260            (cond ((vm-unread-flag (car vm-message-pointer))
261                   (vm-set-unread-flag (car vm-message-pointer) nil))))
262          (vm-update-summary-and-mode-line)
263          (vm-howl-if-eom))
264      (if (fboundp 'hproperty:but-create) (hproperty:but-create))
265      (vm-update-summary-and-mode-line))))
266
267 ;;; Overlay version of this function from "vm-page.el" to treat end of
268 ;;; text (excluding Hyperbole button data) as end of message.
269 (defun vm-scroll-forward-internal (arg)
270   (let ((direction (prefix-numeric-value arg))
271         (w (selected-window)))
272     (condition-case error-data
273         (progn (scroll-up arg) nil)
274       (error
275        (if (or (and (< direction 0)
276                     (> (point-min) (vm-text-of (car vm-message-pointer))))
277                (and (>= direction 0)
278                     (/= (point-max)
279                         (save-restriction
280                           (hmail:hbdata-start
281                            (point-min)
282                            (vm-text-end-of
283                             (car vm-message-pointer)))))))
284            (progn
285              (vm-widen-page)
286              (if (>= direction 0)
287                  (progn
288                    (forward-page 1)
289                    (set-window-start w (point))
290                    nil )
291                (if (or (bolp)
292                        (not (save-excursion
293                               (beginning-of-line)
294                               (looking-at page-delimiter))))
295                    (forward-page -1))
296                (beginning-of-line)
297                (set-window-start w (point))
298                'tryagain))
299          (if (eq (car error-data) 'end-of-buffer)
300              (if vm-auto-next-message
301                  'next-message
302                (set-window-point w (point))
303                'end-of-message)))))))
304
305 ;;; Overlay version of this function from "vm-page.el" (called by
306 ;;; vm-scroll-* functions).  Make it keep Hyperbole button data hidden.
307 (defun vm-widen-page ()
308   (if (or (> (point-min) (vm-text-of (car vm-message-pointer)))
309           (/= (point-max) (vm-text-end-of (car vm-message-pointer))))
310       (hmail:msg-narrow (vm-vheaders-of (car vm-message-pointer))
311                         (if (or (vm-new-flag (car vm-message-pointer))
312                                 (vm-unread-flag (car vm-message-pointer)))
313                             (vm-text-of (car vm-message-pointer))
314                           (vm-text-end-of (car vm-message-pointer))))))
315
316 ;;; Overlay version of this function from "vm-edit.el" to hide
317 ;;; Hyperbole button data when insert edited message from temporary buffer.
318 (hypb:function-overload 'vm-edit-message nil '(hmail:msg-narrow))
319
320 ;;; Overlay version of this function from "vm-edit.el" to hide
321 ;;; Hyperbole button data when insert edited message from temporary buffer.
322 (defun vm-edit-message-end ()
323   "End the edit of a message and copy the result to its folder."
324   (interactive)
325   (if (null vm-message-pointer)
326       (error "This is not a VM message edit buffer."))
327   (if (null (buffer-name (vm-buffer-of (car vm-message-pointer))))
328       (error "The folder buffer for this message has been killed."))
329   (let ((pos-offset (- (point) (point-min))))
330     ;; make sure the message ends with a newline
331     (goto-char (point-max))
332     (and (/= (preceding-char) ?\n) (insert ?\n))
333     ;; munge message separators found in the edited message to
334     ;; prevent message from being split into several messages.
335     (vm-munge-message-separators (vm-message-type-of (car vm-message-pointer))
336                                  (point-min) (point-max))
337     ;; for From_-with-Content-Length recompute the Content-Length header
338     (if (eq (vm-message-type-of (car vm-message-pointer))
339             'From_-with-Content-Length)
340         (let ((buffer-read-only nil)
341               length)
342           (goto-char (point-min))
343           ;; first delete all copies of Content-Length
344           (while (and (re-search-forward vm-content-length-search-regexp nil t)
345                       (null (match-beginning 1))
346                       (progn (goto-char (match-beginning 0))
347                              (vm-match-header vm-content-length-header)))
348             (delete-region (vm-matched-header-start) (vm-matched-header-end)))
349           ;; now compute the message body length
350           (goto-char (point-min))
351           (search-forward "\n\n" nil 0)
352           (setq length (- (point-max) (point)))
353           ;; insert the header
354           (goto-char (point-min))
355           (insert vm-content-length-header " " (int-to-string length) "\n")))
356     (let ((edit-buf (current-buffer))
357           (mp vm-message-pointer))
358       (if (buffer-modified-p)
359           (progn
360             (widen)
361             (save-excursion
362               (set-buffer (vm-buffer-of (vm-real-message-of (car mp))))
363               (if (not (memq (vm-real-message-of (car mp)) vm-message-list))
364                   (error "The original copy of this message has been expunged."))
365               (vm-save-restriction
366                (widen)
367                (goto-char (vm-headers-of (vm-real-message-of (car mp))))
368                (let ((vm-message-pointer mp)
369                      opoint
370                      (buffer-read-only nil))
371                  (setq opoint (point))
372                  (insert-buffer-substring edit-buf)
373                  (delete-region
374                   (point) (vm-text-end-of (vm-real-message-of (car mp))))
375                  (vm-discard-cached-data))
376                (hmail:msg-narrow)
377                (vm-set-edited-flag-of (car mp) t)
378                (vm-set-edit-buffer-of (car mp) nil))
379               (set-buffer (vm-buffer-of (car mp)))
380               (if (eq (vm-real-message-of (car mp))
381                       (vm-real-message-of (car vm-message-pointer)))
382                   (progn
383                     (vm-preview-current-message)
384                     ;; Try to position the cursor in the message
385                     ;; window close to where it was in the edit
386                     ;; window.  This works well for non MIME
387                     ;; messages, but the cursor drifts badly for
388                     ;; MIME and for refilled messages.
389                     (vm-save-buffer-excursion
390                      (and vm-presentation-buffer
391                           (set-buffer vm-presentation-buffer))
392                      (vm-save-restriction
393                       (vm-save-buffer-excursion
394                        (widen)
395                        (let ((osw (selected-window))
396                              (new-win (vm-get-visible-buffer-window
397                                        (current-buffer))))
398                          (unwind-protect
399                              (if new-win
400                                  (progn
401                                    (select-window new-win)
402                                    (goto-char (vm-headers-of
403                                                (car vm-message-pointer)))
404                                    (condition-case nil
405                                        (forward-char pos-offset)
406                                      (error nil))))
407                            (if (not (eq osw (selected-window)))
408                                (select-window osw))))))))
409                 (vm-update-summary-and-mode-line))))
410         (message "No change."))
411       (vm-display edit-buf nil '(vm-edit-message-end)
412                   '(vm-edit-message-end reading-message startup))
413       (set-buffer-modified-p nil)
414       (kill-buffer edit-buf))))
415
416 ;;; Define this function if the VM version in use doesn't have it.
417 (or (fboundp 'vm-goto-message-at-point)
418 (defun vm-goto-message-at-point ()
419   "In a VM folder buffer, select the message that contains point."
420   (cond ((fboundp 'vm-update-search-position)
421          (vm-update-search-position t)
422          ;; vm-show-current-message only adjusts (point-max),
423          ;; it doesn't change (point-min).
424          (narrow-to-region
425           (vm-vheaders-of (car vm-message-pointer))
426           (point-max))
427          (vm-show-current-message)
428          (setq vm-system-state 'reading))
429         ((fboundp 'vm-isearch-update)
430          (vm-isearch-update)
431          (narrow-to-region
432           (vm-vheaders-of (car vm-message-pointer))
433           (point-max))
434          (vm-show-current-message)
435          (setq vm-system-state 'reading))
436         (t (error "vm search code is missing, can't continue"))))
437 )
438
439 ;;; Hide any Hyperbole button data when reply to or forward a message.
440 ;;; See "vm-reply.el".
441 (var:append 'vm-mail-mode-hook '(hmail:msg-narrow))
442
443 ;;; Overlay this function from "vm-folder.el" called whenever new mail is
444 ;;; incorporated so that it will highlight Hyperbole buttons when possible.
445 ;;  Returns non-nil if there were any new messages.
446 (defun vm-assimilate-new-messages (&optional
447                                    dont-read-attributes
448                                    gobble-order
449                                    labels first-time)
450   (let ((tail-cons (vm-last vm-message-list))
451         b-list new-messages)
452     (save-excursion
453       (vm-save-restriction
454        (widen)
455        (if (fboundp 'hproperty:but-create)
456            (hproperty:but-create))
457        (vm-build-message-list)
458        (if (or (null tail-cons) (cdr tail-cons))
459            (progn
460              (setq vm-ml-sort-keys nil)
461              (if dont-read-attributes
462                  (vm-set-default-attributes (cdr tail-cons))
463                (vm-read-attributes (cdr tail-cons)))
464              ;; Yuck.  This has to be done here instead of in the
465              ;; vm function because this needs to be done before
466              ;; any initial thread sort (so that if the thread
467              ;; sort matches the saved order the folder won't be
468              ;; modified) but after the message list is created.
469              ;; Since thread sorting is done here this has to be
470              ;; done here too.
471              (if gobble-order
472                  (vm-gobble-message-order))
473              (if (or (vectorp vm-thread-obarray)
474                      vm-summary-show-threads)
475                  (vm-build-threads (cdr tail-cons))))))
476       (setq new-messages (if tail-cons (cdr tail-cons) vm-message-list))
477       (vm-set-numbering-redo-start-point new-messages)
478       (vm-set-summary-redo-start-point new-messages))
479     ;; Only update the folders summary count here if new messages
480     ;; have arrived, not when we're reading the folder for the
481     ;; first time, and not if we cannot assume that all the arrived
482     ;; messages should be considered new.  Use gobble-order as a
483     ;; first time indicator along with the new messages being equal
484     ;; to the whole message list.
485     (if (and new-messages dont-read-attributes
486              (or (not (eq new-messages vm-message-list))
487                  (null gobble-order)))
488         (vm-modify-folder-totals buffer-file-name 'arrived
489                                  (length new-messages)))
490     ;; copy the new-messages list because sorting might scramble
491     ;; it.  Also something the user does when
492     ;; vm-arrived-message-hook is run might affect it.
493     ;; vm-assimilate-new-messages returns this value so it must
494     ;; not be mangled.
495     (setq new-messages (copy-sequence new-messages))
496     ;; add the labels
497     (if (and new-messages labels vm-burst-digest-messages-inherit-labels)
498         (let ((mp new-messages))
499           (while mp
500             (vm-set-labels-of (car mp) (copy-sequence labels))
501             (setq mp (cdr mp)))))
502     (if (and new-messages vm-summary-show-threads)
503         (progn
504           ;; get numbering and summary of new messages done now
505           ;; so that the sort code only has to worry about the
506           ;; changes it needs to make.
507           (vm-update-summary-and-mode-line)
508           (vm-sort-messages "thread")))
509     (if (and new-messages
510              (or vm-arrived-message-hook vm-arrived-messages-hook)
511              ;; Run the hooks only if this is not the first
512              ;; time vm-assimilate-new-messages has been called
513              ;; in this folder. 
514              (not first-time))
515         (let ((new-messages new-messages))
516           ;; seems wise to do this so that if the user runs VM
517           ;; commands here they start with as much of a clean
518           ;; slate as we can provide, given we're currently deep
519           ;; in the guts of VM.
520           (vm-update-summary-and-mode-line)
521           (if vm-arrived-message-hook
522               (while new-messages
523                 (vm-run-message-hook (car new-messages)
524                                      'vm-arrived-message-hook)
525                 (setq new-messages (cdr new-messages))))
526           (run-hooks 'vm-arrived-messages-hook)))
527     (if (and new-messages vm-virtual-buffers)
528         (save-excursion
529           (setq b-list vm-virtual-buffers)
530           (while b-list
531             ;; buffer might be dead
532             (if (buffer-name (car b-list))
533                 (let (tail-cons)
534                   (set-buffer (car b-list))
535                   (setq tail-cons (vm-last vm-message-list))
536                   (vm-build-virtual-message-list new-messages)
537                   (if (or (null tail-cons) (cdr tail-cons))
538                       (progn
539                         (setq vm-ml-sort-keys nil)
540                         (if (vectorp vm-thread-obarray)
541                             (vm-build-threads (cdr tail-cons)))
542                         (vm-set-summary-redo-start-point
543                          (or (cdr tail-cons) vm-message-list))
544                         (vm-set-numbering-redo-start-point
545                          (or (cdr tail-cons) vm-message-list))
546                         (if (null vm-message-pointer)
547                             (progn (setq vm-message-pointer vm-message-list
548                                          vm-need-summary-pointer-update t)
549                                    (if vm-message-pointer
550                                        (vm-preview-current-message))))
551                         (if vm-summary-show-threads
552                             (progn
553                               (vm-update-summary-and-mode-line)
554                               (vm-sort-messages "thread")))))))
555             (setq b-list (cdr b-list)))))
556     new-messages ))
557
558 ;;; Overlay version of `vm-force-mode-line-update' from "vm-folder.el"
559 ;;; to highlight Hyperbole buttons in summary buffers.
560 (defun vm-force-mode-line-update ()
561   "Force a mode line update in all frames."
562   (if vm-summary-buffer
563       (save-excursion
564         (set-buffer vm-summary-buffer)
565         (if (fboundp 'hproperty:but-create) (hproperty:but-create))))
566   (if (fboundp 'force-mode-line-update)
567       (force-mode-line-update t)
568     (save-excursion
569       (set-buffer (other-buffer))
570       (set-buffer-modified-p (buffer-modified-p)))))
571
572 ;;;
573 ;;; Private variables
574 ;;;
575
576 (defvar Vm-msg-start-regexp "\n\nFrom \\|\n\001\001\001\001"
577   "Regular expression that begins a Vm mail message.")
578
579 (provide 'hvm)
580
581 ;;; hvm.el ends here