Update.
[elisp/wanderlust.git] / wl / wl-message.el
1 ;;; wl-message.el -- Message displaying modules for Wanderlust.
2
3 ;; Copyright 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
4
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, net news
7 ;; Time-stamp: <2000-03-17 10:19:41 teranisi>
8
9 ;; This file is part of Wanderlust (Yet Another Message Interface on Emacsen).
10
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15 ;;
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 ;; GNU General Public License for more details.
20 ;;
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25 ;;
26
27 ;;; Commentary:
28 ;; 
29
30 ;;; Code:
31 ;; 
32
33 (require 'wl-vars)
34 (require 'wl-highlight)
35
36 (eval-when-compile
37   (if wl-use-semi
38       (progn
39         (require 'wl-mime)
40         (require 'mime-view)
41         (require 'mmelmo-imap4))
42     (require 'tm-wl))
43   (mapcar
44    (function
45     (lambda (symbol)
46       (unless (boundp symbol)
47         (set (make-local-variable symbol) nil))))
48    '(mime-view-ignored-field-list mmelmo-imap4-skipped-parts))
49   (defun-maybe event-window (a))
50   (defun-maybe posn-window (a))
51   (defun-maybe event-start (a))
52   (defun-maybe mime-open-entity (a b)))
53
54 (defvar wl-original-buf-name "*Message*")
55 (defvar wl-message-buf-name "Message")
56 (defvar wl-message-buffer-cur-summary-buffer nil)
57 (defvar wl-message-buffer-cur-folder nil)
58 (defvar wl-message-buffer-cur-number nil)
59
60 (defvar wl-original-buffer-cur-folder nil)
61 (defvar wl-original-buffer-cur-number nil)
62 (defvar wl-original-buffer-cur-msgdb  nil)
63
64 (mapcar 
65  (function make-variable-buffer-local)
66  (list 'wl-message-buffer-cur-folder
67        'wl-message-buffer-cur-number))
68
69 (provide 'wl-message)
70
71 (defvar wl-fixed-window-configuration nil)
72
73 (defun wl-message-buffer-window ()
74   (let* ((mes-buf (concat "^" (default-value 'wl-message-buf-name)))
75          (start-win (selected-window))
76          (cur-win start-win))
77     (catch 'found
78       (while (progn
79                (setq cur-win (next-window cur-win))
80                (if (string-match mes-buf (buffer-name (window-buffer cur-win)))
81                    (throw 'found cur-win))
82                (not (eq cur-win start-win)))))))
83
84 (defun wl-select-buffer (buffer)
85   (let ((gbw (or (get-buffer-window buffer)
86                  (wl-message-buffer-window)))
87         (sum (car wl-message-window-size))
88         (mes (cdr wl-message-window-size))
89         whi)
90     (when (and gbw
91                (not (eq (save-excursion (set-buffer (window-buffer gbw))
92                                         wl-message-buffer-cur-summary-buffer)
93                         (current-buffer))))
94       (delete-window gbw)
95       (run-hooks 'wl-message-window-deleted-hook)
96       (setq gbw nil))
97     (if gbw
98         (select-window gbw)
99 ;      (if (or (null mes)
100 ;             wl-stay-folder-window)
101 ;         (delete-other-windows))
102       (when wl-fixed-window-configuration
103         (delete-other-windows)
104         (and wl-stay-folder-window
105              (wl-summary-toggle-disp-folder)))
106       (setq whi (1- (window-height)))
107       (if mes
108           (progn
109             (let ((total (+ sum mes)))
110               (setq sum (max window-min-height (/ (* whi sum) total)))
111               (setq mes (max window-min-height (/ (* whi mes) total))))
112             (if (< whi (+ sum mes))
113                 (enlarge-window (- (+ sum mes) whi)))))
114       (split-window (get-buffer-window (current-buffer)) sum)
115       (other-window 1))
116     (switch-to-buffer buffer)))
117
118 ;;
119 ;; called by wl-summary-mode buffer
120 ;;
121 (defvar wl-message-func-called-hook nil)
122
123 (defun wl-message-scroll-down (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)
127     (if (bobp)
128         ()
129       (scroll-down))
130     (select-window (get-buffer-window cur-buf))))  
131
132 (defun wl-message-scroll-up (amount)
133   (let ((view-message-buffer (get-buffer-create wl-message-buf-name))
134         (cur-buf (current-buffer)))
135     (wl-select-buffer view-message-buffer)
136     (save-excursion
137       (save-restriction
138         (widen)
139         (forward-page 1)
140         (if (pos-visible-in-window-p (point))
141             (wl-message-narrow-to-page 1))))            ;Go to next page.
142     (if (eobp)
143         ()
144       (scroll-up))
145     (select-window (get-buffer-window cur-buf))))
146   
147 (defun wl-message-follow-current-entity (buffer)
148   "Follow to current message"
149   (wl-draft-reply (wl-message-get-original-buffer)
150                   'to-all wl-message-buffer-cur-summary-buffer)
151   (let ((mail-reply-buffer buffer))
152     (wl-draft-yank-from-mail-reply-buffer nil)))
153
154 (defun wl-message-original-mode ()
155   (setq major-mode 'wl-message-original-mode)
156   (setq mode-name "Original")
157   (setq buffer-read-only t)
158   (if (fboundp 'set-buffer-file-coding-system)
159       (set-buffer-file-coding-system wl-cs-noconv)))
160
161 (defun wl-message-mode ()
162   (interactive)
163   (setq major-mode 'wl-message-mode)
164   (setq buffer-read-only t)
165   (setq mode-name "Message"))
166
167 (defun wl-message-get-buffer-create ()
168   (let ((buf-name wl-message-buf-name))
169     (or (get-buffer buf-name)
170         (save-excursion
171           (set-buffer (get-buffer-create buf-name))
172           (wl-message-mode)
173           (run-hooks 'wl-message-buffer-created-hook)
174           (get-buffer buf-name)))))
175
176 (defun wl-message-original-get-buffer-create ()
177   (or (get-buffer wl-original-buf-name)
178       (save-excursion
179         (set-buffer (get-buffer-create wl-original-buf-name))
180         (wl-message-original-mode)
181         (get-buffer wl-original-buf-name))))
182   
183 (defun wl-message-exit ()
184   (interactive)
185   (let (summary-buf summary-win)
186     (if (setq summary-buf wl-message-buffer-cur-summary-buffer)
187         (if (setq summary-win (get-buffer-window summary-buf))
188             (select-window summary-win)
189           (switch-to-buffer summary-buf)
190           (wl-select-buffer wl-message-buf-name)
191           (select-window (get-buffer-window summary-buf))))
192     (run-hooks 'wl-message-exit-hook)))
193
194 (defun wl-message-decode (outbuf inbuf flag)
195   (cond
196    ((eq flag 'all-header)
197     (save-excursion
198       (set-buffer inbuf)
199       (let ((buffer-read-only nil))
200         (decode-mime-charset-region (point-min) 
201                                     (save-excursion
202                                       (goto-char (point-min))
203                                       (re-search-forward "^$" nil t)
204                                       (point))
205                                     wl-mime-charset)))
206     (wl-message-decode-with-all-header outbuf inbuf))
207    ((eq flag 'no-mime)
208     (save-excursion
209       (set-buffer inbuf)
210       (let ((buffer-read-only nil))
211         (save-excursion 
212           (set-buffer outbuf)
213           (elmo-set-buffer-multibyte nil))
214         (copy-to-buffer outbuf (point-min) (point-max))
215         (set-buffer outbuf)
216         (local-set-key "q" 'wl-message-exit)
217         (local-set-key "p" 'wl-message-exit)
218         (local-set-key "n" 'wl-message-exit)
219         (elmo-set-buffer-multibyte default-enable-multibyte-characters)
220         ;;(decode-mime-charset-region (point-min) (point-max) wl-mime-charset)
221         ;; we can call decode-coding-region() directly, because multibyte flag is t.
222         (decode-coding-region (point-min) (point-max) wl-cs-autoconv)
223         (wl-highlight-message (point-min)
224                               (save-excursion
225                                 (goto-char (point-min))
226                                 (re-search-forward "^$" nil t)) nil))))
227    (t                                   ; normal
228     (save-excursion
229       (set-buffer inbuf)
230       (let ((buffer-read-only nil))
231         (decode-mime-charset-region (point-min) 
232                                     (save-excursion
233                                       (goto-char (point-min))
234                                       (re-search-forward "^$" nil t)
235                                       (point))
236                                     wl-mime-charset)))
237     (wl-message-decode-mode outbuf inbuf))))
238
239 (defun wl-message-prev-page (&optional lines)
240   "Scroll down this message. Returns non-nil if top of message"
241   (interactive)
242   (let ((cur-buf (current-buffer))
243         (view-message-buffer (get-buffer-create wl-message-buf-name))
244         ret-val)  
245     (wl-select-buffer view-message-buffer)
246     (move-to-window-line 0)
247     (if (and wl-break-pages
248              (bobp)
249              (not (save-restriction (widen) (bobp))))
250         (progn
251           (wl-message-narrow-to-page -1)
252           (goto-char (point-max))
253           (recenter -1))
254       (if (not (bobp))
255           (scroll-down lines)
256         (setq ret-val t)))
257     (select-window (get-buffer-window cur-buf))
258     ret-val))
259
260 (static-if (fboundp 'luna-make-entity)
261     (defsubst wl-message-make-mime-entity (backend number backend folder msgdb)
262       (luna-make-entity (mm-expand-class-name 'elmo)
263                         :location (get-buffer-create 
264                                    (concat mmelmo-entity-buffer-name "0"))
265                         :imap (eq backend 'elmo-imap4) 
266                         :folder folder
267                         :number number
268                         :msgdb msgdb :size 0))
269   (defsubst wl-message-make-mime-entity (backend number backend folder msgdb)
270     (mime-open-entity backend (list folder number msgdb nil))))
271
272 (defun wl-message-next-page (&optional lines)
273   "Scroll up this message. Returns non-nil if bottom of message"
274   (interactive)
275   (let ((cur-buf (current-buffer))
276         (view-message-buffer (get-buffer-create wl-message-buf-name))
277         ret-val)
278     (wl-select-buffer view-message-buffer)
279     (move-to-window-line -1)
280     (if (save-excursion
281           (end-of-line)
282           (and (pos-visible-in-window-p)
283                (eobp)))
284         (if (or (null wl-break-pages)
285                 (save-excursion
286                   (save-restriction
287                     (widen) (forward-line) (eobp))))
288             (setq ret-val t)
289           (wl-message-narrow-to-page 1)
290           (setq ret-val nil))
291       (condition-case ()
292           (scroll-up lines)
293         (end-of-buffer
294          (goto-char (point-max))))
295       (setq ret-val nil))
296     (select-window (get-buffer-window cur-buf))
297     ret-val
298     ))
299
300 (defun wl-message-narrow-to-page (&optional arg)
301   (interactive "P")
302   (setq arg (if arg (prefix-numeric-value arg) 0))
303   (save-excursion
304     (condition-case ()
305         (forward-page -1)               ; Beginning of current page.
306       (beginning-of-buffer
307        (goto-char (point-min))))
308     (forward-char 1)  ; for compatibility with emacs-19.28 and emacs-19.29
309     (widen)
310     (cond
311      ((> arg 0) (forward-page arg))
312      ((< arg 0) (forward-page (1- arg))))
313     (forward-page)
314     (if wl-break-pages
315         (narrow-to-region (point)
316                           (progn
317                             (forward-page -1)
318                             (if (and (eolp) (not (bobp)))
319                                 (forward-line))
320                             (point)))) ))
321
322 (defun wl-message-toggle-disp-summary ()
323   (interactive)
324   (let ((summary-buf (get-buffer wl-message-buffer-cur-summary-buffer))
325         summary-win)
326     (if (and summary-buf
327              (buffer-live-p summary-buf))
328         (if (setq summary-win (get-buffer-window summary-buf))
329             (delete-window summary-win)
330           (switch-to-buffer summary-buf)
331           (wl-select-buffer wl-message-buf-name))
332       (wl-summary-goto-folder-subr wl-message-buffer-cur-folder 'no-sync
333                                    nil nil t)
334                                         ; no summary-buf
335       (let ((sum-buf (current-buffer)))
336         (wl-select-buffer wl-message-buf-name)
337         (setq wl-message-buffer-cur-summary-buffer sum-buf)))))
338
339 (defun wl-message-normal-get-original-buffer ()
340   (let (ret-val)
341     (if (setq ret-val (get-buffer wl-original-buf-name))
342         ret-val
343       (set-buffer (setq ret-val 
344                         (get-buffer-create wl-original-buf-name)))
345       (wl-message-original-mode)
346       ret-val)))
347
348
349 (if wl-use-semi
350     (defalias 'wl-message-get-original-buffer 
351       'mmelmo-get-original-buffer)
352   (defalias 'wl-message-get-original-buffer 
353     'wl-message-normal-get-original-buffer))
354
355 (defvar wl-message-redisplay-func 'wl-normal-message-redisplay)
356 (defvar wl-message-cache-used nil) ;whether cache is used or not.
357
358 (defun wl-message-redisplay (folder number flag msgdb &optional force-reload)
359   (let ((default-mime-charset wl-mime-charset)
360         (buffer-read-only nil))
361     (setq wl-message-cache-used nil)
362     (if wl-message-redisplay-func
363         (funcall wl-message-redisplay-func
364                  folder number flag msgdb force-reload))))
365
366 ;; nil means don't fetch all.
367 (defun wl-message-decide-backend (folder number message-id size)
368   (let ((dont-do-that (and 
369                        (not (setq wl-message-cache-used
370                                   (or
371                                    (elmo-buffer-cache-hit
372                                     (list folder number message-id))
373                                    (elmo-cache-exists-p message-id 
374                                                         folder number))))
375                        (integerp size)
376                        (not (elmo-local-file-p folder number))
377                        wl-fetch-confirm-threshold
378                        (>= size wl-fetch-confirm-threshold)
379                        (not (y-or-n-p 
380                              (format "Fetch entire message? (%dbytes)" 
381                                      size))))))
382     (message "")
383     (cond ((and dont-do-that
384                 (eq (elmo-folder-number-get-type folder number) 'imap4)
385                 (not (and (elmo-use-cache-p folder number)
386                           (elmo-cache-exists-p message-id folder number))))
387            'elmo-imap4)
388           (t (if (not dont-do-that) 'elmo)))))
389
390 (defmacro wl-message-original-buffer-folder ()
391   wl-original-buffer-cur-folder)
392
393 (defmacro wl-message-original-buffer-number ()
394   wl-original-buffer-cur-number)
395
396 (defun wl-message-set-original-buffer-information (folder number)
397   (when (or (not (string= folder (or wl-original-buffer-cur-folder "")))
398             (not (eq number (or wl-original-buffer-cur-number 0))))
399     (setq wl-original-buffer-cur-folder folder)
400     (setq wl-original-buffer-cur-number number)))
401
402 ;; Works on FLIM-1.9.0/SEMI-1.8.2 or later (maybe).
403 (defun wl-mmelmo-message-redisplay (folder number flag msgdb
404                                            &optional force-reload)
405   (let* ((cur-buf (current-buffer))
406          (view-message-buffer (wl-message-get-buffer-create))
407          (message-id (cdr (assq number 
408                                 (elmo-msgdb-get-number-alist msgdb))))
409          (size (elmo-msgdb-overview-entity-get-size
410                 (assoc message-id 
411                        (elmo-msgdb-get-overview msgdb))))
412          (backend (wl-message-decide-backend folder number message-id size))
413          cur-entity ret-val header-end real-fld-num summary-win)
414     (require 'mmelmo)
415     (wl-select-buffer view-message-buffer)
416     (set-buffer view-message-buffer)
417     (unwind-protect
418         (progn
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)
423           (erase-buffer)
424           (if backend
425               (let (mime-display-header-hook ;; bind to nil...
426                     (mime-view-ignored-field-list 
427                      (if (eq flag 'all-header)
428                          nil
429                        mime-view-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
433                                     folder number))
434                 (setq cur-entity
435                       (wl-message-make-mime-entity
436                        backend 
437                        (if (eq backend 'elmo-imap4)
438                            (cdr real-fld-num)
439                          number)
440                        backend
441                        (if (eq backend 'elmo-imap4)
442                            (car real-fld-num)
443                          folder)
444                        msgdb))
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                 (wl-mime-display-message cur-entity view-message-buffer
449                                          nil nil 'mmelmo-original-mode)
450                 (if mmelmo-imap4-skipped-parts
451                     (progn
452                       (message "Skipped fetching of %s."
453                                (mapconcat 
454                                 (lambda (x)
455                                   (format "[%s]" x))
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.")
462             (setq ret-val nil)))
463       (setq buffer-read-only nil)
464       (wl-message-set-original-buffer-information folder number)
465       (wl-message-overload-functions)
466       ;; highlight body
467       (when wl-highlight-body-too
468         (wl-highlight-body))
469       (condition-case ()
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)
476                       folder) number))
477       (goto-char (point-min))
478       (unwind-protect
479           (save-excursion
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)
484         (set-buffer cur-buf)
485         (setq summary-win (get-buffer-window cur-buf))
486         (if (window-live-p summary-win)
487             (select-window summary-win))))
488     ret-val
489     ))
490
491 (defun wl-normal-message-redisplay (folder number flag msgdb 
492                                            &optional force-reload)
493   (interactive)
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                 (assoc message-id 
501                        (elmo-msgdb-get-overview msgdb))))
502          header-end ret-val summary-win
503          )
504     (wl-select-buffer view-message-buffer)
505     (unwind-protect
506         (progn
507           (setq wl-message-buffer-cur-summary-buffer cur-buf)
508           (setq wl-message-buffer-cur-folder folder)
509           (setq wl-message-buffer-cur-number number)
510           (setq buffer-read-only nil)
511           (erase-buffer)
512           (if (or (eq (elmo-folder-number-get-type folder number) 'localdir)
513                   (not (and (integerp size)
514                             wl-fetch-confirm-threshold
515                             (>= size wl-fetch-confirm-threshold)
516                             (not (elmo-cache-exists-p message-id 
517                                                       folder number))
518                             (not (y-or-n-p
519                                   (format "Fetch entire message? (%dbytes)" 
520                                           size))))))
521               (progn
522                 (save-excursion
523                   (set-buffer original-message-buffer)
524                   (let ((buffer-read-only nil))
525                     (elmo-read-msg-with-buffer-cache
526                      folder number original-message-buffer msgdb force-reload)))
527                 ;; decode MIME message.
528                 (wl-message-decode 
529                  view-message-buffer 
530                  original-message-buffer flag)
531                 (setq ret-val t))
532             (save-excursion
533               (set-buffer view-message-buffer)
534               (insert "\n\n"))))
535       (setq buffer-read-only nil)
536       (wl-message-set-original-buffer-information folder number)
537       (wl-message-overload-functions)
538       ;; highlight body
539       (and wl-highlight-body-too (wl-highlight-body))
540       (condition-case ()
541           (wl-message-narrow-to-page)
542         (error nil)) ; ignore errors.
543       (setq mode-line-buffer-identification
544             (format "Wanderlust: << %s / %s >>" 
545                     (if (memq 'modeline wl-use-folder-petname)
546                         (wl-folder-get-petname folder)
547                       folder)
548                     number))
549       (goto-char (point-min))
550       (unwind-protect
551           (run-hooks 'wl-message-redisplay-hook)
552         ;; go back to summary mode
553         (set-buffer-modified-p nil)      
554         (setq buffer-read-only t)
555         (set-buffer cur-buf)
556         (setq summary-win (get-buffer-window cur-buf))
557         (if (window-live-p summary-win)
558             (select-window summary-win)))
559       ret-val
560       )))
561
562 (defun wl-message-refer-article-or-url (e)
563   "Read article specified by message-id around point. If failed,
564    attempt to execute button-dispatcher."
565   (interactive "e")
566   (let ((window (get-buffer-window (current-buffer)))
567         mouse-window point beg end msg-id)
568     (unwind-protect
569         (progn
570           (mouse-set-point e)
571           (setq mouse-window (get-buffer-window (current-buffer)))
572           (setq point (point))
573           (setq beg (save-excursion (beginning-of-line) (point)))
574           (setq end (save-excursion (end-of-line) (point)))
575           (search-forward ">" end t)      ;Move point to end of "<....>".
576           (if (and (re-search-backward "\\(<[^<> \t\n]+@[^<> \t\n]+>\\)" 
577                                        beg t)
578                    (not (string-match "mailto:" 
579                                       (setq msg-id (wl-match-buffer 1)))))
580               (progn
581                 (goto-char point)
582                 (switch-to-buffer-other-window 
583                  wl-message-buffer-cur-summary-buffer)
584                 (if (wl-summary-jump-to-msg-by-message-id msg-id)
585                     (wl-summary-redisplay)))
586             (wl-message-button-dispatcher e)))
587       (if (eq mouse-window (get-buffer-window (current-buffer)))
588           (select-window window)))))
589
590 (defun wl-message-uu-substring (buf outbuf &optional first last)
591   (save-excursion
592     (set-buffer buf)
593     (search-forward "\n\n")
594     (let ((sp (point))
595           ep filename case-fold-search)
596       (if first
597           (progn
598             (re-search-forward "^begin[ \t]+[0-9]+[ \t]+\\([^ ].*\\)" nil t)
599             (setq filename (buffer-substring (match-beginning 1)(match-end 1))))
600         (re-search-forward "^M.*$" nil t)) ; uuencoded string
601       (beginning-of-line)
602       (setq sp (point))
603       (goto-char (point-max))
604       (if last
605           (re-search-backward "^end" sp t)
606         (re-search-backward "^M.*$" sp t)) ; uuencoded string
607       (forward-line 1)
608       (setq ep (point))
609       (set-buffer outbuf)
610       (goto-char (point-max))
611       (insert-buffer-substring buf sp ep)
612       (set-buffer buf)
613       filename)))
614
615 ;;; wl-message.el ends here