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)
291 (goto-char (point-max))))
293 (select-window (get-buffer-window cur-buf))
297 (defun wl-message-narrow-to-page (&optional arg)
299 (setq arg (if arg (prefix-numeric-value arg) 0))
302 (forward-page -1) ; Beginning of current page.
304 (goto-char (point-min))))
305 (forward-char 1) ; for compatibility with emacs-19.28 and emacs-19.29
308 ((> arg 0) (forward-page arg))
309 ((< arg 0) (forward-page (1- arg))))
312 (narrow-to-region (point)
315 (if (and (eolp) (not (bobp)))
319 (defun wl-message-toggle-disp-summary ()
321 (let ((summary-buf (get-buffer wl-message-buffer-cur-summary-buffer))
324 (buffer-live-p summary-buf))
325 (if (setq summary-win (get-buffer-window summary-buf))
326 (delete-window summary-win)
327 (switch-to-buffer summary-buf)
328 (wl-select-buffer wl-message-buf-name))
329 (wl-summary-goto-folder-subr wl-message-buffer-cur-folder 'no-sync
332 (let ((sum-buf (current-buffer)))
333 (wl-select-buffer wl-message-buf-name)
334 (setq wl-message-buffer-cur-summary-buffer sum-buf)))))
336 (defun wl-message-normal-get-original-buffer ()
337 (let ((ret-val (get-buffer wl-original-buf-name)))
340 (set-buffer (setq ret-val
341 (get-buffer-create wl-original-buf-name)))
342 (wl-message-original-mode)))
347 (defalias 'wl-message-get-original-buffer
348 'mmelmo-get-original-buffer)
349 (defalias 'wl-message-get-original-buffer
350 'wl-message-normal-get-original-buffer))
352 (defvar wl-message-redisplay-func 'wl-normal-message-redisplay)
353 (defvar wl-message-cache-used nil) ;whether cache is used or not.
355 (defun wl-message-redisplay (folder number flag msgdb &optional force-reload)
356 (let ((default-mime-charset wl-mime-charset)
357 (buffer-read-only nil))
358 (setq wl-message-cache-used nil)
359 (if wl-message-redisplay-func
360 (funcall wl-message-redisplay-func
361 folder number flag msgdb force-reload))))
363 ;; nil means don't fetch all.
364 (defun wl-message-decide-backend (folder number message-id size)
365 (let ((dont-do-that (and
366 (not (setq wl-message-cache-used
368 (elmo-buffer-cache-hit
369 (list folder number message-id))
370 (elmo-cache-exists-p message-id
373 (not (elmo-local-file-p folder number))
374 wl-fetch-confirm-threshold
375 (>= size wl-fetch-confirm-threshold)
377 (format "Fetch entire message? (%dbytes)"
380 (cond ((and dont-do-that
381 (eq (elmo-folder-number-get-type folder number) 'imap4)
382 (not (and (elmo-use-cache-p folder number)
383 (elmo-cache-exists-p message-id folder number))))
385 (t (if (not dont-do-that) 'elmo)))))
387 (defmacro wl-message-original-buffer-folder ()
388 wl-original-buffer-cur-folder)
390 (defmacro wl-message-original-buffer-number ()
391 wl-original-buffer-cur-number)
393 (defun wl-message-set-original-buffer-information (folder number)
394 (when (or (not (string= folder (or wl-original-buffer-cur-folder "")))
395 (not (eq number (or wl-original-buffer-cur-number 0))))
396 (setq wl-original-buffer-cur-folder folder)
397 (setq wl-original-buffer-cur-number number)))
399 ;; Works on FLIM-1.9.0/SEMI-1.8.2 or later (maybe).
400 (defun wl-mmelmo-message-redisplay (folder number flag msgdb
401 &optional force-reload)
402 (let* ((cur-buf (current-buffer))
403 (view-message-buffer (wl-message-get-buffer-create))
404 (message-id (cdr (assq number
405 (elmo-msgdb-get-number-alist msgdb))))
406 (size (elmo-msgdb-overview-entity-get-size
407 (elmo-msgdb-overview-get-entity number msgdb)))
408 (backend (wl-message-decide-backend folder number message-id size))
409 cur-entity ret-val header-end real-fld-num summary-win)
411 (wl-select-buffer view-message-buffer)
412 (set-buffer view-message-buffer)
415 (setq wl-message-buffer-cur-summary-buffer cur-buf)
416 (setq wl-message-buffer-cur-folder folder)
417 (setq wl-message-buffer-cur-number number)
418 (setq buffer-read-only nil)
421 (let (mime-display-header-hook ;; bind to nil...
422 (wl-message-ignored-field-list
423 (if (eq flag 'all-header)
425 wl-message-ignored-field-list))
426 (mmelmo-force-reload force-reload)
427 (mmelmo-imap4-threshold wl-fetch-confirm-threshold))
428 (setq real-fld-num (elmo-get-real-folder-number
431 (wl-message-make-mime-entity
433 (if (eq backend 'elmo-imap4)
437 (if (eq backend 'elmo-imap4)
441 (setq mmelmo-imap4-skipped-parts nil)
442 ;; mime-display-message sets buffer-read-only variable as t.
443 ;; which makes buffer read-only status confused...
444 (mime-display-message cur-entity view-message-buffer
445 nil nil 'mmelmo-original-mode)
446 (if mmelmo-imap4-skipped-parts
448 (message "Skipped fetching of %s."
452 mmelmo-imap4-skipped-parts ","))))
453 (if (and (eq backend 'elmo-imap4)
454 (null mmelmo-imap4-skipped-parts))
455 (message "No required part was skipped."))
456 (setq ret-val (not (eq backend 'elmo-imap4))))
457 (message "Skipped fetching.")
459 (setq buffer-read-only nil)
460 (wl-message-set-original-buffer-information folder number)
461 (wl-message-overload-functions)
463 (when wl-highlight-body-too
466 (wl-message-narrow-to-page)
467 (error nil));; ignore errors.
468 (setq mode-line-buffer-identification
469 (format "Wanderlust: << %s / %s >>"
470 (if (memq 'modeline wl-use-folder-petname)
471 (wl-folder-get-petname folder)
473 (goto-char (point-min))
476 (run-hooks 'wl-message-redisplay-hook))
477 ;; go back to summary mode
478 (set-buffer-modified-p nil)
479 (setq buffer-read-only t)
481 (setq summary-win (get-buffer-window cur-buf))
482 (if (window-live-p summary-win)
483 (select-window summary-win))))
487 (defun wl-normal-message-redisplay (folder number flag msgdb
488 &optional force-reload)
490 (let* ((cur-buf (current-buffer))
491 (original-message-buffer (wl-message-get-original-buffer))
492 (view-message-buffer (wl-message-get-buffer-create))
493 (message-id (cdr (assq number
494 (elmo-msgdb-get-number-alist msgdb))))
495 (size (elmo-msgdb-overview-entity-get-size
496 (elmo-msgdb-overview-get-entity number msgdb)))
497 header-end ret-val summary-win)
498 (wl-select-buffer view-message-buffer)
501 (setq wl-message-buffer-cur-summary-buffer cur-buf)
502 (setq wl-message-buffer-cur-folder folder)
503 (setq wl-message-buffer-cur-number number)
504 (setq buffer-read-only nil)
506 (if (or (eq (elmo-folder-number-get-type folder number) 'localdir)
507 (not (and (integerp size)
508 wl-fetch-confirm-threshold
509 (>= size wl-fetch-confirm-threshold)
510 (not (elmo-cache-exists-p message-id
513 (format "Fetch entire message? (%dbytes)"
517 (set-buffer original-message-buffer)
518 (let ((buffer-read-only nil))
519 (elmo-read-msg-with-buffer-cache
520 folder number original-message-buffer msgdb force-reload)))
521 ;; decode MIME message.
524 original-message-buffer flag)
527 (set-buffer view-message-buffer)
529 (setq buffer-read-only nil)
530 (wl-message-set-original-buffer-information folder number)
531 (wl-message-overload-functions)
533 (and wl-highlight-body-too (wl-highlight-body))
535 (wl-message-narrow-to-page)
536 (error nil)) ; ignore errors.
537 (setq mode-line-buffer-identification
538 (format "Wanderlust: << %s / %s >>"
539 (if (memq 'modeline wl-use-folder-petname)
540 (wl-folder-get-petname folder)
543 (goto-char (point-min))
545 (run-hooks 'wl-message-redisplay-hook)
546 ;; go back to summary mode
547 (set-buffer-modified-p nil)
548 (setq buffer-read-only t)
550 (setq summary-win (get-buffer-window cur-buf))
551 (if (window-live-p summary-win)
552 (select-window summary-win)))
556 (defvar wl-message-button-map (make-sparse-keymap))
558 (defun wl-message-add-button (from to function &optional data)
559 "Create a button between FROM and TO with callback FUNCTION and DATA."
562 (nconc (list 'wl-message-button-callback function)
564 (list 'wl-message-button-data data))))
565 (let ((ov (make-overlay from to)))
566 (overlay-put ov 'mouse-face 'highlight)
567 (overlay-put ov 'local-map wl-message-button-map)
568 (overlay-put ov 'evaporate t)))
570 (defun wl-message-button-dispatcher (event)
571 "Select the button under point."
573 (mouse-set-point event)
574 (let ((callback (get-text-property (point) 'wl-message-button-callback))
575 (data (get-text-property (point) 'wl-message-button-data)))
577 (funcall callback data)
578 (wl-message-button-dispatcher-internal event))))
580 (defun wl-message-button-refer-article (data)
581 "Read article specified by Message-ID DATA at point."
582 (switch-to-buffer-other-window
583 wl-message-buffer-cur-summary-buffer)
584 (if (wl-summary-jump-to-msg-by-message-id data)
585 (wl-summary-redisplay)))
587 (defun wl-message-refer-article-or-url (e)
588 "Read article specified by message-id around point.
589 If failed, attempt to execute button-dispatcher."
591 (let ((window (get-buffer-window (current-buffer)))
592 mouse-window point beg end msg-id)
596 (setq mouse-window (get-buffer-window (current-buffer)))
598 (setq beg (save-excursion (beginning-of-line) (point)))
599 (setq end (save-excursion (end-of-line) (point)))
600 (search-forward ">" end t) ;Move point to end of "<....>".
601 (if (and (re-search-backward "\\(<[^<> \t\n]+@[^<> \t\n]+>\\)"
603 (not (string-match "mailto:"
604 (setq msg-id (wl-match-buffer 1)))))
607 (switch-to-buffer-other-window
608 wl-message-buffer-cur-summary-buffer)
609 (if (wl-summary-jump-to-msg-by-message-id msg-id)
610 (wl-summary-redisplay)))
611 (wl-message-button-dispatcher-internal e)))
612 (if (eq mouse-window (get-buffer-window (current-buffer)))
613 (select-window window)))))
615 (defun wl-message-uu-substring (buf outbuf &optional first last)
618 (search-forward "\n\n")
620 ep filename case-fold-search)
624 (if (re-search-forward "^begin[ \t]+[0-9]+[ \t]+\\([^ ].*\\)" nil t)
625 (setq filename (buffer-substring (match-beginning 1)(match-end 1)))
627 (re-search-forward "^M.*$" nil t)) ; uuencoded string
630 (goto-char (point-max))
632 (re-search-backward "^end" sp t)
633 (re-search-backward "^M.*$" sp t)) ; uuencoded string
637 (goto-char (point-max))
638 (insert-buffer-substring buf sp ep)
643 (product-provide (provide 'wl-message) (require 'wl-version))
645 ;;; wl-message.el ends here