comparison lisp/mh-e/mh-seq.el @ 88155:d7ddb3e565de

sync with trunk
author Henrik Enberg <henrik.enberg@telia.com>
date Mon, 16 Jan 2006 00:03:54 +0000
parents b35587af8747
children
comparison
equal deleted inserted replaced
88154:8ce476d3ba36 88155:d7ddb3e565de
1 ;;; mh-seq.el --- MH-E sequences support 1 ;;; mh-seq.el --- MH-E sequences support
2 2
3 ;; Copyright (C) 1993, 1995, 2001, 2002 Free Software Foundation, Inc. 3 ;; Copyright (C) 1993, 1995,
4 ;; 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
4 5
5 ;; Author: Bill Wohler <wohler@newt.com> 6 ;; Author: Bill Wohler <wohler@newt.com>
6 ;; Maintainer: Bill Wohler <wohler@newt.com> 7 ;; Maintainer: Bill Wohler <wohler@newt.com>
7 ;; Keywords: mail 8 ;; Keywords: mail
8 ;; See: mh-e.el 9 ;; See: mh-e.el
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details. 21 ;; GNU General Public License for more details.
21 22
22 ;; You should have received a copy of the GNU General Public License 23 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the 24 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, 25 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02111-1307, USA. 26 ;; Boston, MA 02110-1301, USA.
26 27
27 ;;; Commentary: 28 ;;; Commentary:
28 ;; 29 ;;
29 ;; This tries to implement the algorithm described at: 30 ;; This tries to implement the algorithm described at:
30 ;; http://www.jwz.org/doc/threading.html 31 ;; http://www.jwz.org/doc/threading.html
66 67
67 ;; Internal support for MH-E package. 68 ;; Internal support for MH-E package.
68 69
69 ;;; Change Log: 70 ;;; Change Log:
70 71
71 ;; $Id: mh-seq.el,v 1.101 2003/01/26 00:57:35 jchonig Exp $
72
73 ;;; Code: 72 ;;; Code:
74 73
75 (require 'cl) 74 ;;(message "> mh-seq")
75 (eval-when-compile (require 'mh-acros))
76 (mh-require-cl)
77
78 (require 'mh-buffers)
76 (require 'mh-e) 79 (require 'mh-e)
77 80 ;;(message "< mh-seq")
78 ;; Shush the byte-compiler 81
79 (defvar tool-bar-mode) 82
80 83
81 ;;; Data structures (used in message threading)... 84 ;;; Data structures (used in message threading)...
82 (defstruct (mh-thread-message (:conc-name mh-message-) 85
83 (:constructor mh-thread-make-message)) 86 (mh-defstruct (mh-thread-message (:conc-name mh-message-)
87 (:constructor mh-thread-make-message))
84 (id nil) 88 (id nil)
85 (references ()) 89 (references ())
86 (subject "") 90 (subject "")
87 (subject-re-p nil)) 91 (subject-re-p nil))
88 92
89 (defstruct (mh-thread-container (:conc-name mh-container-) 93 (mh-defstruct (mh-thread-container (:conc-name mh-container-)
90 (:constructor mh-thread-make-container)) 94 (:constructor mh-thread-make-container))
91 message parent children 95 message parent children
92 (real-child-p t)) 96 (real-child-p t))
93 97
98
94 99
95 ;;; Internal variables: 100 ;;; Internal variables:
101
96 (defvar mh-last-seq-used nil 102 (defvar mh-last-seq-used nil
97 "Name of seq to which a msg was last added.") 103 "Name of seq to which a msg was last added.")
98 104
99 (defvar mh-non-seq-mode-line-annotation nil 105 (defvar mh-non-seq-mode-line-annotation nil
100 "Saved value of `mh-mode-line-annotation' when narrowed to a seq.") 106 "Saved value of `mh-mode-line-annotation' when narrowed to a seq.")
101 107
108
109
102 ;;; Maps and hashes... 110 ;;; Maps and hashes...
111
103 (defvar mh-thread-id-hash nil 112 (defvar mh-thread-id-hash nil
104 "Hashtable used to canonicalize message identifiers.") 113 "Hashtable used to canonicalize message identifiers.")
105 (defvar mh-thread-subject-hash nil 114 (defvar mh-thread-subject-hash nil
106 "Hashtable used to canonicalize subject strings.") 115 "Hashtable used to canonicalize subject strings.")
107 (defvar mh-thread-id-table nil 116 (defvar mh-thread-id-table nil
110 "Table to look up message index number from message identifier.") 119 "Table to look up message index number from message identifier.")
111 (defvar mh-thread-index-id-map nil 120 (defvar mh-thread-index-id-map nil
112 "Table to look up message identifier from message index.") 121 "Table to look up message identifier from message index.")
113 (defvar mh-thread-scan-line-map nil 122 (defvar mh-thread-scan-line-map nil
114 "Map of message index to various parts of the scan line.") 123 "Map of message index to various parts of the scan line.")
115 (defvar mh-thread-old-scan-line-map nil 124 (defvar mh-thread-scan-line-map-stack nil
116 "Old map of message index to various parts of the scan line. 125 "Old map of message index to various parts of the scan line.
117 This is the original map that is stored when the folder is narrowed.") 126 This is the original map that is stored when the folder is
127 narrowed.")
118 (defvar mh-thread-subject-container-hash nil 128 (defvar mh-thread-subject-container-hash nil
119 "Hashtable used to group messages by subject.") 129 "Hashtable used to group messages by subject.")
120 (defvar mh-thread-duplicates nil 130 (defvar mh-thread-duplicates nil
121 "Hashtable used to associate messages with the same message identifier.") 131 "Hashtable used to associate messages with the same message identifier.")
122 (defvar mh-thread-history () 132 (defvar mh-thread-history ()
123 "Variable to remember the transformations to the thread tree. 133 "Variable to remember the transformations to the thread tree.
124 When new messages are added, these transformations are rewound, then the 134 When new messages are added, these transformations are rewound,
125 links are added from the newly seen messages. Finally the transformations are 135 then the links are added from the newly seen messages. Finally
126 redone to get the new thread tree. This makes incremental threading easier.") 136 the transformations are redone to get the new thread tree. This
137 makes incremental threading easier.")
127 (defvar mh-thread-body-width nil 138 (defvar mh-thread-body-width nil
128 "Width of scan substring that contains subject and body of message.") 139 "Width of scan substring that contains subject and body of message.")
129 140
130 (make-variable-buffer-local 'mh-thread-id-hash) 141 (make-variable-buffer-local 'mh-thread-id-hash)
131 (make-variable-buffer-local 'mh-thread-subject-hash) 142 (make-variable-buffer-local 'mh-thread-subject-hash)
132 (make-variable-buffer-local 'mh-thread-id-table) 143 (make-variable-buffer-local 'mh-thread-id-table)
133 (make-variable-buffer-local 'mh-thread-id-index-map) 144 (make-variable-buffer-local 'mh-thread-id-index-map)
134 (make-variable-buffer-local 'mh-thread-index-id-map) 145 (make-variable-buffer-local 'mh-thread-index-id-map)
135 (make-variable-buffer-local 'mh-thread-scan-line-map) 146 (make-variable-buffer-local 'mh-thread-scan-line-map)
136 (make-variable-buffer-local 'mh-thread-old-scan-line-map) 147 (make-variable-buffer-local 'mh-thread-scan-line-map-stack)
137 (make-variable-buffer-local 'mh-thread-subject-container-hash) 148 (make-variable-buffer-local 'mh-thread-subject-container-hash)
138 (make-variable-buffer-local 'mh-thread-duplicates) 149 (make-variable-buffer-local 'mh-thread-duplicates)
139 (make-variable-buffer-local 'mh-thread-history) 150 (make-variable-buffer-local 'mh-thread-history)
140 151
141 ;;;###mh-autoload 152 ;;;###mh-autoload
142 (defun mh-delete-seq (sequence) 153 (defun mh-delete-seq (sequence)
143 "Delete the SEQUENCE." 154 "Delete SEQUENCE.
155
156 You are prompted for the sequence to delete. Note that this
157 deletes only the sequence, not the messages in the sequence. If
158 you want to delete the messages, use \"\\[universal-argument]
159 \\[mh-delete-msg]\"."
144 (interactive (list (mh-read-seq-default "Delete" t))) 160 (interactive (list (mh-read-seq-default "Delete" t)))
145 (let ((msg-list (mh-seq-to-msgs sequence))) 161 (let ((msg-list (mh-seq-to-msgs sequence))
162 (internal-flag (mh-internal-seq sequence))
163 (folders-changed (list mh-current-folder)))
164 (mh-iterate-on-range msg sequence
165 (mh-remove-sequence-notation msg internal-flag))
146 (mh-undefine-sequence sequence '("all")) 166 (mh-undefine-sequence sequence '("all"))
147 (mh-delete-seq-locally sequence) 167 (mh-delete-seq-locally sequence)
148 (mh-iterate-on-messages-in-region msg (point-min) (point-max) 168 (when mh-index-data
149 (when (and (member msg msg-list) (not (mh-seq-containing-msg msg nil))) 169 (setq folders-changed
150 (mh-notate nil ? (1+ mh-cmd-note)))))) 170 (append folders-changed
151 171 (mh-index-delete-from-sequence sequence msg-list))))
152 ;; Avoid compiler warnings 172 (when (and (eq sequence mh-unseen-seq) (mh-speed-flists-active-p))
153 (defvar view-exit-action) 173 (apply #'mh-speed-flists t folders-changed))))
174
175 ;; Shush compiler.
176 (eval-when-compile (defvar view-exit-action))
154 177
155 ;;;###mh-autoload 178 ;;;###mh-autoload
156 (defun mh-list-sequences () 179 (defun mh-list-sequences ()
157 "List the sequences defined in the folder being visited." 180 "List all sequences in folder.
181
182 The list appears in a buffer named \"*MH-E Sequences*\"."
158 (interactive) 183 (interactive)
159 (let ((folder mh-current-folder) 184 (let ((folder mh-current-folder)
160 (temp-buffer mh-sequences-buffer) 185 (temp-buffer mh-sequences-buffer)
161 (seq-list mh-seq-list) 186 (seq-list mh-seq-list)
162 (max-len 0)) 187 (max-len 0))
187 (insert (format (format "%%%ss" (length name-spec)) ""))) 212 (insert (format (format "%%%ss" (length name-spec)) "")))
188 (insert next-element))) 213 (insert next-element)))
189 (insert "\n")) 214 (insert "\n"))
190 (setq seq-list (cdr seq-list))) 215 (setq seq-list (cdr seq-list)))
191 (goto-char (point-min)) 216 (goto-char (point-min))
192 (view-mode 1) 217 (view-mode-enter)
193 (setq view-exit-action 'kill-buffer) 218 (setq view-exit-action 'kill-buffer)
194 (message "Listing sequences...done"))))) 219 (message "Listing sequences...done")))))
195 220
196 ;;;###mh-autoload 221 ;;;###mh-autoload
197 (defun mh-msg-is-in-seq (message) 222 (defun mh-msg-is-in-seq (message)
198 "Display the sequences that contain MESSAGE (default: current message)." 223 "Display the sequences in which the current message appears.
199 (interactive (list (mh-get-msg-num t))) 224
225 Use a prefix argument to display the sequences in which another
226 MESSAGE appears."
227 (interactive "P")
228 (if (not message)
229 (setq message (mh-get-msg-num t)))
200 (let* ((dest-folder (loop for seq in mh-refile-list 230 (let* ((dest-folder (loop for seq in mh-refile-list
201 when (member message (cdr seq)) return (car seq))) 231 when (member message (cdr seq)) return (car seq)
232 finally return nil))
202 (deleted-flag (unless dest-folder (member message mh-delete-list)))) 233 (deleted-flag (unless dest-folder (member message mh-delete-list))))
203 (message "Message %d%s is in sequences: %s" 234 (message "Message %d%s is in sequences: %s"
204 message 235 message
205 (cond (dest-folder (format " (to be refiled to %s)" dest-folder)) 236 (cond (dest-folder (format " (to be refiled to %s)" dest-folder))
206 (deleted-flag (format " (to be deleted)")) 237 (deleted-flag (format " (to be deleted)"))
207 (t "")) 238 (t ""))
208 (mapconcat 'concat 239 (mapconcat 'concat
209 (mh-list-to-string (mh-seq-containing-msg message t)) 240 (mh-list-to-string (mh-seq-containing-msg message t))
210 " ")))) 241 " "))))
211 242
243 ;; Shush compiler
244 (eval-when-compile
245 (defvar tool-bar-map)
246 (defvar tool-bar-mode))
247
248 (make-variable-buffer-local 'mh-non-seq-mode-line-annotation)
249
212 ;;;###mh-autoload 250 ;;;###mh-autoload
213 (defun mh-narrow-to-seq (sequence) 251 (defun mh-narrow-to-seq (sequence)
214 "Restrict display of this folder to just messages in SEQUENCE. 252 "Restrict display to messages in SEQUENCE.
215 Use \\<mh-folder-mode-map>\\[mh-widen] to undo this command." 253
254 You are prompted for the name of the sequence. What this command
255 does is show only those messages that are in the selected
256 sequence in the MH-Folder buffer. In addition, it limits further
257 MH-E searches to just those messages.
258
259 When you want to widen the view to all your messages again, use
260 \\[mh-widen]."
216 (interactive (list (mh-read-seq "Narrow to" t))) 261 (interactive (list (mh-read-seq "Narrow to" t)))
217 (with-mh-folder-updating (t) 262 (with-mh-folder-updating (t)
218 (cond ((mh-seq-to-msgs sequence) 263 (cond ((mh-seq-to-msgs sequence)
219 (mh-widen)
220 (mh-remove-all-notation) 264 (mh-remove-all-notation)
221 (let ((eob (point-max)) 265 (let ((eob (point-max))
222 (msg-at-cursor (mh-get-msg-num nil))) 266 (msg-at-cursor (mh-get-msg-num nil)))
223 (setq mh-thread-old-scan-line-map mh-thread-scan-line-map) 267 (push mh-thread-scan-line-map mh-thread-scan-line-map-stack)
224 (setq mh-thread-scan-line-map (make-hash-table :test #'eql)) 268 (setq mh-thread-scan-line-map (make-hash-table :test #'eql))
225 (mh-copy-seq-to-eob sequence) 269 (mh-copy-seq-to-eob sequence)
226 (narrow-to-region eob (point-max)) 270 (push (buffer-substring-no-properties (point-min) eob)
227 (mh-notate-user-sequences) 271 mh-folder-view-stack)
272 (delete-region (point-min) eob)
228 (mh-notate-deleted-and-refiled) 273 (mh-notate-deleted-and-refiled)
229 (mh-notate-cur) 274 (mh-notate-cur)
230 (when msg-at-cursor (mh-goto-msg msg-at-cursor t t)) 275 (when msg-at-cursor (mh-goto-msg msg-at-cursor t t))
231 (make-variable-buffer-local 'mh-non-seq-mode-line-annotation)
232 (setq mh-non-seq-mode-line-annotation mh-mode-line-annotation) 276 (setq mh-non-seq-mode-line-annotation mh-mode-line-annotation)
233 (setq mh-mode-line-annotation (symbol-name sequence)) 277 (setq mh-mode-line-annotation (symbol-name sequence))
234 (mh-make-folder-mode-line) 278 (mh-make-folder-mode-line)
235 (mh-recenter nil) 279 (mh-recenter nil)
236 (if (and (boundp 'tool-bar-mode) tool-bar-mode) 280 (when (and (boundp 'tool-bar-mode) tool-bar-mode)
237 (set (make-local-variable 'tool-bar-map) 281 (set (make-local-variable 'tool-bar-map)
238 mh-folder-seq-tool-bar-map)) 282 mh-folder-seq-tool-bar-map)
239 (setq mh-narrowed-to-seq sequence) 283 (when (buffer-live-p (get-buffer mh-show-buffer))
284 (save-excursion
285 (set-buffer (get-buffer mh-show-buffer))
286 (set (make-local-variable 'tool-bar-map)
287 mh-show-seq-tool-bar-map))))
240 (push 'widen mh-view-ops))) 288 (push 'widen mh-view-ops)))
241 (t 289 (t
242 (error "No messages in sequence `%s'" (symbol-name sequence)))))) 290 (error "No messages in sequence %s" (symbol-name sequence))))))
243 291
244 ;;;###mh-autoload 292 ;;;###mh-autoload
245 (defun mh-put-msg-in-seq (msg-or-seq sequence) 293 (defun mh-put-msg-in-seq (range sequence)
246 "Add MSG-OR-SEQ (default: displayed message) to SEQUENCE. 294 "Add RANGE to SEQUENCE\\<mh-folder-mode-map>.
247 If optional prefix argument provided, then prompt for the message sequence. 295
248 If variable `transient-mark-mode' is non-nil and the mark is active, then 296 Give this command a RANGE and you can add all the messages in a
249 the selected region is added to the sequence." 297 sequence to another sequence (for example,
250 (interactive (list (cond 298 \"\\[universal-argument] \\[mh-put-msg-in-seq] SourceSequence RET
251 ((mh-mark-active-p t) 299 DestSequence RET\"). Check the documentation of
252 (cons (region-beginning) (region-end))) 300 `mh-interactive-range' to see how RANGE is read in interactive
253 (current-prefix-arg 301 use."
254 (mh-read-seq-default "Add messages from" t)) 302 (interactive (list (mh-interactive-range "Add messages from")
255 (t
256 (cons (line-beginning-position) (line-end-position))))
257 (mh-read-seq-default "Add to" nil))) 303 (mh-read-seq-default "Add to" nil)))
258 (let ((internal-seq-flag (mh-internal-seq sequence)) 304 (unless (mh-valid-seq-p sequence)
259 msg-list) 305 (error "Can't put message in invalid sequence %s" sequence))
260 (cond ((and (consp msg-or-seq) 306 (let* ((internal-seq-flag (mh-internal-seq sequence))
261 (numberp (car msg-or-seq)) (numberp (cdr msg-or-seq))) 307 (original-msgs (mh-seq-msgs (mh-find-seq sequence)))
262 (mh-iterate-on-messages-in-region m (car msg-or-seq) (cdr msg-or-seq) 308 (folders (list mh-current-folder))
263 (push m msg-list) 309 (msg-list (mh-range-to-msg-list range)))
264 (unless internal-seq-flag 310 (mh-add-msgs-to-seq msg-list sequence nil t)
265 (mh-notate nil mh-note-seq (1+ mh-cmd-note)))) 311 (mh-iterate-on-range m range
266 (mh-add-msgs-to-seq msg-list sequence internal-seq-flag t)) 312 (unless (memq m original-msgs)
267 ((or (numberp msg-or-seq) (listp msg-or-seq)) 313 (mh-add-sequence-notation m internal-seq-flag)))
268 (when (numberp msg-or-seq)
269 (setq msg-or-seq (list msg-or-seq)))
270 (mh-add-msgs-to-seq msg-or-seq sequence internal-seq-flag))
271 (t (mh-add-msgs-to-seq (mh-seq-to-msgs msg-or-seq) sequence)))
272 (if (not internal-seq-flag) 314 (if (not internal-seq-flag)
273 (setq mh-last-seq-used sequence)))) 315 (setq mh-last-seq-used sequence))
316 (when mh-index-data
317 (setq folders
318 (append folders (mh-index-add-to-sequence sequence msg-list))))
319 (when (and (eq sequence mh-unseen-seq) (mh-speed-flists-active-p))
320 (apply #'mh-speed-flists t folders))))
274 321
275 (defun mh-valid-view-change-operation-p (op) 322 (defun mh-valid-view-change-operation-p (op)
276 "Check if the view change operation can be performed. 323 "Check if the view change operation can be performed.
277 OP is one of 'widen and 'unthread." 324 OP is one of 'widen and 'unthread."
278 (cond ((eq (car mh-view-ops) op) 325 (cond ((eq (car mh-view-ops) op)
279 (pop mh-view-ops)) 326 (pop mh-view-ops))
280 (t nil))) 327 (t nil)))
281 328
282 ;;;###mh-autoload 329 ;;;###mh-autoload
283 (defun mh-widen () 330 (defun mh-widen (&optional all-flag)
284 "Remove restrictions from current folder, thereby showing all messages." 331 "Remove last restriction.
285 (interactive) 332
333 Each limit or sequence restriction can be undone in turn with
334 this command. Give this command a prefix argument ALL-FLAG to
335 remove all limits and sequence restrictions."
336 (interactive "P")
286 (let ((msg (mh-get-msg-num nil))) 337 (let ((msg (mh-get-msg-num nil)))
287 (when mh-narrowed-to-seq 338 (when mh-folder-view-stack
288 (cond ((mh-valid-view-change-operation-p 'widen) nil) 339 (cond (all-flag
340 (while (cdr mh-view-ops)
341 (setq mh-view-ops (cdr mh-view-ops)))
342 (when (eq (car mh-view-ops) 'widen)
343 (setq mh-view-ops (cdr mh-view-ops))))
344 ((mh-valid-view-change-operation-p 'widen) nil)
289 ((memq 'widen mh-view-ops) 345 ((memq 'widen mh-view-ops)
290 (while (not (eq (car mh-view-ops) 'widen)) 346 (while (not (eq (car mh-view-ops) 'widen))
291 (setq mh-view-ops (cdr mh-view-ops))) 347 (setq mh-view-ops (cdr mh-view-ops)))
292 (pop mh-view-ops)) 348 (setq mh-view-ops (cdr mh-view-ops)))
293 (t (error "Widening is not applicable"))) 349 (t (error "Widening is not applicable")))
294 (when (memq 'unthread mh-view-ops) 350 ;; If ALL-FLAG is non-nil then rewind stacks
295 (setq mh-thread-scan-line-map mh-thread-old-scan-line-map)) 351 (when all-flag
352 (while (cdr mh-thread-scan-line-map-stack)
353 (setq mh-thread-scan-line-map-stack
354 (cdr mh-thread-scan-line-map-stack)))
355 (while (cdr mh-folder-view-stack)
356 (setq mh-folder-view-stack (cdr mh-folder-view-stack))))
357 (setq mh-thread-scan-line-map (pop mh-thread-scan-line-map-stack))
296 (with-mh-folder-updating (t) 358 (with-mh-folder-updating (t)
297 (delete-region (point-min) (point-max)) 359 (delete-region (point-min) (point-max))
298 (widen) 360 (insert (pop mh-folder-view-stack))
361 (mh-remove-all-notation)
299 (setq mh-mode-line-annotation mh-non-seq-mode-line-annotation) 362 (setq mh-mode-line-annotation mh-non-seq-mode-line-annotation)
300 (mh-make-folder-mode-line)) 363 (mh-make-folder-mode-line))
301 (if msg 364 (if msg
302 (mh-goto-msg msg t t)) 365 (mh-goto-msg msg t t))
303 (mh-notate-deleted-and-refiled) 366 (mh-notate-deleted-and-refiled)
304 (mh-notate-user-sequences) 367 (mh-notate-user-sequences)
305 (mh-notate-cur) 368 (mh-notate-cur)
306 (mh-recenter nil))) 369 (mh-recenter nil)))
307 (if (and (boundp 'tool-bar-mode) tool-bar-mode) 370 (when (and (null mh-folder-view-stack) (boundp 'tool-bar-mode) tool-bar-mode)
308 (set (make-local-variable 'tool-bar-map) mh-folder-tool-bar-map)) 371 (set (make-local-variable 'tool-bar-map) mh-folder-tool-bar-map)
309 (setq mh-narrowed-to-seq nil)) 372 (when (buffer-live-p (get-buffer mh-show-buffer))
373 (save-excursion
374 (set-buffer (get-buffer mh-show-buffer))
375 (set (make-local-variable 'tool-bar-map) mh-show-tool-bar-map)))))
310 376
311 ;; FIXME? We may want to clear all notations and add one for current-message 377 ;; FIXME? We may want to clear all notations and add one for current-message
312 ;; and process user sequences. 378 ;; and process user sequences.
379 ;;;###mh-autoload
313 (defun mh-notate-deleted-and-refiled () 380 (defun mh-notate-deleted-and-refiled ()
314 "Notate messages marked for deletion or refiling. 381 "Notate messages marked for deletion or refiling.
315 Messages to be deleted are given by `mh-delete-list' while messages to be 382 Messages to be deleted are given by `mh-delete-list' while
316 refiled are present in `mh-refile-list'." 383 messages to be refiled are present in `mh-refile-list'."
317 (let ((refiled-hash (make-hash-table)) 384 (let ((refiled-hash (make-hash-table))
318 (deleted-hash (make-hash-table))) 385 (deleted-hash (make-hash-table)))
319 (dolist (msg mh-delete-list) 386 (dolist (msg mh-delete-list)
320 (setf (gethash msg deleted-hash) t)) 387 (setf (gethash msg deleted-hash) t))
321 (dolist (dest-msg-list mh-refile-list) 388 (dolist (dest-msg-list mh-refile-list)
327 ((gethash msg deleted-hash) 394 ((gethash msg deleted-hash)
328 (mh-notate nil mh-note-deleted mh-cmd-note)))))) 395 (mh-notate nil mh-note-deleted mh-cmd-note))))))
329 396
330 397
331 398
332 ;;; Commands to manipulate sequences. Sequences are stored in an alist 399 ;;; Commands to manipulate sequences.
333 ;;; of the form: 400
334 ;;; ((seq-name msgs ...) (seq-name msgs ...) ...) 401 ;; Sequences are stored in an alist of the form:
335 402 ;; ((seq-name msgs ...) (seq-name msgs ...) ...)
403
404 (defvar mh-sequence-history ())
405
406 ;;;###mh-autoload
336 (defun mh-read-seq-default (prompt not-empty) 407 (defun mh-read-seq-default (prompt not-empty)
337 "Read and return sequence name with default narrowed or previous sequence. 408 "Read and return sequence name with default narrowed or previous sequence.
338 PROMPT is the prompt to use when reading. If NOT-EMPTY is non-nil then a 409 PROMPT is the prompt to use when reading. If NOT-EMPTY is non-nil
339 non-empty sequence is read." 410 then a non-empty sequence is read."
340 (mh-read-seq prompt not-empty 411 (mh-read-seq prompt not-empty
341 (or mh-narrowed-to-seq 412 (or mh-last-seq-used
342 mh-last-seq-used
343 (car (mh-seq-containing-msg (mh-get-msg-num nil) nil))))) 413 (car (mh-seq-containing-msg (mh-get-msg-num nil) nil)))))
344 414
345 (defun mh-read-seq (prompt not-empty &optional default) 415 (defun mh-read-seq (prompt not-empty &optional default)
346 "Read and return a sequence name. 416 "Read and return a sequence name.
347 Prompt with PROMPT, raise an error if the sequence is empty and the NOT-EMPTY 417 Prompt with PROMPT, raise an error if the sequence is empty and
348 flag is non-nil, and supply an optional DEFAULT sequence. A reply of '%' 418 the NOT-EMPTY flag is non-nil, and supply an optional DEFAULT
349 defaults to the first sequence containing the current message." 419 sequence. A reply of '%' defaults to the first sequence
350 (let* ((input (completing-read (format "%s %s %s" prompt "sequence:" 420 containing the current message."
421 (let* ((input (completing-read (format "%s sequence%s: " prompt
351 (if default 422 (if default
352 (format "[%s] " default) 423 (format " (default %s)" default)
353 "")) 424 ""))
354 (mh-seq-names mh-seq-list))) 425 (mh-seq-names mh-seq-list)
426 nil nil nil 'mh-sequence-history))
355 (seq (cond ((equal input "%") 427 (seq (cond ((equal input "%")
356 (car (mh-seq-containing-msg (mh-get-msg-num t) nil))) 428 (car (mh-seq-containing-msg (mh-get-msg-num t) nil)))
357 ((equal input "") default) 429 ((equal input "") default)
358 (t (intern input)))) 430 (t (intern input))))
359 (msgs (mh-seq-to-msgs seq))) 431 (msgs (mh-seq-to-msgs seq)))
360 (if (and (null msgs) not-empty) 432 (if (and (null msgs) not-empty)
361 (error "No messages in sequence `%s'" seq)) 433 (error "No messages in sequence %s" seq))
362 seq)) 434 seq))
435
436
437
438 ;;; Functions to read ranges with completion...
439
440 (defvar mh-range-seq-names)
441 (defvar mh-range-history ())
442 (defvar mh-range-completion-map (copy-keymap minibuffer-local-completion-map))
443 (define-key mh-range-completion-map " " 'self-insert-command)
444
445 (defun mh-range-completion-function (string predicate flag)
446 "Programmable completion of message ranges.
447 STRING is the user input that is to be completed. PREDICATE if non-nil is a
448 function used to filter the possible choices and FLAG determines whether the
449 completion is over."
450 (let* ((candidates mh-range-seq-names)
451 (last-char (and (not (equal string ""))
452 (aref string (1- (length string)))))
453 (last-word (cond ((null last-char) "")
454 ((memq last-char '(? ?- ?:)) "")
455 (t (car (last (split-string string "[ -:]+"))))))
456 (prefix (substring string 0 (- (length string) (length last-word)))))
457 (cond ((eq flag nil)
458 (let ((res (try-completion last-word candidates predicate)))
459 (cond ((null res) nil)
460 ((eq res t) t)
461 (t (concat prefix res)))))
462 ((eq flag t)
463 (all-completions last-word candidates predicate))
464 ((eq flag 'lambda)
465 (loop for x in candidates
466 when (equal x last-word) return t
467 finally return nil)))))
468
469 ;;;###mh-autoload
470 (defun mh-read-range (prompt &optional folder default
471 expand-flag ask-flag number-as-range-flag)
472 "Read a message range with PROMPT.
473
474 If FOLDER is non-nil then a range is read from that folder, otherwise
475 use `mh-current-folder'.
476
477 If DEFAULT is a string then use that as default range to return. If
478 DEFAULT is nil then ask user with default answer a range based on the
479 sequences that seem relevant. Finally if DEFAULT is t, try to avoid
480 prompting the user. Unseen messages, if present, are returned. If the
481 folder has fewer than `mh-large-folder' messages then \"all\" messages
482 are returned. Finally as a last resort prompt the user.
483
484 If EXPAND-FLAG is non-nil then a list of message numbers corresponding
485 to the input is returned. If this list is empty then an error is
486 raised. If EXPAND-FLAG is nil just return the input string. In this
487 case we don't check if the range is empty.
488
489 If ASK-FLAG is non-nil, then the user is always queried for a range of
490 messages. If ASK-FLAG is nil, then the function checks if the unseen
491 sequence is non-empty. If that is the case, `mh-unseen-seq', or the
492 list of messages in it depending on the value of EXPAND, is returned.
493 Otherwise if the folder has fewer than `mh-large-folder' messages then
494 the list of messages corresponding to \"all\" is returned. If neither
495 of the above holds then as a last resort the user is queried for a
496 range of messages.
497
498 If NUMBER-AS-RANGE-FLAG is non-nil, then if a number, N is read as
499 input, it is interpreted as the range \"last:N\".
500
501 This function replaces the existing function `mh-read-msg-range'.
502 Calls to:
503
504 (mh-read-msg-range folder flag)
505
506 should be replaced with:
507
508 (mh-read-range \"Suitable prompt\" folder t nil flag
509 mh-interpret-number-as-range-flag)"
510 (setq default (or default mh-last-seq-used
511 (car (mh-seq-containing-msg (mh-get-msg-num nil) t)))
512 prompt (format "%s range" prompt))
513 (let* ((folder (or folder mh-current-folder))
514 (guess (eq default t))
515 (counts (and guess (mh-folder-size folder)))
516 (unseen (and counts (> (cadr counts) 0)))
517 (large (and counts mh-large-folder (> (car counts) mh-large-folder)))
518 (default (cond ((and guess large) (format "last:%s" mh-large-folder))
519 ((and guess (not large)) "all")
520 ((stringp default) default)
521 ((symbolp default) (symbol-name default))))
522 (prompt (cond ((and guess large default)
523 (format "%s (folder has %s messages, default %s)"
524 prompt (car counts) default))
525 ((and guess large)
526 (format "%s (folder has %s messages)"
527 prompt (car counts)))
528 (default
529 (format "%s (default %s)" prompt default))))
530 (minibuffer-local-completion-map mh-range-completion-map)
531 (seq-list (if (eq folder mh-current-folder)
532 mh-seq-list
533 (mh-read-folder-sequences folder nil)))
534 (mh-range-seq-names
535 (append '(("first") ("last") ("all") ("prev") ("next"))
536 (mh-seq-names seq-list)))
537 (input (cond ((and (not ask-flag) unseen) (symbol-name mh-unseen-seq))
538 ((and (not ask-flag) (not large)) "all")
539 (t (completing-read (format "%s: " prompt)
540 'mh-range-completion-function nil nil
541 nil 'mh-range-history default))))
542 msg-list)
543 (when (and number-as-range-flag
544 (string-match "^[ \t]*\\([0-9]+\\)[ \t]*$" input))
545 (setq input (concat "last:" (match-string 1 input))))
546 (cond ((not expand-flag) input)
547 ((assoc (intern input) seq-list)
548 (cdr (assoc (intern input) seq-list)))
549 ((setq msg-list (mh-translate-range folder input)) msg-list)
550 (t (error "No messages in range %s" input)))))
551
552 ;;;###mh-autoload
553 (defun mh-translate-range (folder expr)
554 "In FOLDER, translate the string EXPR to a list of messages numbers."
555 (save-excursion
556 (let ((strings (delete "" (split-string expr "[ \t\n]")))
557 (result ()))
558 (ignore-errors
559 (apply #'mh-exec-cmd-quiet nil "mhpath" folder strings)
560 (set-buffer mh-temp-buffer)
561 (goto-char (point-min))
562 (while (re-search-forward "/\\([0-9]*\\)$" nil t)
563 (push (car (read-from-string (match-string 1))) result))
564 (nreverse result)))))
363 565
364 (defun mh-seq-names (seq-list) 566 (defun mh-seq-names (seq-list)
365 "Return an alist containing the names of the SEQ-LIST." 567 "Return an alist containing the names of the SEQ-LIST."
366 (mapcar (lambda (entry) (list (symbol-name (mh-seq-name entry)))) 568 (mapcar (lambda (entry) (list (symbol-name (mh-seq-name entry))))
367 seq-list)) 569 seq-list))
378 (mh-define-sequence new-name (mh-seq-msgs old-seq)) 580 (mh-define-sequence new-name (mh-seq-msgs old-seq))
379 (mh-undefine-sequence sequence (mh-seq-msgs old-seq)) 581 (mh-undefine-sequence sequence (mh-seq-msgs old-seq))
380 (rplaca old-seq new-name))) 582 (rplaca old-seq new-name)))
381 583
382 ;;;###mh-autoload 584 ;;;###mh-autoload
383 (defun mh-map-to-seq-msgs (func seq &rest args)
384 "Invoke the FUNC at each message in the SEQ.
385 SEQ can either be a list of messages or a MH sequence. The remaining ARGS are
386 passed as arguments to FUNC."
387 (save-excursion
388 (let ((msgs (if (listp seq) seq (mh-seq-to-msgs seq))))
389 (while msgs
390 (if (mh-goto-msg (car msgs) t t)
391 (apply func (car msgs) args))
392 (setq msgs (cdr msgs))))))
393
394 ;;;###mh-autoload
395 (defun mh-notate-seq (seq notation offset)
396 "Mark the scan listing.
397 All messages in SEQ are marked with NOTATION at OFFSET from the beginning of
398 the line."
399 (let ((msg-list (mh-seq-to-msgs seq)))
400 (mh-iterate-on-messages-in-region msg (point-min) (point-max)
401 (when (member msg msg-list)
402 (mh-notate nil notation offset)))))
403
404 ;;;###mh-autoload
405 (defun mh-notate-cur () 585 (defun mh-notate-cur ()
406 "Mark the MH sequence cur. 586 "Mark the MH sequence cur.
407 In addition to notating the current message with `mh-note-cur' the function 587 In addition to notating the current message with `mh-note-cur'
408 uses `overlay-arrow-position' to put a marker in the fringe." 588 the function uses `overlay-arrow-position' to put a marker in the
589 fringe."
409 (let ((cur (car (mh-seq-to-msgs 'cur)))) 590 (let ((cur (car (mh-seq-to-msgs 'cur))))
410 (when (and cur (mh-goto-msg cur t t)) 591 (when (and cur (mh-goto-msg cur t t))
411 (mh-notate nil mh-note-cur mh-cmd-note)
412 (beginning-of-line) 592 (beginning-of-line)
593 (when (looking-at mh-scan-good-msg-regexp)
594 (mh-notate nil mh-note-cur mh-cmd-note))
413 (setq mh-arrow-marker (set-marker mh-arrow-marker (point))) 595 (setq mh-arrow-marker (set-marker mh-arrow-marker (point)))
414 (setq overlay-arrow-position mh-arrow-marker)))) 596 (setq overlay-arrow-position mh-arrow-marker))))
415 597
416 ;;;###mh-autoload 598 ;;;###mh-autoload
417 (defun mh-add-to-sequence (seq msgs) 599 (defun mh-add-to-sequence (seq msgs)
418 "The sequence SEQ is augmented with the messages in MSGS." 600 "The sequence SEQ is augmented with the messages in MSGS."
419 ;; Add to a SEQUENCE each message the list of MSGS. 601 ;; Add to a SEQUENCE each message the list of MSGS.
420 (if (not (mh-folder-name-p seq)) 602 (if (and (mh-valid-seq-p seq) (not (mh-folder-name-p seq)))
421 (if msgs 603 (if msgs
422 (apply 'mh-exec-cmd "mark" mh-current-folder "-add" 604 (apply 'mh-exec-cmd "mark" mh-current-folder "-add"
423 "-sequence" (symbol-name seq) 605 "-sequence" (symbol-name seq)
424 (mh-coalesce-msg-list msgs))))) 606 (mh-coalesce-msg-list msgs)))))
425 607
426 ;; This has a tricky bug. mh-map-to-seq-msgs uses mh-goto-msg, which assumes 608 (defvar mh-thread-last-ancestor)
427 ;; that the folder buffer is sorted. However in this case that assumption
428 ;; doesn't hold. So we will do this the dumb way.
429 ;(defun mh-copy-seq-to-point (seq location)
430 ; ;; Copy the scan listing of the messages in SEQUENCE to after the point
431 ; ;; LOCATION in the current buffer.
432 ; (mh-map-to-seq-msgs 'mh-copy-line-to-point seq location))
433 609
434 (defun mh-copy-seq-to-eob (seq) 610 (defun mh-copy-seq-to-eob (seq)
435 "Copy SEQ to the end of the buffer." 611 "Copy SEQ to the end of the buffer."
436 ;; It is quite involved to write something which will work at any place in 612 ;; It is quite involved to write something which will work at any place in
437 ;; the buffer, so we will write something which works only at the end of 613 ;; the buffer, so we will write something which works only at the end of
444 (save-restriction 620 (save-restriction
445 (narrow-to-region (point) (point)) 621 (narrow-to-region (point) (point))
446 (mh-regenerate-headers coalesced-msgs t) 622 (mh-regenerate-headers coalesced-msgs t)
447 (cond ((memq 'unthread mh-view-ops) 623 (cond ((memq 'unthread mh-view-ops)
448 ;; Populate restricted scan-line map 624 ;; Populate restricted scan-line map
449 (goto-char (point-min)) 625 (mh-remove-all-notation)
450 (while (not (eobp)) 626 (mh-iterate-on-range msg (cons (point-min) (point-max))
451 (let ((msg (mh-get-msg-num nil))) 627 (setf (gethash msg mh-thread-scan-line-map)
452 (when (numberp msg) 628 (mh-thread-parse-scan-line)))
453 (setf (gethash msg mh-thread-scan-line-map)
454 (mh-thread-parse-scan-line))))
455 (forward-line))
456 ;; Remove scan lines and read results from pre-computed tree 629 ;; Remove scan lines and read results from pre-computed tree
457 (delete-region (point-min) (point-max)) 630 (delete-region (point-min) (point-max))
458 (let ((thread-tree (mh-thread-generate mh-current-folder ())) 631 (mh-thread-print-scan-lines
459 (mh-thread-body-width 632 (mh-thread-generate mh-current-folder ()))
460 (- (window-width) mh-cmd-note 633 (mh-notate-user-sequences))
461 (1- mh-scan-field-subject-start-offset)))
462 (mh-thread-last-ancestor nil))
463 (mh-thread-generate-scan-lines thread-tree -2)))
464 (mh-index-data 634 (mh-index-data
465 (mh-index-insert-folder-headers))))))) 635 (mh-index-insert-folder-headers)))))))
466 636
467 (defun mh-copy-line-to-point (msg location)
468 "Copy current message line to a specific location.
469 The argument MSG is not used. The message in the current line is copied to
470 LOCATION."
471 ;; msg is not used?
472 ;; Copy the current line to the LOCATION in the current buffer.
473 (beginning-of-line)
474 (save-excursion
475 (let ((beginning-of-line (point))
476 end)
477 (forward-line 1)
478 (setq end (point))
479 (goto-char location)
480 (insert-buffer-substring (current-buffer) beginning-of-line end))))
481
482 ;;;###mh-autoload 637 ;;;###mh-autoload
483 (defmacro mh-iterate-on-messages-in-region (var begin end &rest body) 638 (defmacro mh-iterate-on-messages-in-region (var begin end &rest body)
484 "Iterate over region. 639 "Iterate over region.
485 VAR is bound to the message on the current line as we loop starting from BEGIN 640
486 till END. In each step BODY is executed. 641 VAR is bound to the message on the current line as we loop
642 starting from BEGIN till END. In each step BODY is executed.
487 643
488 If VAR is nil then the loop is executed without any binding." 644 If VAR is nil then the loop is executed without any binding."
489 (unless (symbolp var) 645 (unless (symbolp var)
490 (error "Can not bind the non-symbol %s" var)) 646 (error "Can not bind the non-symbol %s" var))
491 (let ((binding-needed-flag var)) 647 (let ((binding-needed-flag var))
492 `(save-excursion 648 `(save-excursion
493 (goto-char ,begin) 649 (goto-char ,begin)
650 (beginning-of-line)
494 (while (and (<= (point) ,end) (not (eobp))) 651 (while (and (<= (point) ,end) (not (eobp)))
495 (when (looking-at mh-scan-valid-regexp) 652 (when (looking-at mh-scan-valid-regexp)
496 (let ,(if binding-needed-flag `((,var (mh-get-msg-num t))) ()) 653 (let ,(if binding-needed-flag `((,var (mh-get-msg-num t))) ())
497 ,@body)) 654 ,@body))
498 (forward-line 1))))) 655 (forward-line 1)))))
499 656
500 ;;;###mh-autoload 657 (put 'mh-iterate-on-messages-in-region 'lisp-indent-hook 'defun)
501 (defun mh-region-to-msg-list (begin end) 658
502 "Return a list of messages within the region between BEGIN and END." 659 ;;;###mh-autoload
503 ;; If end is end of buffer back up one position 660 (defmacro mh-iterate-on-range (var range &rest body)
504 (setq end (if (equal end (point-max)) (1- end) end)) 661 "Iterate an operation over a region or sequence.
505 (let ((result)) 662
506 (mh-iterate-on-messages-in-region index begin end 663 VAR is bound to each message in turn in a loop over RANGE, which
507 (when (numberp index) (push index result))) 664 can be a message number, a list of message numbers, a sequence, a
508 result)) 665 region in a cons cell, or a MH range (something like last:20) in
666 a string. In each iteration, BODY is executed.
667
668 The parameter RANGE is usually created with
669 `mh-interactive-range' in order to provide a uniform interface to
670 MH-E functions."
671 (unless (symbolp var)
672 (error "Can not bind the non-symbol %s" var))
673 (let ((binding-needed-flag var)
674 (msgs (make-symbol "msgs"))
675 (seq-hash-table (make-symbol "seq-hash-table")))
676 `(cond ((numberp ,range)
677 (when (mh-goto-msg ,range t t)
678 (let ,(if binding-needed-flag `((,var ,range)) ())
679 ,@body)))
680 ((and (consp ,range)
681 (numberp (car ,range)) (numberp (cdr ,range)))
682 (mh-iterate-on-messages-in-region ,var
683 (car ,range) (cdr ,range)
684 ,@body))
685 (t (let ((,msgs (cond ((and ,range (symbolp ,range))
686 (mh-seq-to-msgs ,range))
687 ((stringp ,range)
688 (mh-translate-range mh-current-folder
689 ,range))
690 (t ,range)))
691 (,seq-hash-table (make-hash-table)))
692 (dolist (msg ,msgs)
693 (setf (gethash msg ,seq-hash-table) t))
694 (mh-iterate-on-messages-in-region v (point-min) (point-max)
695 (when (gethash v ,seq-hash-table)
696 (let ,(if binding-needed-flag `((,var v)) ())
697 ,@body))))))))
698
699 (put 'mh-iterate-on-range 'lisp-indent-hook 'defun)
700
701 ;;;###mh-autoload
702 (defun mh-range-to-msg-list (range)
703 "Return a list of messages for RANGE.
704
705 Check the documentation of `mh-interactive-range' to see how
706 RANGE is read in interactive use."
707 (let (msg-list)
708 (mh-iterate-on-range msg range
709 (push msg msg-list))
710 (nreverse msg-list)))
711
712 ;;;###mh-autoload
713 (defun mh-interactive-range (range-prompt &optional default)
714 "Return interactive specification for message, sequence, range or region.
715 By convention, the name of this argument is RANGE.
716
717 If variable `transient-mark-mode' is non-nil and the mark is active,
718 then this function returns a cons-cell of the region.
719
720 If optional prefix argument is provided, then prompt for message range
721 with RANGE-PROMPT. A list of messages in that range is returned.
722
723 If a MH range is given, say something like last:20, then a list
724 containing the messages in that range is returned.
725
726 If DEFAULT non-nil then it is returned.
727
728 Otherwise, the message number at point is returned.
729
730 This function is usually used with `mh-iterate-on-range' in order to
731 provide a uniform interface to MH-E functions."
732 (cond ((mh-mark-active-p t) (cons (region-beginning) (region-end)))
733 (current-prefix-arg (mh-read-range range-prompt nil nil t t))
734 (default default)
735 (t (mh-get-msg-num t))))
509 736
510 737
511 738
512 ;;; Commands to handle new 'subject sequence. 739 ;;; Commands to handle new 'subject sequence ("Poor man's threading" by psg)
513 ;;; Or "Poor man's threading" by psg. 740
514 741 ;; XXX: The function mh-subject-to-sequence-unthreaded uses the magic number
742 ;; 41 for the max size of the subject part. Avoiding this would be desirable.
515 (defun mh-subject-to-sequence (all) 743 (defun mh-subject-to-sequence (all)
516 "Put all following messages with same subject in sequence 'subject. 744 "Put all following messages with same subject in sequence 'subject.
517 If arg ALL is t, move to beginning of folder buffer to collect all messages. 745 If arg ALL is t, move to beginning of folder buffer to collect all
746 messages.
518 If arg ALL is nil, collect only messages fron current one on forward. 747 If arg ALL is nil, collect only messages fron current one on forward.
519 748
520 Return number of messages put in the sequence: 749 Return number of messages put in the sequence:
521 750
522 nil -> there was no subject line. 751 nil -> there was no subject line.
523 0 -> there were no later messages with the same subject (sequence not made) 752
753 0 -> there were no later messages with the same
754 subject (sequence not made)
755
756 >1 -> the total number of messages including current one."
757 (if (memq 'unthread mh-view-ops)
758 (mh-subject-to-sequence-threaded all)
759 (mh-subject-to-sequence-unthreaded all)))
760
761 (defun mh-subject-to-sequence-unthreaded (all)
762 "Put all following messages with same subject in sequence 'subject.
763
764 This function only works with an unthreaded folder. If arg ALL is
765 t, move to beginning of folder buffer to collect all messages. If
766 arg ALL is nil, collect only messages fron current one on
767 forward.
768
769 Return number of messages put in the sequence:
770
771 nil -> there was no subject line.
772 0 -> there were no later messages with the same
773 subject (sequence not made)
524 >1 -> the total number of messages including current one." 774 >1 -> the total number of messages including current one."
525 (if (not (eq major-mode 'mh-folder-mode)) 775 (if (not (eq major-mode 'mh-folder-mode))
526 (error "Not in a folder buffer")) 776 (error "Not in a folder buffer"))
527 (save-excursion 777 (save-excursion
528 (beginning-of-line) 778 (beginning-of-line)
529 (if (or (not (looking-at mh-scan-subject-regexp)) 779 (if (or (not (looking-at mh-scan-subject-regexp))
530 (not (match-string 3)) 780 (not (match-string 3))
531 (string-equal "" (match-string 3))) 781 (string-equal "" (match-string 3)))
532 (progn (message "No subject line.") 782 (progn (message "No subject line")
533 nil) 783 nil)
534 (let ((subject (match-string-no-properties 3)) 784 (let ((subject (match-string-no-properties 3))
535 (list)) 785 (list))
536 (if (> (length subject) 41) 786 (if (> (length subject) 41)
537 (setq subject (substring subject 0 41))) 787 (setq subject (substring subject 0 41)))
547 (cond 797 (cond
548 (list 798 (list
549 ;; If we created a new sequence, add the initial message to it too. 799 ;; If we created a new sequence, add the initial message to it too.
550 (if (not (member (mh-get-msg-num t) list)) 800 (if (not (member (mh-get-msg-num t) list))
551 (setq list (cons (mh-get-msg-num t) list))) 801 (setq list (cons (mh-get-msg-num t) list)))
552 (if (member '("subject") (mh-seq-names mh-seq-list)) 802 (if (assoc 'subject mh-seq-list) (mh-delete-seq 'subject))
553 (mh-delete-seq 'subject))
554 ;; sort the result into a sequence 803 ;; sort the result into a sequence
555 (let ((sorted-list (sort (copy-sequence list) 'mh-lessp))) 804 (let ((sorted-list (sort (copy-sequence list) 'mh-lessp)))
556 (while sorted-list 805 (while sorted-list
557 (mh-add-msgs-to-seq (car sorted-list) 'subject nil) 806 (mh-add-msgs-to-seq (car sorted-list) 'subject nil)
558 (setq sorted-list (cdr sorted-list))) 807 (setq sorted-list (cdr sorted-list)))
559 (safe-length list))) 808 (safe-length list)))
560 (t 809 (t
561 0)))))) 810 0))))))
562 811
563 ;;;###mh-autoload 812 (defun mh-subject-to-sequence-threaded (all)
564 (defun mh-narrow-to-subject () 813 "Put all messages with the same subject in the 'subject sequence.
565 "Narrow to a sequence containing all following messages with same subject." 814
566 (interactive) 815 This function works when the folder is threaded. In this
567 (let ((num (mh-get-msg-num nil)) 816 situation the subject could get truncated and so the normal
568 (count (mh-subject-to-sequence t))) 817 matching doesn't work.
569 (cond 818
570 ((not count) ; No subject line, delete msg anyway 819 The parameter ALL is non-nil then all the messages in the buffer
571 nil) 820 are considered, otherwise only the messages after the current one
572 ((= 0 count) ; No other msgs, delete msg anyway. 821 are taken into account."
573 (message "No other messages with same Subject following this one.") 822 (let* ((cur (mh-get-msg-num nil))
574 nil) 823 (subject (mh-thread-find-msg-subject cur))
575 (t ; We have a subject sequence. 824 region msgs)
576 (message "Found %d messages for subject sequence." count) 825 (if (null subject)
577 (mh-narrow-to-seq 'subject) 826 (and (message "No subject line") nil)
578 (if (numberp num) 827 (setq region (cons (if all (point-min) (point)) (point-max)))
579 (mh-goto-msg num t t)))))) 828 (mh-iterate-on-range msg region
829 (when (eq (mh-thread-find-msg-subject msg) subject)
830 (push msg msgs)))
831 (setq msgs (sort msgs #'mh-lessp))
832 (if (null msgs)
833 0
834 (when (assoc 'subject mh-seq-list)
835 (mh-delete-seq 'subject))
836 (mh-add-msgs-to-seq msgs 'subject)
837 (length msgs)))))
838
839 (defun mh-thread-find-msg-subject (msg)
840 "Find canonicalized subject of MSG.
841 This function can only be used the folder is threaded."
842 (ignore-errors
843 (mh-message-subject
844 (mh-container-message (gethash (gethash msg mh-thread-index-id-map)
845 mh-thread-id-table)))))
846
847 (defun mh-edit-pick-expr (default)
848 "With prefix arg edit a pick expression.
849 If no prefix arg is given, then return DEFAULT."
850 (let ((default-string (loop for x in default concat (format " %s" x))))
851 (if (or current-prefix-arg (equal default-string ""))
852 (mh-pick-args-list (read-string "Pick expression: "
853 default-string))
854 default)))
855
856 (defun mh-pick-args-list (s)
857 "Form list by grouping elements in string S suitable for pick arguments.
858 For example, the string \"-subject a b c -from Joe User
859 <user@domain.com>\" is converted to (\"-subject\" \"a b c\"
860 \"-from\" \"Joe User <user@domain.com>\""
861 (let ((full-list (split-string s))
862 current-arg collection arg-list)
863 (while full-list
864 (setq current-arg (car full-list))
865 (if (null (string-match "^-" current-arg))
866 (setq collection
867 (if (null collection)
868 current-arg
869 (format "%s %s" collection current-arg)))
870 (when collection
871 (setq arg-list (append arg-list (list collection)))
872 (setq collection nil))
873 (setq arg-list (append arg-list (list current-arg))))
874 (setq full-list (cdr full-list)))
875 (when collection
876 (setq arg-list (append arg-list (list collection))))
877 arg-list))
878
879 ;;;###mh-autoload
880 (defun mh-narrow-to-subject (&optional pick-expr)
881 "Limit to messages with same subject.
882 With a prefix argument, edit PICK-EXPR.
883
884 Use \\<mh-folder-mode-map>\\[mh-widen] to undo this command."
885 (interactive
886 (list (mh-edit-pick-expr (mh-current-message-header-field 'subject))))
887 (mh-narrow-to-header-field 'subject pick-expr))
888
889 ;;;###mh-autoload
890 (defun mh-narrow-to-from (&optional pick-expr)
891 "Limit to messages with the same \"From:\" field.
892 With a prefix argument, edit PICK-EXPR.
893
894 Use \\<mh-folder-mode-map>\\[mh-widen] to undo this command."
895 (interactive
896 (list (mh-edit-pick-expr (mh-current-message-header-field 'from))))
897 (mh-narrow-to-header-field 'from pick-expr))
898
899 ;;;###mh-autoload
900 (defun mh-narrow-to-cc (&optional pick-expr)
901 "Limit to messages with the same \"Cc:\" field.
902 With a prefix argument, edit PICK-EXPR.
903
904 Use \\<mh-folder-mode-map>\\[mh-widen] to undo this command."
905 (interactive
906 (list (mh-edit-pick-expr (mh-current-message-header-field 'cc))))
907 (mh-narrow-to-header-field 'cc pick-expr))
908
909 ;;;###mh-autoload
910 (defun mh-narrow-to-to (&optional pick-expr)
911 "Limit to messages with the same \"To:\" field.
912 With a prefix argument, edit PICK-EXPR.
913
914 Use \\<mh-folder-mode-map>\\[mh-widen] to undo this command."
915 (interactive
916 (list (mh-edit-pick-expr (mh-current-message-header-field 'to))))
917 (mh-narrow-to-header-field 'to pick-expr))
918
919 (defun mh-narrow-to-header-field (header-field pick-expr)
920 "Limit to messages whose HEADER-FIELD match PICK-EXPR.
921 The MH command pick is used to do the match."
922 (let ((folder mh-current-folder)
923 (original (mh-coalesce-msg-list
924 (mh-range-to-msg-list (cons (point-min) (point-max)))))
925 (msg-list ()))
926 (with-temp-buffer
927 (apply #'mh-exec-cmd-output "pick" nil folder
928 (append original (list "-list") pick-expr))
929 (goto-char (point-min))
930 (while (not (eobp))
931 (let ((num (read-from-string
932 (buffer-substring (point) (line-end-position)))))
933 (when (numberp (car num)) (push (car num) msg-list))
934 (forward-line))))
935 (if (null msg-list)
936 (message "No matches")
937 (when (assoc 'header mh-seq-list) (mh-delete-seq 'header))
938 (mh-add-msgs-to-seq msg-list 'header)
939 (mh-narrow-to-seq 'header))))
940
941 (defun mh-current-message-header-field (header-field)
942 "Return a pick regexp to match HEADER-FIELD of the message at point."
943 (let ((num (mh-get-msg-num nil)))
944 (when num
945 (let ((folder mh-current-folder))
946 (with-temp-buffer
947 (insert-file-contents-literally (mh-msg-filename num folder))
948 (goto-char (point-min))
949 (when (search-forward "\n\n" nil t)
950 (narrow-to-region (point-min) (point)))
951 (let* ((field (or (message-fetch-field (format "%s" header-field))
952 ""))
953 (field-option (format "-%s" header-field))
954 (patterns (loop for x in (split-string field "[ ]*,[ ]*")
955 unless (equal x "")
956 collect (if (string-match "<\\(.*@.*\\)>" x)
957 (match-string 1 x)
958 x))))
959 (when patterns
960 (loop with accum = `(,field-option ,(car patterns))
961 for e in (cdr patterns)
962 do (setq accum `(,field-option ,e "-or" ,@accum))
963 finally return accum))))))))
964
965 ;;;###mh-autoload
966 (defun mh-narrow-to-range (range)
967 "Limit to RANGE.
968
969 Check the documentation of `mh-interactive-range' to see how
970 RANGE is read in interactive use.
971
972 Use \\<mh-folder-mode-map>\\[mh-widen] to undo this command."
973 (interactive (list (mh-interactive-range "Narrow to")))
974 (when (assoc 'range mh-seq-list) (mh-delete-seq 'range))
975 (mh-add-msgs-to-seq (mh-range-to-msg-list range) 'range)
976 (mh-narrow-to-seq 'range))
977
580 978
581 ;;;###mh-autoload 979 ;;;###mh-autoload
582 (defun mh-delete-subject () 980 (defun mh-delete-subject ()
583 "Mark all following messages with same subject to be deleted. 981 "Delete messages with same subject\\<mh-folder-mode-map>.
584 This puts the messages in a sequence named subject. You can undo the last 982
585 deletion marks using `mh-undo' with a prefix argument and then specifying the 983 To delete messages faster, you can use this command to delete all
586 subject sequence." 984 the messages with the same subject as the current message. This
985 command puts these messages in a sequence named \"subject\". You
986 can undo this action by using \\[mh-undo] with a prefix argument
987 and then specifying the \"subject\" sequence."
587 (interactive) 988 (interactive)
588 (let ((count (mh-subject-to-sequence nil))) 989 (let ((count (mh-subject-to-sequence nil)))
589 (cond 990 (cond
590 ((not count) ; No subject line, delete msg anyway 991 ((not count) ; No subject line, delete msg anyway
591 (mh-delete-msg (mh-get-msg-num t))) 992 (mh-delete-msg (mh-get-msg-num t)))
592 ((= 0 count) ; No other msgs, delete msg anyway. 993 ((= 0 count) ; No other msgs, delete msg anyway.
593 (message "No other messages with same Subject following this one.") 994 (message "No other messages with same Subject following this one")
594 (mh-delete-msg (mh-get-msg-num t))) 995 (mh-delete-msg (mh-get-msg-num t)))
595 (t ; We have a subject sequence. 996 (t ; We have a subject sequence.
596 (message "Marked %d messages for deletion" count) 997 (message "Marked %d messages for deletion" count)
597 (mh-delete-msg 'subject))))) 998 (mh-delete-msg 'subject)))))
598 999
599 ;;;###mh-autoload 1000 ;;;###mh-autoload
600 (defun mh-delete-subject-or-thread () 1001 (defun mh-delete-subject-or-thread ()
601 "Mark messages for deletion intelligently. 1002 "Delete messages with same subject or thread\\<mh-folder-mode-map>.
602 If the folder is threaded then `mh-thread-delete' is used to mark the current 1003
603 message and all its descendants for deletion. Otherwise `mh-delete-subject' is 1004 To delete messages faster, you can use this command to delete all
604 used to mark the current message and all messages following it with the same 1005 the messages with the same subject as the current message. This
605 subject for deletion." 1006 command puts these messages in a sequence named \"subject\". You
1007 can undo this action by using \\[mh-undo] with a prefix argument
1008 and then specifying the \"subject\" sequence.
1009
1010 However, if the buffer is displaying a threaded view of the
1011 folder then this command behaves like \\[mh-thread-delete]."
606 (interactive) 1012 (interactive)
607 (if (memq 'unthread mh-view-ops) 1013 (if (memq 'unthread mh-view-ops)
608 (mh-thread-delete) 1014 (mh-thread-delete)
609 (mh-delete-subject))) 1015 (mh-delete-subject)))
610 1016
1017
1018
611 ;;; Message threading: 1019 ;;; Message threading:
612 1020
1021 (defmacro mh-thread-initialize-hash (var test)
1022 "Initialize the hash table in VAR.
1023 TEST is the test to use when creating a new hash table."
1024 (unless (symbolp var) (error "Expected a symbol: %s" var))
1025 `(if ,var (clrhash ,var) (setq ,var (make-hash-table :test ,test))))
1026
613 (defun mh-thread-initialize () 1027 (defun mh-thread-initialize ()
614 "Make hash tables, otherwise clear them." 1028 "Make new hash tables, or clear them if already present."
615 (cond 1029 (mh-thread-initialize-hash mh-thread-id-hash #'equal)
616 (mh-thread-id-hash 1030 (mh-thread-initialize-hash mh-thread-subject-hash #'equal)
617 (clrhash mh-thread-id-hash) 1031 (mh-thread-initialize-hash mh-thread-id-table #'eq)
618 (clrhash mh-thread-subject-hash) 1032 (mh-thread-initialize-hash mh-thread-id-index-map #'eq)
619 (clrhash mh-thread-id-table) 1033 (mh-thread-initialize-hash mh-thread-index-id-map #'eql)
620 (clrhash mh-thread-id-index-map) 1034 (mh-thread-initialize-hash mh-thread-scan-line-map #'eql)
621 (clrhash mh-thread-index-id-map) 1035 (mh-thread-initialize-hash mh-thread-subject-container-hash #'eq)
622 (clrhash mh-thread-scan-line-map) 1036 (mh-thread-initialize-hash mh-thread-duplicates #'eq)
623 (clrhash mh-thread-subject-container-hash) 1037 (setq mh-thread-history ()))
624 (clrhash mh-thread-duplicates)
625 (setq mh-thread-history ()))
626 (t (setq mh-thread-id-hash (make-hash-table :test #'equal))
627 (setq mh-thread-subject-hash (make-hash-table :test #'equal))
628 (setq mh-thread-id-table (make-hash-table :test #'eq))
629 (setq mh-thread-id-index-map (make-hash-table :test #'eq))
630 (setq mh-thread-index-id-map (make-hash-table :test #'eql))
631 (setq mh-thread-scan-line-map (make-hash-table :test #'eql))
632 (setq mh-thread-subject-container-hash (make-hash-table :test #'eq))
633 (setq mh-thread-duplicates (make-hash-table :test #'eq))
634 (setq mh-thread-history ()))))
635 1038
636 (defsubst mh-thread-id-container (id) 1039 (defsubst mh-thread-id-container (id)
637 "Given ID, return the corresponding container in `mh-thread-id-table'. 1040 "Given ID, return the corresponding container in `mh-thread-id-table'.
638 If no container exists then a suitable container is created and the id-table 1041 If no container exists then a suitable container is created and
639 is updated." 1042 the id-table is updated."
640 (when (not id) 1043 (when (not id)
641 (error "1")) 1044 (error "1"))
642 (or (gethash id mh-thread-id-table) 1045 (or (gethash id mh-thread-id-table)
643 (setf (gethash id mh-thread-id-table) 1046 (setf (gethash id mh-thread-id-table)
644 (let ((message (mh-thread-make-message :id id))) 1047 (let ((message (mh-thread-make-message :id id)))
655 unless (eq child-container elem) collect elem)) 1058 unless (eq child-container elem) collect elem))
656 (setf (mh-container-parent child-container) nil)))) 1059 (setf (mh-container-parent child-container) nil))))
657 1060
658 (defsubst mh-thread-add-link (parent child &optional at-end-p) 1061 (defsubst mh-thread-add-link (parent child &optional at-end-p)
659 "Add links so that PARENT becomes a parent of CHILD. 1062 "Add links so that PARENT becomes a parent of CHILD.
660 Doesn't make any changes if CHILD is already an ancestor of PARENT. If 1063 Doesn't make any changes if CHILD is already an ancestor of
661 optional argument AT-END-P is non-nil, the CHILD is added to the end of the 1064 PARENT. If optional argument AT-END-P is non-nil, the CHILD is
662 children list of PARENT." 1065 added to the end of the children list of PARENT."
663 (let ((parent-container (cond ((null parent) nil) 1066 (let ((parent-container (cond ((null parent) nil)
664 ((mh-thread-container-p parent) parent) 1067 ((mh-thread-container-p parent) parent)
665 (t (mh-thread-id-container parent)))) 1068 (t (mh-thread-id-container parent))))
666 (child-container (if (mh-thread-container-p child) 1069 (child-container (if (mh-thread-container-p child)
667 child (mh-thread-id-container child)))) 1070 child (mh-thread-id-container child))))
681 (unless parent-container 1084 (unless parent-container
682 (mh-thread-remove-parent-link child-container)))) 1085 (mh-thread-remove-parent-link child-container))))
683 1086
684 (defun mh-thread-ancestor-p (ancestor successor) 1087 (defun mh-thread-ancestor-p (ancestor successor)
685 "Return t if ANCESTOR is really an ancestor of SUCCESSOR and nil otherwise. 1088 "Return t if ANCESTOR is really an ancestor of SUCCESSOR and nil otherwise.
686 In the limit, the function returns t if ANCESTOR and SUCCESSOR are the same 1089 In the limit, the function returns t if ANCESTOR and SUCCESSOR
687 containers." 1090 are the same containers."
688 (block nil 1091 (block nil
689 (while successor 1092 (while successor
690 (when (eq ancestor successor) (return t)) 1093 (when (eq ancestor successor) (return t))
691 (setq successor (mh-container-parent successor))) 1094 (setq successor (mh-container-parent successor)))
692 nil)) 1095 nil))
693 1096
694 (defsubst mh-thread-get-message-container (message) 1097 (defsubst mh-thread-get-message-container (message)
695 "Return container which has MESSAGE in it. 1098 "Return container which has MESSAGE in it.
696 If there is no container present then a new container is allocated." 1099 If there is no container present then a new container is
1100 allocated."
697 (let* ((id (mh-message-id message)) 1101 (let* ((id (mh-message-id message))
698 (container (gethash id mh-thread-id-table))) 1102 (container (gethash id mh-thread-id-table)))
699 (cond (container (setf (mh-container-message container) message) 1103 (cond (container (setf (mh-container-message container) message)
700 container) 1104 container)
701 (t (setf (gethash id mh-thread-id-table) 1105 (t (setf (gethash id mh-thread-id-table)
702 (mh-thread-make-container :message message)))))) 1106 (mh-thread-make-container :message message))))))
703 1107
704 (defsubst mh-thread-get-message (id subject-re-p subject refs) 1108 (defsubst mh-thread-get-message (id subject-re-p subject refs)
705 "Return appropriate message. 1109 "Return appropriate message.
706 Otherwise update message already present to have the proper ID, SUBJECT-RE-P, 1110 Otherwise update message already present to have the proper ID,
707 SUBJECT and REFS fields." 1111 SUBJECT-RE-P, SUBJECT and REFS fields."
708 (let* ((container (gethash id mh-thread-id-table)) 1112 (let* ((container (gethash id mh-thread-id-table))
709 (message (if container (mh-container-message container) nil))) 1113 (message (if container (mh-container-message container) nil)))
710 (cond (message 1114 (cond (message
711 (setf (mh-message-subject-re-p message) subject-re-p) 1115 (setf (mh-message-subject-re-p message) subject-re-p)
712 (setf (mh-message-subject message) subject) 1116 (setf (mh-message-subject message) subject)
713 (setf (mh-message-id message) id) 1117 (setf (mh-message-id message) id)
714 (setf (mh-message-references message) refs) 1118 (setf (mh-message-references message) refs)
715 message) 1119 message)
716 (container 1120 (container
717 (setf (mh-container-message container) 1121 (setf (mh-container-message container)
718 (mh-thread-make-message :subject subject 1122 (mh-thread-make-message :id id :references refs
719 :subject-re-p subject-re-p 1123 :subject subject
720 :id id :references refs))) 1124 :subject-re-p subject-re-p)))
721 (t (let ((message (mh-thread-make-message 1125 (t (let ((message (mh-thread-make-message :id id :references refs
722 :subject subject 1126 :subject-re-p subject-re-p
723 :subject-re-p subject-re-p 1127 :subject subject)))
724 :id id :references refs)))
725 (prog1 message 1128 (prog1 message
726 (mh-thread-get-message-container message))))))) 1129 (mh-thread-get-message-container message)))))))
727 1130
728 (defsubst mh-thread-canonicalize-id (id) 1131 (defsubst mh-thread-canonicalize-id (id)
729 "Produce canonical string representation for ID. 1132 "Produce canonical string representation for ID.
732 (gethash id mh-thread-id-hash) 1135 (gethash id mh-thread-id-hash)
733 (setf (gethash id mh-thread-id-hash) id))) 1136 (setf (gethash id mh-thread-id-hash) id)))
734 1137
735 (defsubst mh-thread-prune-subject (subject) 1138 (defsubst mh-thread-prune-subject (subject)
736 "Prune leading Re:'s, Fwd:'s etc. and trailing (fwd)'s from SUBJECT. 1139 "Prune leading Re:'s, Fwd:'s etc. and trailing (fwd)'s from SUBJECT.
737 If the result after pruning is not the empty string then it is canonicalized 1140 If the result after pruning is not the empty string then it is
738 so that subjects can be tested for equality with eq. This is done so that all 1141 canonicalized so that subjects can be tested for equality with
739 the messages without a subject are not put into a single thread." 1142 eq. This is done so that all the messages without a subject are
1143 not put into a single thread."
740 (let ((case-fold-search t) 1144 (let ((case-fold-search t)
741 (subject-pruned-flag nil)) 1145 (subject-pruned-flag nil))
742 ;; Prune subject leader 1146 ;; Prune subject leader
743 (while (or (string-match "^[ \t]*\\(re\\|fwd?\\)\\(\\[[0-9]*\\]\\)?:[ \t]*" 1147 (while (or (string-match "^[ \t]*\\(re\\|fwd?\\)\\(\\[[0-9]*\\]\\)?:[ \t]*"
744 subject) 1148 subject)
757 (setf (gethash subject mh-thread-subject-hash) subject)) 1161 (setf (gethash subject mh-thread-subject-hash) subject))
758 subject-pruned-flag))))) 1162 subject-pruned-flag)))))
759 1163
760 (defun mh-thread-container-subject (container) 1164 (defun mh-thread-container-subject (container)
761 "Return the subject of CONTAINER. 1165 "Return the subject of CONTAINER.
762 If CONTAINER is empty return the subject info of one of its children." 1166 If CONTAINER is empty return the subject info of one of its
1167 children."
763 (cond ((and (mh-container-message container) 1168 (cond ((and (mh-container-message container)
764 (mh-message-id (mh-container-message container))) 1169 (mh-message-id (mh-container-message container)))
765 (mh-message-subject (mh-container-message container))) 1170 (mh-message-subject (mh-container-message container)))
766 (t (block nil 1171 (t (block nil
767 (dolist (kid (mh-container-children container)) 1172 (dolist (kid (mh-container-children container))
768 (when (and (mh-container-message kid) 1173 (when (and (mh-container-message kid)
769 (mh-message-id (mh-container-message kid))) 1174 (mh-message-id (mh-container-message kid)))
770 (let ((kid-message (mh-container-message kid))) 1175 (let ((kid-message (mh-container-message kid)))
771 (return (mh-message-subject kid-message))))) 1176 (return (mh-message-subject kid-message)))))
772 (error "This can't happen!"))))) 1177 (error "This can't happen")))))
773 1178
774 (defun mh-thread-rewind-pruning () 1179 (defun mh-thread-rewind-pruning ()
775 "Restore the thread tree to its state before pruning." 1180 "Restore the thread tree to its state before pruning."
776 (while mh-thread-history 1181 (while mh-thread-history
777 (let ((action (pop mh-thread-history))) 1182 (let ((action (pop mh-thread-history)))
862 (and (integerp index-x) (integerp index-y) 1267 (and (integerp index-x) (integerp index-y)
863 (< index-x index-y))))))) 1268 (< index-x index-y)))))))
864 1269
865 (defsubst mh-thread-group-by-subject (roots) 1270 (defsubst mh-thread-group-by-subject (roots)
866 "Group the set of message containers, ROOTS based on subject. 1271 "Group the set of message containers, ROOTS based on subject.
867 Bug: Check for and make sure that something without Re: is made the parent in 1272 Bug: Check for and make sure that something without Re: is made
868 preference to something that has it." 1273 the parent in preference to something that has it."
869 (clrhash mh-thread-subject-container-hash) 1274 (clrhash mh-thread-subject-container-hash)
870 (let ((results ())) 1275 (let ((results ()))
871 (dolist (root roots) 1276 (dolist (root roots)
872 (let* ((subject (mh-thread-container-subject root)) 1277 (let* ((subject (mh-thread-container-subject root))
873 (parent (gethash subject mh-thread-subject-container-hash))) 1278 (parent (gethash subject mh-thread-subject-container-hash)))
878 (t 1283 (t
879 (setf (gethash subject mh-thread-subject-container-hash) root) 1284 (setf (gethash subject mh-thread-subject-container-hash) root)
880 (push root results))))) 1285 (push root results)))))
881 (nreverse results))) 1286 (nreverse results)))
882 1287
883 (defsubst mh-thread-process-in-reply-to (reply-to-header) 1288 (defun mh-thread-process-in-reply-to (reply-to-header)
884 "Extract message id's from REPLY-TO-HEADER. 1289 "Extract message id's from REPLY-TO-HEADER.
885 Ideally this should have some regexp which will try to guess if a string 1290 Ideally this should have some regexp which will try to guess if a
886 between < and > is a message id and not an email address. For now it will 1291 string between < and > is a message id and not an email address.
887 take the last string inside angles." 1292 For now it will take the last string inside angles."
888 (let ((end (mh-search-from-end ?> reply-to-header))) 1293 (let ((end (mh-search-from-end ?> reply-to-header)))
889 (when (numberp end) 1294 (when (numberp end)
890 (let ((begin (mh-search-from-end ?< (substring reply-to-header 0 end)))) 1295 (let ((begin (mh-search-from-end ?< (substring reply-to-header 0 end))))
891 (when (numberp begin) 1296 (when (numberp begin)
892 (list (substring reply-to-header begin (1+ end)))))))) 1297 (list (substring reply-to-header begin (1+ end))))))))
908 (setq mh-thread-duplicates (mh-get-table 'mh-thread-duplicates)) 1313 (setq mh-thread-duplicates (mh-get-table 'mh-thread-duplicates))
909 (setq mh-thread-history (mh-get-table 'mh-thread-history)))) 1314 (setq mh-thread-history (mh-get-table 'mh-thread-history))))
910 1315
911 (defsubst mh-thread-update-id-index-maps (id index) 1316 (defsubst mh-thread-update-id-index-maps (id index)
912 "Message with id, ID is the message in INDEX. 1317 "Message with id, ID is the message in INDEX.
913 The function also checks for duplicate messages (that is multiple messages 1318 The function also checks for duplicate messages (that is multiple
914 with the same ID). These messages are put in the `mh-thread-duplicates' hash 1319 messages with the same ID). These messages are put in the
915 table." 1320 `mh-thread-duplicates' hash table."
916 (let ((old-index (gethash id mh-thread-id-index-map))) 1321 (let ((old-index (gethash id mh-thread-id-index-map)))
917 (when old-index (push old-index (gethash id mh-thread-duplicates))) 1322 (when old-index (push old-index (gethash id mh-thread-duplicates)))
918 (setf (gethash id mh-thread-id-index-map) index) 1323 (setf (gethash id mh-thread-id-index-map) index)
919 (setf (gethash index mh-thread-index-id-map) id))) 1324 (setf (gethash index mh-thread-index-id-map) id)))
920 1325
990 ;;;###mh-autoload 1395 ;;;###mh-autoload
991 (defun mh-thread-inc (folder start-point) 1396 (defun mh-thread-inc (folder start-point)
992 "Update thread tree for FOLDER. 1397 "Update thread tree for FOLDER.
993 All messages after START-POINT are added to the thread tree." 1398 All messages after START-POINT are added to the thread tree."
994 (mh-thread-rewind-pruning) 1399 (mh-thread-rewind-pruning)
1400 (mh-remove-all-notation)
995 (goto-char start-point) 1401 (goto-char start-point)
996 (let ((msg-list ())) 1402 (let ((msg-list ()))
997 (while (not (eobp)) 1403 (while (not (eobp))
998 (let ((index (mh-get-msg-num nil))) 1404 (let ((index (mh-get-msg-num nil)))
999 (when (numberp index) 1405 (when (numberp index)
1003 (forward-line))) 1409 (forward-line)))
1004 (let ((thread-tree (mh-thread-generate folder msg-list)) 1410 (let ((thread-tree (mh-thread-generate folder msg-list))
1005 (buffer-read-only nil) 1411 (buffer-read-only nil)
1006 (old-buffer-modified-flag (buffer-modified-p))) 1412 (old-buffer-modified-flag (buffer-modified-p)))
1007 (delete-region (point-min) (point-max)) 1413 (delete-region (point-min) (point-max))
1008 (let ((mh-thread-body-width (- (window-width) mh-cmd-note 1414 (mh-thread-print-scan-lines thread-tree)
1009 (1- mh-scan-field-subject-start-offset)))
1010 (mh-thread-last-ancestor nil))
1011 (mh-thread-generate-scan-lines thread-tree -2))
1012 (mh-notate-user-sequences) 1415 (mh-notate-user-sequences)
1013 (mh-notate-deleted-and-refiled) 1416 (mh-notate-deleted-and-refiled)
1014 (mh-notate-cur) 1417 (mh-notate-cur)
1015 (set-buffer-modified-p old-buffer-modified-flag)))) 1418 (set-buffer-modified-p old-buffer-modified-flag))))
1016 1419
1017 (defvar mh-thread-last-ancestor)
1018
1019 (defun mh-thread-generate-scan-lines (tree level) 1420 (defun mh-thread-generate-scan-lines (tree level)
1020 "Generate scan lines. 1421 "Generate scan lines.
1021 TREE is the hierarchical tree of messages, SCAN-LINE-MAP maps message indices 1422 TREE is the hierarchical tree of messages, SCAN-LINE-MAP maps
1022 to the corresponding scan lines and LEVEL used to determine indentation of 1423 message indices to the corresponding scan lines and LEVEL used to
1023 the message." 1424 determine indentation of the message."
1024 (cond ((null tree) nil) 1425 (cond ((null tree) nil)
1025 ((mh-thread-container-p tree) 1426 ((mh-thread-container-p tree)
1026 (let* ((message (mh-container-message tree)) 1427 (let* ((message (mh-container-message tree))
1027 (id (mh-message-id message)) 1428 (id (mh-message-id message))
1028 (index (gethash id mh-thread-id-index-map)) 1429 (index (gethash id mh-thread-id-index-map))
1069 1470
1070 ;; Another and may be better approach would be to generate all the info from 1471 ;; Another and may be better approach would be to generate all the info from
1071 ;; the scan which generates the threading info. For now this will have to do. 1472 ;; the scan which generates the threading info. For now this will have to do.
1072 (defun mh-thread-parse-scan-line (&optional string) 1473 (defun mh-thread-parse-scan-line (&optional string)
1073 "Parse a scan line. 1474 "Parse a scan line.
1074 If optional argument STRING is given then that is assumed to be the scan line. 1475 If optional argument STRING is given then that is assumed to be
1075 Otherwise uses the line at point as the scan line to parse." 1476 the scan line. Otherwise uses the line at point as the scan line
1477 to parse."
1076 (let* ((string (or string 1478 (let* ((string (or string
1077 (buffer-substring-no-properties (line-beginning-position) 1479 (buffer-substring-no-properties (line-beginning-position)
1078 (line-end-position)))) 1480 (line-end-position))))
1079 (first-string (substring string 0 (+ mh-cmd-note 8)))) 1481 (address-start (+ mh-cmd-note mh-scan-field-from-start-offset))
1080 (setf (elt first-string mh-cmd-note) ? ) 1482 (body-start (+ mh-cmd-note mh-scan-field-from-end-offset))
1081 (when (equal (elt first-string (1+ mh-cmd-note)) (elt mh-note-seq 0)) 1483 (first-string (substring string 0 address-start)))
1082 (setf (elt first-string (1+ mh-cmd-note)) ? ))
1083 (list first-string 1484 (list first-string
1084 (substring string 1485 (substring string address-start (- body-start 2))
1085 (+ mh-cmd-note mh-scan-field-from-start-offset) 1486 (substring string body-start)
1086 (+ mh-cmd-note mh-scan-field-from-end-offset -2))
1087 (substring string (+ mh-cmd-note mh-scan-field-from-end-offset))
1088 string))) 1487 string)))
1488
1489 ;;;###mh-autoload
1490 (defun mh-thread-update-scan-line-map (msg notation offset)
1491 "In threaded view update `mh-thread-scan-line-map'.
1492 MSG is the message being notated with NOTATION at OFFSET."
1493 (let* ((msg (or msg (mh-get-msg-num nil)))
1494 (cur-scan-line (and mh-thread-scan-line-map
1495 (gethash msg mh-thread-scan-line-map)))
1496 (old-scan-lines (loop for map in mh-thread-scan-line-map-stack
1497 collect (and map (gethash msg map)))))
1498 (when cur-scan-line
1499 (setf (aref (car cur-scan-line) offset) notation))
1500 (dolist (line old-scan-lines)
1501 (when line (setf (aref (car line) offset) notation)))))
1089 1502
1090 ;;;###mh-autoload 1503 ;;;###mh-autoload
1091 (defun mh-thread-add-spaces (count) 1504 (defun mh-thread-add-spaces (count)
1092 "Add COUNT spaces to each scan line in `mh-thread-scan-line-map'." 1505 "Add COUNT spaces to each scan line in `mh-thread-scan-line-map'."
1093 (let ((spaces (format (format "%%%ss" count) ""))) 1506 (let ((spaces (format (format "%%%ss" count) "")))
1097 (when (numberp msg-num) 1510 (when (numberp msg-num)
1098 (setf (gethash msg-num mh-thread-scan-line-map) 1511 (setf (gethash msg-num mh-thread-scan-line-map)
1099 (mh-thread-parse-scan-line (format "%s%s" spaces old-line))))) 1512 (mh-thread-parse-scan-line (format "%s%s" spaces old-line)))))
1100 (forward-line 1)))) 1513 (forward-line 1))))
1101 1514
1515 (defun mh-thread-print-scan-lines (thread-tree)
1516 "Print scan lines in THREAD-TREE in threaded mode."
1517 (let ((mh-thread-body-width (- (window-width) mh-cmd-note
1518 (1- mh-scan-field-subject-start-offset)))
1519 (mh-thread-last-ancestor nil))
1520 (if (null mh-index-data)
1521 (mh-thread-generate-scan-lines thread-tree -2)
1522 (loop for x in (mh-index-group-by-folder)
1523 do (let* ((old-map mh-thread-scan-line-map)
1524 (mh-thread-scan-line-map (make-hash-table)))
1525 (setq mh-thread-last-ancestor nil)
1526 (loop for msg in (cdr x)
1527 do (let ((v (gethash msg old-map)))
1528 (when v
1529 (setf (gethash msg mh-thread-scan-line-map) v))))
1530 (when (> (hash-table-count mh-thread-scan-line-map) 0)
1531 (insert (if (bobp) "" "\n") (car x) "\n")
1532 (mh-thread-generate-scan-lines thread-tree -2))))
1533 (mh-index-create-imenu-index))))
1534
1102 (defun mh-thread-folder () 1535 (defun mh-thread-folder ()
1103 "Generate thread view of folder." 1536 "Generate thread view of folder."
1104 (message "Threading %s..." (buffer-name)) 1537 (message "Threading %s..." (buffer-name))
1105 (mh-thread-initialize) 1538 (mh-thread-initialize)
1106 (goto-char (point-min)) 1539 (goto-char (point-min))
1540 (mh-remove-all-notation)
1107 (let ((msg-list ())) 1541 (let ((msg-list ()))
1108 (while (not (eobp)) 1542 (mh-iterate-on-range msg (cons (point-min) (point-max))
1109 (let ((index (mh-get-msg-num nil))) 1543 (setf (gethash msg mh-thread-scan-line-map) (mh-thread-parse-scan-line))
1110 (when (numberp index) 1544 (push msg msg-list))
1111 (push index msg-list)
1112 (setf (gethash index mh-thread-scan-line-map)
1113 (mh-thread-parse-scan-line))))
1114 (forward-line))
1115 (let* ((range (mh-coalesce-msg-list msg-list)) 1545 (let* ((range (mh-coalesce-msg-list msg-list))
1116 (thread-tree (mh-thread-generate (buffer-name) range))) 1546 (thread-tree (mh-thread-generate (buffer-name) range)))
1117 (delete-region (point-min) (point-max)) 1547 (delete-region (point-min) (point-max))
1118 (let ((mh-thread-body-width (- (window-width) mh-cmd-note 1548 (mh-thread-print-scan-lines thread-tree)
1119 (1- mh-scan-field-subject-start-offset)))
1120 (mh-thread-last-ancestor nil))
1121 (mh-thread-generate-scan-lines thread-tree -2))
1122 (mh-notate-user-sequences) 1549 (mh-notate-user-sequences)
1123 (mh-notate-deleted-and-refiled) 1550 (mh-notate-deleted-and-refiled)
1124 (mh-notate-cur) 1551 (mh-notate-cur)
1125 (message "Threading %s...done" (buffer-name))))) 1552 (message "Threading %s...done" (buffer-name)))))
1126 1553
1135 (unless (mh-valid-view-change-operation-p 'unthread) 1562 (unless (mh-valid-view-change-operation-p 'unthread)
1136 (error "Can't unthread folder")) 1563 (error "Can't unthread folder"))
1137 (let ((msg-list ())) 1564 (let ((msg-list ()))
1138 (goto-char (point-min)) 1565 (goto-char (point-min))
1139 (while (not (eobp)) 1566 (while (not (eobp))
1140 (let ((index (mh-get-msg-num t))) 1567 (let ((index (mh-get-msg-num nil)))
1141 (when index 1568 (when index
1142 (push index msg-list))) 1569 (push index msg-list)))
1143 (forward-line)) 1570 (forward-line))
1144 (mh-scan-folder mh-current-folder 1571 (mh-scan-folder mh-current-folder
1145 (mapcar #'(lambda (x) (format "%s" x)) 1572 (mapcar #'(lambda (x) (format "%s" x))
1159 "Forget the message INDEX from the threading tables." 1586 "Forget the message INDEX from the threading tables."
1160 (let* ((id (gethash index mh-thread-index-id-map)) 1587 (let* ((id (gethash index mh-thread-index-id-map))
1161 (id-index (gethash id mh-thread-id-index-map)) 1588 (id-index (gethash id mh-thread-id-index-map))
1162 (duplicates (gethash id mh-thread-duplicates))) 1589 (duplicates (gethash id mh-thread-duplicates)))
1163 (remhash index mh-thread-index-id-map) 1590 (remhash index mh-thread-index-id-map)
1591 (remhash index mh-thread-scan-line-map)
1164 (cond ((and (eql index id-index) (null duplicates)) 1592 (cond ((and (eql index id-index) (null duplicates))
1165 (remhash id mh-thread-id-index-map)) 1593 (remhash id mh-thread-id-index-map))
1166 ((eql index id-index) 1594 ((eql index id-index)
1167 (setf (gethash id mh-thread-id-index-map) (car duplicates)) 1595 (setf (gethash id mh-thread-id-index-map) (car duplicates))
1168 (setf (gethash (car duplicates) mh-thread-index-id-map) id) 1596 (setf (gethash (car duplicates) mh-thread-index-id-map) id)
1188 (forward-char)) 1616 (forward-char))
1189 level))) 1617 level)))
1190 1618
1191 ;;;###mh-autoload 1619 ;;;###mh-autoload
1192 (defun mh-thread-next-sibling (&optional previous-flag) 1620 (defun mh-thread-next-sibling (&optional previous-flag)
1193 "Jump to next sibling. 1621 "Display next sibling.
1194 With non-nil optional argument PREVIOUS-FLAG jump to the previous sibling." 1622
1623 With non-nil optional argument PREVIOUS-FLAG jump to the previous
1624 sibling."
1195 (interactive) 1625 (interactive)
1196 (cond ((not (memq 'unthread mh-view-ops)) 1626 (cond ((not (memq 'unthread mh-view-ops))
1197 (error "Folder isn't threaded")) 1627 (error "Folder isn't threaded"))
1198 ((eobp) 1628 ((eobp)
1199 (error "No message at point"))) 1629 (error "No message at point")))
1215 (t (message "No %s sibling" (if previous-flag "previous" "next")) 1645 (t (message "No %s sibling" (if previous-flag "previous" "next"))
1216 (goto-char point))))) 1646 (goto-char point)))))
1217 1647
1218 ;;;###mh-autoload 1648 ;;;###mh-autoload
1219 (defun mh-thread-previous-sibling () 1649 (defun mh-thread-previous-sibling ()
1220 "Jump to previous sibling." 1650 "Display previous sibling."
1221 (interactive) 1651 (interactive)
1222 (mh-thread-next-sibling t)) 1652 (mh-thread-next-sibling t))
1223 1653
1224 (defun mh-thread-immediate-ancestor () 1654 (defun mh-thread-immediate-ancestor ()
1225 "Jump to immediate ancestor in thread tree." 1655 "Jump to immediate ancestor in thread tree."
1236 (goto-char point)) 1666 (goto-char point))
1237 done))) 1667 done)))
1238 1668
1239 ;;;###mh-autoload 1669 ;;;###mh-autoload
1240 (defun mh-thread-ancestor (&optional thread-root-flag) 1670 (defun mh-thread-ancestor (&optional thread-root-flag)
1241 "Jump to the ancestor of current message. 1671 "Display ancestor of current message.
1242 If optional argument THREAD-ROOT-FLAG is non-nil then jump to the root of the 1672
1243 thread tree the message belongs to." 1673 If you do not care for the way a particular thread has turned,
1674 you can move up the chain of messages with this command. This
1675 command can also take a prefix argument THREAD-ROOT-FLAG to jump
1676 to the message that started everything."
1244 (interactive "P") 1677 (interactive "P")
1245 (beginning-of-line) 1678 (beginning-of-line)
1246 (cond ((not (memq 'unthread mh-view-ops)) 1679 (cond ((not (memq 'unthread mh-view-ops))
1247 (error "Folder isn't threaded")) 1680 (error "Folder isn't threaded"))
1248 ((eobp) 1681 ((eobp)
1256 (t (mh-thread-immediate-ancestor) 1689 (t (mh-thread-immediate-ancestor)
1257 (mh-maybe-show))))) 1690 (mh-maybe-show)))))
1258 1691
1259 (defun mh-thread-find-children () 1692 (defun mh-thread-find-children ()
1260 "Return a region containing the current message and its children. 1693 "Return a region containing the current message and its children.
1261 The result is returned as a list of two elements. The first is the point at the 1694 The result is returned as a list of two elements. The first is
1262 start of the region and the second is the point at the end." 1695 the point at the start of the region and the second is the point
1696 at the end."
1263 (beginning-of-line) 1697 (beginning-of-line)
1264 (if (eobp) 1698 (if (eobp)
1265 nil 1699 nil
1266 (let ((address-start-offset (+ mh-cmd-note mh-scan-date-flag-width 1700 (let ((address-start-offset (+ mh-cmd-note mh-scan-date-flag-width
1267 mh-scan-date-width 1)) 1701 mh-scan-date-width 1))
1282 (forward-line))) 1716 (forward-line)))
1283 (list begin (point))))) 1717 (list begin (point)))))
1284 1718
1285 ;;;###mh-autoload 1719 ;;;###mh-autoload
1286 (defun mh-thread-delete () 1720 (defun mh-thread-delete ()
1287 "Mark current message and all its children for subsequent deletion." 1721 "Delete thread."
1288 (interactive) 1722 (interactive)
1289 (cond ((not (memq 'unthread mh-view-ops)) 1723 (cond ((not (memq 'unthread mh-view-ops))
1290 (error "Folder isn't threaded")) 1724 (error "Folder isn't threaded"))
1291 ((eobp) 1725 ((eobp)
1292 (error "No message at point")) 1726 (error "No message at point"))
1295 (mh-delete-a-msg nil)) 1729 (mh-delete-a-msg nil))
1296 (mh-next-msg))))) 1730 (mh-next-msg)))))
1297 1731
1298 ;;;###mh-autoload 1732 ;;;###mh-autoload
1299 (defun mh-thread-refile (folder) 1733 (defun mh-thread-refile (folder)
1300 "Mark current message and all its children for refiling to FOLDER." 1734 "Refile (output) thread into FOLDER."
1301 (interactive (list (intern (mh-prompt-for-refile-folder)))) 1735 (interactive (list (intern (mh-prompt-for-refile-folder))))
1302 (cond ((not (memq 'unthread mh-view-ops)) 1736 (cond ((not (memq 'unthread mh-view-ops))
1303 (error "Folder isn't threaded")) 1737 (error "Folder isn't threaded"))
1304 ((eobp) 1738 ((eobp)
1305 (error "No message at point")) 1739 (error "No message at point"))
1306 (t (let ((region (mh-thread-find-children))) 1740 (t (let ((region (mh-thread-find-children)))
1307 (mh-iterate-on-messages-in-region () (car region) (cadr region) 1741 (mh-iterate-on-messages-in-region () (car region) (cadr region)
1308 (mh-refile-a-msg nil folder)) 1742 (mh-refile-a-msg nil folder))
1309 (mh-next-msg))))) 1743 (mh-next-msg)))))
1310 1744
1745
1746
1747 ;; Tick mark handling
1748
1749 ;;;###mh-autoload
1750 (defun mh-toggle-tick (range)
1751 "Toggle tick mark of RANGE.
1752
1753 This command adds messages to the \"tick\" sequence (which you can customize
1754 via the option `mh-tick-seq'). This sequence can be viewed later with the
1755 \\[mh-index-ticked-messages] command.
1756
1757 Check the documentation of `mh-interactive-range' to see how RANGE is read in
1758 interactive use."
1759 (interactive (list (mh-interactive-range "Tick")))
1760 (unless mh-tick-seq
1761 (error "Enable ticking by customizing `mh-tick-seq'"))
1762 (let* ((tick-seq (mh-find-seq mh-tick-seq))
1763 (tick-seq-msgs (mh-seq-msgs tick-seq))
1764 (ticked ())
1765 (unticked ()))
1766 (mh-iterate-on-range msg range
1767 (cond ((member msg tick-seq-msgs)
1768 (push msg unticked)
1769 (setcdr tick-seq (delq msg (cdr tick-seq)))
1770 (when (null (cdr tick-seq)) (setq mh-last-seq-used nil))
1771 (mh-remove-sequence-notation msg (mh-colors-in-use-p)))
1772 (t
1773 (push msg ticked)
1774 (setq mh-last-seq-used mh-tick-seq)
1775 (let ((mh-seq-list (cons `(,mh-tick-seq ,msg) mh-seq-list)))
1776 (mh-add-sequence-notation msg (mh-colors-in-use-p))))))
1777 (mh-add-msgs-to-seq ticked mh-tick-seq nil t)
1778 (mh-undefine-sequence mh-tick-seq unticked)
1779 (when mh-index-data
1780 (mh-index-add-to-sequence mh-tick-seq ticked)
1781 (mh-index-delete-from-sequence mh-tick-seq unticked))))
1782
1783 ;;;###mh-autoload
1784 (defun mh-narrow-to-tick ()
1785 "Limit to ticked messages.
1786
1787 What this command does is show only those messages that are in
1788 the \"tick\" sequence (which you can customize via the
1789 `mh-tick-seq' option) in the MH-Folder buffer. In addition, it
1790 limits further MH-E searches to just those messages. When you
1791 want to widen the view to all your messages again, use
1792 \\[mh-widen]."
1793 (interactive)
1794 (cond ((not mh-tick-seq)
1795 (error "Enable ticking by customizing `mh-tick-seq'"))
1796 ((null (mh-seq-msgs (mh-find-seq mh-tick-seq)))
1797 (message "No messages in %s sequence" mh-tick-seq))
1798 (t (mh-narrow-to-seq mh-tick-seq))))
1799
1311 (provide 'mh-seq) 1800 (provide 'mh-seq)
1312 1801
1313 ;;; Local Variables: 1802 ;; Local Variables:
1314 ;;; indent-tabs-mode: nil 1803 ;; indent-tabs-mode: nil
1315 ;;; sentence-end-double-space: nil 1804 ;; sentence-end-double-space: nil
1316 ;;; End: 1805 ;; End:
1317 1806
1807 ;; arch-tag: 8e952711-01a2-485b-bf21-c9e3ad4de942
1318 ;;; mh-seq.el ends here 1808 ;;; mh-seq.el ends here