1 ;;; hvm.el --- Support Hyperbole buttons in mail reader: Vm.
3 ;; Copyright (C) 1991-1995, BeOpen.com and the Free Software Foundation, Inc.
4 ;; Developed with support from Motorola Inc.
6 ;; Author: Bob Weiner, Brown U.
7 ;; Maintainer: Mats Lidell <matsl@contactor.se>
8 ;; Keywords: hypermedia, mail
10 ;; This file is part of GNU Hyperbole.
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.
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.
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.
29 ;; Automatically configured for use in "hyperbole.el".
30 ;; If hsite loading fails prior to initializing Hyperbole Vm support,
40 ;;; Other required Elisp libraries
46 (or (and (fboundp 'vm-edit-message) (fboundp 'vm-edit-message-end))
48 (vm-session-initialization)
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.")
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)
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."
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)
83 "Initializes Hyperbole support for Vm mail reading."
85 (setq hmail:composer 'mail-mode
86 hmail:lister 'vm-summary-mode
87 hmail:modifier 'vm-edit-mode
88 hmail:reader 'vm-mode)
90 ;; Setup public abstract interface to Hyperbole defined mail
91 ;; reader-specific functions used in "hmail.el".
95 ;; Setup private abstract interface to mail reader-specific functions
96 ;; used in "hmail.el".
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)
103 (message "Hyperbole VM mail reader support initialized."))
106 (defun Vm-msg-hdrs-full (toggled)
107 "If TOGGLED is non-nil, toggle full/hidden headers, else show full headers."
110 (let ((exposed (= (point-min)
111 (vm-start-of (car vm-message-pointer)))))
113 (progn (vm-expose-hidden-headers)
117 (defun Vm-msg-narrow ()
118 "Narrows mail reader buffer to current message.
119 This includes Hyperbole button data."
121 (vm-select-folder-buffer)
122 (narrow-to-region (point-min) (Vm-msg-end))))
124 (defun Vm-msg-next () (vm-next-message 1))
127 "Returns number of vm mail message that point is within, in physical message order."
134 (while (re-search-backward Vm-msg-start-regexp nil t)
135 (setq count (1+ count)))))
138 (defun Vm-msg-prev () (vm-previous-message 1))
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))
145 (vm-visit-folder mail-file)
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
153 (setq buffer-read-only t)
154 (vm-goto-message-at-point)
157 (defun Vm-msg-widen ()
158 "Widens buffer to full current message including Hyperbole button data."
160 (vm-select-folder-buffer)
161 (narrow-to-region (point-min) (Vm-msg-end))))
164 "Sets current buffer to a mail reader buffer."
165 (and (eq major-mode 'vm-summary-mode) (set-buffer vm-mail-buffer)))
167 (defun Vm-Summ-delete ()
168 (vm-follow-summary-cursor)
169 (vm-delete-message 1))
171 (defalias 'Vm-Summ-expunge 'vm-expunge-folder)
173 (defalias 'Vm-Summ-goto 'vm-follow-summary-cursor)
176 "Sets current buffer to a mail listing buffer."
177 (and (eq major-mode 'vm-mode) (set-buffer vm-summary-buffer)))
179 (defun Vm-Summ-undelete-all ()
181 "(Vm-Summ-undelete-all: Vm doesn't have an undelete all msgs function."))
184 ;;; Private functions
188 "Returns end point for current Vm message, including Hyperbole button data.
189 Has side-effect of widening buffer."
191 (goto-char (point-min))
193 (if (let ((case-fold-search))
194 (re-search-forward Vm-msg-start-regexp nil t))
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
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)))
208 (vm-decode-mime-message)
209 (vm-mime-error (vm-set-mime-layout-of (car vm-message-pointer)
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)))
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)))
224 (message "Searching for paragraphs to fill... done"))))
225 (vm-save-buffer-excursion
228 (goto-char (point-min))
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
235 (if (search-forward page-delimiter nil t)
237 (goto-char (match-beginning 0))
238 (not (looking-at (regexp-quote hmail:hbdata-sep))))))
240 (if (looking-at page-delimiter)
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))
247 (setq vm-system-state 'showing)
249 (vm-set-buffer-variable vm-mail-buffer 'vm-system-state
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
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)
264 (if (fboundp 'hproperty:but-create) (hproperty:but-create))
265 (vm-update-summary-and-mode-line))))
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)
275 (if (or (and (< direction 0)
276 (> (point-min) (vm-text-of (car vm-message-pointer))))
277 (and (>= direction 0)
283 (car vm-message-pointer)))))))
289 (set-window-start w (point))
294 (looking-at page-delimiter))))
297 (set-window-start w (point))
299 (if (eq (car error-data) 'end-of-buffer)
300 (if vm-auto-next-message
302 (set-window-point w (point))
303 'end-of-message)))))))
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))))))
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))
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."
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)
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)))
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)
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."))
367 (goto-char (vm-headers-of (vm-real-message-of (car mp))))
368 (let ((vm-message-pointer mp)
370 (buffer-read-only nil))
371 (setq opoint (point))
372 (insert-buffer-substring edit-buf)
374 (point) (vm-text-end-of (vm-real-message-of (car mp))))
375 (vm-discard-cached-data))
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)))
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))
393 (vm-save-buffer-excursion
395 (let ((osw (selected-window))
396 (new-win (vm-get-visible-buffer-window
401 (select-window new-win)
402 (goto-char (vm-headers-of
403 (car vm-message-pointer)))
405 (forward-char pos-offset)
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))))
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).
425 (vm-vheaders-of (car vm-message-pointer))
427 (vm-show-current-message)
428 (setq vm-system-state 'reading))
429 ((fboundp 'vm-isearch-update)
432 (vm-vheaders-of (car vm-message-pointer))
434 (vm-show-current-message)
435 (setq vm-system-state 'reading))
436 (t (error "vm search code is missing, can't continue"))))
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))
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
450 (let ((tail-cons (vm-last vm-message-list))
455 (if (fboundp 'hproperty:but-create)
456 (hproperty:but-create))
457 (vm-build-message-list)
458 (if (or (null tail-cons) (cdr tail-cons))
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
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
495 (setq new-messages (copy-sequence new-messages))
497 (if (and new-messages labels vm-burst-digest-messages-inherit-labels)
498 (let ((mp new-messages))
500 (vm-set-labels-of (car mp) (copy-sequence labels))
501 (setq mp (cdr mp)))))
502 (if (and new-messages vm-summary-show-threads)
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
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
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)
529 (setq b-list vm-virtual-buffers)
531 ;; buffer might be dead
532 (if (buffer-name (car b-list))
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))
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
553 (vm-update-summary-and-mode-line)
554 (vm-sort-messages "thread")))))))
555 (setq b-list (cdr b-list)))))
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
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)
569 (set-buffer (other-buffer))
570 (set-buffer-modified-p (buffer-modified-p)))))
573 ;;; Private variables
576 (defvar Vm-msg-start-regexp "\n\nFrom \\|\n\001\001\001\001"
577 "Regular expression that begins a Vm mail message.")