* wl-addrmgr.el (wl-addrmgr-replace-field): Use `point-at-bol'.
[elisp/wanderlust.git] / wl / wl-action.el
1 ;;; wl-action.el --- Mark and actions in the Summary mode for Wanderlust.
2
3 ;; Copyright (C) 2003 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-summary)
33
34 (eval-when-compile
35   (defalias-maybe 'wl-summary-target-mark 'ignore)
36   (defalias-maybe 'wl-summary-target-mark-region 'ignore))
37
38 (defsubst wl-summary-action-mark (action)
39   (nth 0 action))
40 (defsubst wl-summary-action-symbol (action)
41   (nth 1 action))
42 (defsubst wl-summary-action-argument-function (action)
43   (nth 2 action))
44 (defsubst wl-summary-action-set-function (action)
45   (nth 3 action))
46 (defsubst wl-summary-action-exec-function (action)
47   (nth 4 action))
48 (defsubst wl-summary-action-face (action)
49   (nth 5 action))
50 (defsubst wl-summary-action-docstring (action)
51   (concat (nth 6 action)
52           "\nThis function is defined by `wl-summary-define-mark-action'."))
53
54 ;; Set mark
55 (defun wl-summary-set-mark (&optional set-mark number interactive data)
56   "Set temporary mark SET-MARK on the message with NUMBER.
57 NUMBER is the message number to set the mark on.
58 INTERACTIVE is set as t if it have to run interactively.
59 DATA is passed to the set-action function of the action as an argument.
60 Return number if put mark succeed"
61   (let* ((set-mark (or set-mark
62                        (completing-read "Mark: " wl-summary-mark-action-list)))
63          (current (wl-summary-message-number))
64          (action (assoc set-mark wl-summary-mark-action-list))
65          visible mark cur-mark)
66     (when (zerop (elmo-folder-length wl-summary-buffer-elmo-folder))
67       (error "Set mark failed"))
68     (prog1
69         (save-excursion
70           ;; Put mark
71           (if number
72               ;; Jump to message if cursor is not on the message.
73               (when (and (setq visible (wl-summary-message-visible-p number))
74                          (not (eq number current)))
75                 (wl-summary-jump-to-msg number))
76             (setq visible t
77                   number current))
78           (setq cur-mark (nth 1 (wl-summary-registered-temp-mark number)))
79           (unless number
80             (error "No message"))
81           (if (wl-summary-reserve-temp-mark-p cur-mark)
82               (when interactive
83                 (error "Already marked as `%s'" cur-mark))
84             (when (and interactive
85                        (null data)
86                        (wl-summary-action-argument-function action))
87               (setq data (funcall (wl-summary-action-argument-function action)
88                                   (wl-summary-action-symbol action)
89                                   number)))
90             ;; Unset the current mark.
91             (wl-summary-unset-mark number)
92             ;; Set action.
93             (funcall (wl-summary-action-set-function action)
94                      number
95                      (wl-summary-action-mark action)
96                      data)
97             (when visible
98               (wl-summary-put-temp-mark set-mark)
99               (when wl-summary-highlight
100                 (wl-highlight-summary-current-line))
101               (when data
102                 (wl-summary-print-argument number data)))
103             (when (and (eq wl-summary-buffer-view 'thread)
104                        interactive)
105               (wl-thread-open-children number))
106             (set-buffer-modified-p nil)
107             ;; Return value.
108             number))
109       ;; Move the cursor.
110       (if interactive
111           (if (eq wl-summary-move-direction-downward nil)
112               (wl-summary-prev)
113             (wl-summary-next))))))
114
115 (defun wl-summary-register-target-mark (number mark data)
116   (or (memq number wl-summary-buffer-target-mark-list)
117       (setq wl-summary-buffer-target-mark-list
118             (cons number wl-summary-buffer-target-mark-list))))
119
120 (defun wl-summary-unregister-target-mark (number)
121   (setq wl-summary-buffer-target-mark-list
122         (delq number wl-summary-buffer-target-mark-list)))
123
124 (defun wl-summary-have-target-mark-p (number)
125   (memq number wl-summary-buffer-target-mark-list))
126
127 (defun wl-summary-target-mark-set-action (action)
128   (unless (eq (wl-summary-action-symbol action) 'target-mark)
129     (unless wl-summary-buffer-target-mark-list (error "no target"))
130     (save-excursion
131       (goto-char (point-min))
132       (let ((numlist wl-summary-buffer-number-list)
133             number mlist data)
134         ;; use firstly marked message.
135         (when (wl-summary-action-argument-function action)
136           (while numlist
137             (if (memq (car numlist) wl-summary-buffer-target-mark-list)
138                 (setq number (car numlist)
139                       numlist nil))
140             (setq numlist (cdr numlist)))
141           (wl-summary-jump-to-msg number)
142           (setq data (funcall (wl-summary-action-argument-function action)
143                               (wl-summary-action-symbol action) number)))
144         (while (not (eobp))
145           (when (string= (wl-summary-temp-mark) "*")
146             (let (wl-summary-buffer-disp-msg)
147               (when (setq number (wl-summary-message-number))
148                 (wl-summary-set-mark (wl-summary-action-mark action)
149                                      nil nil data)
150                 (setq wl-summary-buffer-target-mark-list
151                       (delq number wl-summary-buffer-target-mark-list)))))
152           (forward-line 1))
153         (setq mlist wl-summary-buffer-target-mark-list)
154         (while mlist
155           (wl-summary-register-temp-mark (car mlist)
156                                          (wl-summary-action-mark action) data)
157           (setq wl-summary-buffer-target-mark-list
158                 (delq (car mlist) wl-summary-buffer-target-mark-list))
159           (setq mlist (cdr mlist)))))))
160
161 ;; wl-summary-buffer-temp-mark-list specification
162 ;; ((1 "D" nil)(2 "o" "+fuga")(3 "O" "+hoge"))
163 (defun wl-summary-register-temp-mark (number mark mark-info)
164   (let ((elem (assq number wl-summary-buffer-temp-mark-list)))
165     (setq wl-summary-buffer-temp-mark-list
166           (delq elem wl-summary-buffer-temp-mark-list)))
167   (setq wl-summary-buffer-temp-mark-list
168         (cons (list number mark mark-info) wl-summary-buffer-temp-mark-list)))
169
170 (defun wl-summary-unregister-temp-mark (number)
171   (let ((elem (assq number wl-summary-buffer-temp-mark-list)))
172     (setq wl-summary-buffer-temp-mark-list
173           (delq elem wl-summary-buffer-temp-mark-list))))
174
175 (defun wl-summary-registered-temp-mark (number)
176   (and wl-summary-buffer-temp-mark-list
177        (assq number wl-summary-buffer-temp-mark-list)))
178
179 (defun wl-summary-collect-temp-mark (mark &optional begin end)
180   (if (or begin end)
181       (save-excursion
182         (save-restriction
183           (let (mark-list)
184             (narrow-to-region (or begin (point-min))(or end (point-max)))
185             (goto-char (point-min))
186             ;; for thread...
187             (if (eq wl-summary-buffer-view 'thread)
188                 (let (number entity mark-info)
189                   (while (not (eobp))
190                     (setq number (wl-summary-message-number)
191                           entity (wl-thread-get-entity number)
192                           mark-info (wl-summary-registered-temp-mark number))
193                     ;; toplevel message mark.
194                     (when (string= (nth 1 mark-info) mark)
195                       (setq mark-list (cons mark-info mark-list)))
196                     ;; When thread is closed...children should also be checked.
197                     (unless (wl-thread-entity-get-opened entity)
198                       (dolist (msg (wl-thread-get-children-msgs number))
199                         (setq mark-info (wl-summary-registered-temp-mark
200                                          msg))
201                         (when (string= (nth 1 mark-info) mark)
202                           (setq mark-list (cons mark-info mark-list)))))
203                     (forward-line 1)))
204               (let (number mark-info)
205                 (while (not (eobp))
206                   (setq number (wl-summary-message-number)
207                         mark-info (wl-summary-registered-temp-mark number))
208                   (when (string= (nth 1 mark-info) mark)
209                     (setq mark-list (cons mark-info mark-list)))
210                   (forward-line 1))))
211             mark-list)))
212     (let (mark-list)
213       (dolist (mark-info wl-summary-buffer-temp-mark-list)
214         (when (string= (nth 1 mark-info) mark)
215           (setq mark-list (cons mark-info mark-list))))
216       mark-list)))
217
218 ;; Unset mark
219 (defun wl-summary-unset-mark (&optional number interactive force)
220   "Unset temporary mark of the message with NUMBER.
221 NUMBER is the message number to unset the mark.
222 If not specified, the message on the cursor position is treated.
223 Optional INTERACTIVE is non-nil when it should be called interactively.
224 If optional FORCE is non-nil, remove scored mark too.
225 Return number if put mark succeed"
226   (interactive)
227   (save-excursion
228     (beginning-of-line)
229     (let ((buffer-read-only nil)
230           visible mark action)
231       (if number
232           ;; Jump to message
233           (when (and (setq visible (wl-summary-message-visible-p number))
234                      (not (eq number (wl-summary-message-number))))
235             (wl-summary-jump-to-msg number))
236         (setq visible t
237               number (wl-summary-message-number)))
238       (setq mark (wl-summary-temp-mark))
239       ;; Remove from temporal mark structure.
240       (wl-summary-unregister-target-mark number)
241       (wl-summary-unregister-temp-mark number)
242       ;; Delete mark on buffer.
243       (when visible
244         (unless (string= mark " ")
245           (wl-summary-put-temp-mark
246            (or (unless force (wl-summary-get-score-mark number))
247                " "))
248           (setq action (assoc mark wl-summary-mark-action-list))
249           (when wl-summary-highlight
250             (wl-highlight-summary-current-line))
251           (when (wl-summary-action-argument-function action)
252             (wl-summary-remove-argument)))
253         (set-buffer-modified-p nil))))
254   ;; Move the cursor.
255   ;;  (if (or interactive (interactive-p))
256   ;;      (if (eq wl-summary-move-direction-downward nil)
257   ;;      (wl-summary-prev)
258   ;;    (wl-summary-next))))
259   )
260
261 (defun wl-summary-make-destination-numbers-list (mark-list)
262   (let (dest-numbers dest-number)
263     (dolist (elem mark-list)
264       (setq dest-number (assoc (nth 2 elem) dest-numbers))
265       (if dest-number
266           (unless (memq (car elem) (cdr dest-number))
267             (nconc dest-number (list (car elem))))
268         (setq dest-numbers (nconc dest-numbers
269                                   (list
270                                    (list (nth 2 elem)
271                                          (car elem)))))))
272     dest-numbers))
273
274 (defun wl-summary-move-mark-list-messages (mark-list folder-name message)
275   (if (null mark-list)
276       (message "No marks")
277     (save-excursion
278       (let ((start (point))
279             (refiles (mapcar 'car mark-list))
280             (refile-failures 0)
281             dst-msgs                    ; loop counter
282             result)
283         ;; begin refile...
284         (goto-char start)               ; avoid moving cursor to
285                                         ; the bottom line.
286         (elmo-with-progress-display
287             (elmo-folder-move-messages (length refiles))
288             message
289           (setq result nil)
290           (condition-case nil
291               (setq result (elmo-folder-move-messages
292                             wl-summary-buffer-elmo-folder
293                             refiles
294                             (if (eq folder-name 'null)
295                                 'null
296                               (wl-folder-get-elmo-folder folder-name))))
297             (error nil))
298           (when result          ; succeeded.
299             ;; update buffer.
300             (wl-summary-delete-messages-on-buffer refiles)
301             ;; update wl-summary-buffer-temp-mark-list.
302             (dolist (mark-info mark-list)
303               (setq wl-summary-buffer-temp-mark-list
304                     (delq mark-info wl-summary-buffer-temp-mark-list)))))
305         (wl-summary-set-message-modified)
306         ;; Return the operation failed message numbers.
307         (if result
308             0
309           (length refiles))))))
310
311 (defun wl-summary-get-refile-destination-subr (action number learn)
312   (let* ((number (or number (wl-summary-message-number)))
313          (msgid (and number
314                      (elmo-message-field wl-summary-buffer-elmo-folder
315                                          number 'message-id)))
316          (entity (and number
317                       (elmo-message-entity wl-summary-buffer-elmo-folder
318                                            number)))
319          folder cur-mark tmp-folder)
320     (catch 'done
321       (when (null entity)
322         (message "Cannot decide destination.")
323         (throw 'done nil))
324       (when (null number)
325         (message "No message.")
326         (throw 'done nil))
327       (setq folder (wl-summary-read-folder
328                     (or (wl-refile-guess entity) wl-trash-folder)
329                     (format "for %s " action)))
330       ;; Cache folder hack by okada@opaopa.org
331       (when (and (eq (elmo-folder-type-internal
332                       (wl-folder-get-elmo-folder
333                        (wl-folder-get-realname folder))) 'cache)
334                  (not (string= folder
335                                (setq tmp-folder
336                                      (concat "'cache/"
337                                              (elmo-cache-get-path-subr
338                                               (elmo-msgid-to-cache msgid)))))))
339         (setq folder tmp-folder)
340         (message "Force refile to %s." folder))
341       (if (string= folder (wl-summary-buffer-folder-name))
342           (error "Same folder"))
343       (if (or (not (elmo-folder-writable-p (wl-folder-get-elmo-folder folder)))
344               (string= folder wl-queue-folder)
345               (string= folder wl-draft-folder))
346           (error "Don't set as target: %s" folder))
347       ;; learn for refile.
348       (when learn
349         (wl-refile-learn entity folder))
350       folder)))
351
352 ;;; Actions
353 (defun wl-summary-define-mark-action ()
354   (interactive)
355   (dolist (action wl-summary-mark-action-list)
356     (fset (intern (format "wl-summary-%s" (wl-summary-action-symbol action)))
357           `(lambda (&optional number data)
358              ,(wl-summary-action-docstring action)
359              (interactive)
360              (wl-summary-set-mark ,(wl-summary-action-mark action)
361                                   number (interactive-p) data)))
362     (fset (intern (format "wl-summary-%s-region"
363                           (wl-summary-action-symbol action)))
364           `(lambda (beg end)
365              ,(wl-summary-action-docstring action)
366              (interactive "r")
367              (save-excursion
368                (goto-char beg)
369                (wl-summary-mark-region-subr
370                 (quote ,(intern (format "wl-summary-%s"
371                                         (wl-summary-action-symbol action))))
372                 beg end
373                 (if (quote ,(wl-summary-action-argument-function action))
374                     (funcall (function
375                               ,(wl-summary-action-argument-function action))
376                              (quote ,(wl-summary-action-symbol action))
377                              (wl-summary-message-number)))))))
378     (fset (intern (format "wl-summary-target-mark-%s"
379                           (wl-summary-action-symbol action)))
380           `(lambda ()
381              ,(wl-summary-action-docstring action)
382              (interactive)
383              (wl-summary-target-mark-set-action (quote ,action))))
384     (fset (intern (format "wl-thread-%s"
385                           (wl-summary-action-symbol action)))
386           `(lambda (arg)
387              ,(wl-summary-action-docstring action)
388              (interactive "P")
389              (wl-thread-call-region-func
390               (quote ,(intern (format "wl-summary-%s-region"
391                                       (wl-summary-action-symbol action))))
392               arg)
393              (if arg
394                  (wl-summary-goto-top-of-current-thread))
395              (if (not wl-summary-move-direction-downward)
396                  (wl-summary-prev)
397                (wl-thread-goto-bottom-of-sub-thread)
398                (if wl-summary-buffer-disp-msg
399                    (wl-summary-redisplay)))))))
400
401 (defun wl-summary-get-dispose-folder (folder)
402   (if (string= folder wl-trash-folder)
403       'null
404     (let* ((type (or (wl-get-assoc-list-value wl-dispose-folder-alist folder)
405                      'trash)))
406       (cond ((stringp type)
407              type)
408             ((or (equal type 'remove) (equal type 'null))
409              'null)
410             (t;; (equal type 'trash)
411              (let ((trash-folder (wl-folder-get-elmo-folder wl-trash-folder)))
412                (unless (elmo-folder-exists-p trash-folder)
413                  (if (y-or-n-p
414                       (format "Trash Folder %s does not exist, create it? "
415                               wl-trash-folder))
416                      (elmo-folder-create trash-folder)
417                    (error "Trash Folder is not created"))))
418              wl-trash-folder)))))
419
420 ;; Dispose action.
421 (defun wl-summary-exec-action-dispose (mark-list)
422   (wl-summary-move-mark-list-messages mark-list
423                                       (wl-summary-get-dispose-folder
424                                        (wl-summary-buffer-folder-name))
425                                       "Disposing messages"))
426
427 ;; Delete action.
428 (defun wl-summary-exec-action-delete (mark-list)
429   (wl-summary-move-mark-list-messages mark-list
430                                       'null
431                                       "Deleting messages"))
432
433 ;; Refile action
434 (defun wl-summary-set-action-refile (number mark data)
435   (when (null data)
436     (error "Destination folder is empty"))
437   (wl-summary-register-temp-mark number mark data)
438   (setq wl-summary-buffer-prev-refile-destination data))
439
440 (defun wl-summary-get-refile-destination (action number)
441   "Decide refile destination."
442   (wl-summary-get-refile-destination-subr action number t))
443
444 (defun wl-summary-exec-action-refile (mark-list)
445   (save-excursion
446     (let ((start (point))
447           (failures 0)
448           dst-msgs)
449       ;; begin refile...
450       (setq dst-msgs (wl-summary-make-destination-numbers-list mark-list))
451       (goto-char start) ; avoid moving cursor to the bottom line.
452       (elmo-with-progress-display
453           (elmo-folder-move-messages (length mark-list))
454           "Refiling messages"
455         (dolist (pair dst-msgs)
456           (if (condition-case nil
457                   (elmo-folder-move-messages
458                    wl-summary-buffer-elmo-folder
459                    (cdr pair)
460                    (wl-folder-get-elmo-folder (car pair)))
461                 (error nil))
462               (progn
463                 ;; update buffer.
464                 (wl-summary-delete-messages-on-buffer (cdr pair))
465                 (setq wl-summary-buffer-temp-mark-list
466                       (wl-delete-associations
467                        (cdr pair)
468                        wl-summary-buffer-temp-mark-list)))
469             (setq failures (+ failures (length (cdr pair)))))))
470       failures)))
471
472 ;; Copy action
473 (defun wl-summary-get-copy-destination (action number)
474   (wl-summary-get-refile-destination-subr action number nil))
475
476 (defun wl-summary-exec-action-copy (mark-list)
477   (save-excursion
478     (let ((start (point))
479           (failures 0)
480           dst-msgs)
481       ;; begin refile...
482       (setq dst-msgs
483             (wl-summary-make-destination-numbers-list mark-list))
484       (goto-char start) ; avoid moving cursor to the bottom line.
485       (elmo-with-progress-display
486           (elmo-folder-move-messages (length mark-list))
487           "Copying messages"
488         (dolist (pair dst-msgs)
489           (if (condition-case nil
490                   (elmo-folder-move-messages
491                    wl-summary-buffer-elmo-folder
492                    (cdr pair)
493                    (wl-folder-get-elmo-folder (car pair))
494                    'no-delete)
495                 (error nil))
496               (progn
497                 ;; update buffer.
498                 (wl-summary-delete-copy-marks-on-buffer (cdr pair))
499                 (setq wl-summary-buffer-temp-mark-list
500                       (wl-delete-associations
501                        (cdr pair)
502                        wl-summary-buffer-temp-mark-list)))
503             (setq failures (+ failures (length (cdr pair)))))))
504       failures)))
505
506 ;; Prefetch.
507 (defun wl-summary-exec-action-prefetch (mark-list)
508   (save-excursion
509     (let* ((count 0)
510            (length (length mark-list))
511            (mark-list-copy (copy-sequence mark-list))
512            (pos (point))
513            (failures 0))
514       (dolist (mark-info mark-list-copy)
515         (message "Prefetching...(%d/%d)"
516                  (setq count (+ 1 count)) length)
517         (if (wl-summary-prefetch-msg (car mark-info))
518             (progn
519               (wl-summary-unset-mark (car mark-info))
520               (sit-for 0))
521           (incf failures)))
522       (message "Prefetching...done")
523       0)))
524
525 ;; Resend.
526 (defun wl-summary-get-resend-address (action number)
527   "Decide resend address."
528   (wl-address-read-from-minibuffer "Resend message to: "))
529
530 (defun wl-summary-exec-action-resend (mark-list)
531   (let ((failure 0))
532     (dolist (mark-info mark-list)
533       (if (condition-case nil
534               (progn
535                 (wl-summary-exec-action-resend-subr (car mark-info)
536                                                     (nth 2 mark-info))
537                 t)
538             (error))
539           (wl-summary-unmark (car mark-info))
540         (incf failure)))
541     failure))
542
543 (defun wl-summary-exec-action-resend-subr (number address)
544   "Resend the message with NUMBER to ADDRESS."
545   (message "Resending message to %s..." address)
546   (let ((folder wl-summary-buffer-elmo-folder))
547     (with-current-buffer (get-buffer-create " *wl-draft-resend*")
548       ;; We first set up a normal mail buffer.
549       (set-buffer-multibyte nil)
550       (erase-buffer)
551       (setq wl-sent-message-via nil)
552       ;; Insert our usual headers.
553       (wl-draft-insert-from-field)
554       (wl-draft-insert-date-field)
555       (insert "To: " address "\n")
556       (goto-char (point-min))
557       ;; Rename them all to "Resent-*".
558       (while (re-search-forward "^[A-Za-z]" nil t)
559         (forward-char -1)
560         (insert "Resent-"))
561       (widen)
562       (forward-line)
563       (delete-region (point) (point-max))
564       (let ((beg (point)))
565         ;; Insert the message to be resent.
566         (insert
567          ;; elmo-message-fetch is erase current buffer before fetch message
568          (elmo-message-fetch-string folder number
569                                     (if wl-summary-resend-use-cache
570                                         (elmo-make-fetch-strategy
571                                          'entire 'maybe nil
572                                          (elmo-file-cache-get-path
573                                           (elmo-message-field
574                                            folder number 'message-id)))
575                                       (elmo-make-fetch-strategy 'entire))
576                                     'unread))
577         (goto-char (point-min))
578         (search-forward "\n\n")
579         (forward-char -1)
580         (save-restriction
581           (narrow-to-region beg (point))
582           (wl-draft-delete-fields wl-ignored-resent-headers)
583           (goto-char (point-max)))
584         (insert mail-header-separator)
585         ;; Rename all old ("Previous-")Resent headers.
586         (while (re-search-backward "^\\(Previous-\\)*Resent-" beg t)
587           (beginning-of-line)
588           (insert "Previous-"))
589         ;; Quote any "From " lines at the beginning.
590         (goto-char beg)
591         (when (looking-at "From ")
592           (replace-match "X-From-Line: ")))
593       (run-hooks 'wl-summary-resend-hook)
594       ;; Send it.
595       (wl-draft-dispatch-message)
596       (kill-buffer (current-buffer)))
597     (message "Resending message to %s...done" address)))
598
599 ;;;
600 (defun wl-summary-remove-argument ()
601   (save-excursion
602     (let ((inhibit-read-only t)
603           (buffer-read-only nil)
604           (buf (current-buffer))
605           sol eol rs re)
606       (setq sol (point-at-bol))
607       (beginning-of-line)
608       (search-forward "\r")
609       (forward-char -1)
610       (setq eol (point))
611       (setq rs (next-single-property-change sol 'wl-summary-action-argument
612                                             buf eol))
613       (setq re (next-single-property-change rs 'wl-summary-action-argument
614                                             buf eol))
615       (put-text-property rs re 'wl-summary-action-argument nil)
616       (put-text-property rs re 'invisible nil)
617       (goto-char re)
618       (delete-char (- eol re)))))
619
620 (defun wl-summary-collect-numbers-region (begin end)
621   "Return a list of message number in the region specified by BEGIN and END."
622   (save-excursion
623     (save-restriction
624       (let (numbers)
625         (wl-summary-narrow-to-region (or begin (point-min))(or end (point-max)))
626         (goto-char (point-min))
627         ;; for thread...
628         (if (eq wl-summary-buffer-view 'thread)
629             (let (number entity)
630               (while (not (eobp))
631                 (setq numbers (cons (wl-summary-message-number) numbers)
632                       entity (wl-thread-get-entity number))
633                 ;; When thread is closed...children should also be checked.
634                 (unless (wl-thread-entity-get-opened entity)
635                   (dolist (msg (wl-thread-get-children-msgs number))
636                     (setq numbers (cons msg numbers))))
637                 (forward-line 1)))
638           (let (number)
639             (while (not (eobp))
640               (setq numbers (cons (wl-summary-message-number) numbers))
641               (forward-line 1))))
642         (nreverse (delq nil numbers))))))
643
644 (defun wl-summary-exec (&optional numbers)
645   (interactive)
646   (let ((failures 0)
647         collected pair action modified)
648     (dolist (action wl-summary-mark-action-list)
649       (setq collected (cons (cons
650                              (wl-summary-action-mark action)
651                              nil) collected)))
652     (dolist (mark-info wl-summary-buffer-temp-mark-list)
653       (setq pair
654             (when (or (null numbers)
655                       (memq (nth 0 mark-info) numbers))
656               (assoc (nth 1 mark-info) collected)))
657       (if pair
658           (setcdr pair (cons mark-info (cdr pair)))))
659     ;; collected is a pair of
660     ;; mark-string and a list of mark-info
661     (dolist (pair collected)
662       (when (cdr pair)
663         (setq action (assoc (car pair) wl-summary-mark-action-list))
664         (when (wl-summary-action-exec-function action)
665           (setq modified t)
666           (setq failures (+ failures (funcall
667                                       (wl-summary-action-exec-function action)
668                                       (cdr pair)))))))
669     (when modified
670       (wl-summary-set-message-modified))
671     (run-hooks 'wl-summary-exec-hook)
672     ;; message buffer is not up-to-date
673     (unless (and wl-message-buffer
674                  (eq (wl-summary-message-number)
675                      (with-current-buffer wl-message-buffer
676                        wl-message-buffer-cur-number)))
677       (wl-summary-toggle-disp-msg 'off)
678       (setq wl-message-buffer nil))
679     (set-buffer-modified-p nil)
680     (when (> failures 0)
681       (message "%d execution(s) were failed" failures))))
682
683 (defun wl-summary-exec-region (beg end)
684   (interactive "r")
685   (wl-summary-exec
686    (wl-summary-collect-numbers-region beg end)))
687
688 (defun wl-summary-read-folder (default &optional purpose ignore-error
689                                 no-create init)
690   (let ((fld (completing-read
691               (format "Folder name %s(%s): " (or purpose "")
692                       default)
693               'wl-folder-complete-folder
694               nil nil (or init wl-default-spec)
695               'wl-read-folder-history)))
696     (if (or (string= fld wl-default-spec)
697             (string= fld ""))
698         (setq fld default))
699     (setq fld (elmo-string (wl-folder-get-realname fld)))
700     (if (string-match "\n" fld)
701         (error "Not supported folder name: %s" fld))
702     (unless no-create
703       (if ignore-error
704           (condition-case nil
705               (wl-folder-confirm-existence
706                (wl-folder-get-elmo-folder
707                 fld))
708             (error))
709         (wl-folder-confirm-existence (wl-folder-get-elmo-folder
710                                       fld))))
711     fld))
712
713 (defun wl-summary-print-argument (msg-num data)
714   "Print action argument on line."
715   (when data
716     (wl-summary-remove-argument)
717     (save-excursion
718       (let ((inhibit-read-only t)
719             (data (copy-sequence data))
720             (buffer-read-only nil)
721             len rs re c)
722         (setq len (string-width data))
723         (if (< len 1) ()
724           ;;(end-of-line)
725           (beginning-of-line)
726           (search-forward "\r")
727           (forward-char -1)
728           (setq re (point))
729           (let ((width (cond (wl-summary-width
730                               (1- wl-summary-width))
731                              (wl-summary-print-argument-within-window
732                               (1- (window-width)))))
733                 (c (current-column))
734                 (padding 0))
735             (if (and width
736                      (> (+ c len) width))
737                 (progn
738                   (move-to-column width)
739                   (setq c (current-column))
740                   (while (> (+ c len) width)
741                     (forward-char -1)
742                     (setq c (current-column)))
743                   (when (< (+ c len) width)
744                     (setq data (concat " " data)))
745                   (setq rs (point))
746                   (put-text-property rs re 'invisible t))
747               (when (and width
748                          (> (setq padding (- width len c)) 0))
749                 (setq data (concat (make-string padding (string-to-char " "))
750                                    data)))
751               (setq rs (1- re))))
752           (put-text-property rs re 'wl-summary-action-argument t)
753           (goto-char re)
754           (wl-highlight-action-argument-string data)
755           (insert data)
756           (set-buffer-modified-p nil))))))
757
758 (defsubst wl-summary-reserve-temp-mark-p (mark)
759   "Return t if temporal MARK should be reserved."
760   (member mark wl-summary-reserve-mark-list))
761
762 ;; Refile prev destination
763 (defun wl-summary-refile-prev-destination ()
764   "Refile message to previously refiled destination."
765   (interactive)
766   (funcall (symbol-function 'wl-summary-refile)
767            (wl-summary-message-number)
768            wl-summary-buffer-prev-refile-destination)
769   (if (and (interactive-p)
770            (eq wl-summary-move-direction-downward nil))
771       (wl-summary-prev)
772     (wl-summary-next)))
773
774 (defun wl-summary-refile-prev-destination-region (beg end)
775   "Refile messages in the region to previously refiled destination."
776   (interactive "r")
777   (wl-summary-mark-region-subr 'wl-summary-refile
778                                beg end
779                                wl-summary-buffer-prev-refile-destination))
780
781 (defun wl-thread-refile-prev-destination (arg)
782   "Refile messages in the thread to previously refiled destination."
783   (interactive "P")
784   (wl-thread-call-region-func
785    'wl-summary-refile-prev-destination-region
786    arg))
787
788 (defun wl-summary-target-mark-refile-prev-destination ()
789   "Refile messages with target mark to previously refiled destination."
790   (interactive)
791   (let ((elem wl-summary-mark-action-list)
792         action)
793     (while elem
794       (when (eq (wl-summary-action-symbol (car elem)) 'refile)
795         (setq action (car elem))
796         (setq elem nil))
797       (setq elem (cdr elem)))
798     (wl-summary-target-mark-set-action
799      (list
800       (car action)
801       'refile-prev-destination
802       (lambda (&rest args) wl-summary-buffer-prev-refile-destination)
803       (nth 2 action)
804       (nth 3 action)
805       (nth 4 action)
806       (nth 6 action)))))
807
808 (defsubst wl-summary-no-auto-refile-message-p (number)
809   (member (wl-summary-message-mark wl-summary-buffer-elmo-folder number)
810           wl-summary-auto-refile-skip-marks))
811
812 (defvar wl-auto-refile-guess-functions
813   '(wl-refile-guess-by-rule)
814   "*List of functions which is used for guessing refile destination folder.")
815
816 (defun wl-summary-auto-refile (&optional open-all)
817   "Set refile mark automatically according to 'wl-refile-guess-by-rule'."
818   (interactive "P")
819   (message "Marking...")
820   (save-excursion
821     (if (and (eq wl-summary-buffer-view 'thread)
822              open-all)
823         (wl-thread-open-all))
824     (let* ((spec (wl-summary-buffer-folder-name))
825            checked-dsts
826            (count 0)
827            number dst thr-entity)
828       (goto-char (point-min))
829       (while (not (eobp))
830         (setq number (wl-summary-message-number))
831         (dolist (number (cons number
832                               (and (eq wl-summary-buffer-view 'thread)
833                                    ;; process invisible children.
834                                    (not (wl-thread-entity-get-opened
835                                          (setq thr-entity
836                                                (wl-thread-get-entity number))))
837                                    (wl-thread-entity-get-descendant
838                                     thr-entity))))
839           (when (and (not (wl-summary-no-auto-refile-message-p number))
840                      (not (wl-summary-reserve-temp-mark-p
841                            (nth 1 (wl-summary-registered-temp-mark number))))
842                      (setq dst
843                            (wl-folder-get-realname
844                             (wl-refile-guess
845                              (elmo-message-entity wl-summary-buffer-elmo-folder
846                                                   number)
847                              wl-auto-refile-guess-functions)))
848                      (not (equal dst spec))
849                      (let ((pair (assoc dst checked-dsts))
850                            ret)
851                        (if pair
852                            (cdr pair)
853                          (setq ret
854                                (condition-case nil
855                                    (progn
856                                      (wl-folder-confirm-existence
857                                       (wl-folder-get-elmo-folder dst))
858                                      t)
859                                  (error)))
860                          (setq checked-dsts (cons (cons dst ret) checked-dsts))
861                          ret)))
862             (if (funcall (symbol-function 'wl-summary-refile) number dst)
863                 (incf count))
864             (message "Marking...%d message(s)." count)))
865         (forward-line))
866       (if (eq count 0)
867           (message "No message was marked.")
868         (message "Marked %d message(s)." count)))))
869
870 (defun wl-summary-unmark (&optional number)
871   "Unmark marks (temporary, refile, copy, delete)of current line.
872 If optional argument NUMBER is specified, unmark message specified by NUMBER."
873   (interactive)
874   (wl-summary-unset-mark number (interactive-p)))
875
876 (defun wl-summary-unmark-region (beg end)
877   (interactive "r")
878   (save-excursion
879     (save-restriction
880       (wl-summary-narrow-to-region beg end)
881       (goto-char (point-min))
882       (if (eq wl-summary-buffer-view 'thread)
883           (progn
884             (while (not (eobp))
885               (let* ((number (wl-summary-message-number))
886                      (entity (wl-thread-get-entity number)))
887                 (if (wl-thread-entity-get-opened entity)
888                     ;; opened...unmark line.
889                     (wl-summary-unmark)
890                   ;; closed
891                   (wl-summary-delete-marks-on-buffer
892                    (wl-thread-get-children-msgs number))))
893               (forward-line 1)))
894         (while (not (eobp))
895           (wl-summary-unmark)
896           (forward-line 1))))))
897
898 (defun wl-summary-mark-region-subr (function beg end data)
899   (save-excursion
900     (save-restriction
901       (wl-summary-narrow-to-region beg end)
902       (goto-char (point-min))
903       (if (eq wl-summary-buffer-view 'thread)
904           (progn
905             (while (not (eobp))
906               (let* ((number (wl-summary-message-number))
907                      (entity (wl-thread-get-entity number))
908                      (wl-summary-move-direction-downward t)
909                      children)
910                 (if (wl-thread-entity-get-opened entity)
911                     ;; opened...delete line.
912                     (funcall function nil data)
913                   ;; closed
914                   (setq children (wl-thread-get-children-msgs number))
915                   (while children
916                     (funcall function (pop children) data)))
917                 (forward-line 1))))
918         (while (not (eobp))
919           (funcall function nil data)
920           (forward-line 1))))))
921
922 (defun wl-summary-target-mark-all ()
923   (interactive)
924   (wl-summary-target-mark-region (point-min) (point-max)))
925
926 (defun wl-summary-delete-all-mark (mark)
927   (goto-char (point-min))
928   (while (not (eobp))
929     (when (string= (wl-summary-temp-mark) mark)
930       (wl-summary-unmark))
931     (forward-line 1))
932   (if (string= mark "*")
933       (setq wl-summary-buffer-target-mark-list nil)
934     (let (deleted)
935       (dolist (mark-info wl-summary-buffer-temp-mark-list)
936         (when (string= (nth 1 mark-info) mark)
937           (setq deleted (cons mark-info deleted))))
938       (dolist (delete deleted)
939         (setq wl-summary-buffer-temp-mark-list
940               (delq delete wl-summary-buffer-temp-mark-list))))))
941
942 (defun wl-summary-unmark-all ()
943   "Unmark all according to what you input."
944   (interactive)
945   (let ((unmarks (string-to-char-list (read-from-minibuffer "Unmark: ")))
946         cur-mark)
947     (save-excursion
948       (while unmarks
949         (setq cur-mark (char-to-string (car unmarks)))
950         (wl-summary-delete-all-mark cur-mark)
951         (setq unmarks (cdr unmarks))))))
952
953 (defun wl-summary-target-mark-thread ()
954   (interactive)
955   (wl-thread-call-region-func 'wl-summary-target-mark-region t))
956
957 (require 'product)
958 (product-provide (provide 'wl-action) (require 'wl-version))
959
960 ;;; wl-action.el ends here