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