1 ;;; wl-message.el -- Message displaying modules for Wanderlust.
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, net news
8 ;; This file is part of Wanderlust (Yet Another Message Interface on Emacsen).
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
33 (require 'wl-highlight)
40 (require 'mmelmo-imap4))
42 (defalias-maybe 'event-window 'ignore)
43 (defalias-maybe 'posn-window 'ignore)
44 (defalias-maybe 'event-start 'ignore)
45 (defalias-maybe 'mime-open-entity 'ignore))
47 (defvar wl-original-buf-name "*Message*")
48 (defvar wl-message-buf-name "Message")
49 (defvar wl-message-buffer-cur-summary-buffer nil)
50 (defvar wl-message-buffer-cur-folder nil)
51 (defvar wl-message-buffer-cur-number nil)
53 (defvar wl-original-buffer-cur-folder nil)
54 (defvar wl-original-buffer-cur-number nil)
55 (defvar wl-original-buffer-cur-msgdb nil)
57 (defvar mmelmo-imap4-skipped-parts)
59 (make-variable-buffer-local 'wl-message-buffer-cur-folder)
60 (make-variable-buffer-local 'wl-message-buffer-cur-number)
62 (defvar wl-fixed-window-configuration nil)
64 (defun wl-message-buffer-window ()
65 (let* ((mes-buf (concat "^" (default-value 'wl-message-buf-name)))
66 (start-win (selected-window))
70 (setq cur-win (next-window cur-win))
71 (if (string-match mes-buf (buffer-name (window-buffer cur-win)))
72 (throw 'found cur-win))
73 (not (eq cur-win start-win)))))))
75 (defun wl-select-buffer (buffer)
76 (let ((gbw (or (get-buffer-window buffer)
77 (wl-message-buffer-window)))
78 (sum (car wl-message-window-size))
79 (mes (cdr wl-message-window-size))
82 (not (eq (save-excursion (set-buffer (window-buffer gbw))
83 wl-message-buffer-cur-summary-buffer)
86 (run-hooks 'wl-message-window-deleted-hook)
90 ;;; (if (or (null mes)
91 ;;; wl-stay-folder-window)
92 ;;; (delete-other-windows))
93 (when wl-fixed-window-configuration
94 (delete-other-windows)
95 (and wl-stay-folder-window
96 (wl-summary-toggle-disp-folder)))
97 (setq whi (1- (window-height)))
100 (let ((total (+ sum mes)))
101 (setq sum (max window-min-height (/ (* whi sum) total)))
102 (setq mes (max window-min-height (/ (* whi mes) total))))
103 (if (< whi (+ sum mes))
104 (enlarge-window (- (+ sum mes) whi)))))
105 (split-window (get-buffer-window (current-buffer)) sum)
107 (switch-to-buffer buffer)))
110 ;; called by wl-summary-mode buffer
112 (defvar wl-message-func-called-hook nil)
114 (defun wl-message-scroll-down (amount)
115 (let ((view-message-buffer (get-buffer-create wl-message-buf-name))
116 (cur-buf (current-buffer)))
117 (wl-select-buffer view-message-buffer)
121 (select-window (get-buffer-window cur-buf))))
123 (defun wl-message-scroll-up (amount)
124 (let ((view-message-buffer (get-buffer-create wl-message-buf-name))
125 (cur-buf (current-buffer)))
126 (wl-select-buffer view-message-buffer)
131 (if (pos-visible-in-window-p (point))
132 (wl-message-narrow-to-page 1)))) ; Go to next page.
136 (select-window (get-buffer-window cur-buf))))
138 (defun wl-message-follow-current-entity (buffer)
139 "Follow to current message."
140 (wl-draft-reply (wl-message-get-original-buffer)
141 nil wl-message-buffer-cur-summary-buffer) ; reply to all
142 (let ((mail-reply-buffer buffer))
143 (wl-draft-yank-from-mail-reply-buffer nil)))
145 (defun wl-message-original-mode ()
146 (setq major-mode 'wl-message-original-mode)
147 (setq mode-name "Original")
148 (setq buffer-read-only t)
149 (if (fboundp 'set-buffer-file-coding-system)
150 (set-buffer-file-coding-system wl-cs-noconv)))
152 (defun wl-message-mode ()
154 (setq major-mode 'wl-message-mode)
155 (setq buffer-read-only t)
156 (setq mode-name "Message"))
158 (defun wl-message-get-buffer-create ()
159 (let ((buf-name wl-message-buf-name))
160 (or (get-buffer buf-name)
162 (set-buffer (get-buffer-create buf-name))
164 (run-hooks 'wl-message-buffer-created-hook)
165 (get-buffer buf-name)))))
167 (defun wl-message-original-get-buffer-create ()
168 (or (get-buffer wl-original-buf-name)
170 (set-buffer (get-buffer-create wl-original-buf-name))
171 (wl-message-original-mode)
172 (get-buffer wl-original-buf-name))))
174 (defun wl-message-exit ()
176 (let (summary-buf summary-win)
177 (if (setq summary-buf wl-message-buffer-cur-summary-buffer)
178 (if (setq summary-win (get-buffer-window summary-buf))
179 (select-window summary-win)
180 (switch-to-buffer summary-buf)
181 (wl-select-buffer wl-message-buf-name)
182 (select-window (get-buffer-window summary-buf))))
183 (run-hooks 'wl-message-exit-hook)))
185 (defvar wl-message-mode-map nil)
186 (if wl-message-mode-map
188 (setq wl-message-mode-map (make-sparse-keymap))
189 (define-key wl-message-mode-map "q" 'wl-message-exit)
190 (define-key wl-message-mode-map "n" 'wl-message-exit)
191 (define-key wl-message-mode-map "p" 'wl-message-exit))
193 (defun wl-message-decode (outbuf inbuf flag)
195 ((eq flag 'all-header)
198 (let ((buffer-read-only nil))
199 (decode-mime-charset-region (point-min)
201 (goto-char (point-min))
202 (re-search-forward "^$" nil t)
205 (wl-message-decode-with-all-header outbuf inbuf))
209 (let ((buffer-read-only nil))
212 (elmo-set-buffer-multibyte nil))
213 (copy-to-buffer outbuf (point-min) (point-max))
215 (use-local-map wl-message-mode-map)
216 (elmo-set-buffer-multibyte default-enable-multibyte-characters)
217 ;;; (decode-mime-charset-region (point-min) (point-max) wl-mime-charset)
218 ;; we can call decode-coding-region() directly, because multibyte flag is t.
219 (decode-coding-region (point-min) (point-max) wl-cs-autoconv)
220 (wl-highlight-message (point-min)
222 (goto-char (point-min))
223 (re-search-forward "^$" nil t)) nil))))
227 (let ((buffer-read-only nil))
228 (decode-mime-charset-region (point-min)
230 (goto-char (point-min))
231 (re-search-forward "^$" nil t)
234 (wl-message-decode-mode outbuf inbuf))))
236 (defun wl-message-prev-page (&optional lines)
237 "Scroll down this message. Returns non-nil if top of message."
239 (let ((cur-buf (current-buffer))
240 (view-message-buffer (get-buffer-create wl-message-buf-name))
242 (wl-select-buffer view-message-buffer)
243 (move-to-window-line 0)
244 (if (and wl-break-pages
246 (not (save-restriction (widen) (bobp))))
248 (wl-message-narrow-to-page -1)
249 (goto-char (point-max))
254 (select-window (get-buffer-window cur-buf))
257 (static-if (fboundp 'luna-make-entity)
258 (defsubst wl-message-make-mime-entity (backend number backend folder msgdb)
259 (luna-make-entity (mm-expand-class-name 'elmo)
260 :location (get-buffer-create
261 (concat mmelmo-entity-buffer-name "0"))
262 :imap (eq backend 'elmo-imap4)
265 :msgdb msgdb :size 0))
266 (defsubst wl-message-make-mime-entity (backend number backend folder msgdb)
267 (mime-open-entity backend (list folder number msgdb nil))))
269 (defun wl-message-next-page (&optional lines)
270 "Scroll up this message. Returns non-nil if bottom of message."
272 (let ((cur-buf (current-buffer))
273 (view-message-buffer (get-buffer-create wl-message-buf-name))
275 (wl-select-buffer view-message-buffer)
276 (move-to-window-line -1)
279 (and (pos-visible-in-window-p)
281 (if (or (null wl-break-pages)
284 (widen) (forward-line) (eobp))))
286 (wl-message-narrow-to-page 1)
289 (static-if (boundp 'window-pixel-scroll-increment)
290 ;; XEmacs 21.2.20 and later.
291 (let (window-pixel-scroll-increment)
295 (goto-char (point-max))))
297 (select-window (get-buffer-window cur-buf))
301 (defun wl-message-narrow-to-page (&optional arg)
303 (setq arg (if arg (prefix-numeric-value arg) 0))
306 (forward-page -1) ; Beginning of current page.
308 (goto-char (point-min))))
309 (forward-char 1) ; for compatibility with emacs-19.28 and emacs-19.29
312 ((> arg 0) (forward-page arg))
313 ((< arg 0) (forward-page (1- arg))))
316 (narrow-to-region (point)
319 (if (and (eolp) (not (bobp)))
323 (defun wl-message-toggle-disp-summary ()
325 (let ((summary-buf (get-buffer wl-message-buffer-cur-summary-buffer))
328 (buffer-live-p summary-buf))
329 (if (setq summary-win (get-buffer-window summary-buf))
330 (delete-window summary-win)
331 (switch-to-buffer summary-buf)
332 (wl-select-buffer wl-message-buf-name))
333 (wl-summary-goto-folder-subr wl-message-buffer-cur-folder 'no-sync
336 (let ((sum-buf (current-buffer)))
337 (wl-select-buffer wl-message-buf-name)
338 (setq wl-message-buffer-cur-summary-buffer sum-buf)))))
340 (defun wl-message-normal-get-original-buffer ()
341 (let ((ret-val (get-buffer wl-original-buf-name)))
344 (set-buffer (setq ret-val
345 (get-buffer-create wl-original-buf-name)))
346 (wl-message-original-mode)))
351 (defalias 'wl-message-get-original-buffer
352 'mmelmo-get-original-buffer)
353 (defalias 'wl-message-get-original-buffer
354 'wl-message-normal-get-original-buffer))
356 (defvar wl-message-redisplay-func 'wl-normal-message-redisplay)
357 (defvar wl-message-cache-used nil) ;whether cache is used or not.
359 (defun wl-message-redisplay (folder number flag msgdb &optional force-reload)
360 (let ((default-mime-charset wl-mime-charset)
361 (buffer-read-only nil))
362 (setq wl-message-cache-used nil)
363 (if wl-message-redisplay-func
364 (funcall wl-message-redisplay-func
365 folder number flag msgdb force-reload))))
367 ;; nil means don't fetch all.
368 (defun wl-message-decide-backend (folder number message-id size)
369 (let ((dont-do-that (and
370 (not (setq wl-message-cache-used
372 (elmo-buffer-cache-hit
373 (list folder number message-id))
374 (elmo-cache-exists-p message-id
377 (not (elmo-local-file-p folder number))
378 wl-fetch-confirm-threshold
379 (>= size wl-fetch-confirm-threshold)
381 (format "Fetch entire message? (%dbytes)"
384 (cond ((and dont-do-that
385 (eq (elmo-folder-number-get-type folder number) 'imap4)
386 (not (and (elmo-use-cache-p folder number)
387 (elmo-cache-exists-p message-id folder number))))
389 (t (if (not dont-do-that) 'elmo)))))
391 (defmacro wl-message-original-buffer-folder ()
392 wl-original-buffer-cur-folder)
394 (defmacro wl-message-original-buffer-number ()
395 wl-original-buffer-cur-number)
397 (defun wl-message-set-original-buffer-information (folder number)
398 (when (or (not (string= folder (or wl-original-buffer-cur-folder "")))
399 (not (eq number (or wl-original-buffer-cur-number 0))))
400 (setq wl-original-buffer-cur-folder folder)
401 (setq wl-original-buffer-cur-number number)))
403 ;; Works on FLIM-1.9.0/SEMI-1.8.2 or later (maybe).
404 (defun wl-mmelmo-message-redisplay (folder number flag msgdb
405 &optional force-reload)
406 (let* ((cur-buf (current-buffer))
407 (view-message-buffer (wl-message-get-buffer-create))
408 (message-id (cdr (assq number
409 (elmo-msgdb-get-number-alist msgdb))))
410 (size (elmo-msgdb-overview-entity-get-size
411 (elmo-msgdb-overview-get-entity number msgdb)))
412 (backend (wl-message-decide-backend folder number message-id size))
413 cur-entity ret-val header-end real-fld-num summary-win)
415 (wl-select-buffer view-message-buffer)
416 (set-buffer view-message-buffer)
419 (setq wl-message-buffer-cur-summary-buffer cur-buf)
420 (setq wl-message-buffer-cur-folder folder)
421 (setq wl-message-buffer-cur-number number)
422 (setq buffer-read-only nil)
425 (let (mime-display-header-hook ;; bind to nil...
426 (wl-message-ignored-field-list
427 (if (eq flag 'all-header)
429 wl-message-ignored-field-list))
430 (mmelmo-force-reload force-reload)
431 (mmelmo-imap4-threshold wl-fetch-confirm-threshold))
432 (setq real-fld-num (elmo-get-real-folder-number
435 (wl-message-make-mime-entity
437 (if (eq backend 'elmo-imap4)
441 (if (eq backend 'elmo-imap4)
445 (setq mmelmo-imap4-skipped-parts nil)
446 ;; mime-display-message sets buffer-read-only variable as t.
447 ;; which makes buffer read-only status confused...
448 (mime-display-message cur-entity view-message-buffer
449 nil nil 'mmelmo-original-mode)
450 (if mmelmo-imap4-skipped-parts
452 (message "Skipped fetching of %s."
456 mmelmo-imap4-skipped-parts ","))))
457 (if (and (eq backend 'elmo-imap4)
458 (null mmelmo-imap4-skipped-parts))
459 (message "No required part was skipped."))
460 (setq ret-val (not (eq backend 'elmo-imap4))))
461 (message "Skipped fetching.")
463 (setq buffer-read-only nil)
464 (wl-message-set-original-buffer-information folder number)
465 (wl-message-overload-functions)
467 (when wl-highlight-body-too
470 (wl-message-narrow-to-page)
471 (error nil));; ignore errors.
472 (setq mode-line-buffer-identification
473 (format "Wanderlust: << %s / %s >>"
474 (if (memq 'modeline wl-use-folder-petname)
475 (wl-folder-get-petname folder)
477 (goto-char (point-min))
480 (run-hooks 'wl-message-redisplay-hook))
481 ;; go back to summary mode
482 (set-buffer-modified-p nil)
483 (setq buffer-read-only t)
485 (setq summary-win (get-buffer-window cur-buf))
486 (if (window-live-p summary-win)
487 (select-window summary-win))))
491 (defun wl-normal-message-redisplay (folder number flag msgdb
492 &optional force-reload)
494 (let* ((cur-buf (current-buffer))
495 (original-message-buffer (wl-message-get-original-buffer))
496 (view-message-buffer (wl-message-get-buffer-create))
497 (message-id (cdr (assq number
498 (elmo-msgdb-get-number-alist msgdb))))
499 (size (elmo-msgdb-overview-entity-get-size
500 (elmo-msgdb-overview-get-entity number msgdb)))
501 header-end ret-val summary-win)
502 (wl-select-buffer view-message-buffer)
505 (setq wl-message-buffer-cur-summary-buffer cur-buf)
506 (setq wl-message-buffer-cur-folder folder)
507 (setq wl-message-buffer-cur-number number)
508 (setq buffer-read-only nil)
510 (if (or (eq (elmo-folder-number-get-type folder number) 'localdir)
511 (not (and (integerp size)
512 wl-fetch-confirm-threshold
513 (>= size wl-fetch-confirm-threshold)
514 (not (elmo-cache-exists-p message-id
517 (format "Fetch entire message? (%dbytes)"
521 (set-buffer original-message-buffer)
522 (let ((buffer-read-only nil))
523 (elmo-read-msg-with-buffer-cache
524 folder number original-message-buffer msgdb force-reload)))
525 ;; decode MIME message.
528 original-message-buffer flag)
531 (set-buffer view-message-buffer)
533 (setq buffer-read-only nil)
534 (wl-message-set-original-buffer-information folder number)
535 (wl-message-overload-functions)
537 (and wl-highlight-body-too (wl-highlight-body))
539 (wl-message-narrow-to-page)
540 (error nil)) ; ignore errors.
541 (setq mode-line-buffer-identification
542 (format "Wanderlust: << %s / %s >>"
543 (if (memq 'modeline wl-use-folder-petname)
544 (wl-folder-get-petname folder)
547 (goto-char (point-min))
549 (run-hooks 'wl-message-redisplay-hook)
550 ;; go back to summary mode
551 (set-buffer-modified-p nil)
552 (setq buffer-read-only t)
554 (setq summary-win (get-buffer-window cur-buf))
555 (if (window-live-p summary-win)
556 (select-window summary-win)))
560 (defvar wl-message-button-map (make-sparse-keymap))
562 (defun wl-message-add-button (from to function &optional data)
563 "Create a button between FROM and TO with callback FUNCTION and DATA."
566 (nconc (list 'wl-message-button-callback function)
568 (list 'wl-message-button-data data))))
569 (let ((ov (make-overlay from to)))
570 (overlay-put ov 'mouse-face 'highlight)
571 (overlay-put ov 'local-map wl-message-button-map)
572 (overlay-put ov 'evaporate t)))
574 (defun wl-message-button-dispatcher (event)
575 "Select the button under point."
577 (mouse-set-point event)
578 (let ((callback (get-text-property (point) 'wl-message-button-callback))
579 (data (get-text-property (point) 'wl-message-button-data)))
581 (funcall callback data)
582 (wl-message-button-dispatcher-internal event))))
584 (defun wl-message-button-refer-article (data)
585 "Read article specified by Message-ID DATA at point."
586 (switch-to-buffer-other-window
587 wl-message-buffer-cur-summary-buffer)
588 (if (wl-summary-jump-to-msg-by-message-id data)
589 (wl-summary-redisplay)))
591 (defun wl-message-refer-article-or-url (e)
592 "Read article specified by message-id around point.
593 If failed, attempt to execute button-dispatcher."
595 (let ((window (get-buffer-window (current-buffer)))
596 mouse-window point beg end msg-id)
600 (setq mouse-window (get-buffer-window (current-buffer)))
602 (setq beg (save-excursion (beginning-of-line) (point)))
603 (setq end (save-excursion (end-of-line) (point)))
604 (search-forward ">" end t) ;Move point to end of "<....>".
605 (if (and (re-search-backward "\\(<[^<> \t\n]+@[^<> \t\n]+>\\)"
607 (not (string-match "mailto:"
608 (setq msg-id (wl-match-buffer 1)))))
611 (switch-to-buffer-other-window
612 wl-message-buffer-cur-summary-buffer)
613 (if (wl-summary-jump-to-msg-by-message-id msg-id)
614 (wl-summary-redisplay)))
615 (wl-message-button-dispatcher-internal e)))
616 (if (eq mouse-window (get-buffer-window (current-buffer)))
617 (select-window window)))))
619 (defun wl-message-uu-substring (buf outbuf &optional first last)
622 (search-forward "\n\n")
624 ep filename case-fold-search)
628 (if (re-search-forward "^begin[ \t]+[0-9]+[ \t]+\\([^ ].*\\)" nil t)
629 (setq filename (buffer-substring (match-beginning 1)(match-end 1)))
631 (re-search-forward "^M.*$" nil t)) ; uuencoded string
634 (goto-char (point-max))
636 (re-search-backward "^end" sp t)
637 (re-search-backward "^M.*$" sp t)) ; uuencoded string
641 (goto-char (point-max))
642 (insert-buffer-substring buf sp ep)
647 (product-provide (provide 'wl-message) (require 'wl-version))
649 ;;; wl-message.el ends here