comparison lisp/mail/mh-seq.el @ 49120:30c4902b654d

Upgraded to MH-E version 7.1.
author Bill Wohler <wohler@newt.com>
date Wed, 08 Jan 2003 23:21:16 +0000
parents 8aaba207e44b
children
comparison
equal deleted inserted replaced
49119:938f153410ae 49120:30c4902b654d
65 65
66 ;; Internal support for MH-E package. 66 ;; Internal support for MH-E package.
67 67
68 ;;; Change Log: 68 ;;; Change Log:
69 69
70 ;; $Id: mh-seq.el,v 1.71 2002/11/14 20:41:12 wohler Exp $ 70 ;; $Id: mh-seq.el,v 1.84 2003/01/07 21:15:33 satyaki Exp $
71 71
72 ;;; Code: 72 ;;; Code:
73 73
74 (require 'cl) 74 (require 'cl)
75 (require 'mh-e) 75 (require 'mh-e)
135 (make-variable-buffer-local 'mh-thread-old-scan-line-map) 135 (make-variable-buffer-local 'mh-thread-old-scan-line-map)
136 (make-variable-buffer-local 'mh-thread-subject-container-hash) 136 (make-variable-buffer-local 'mh-thread-subject-container-hash)
137 (make-variable-buffer-local 'mh-thread-duplicates) 137 (make-variable-buffer-local 'mh-thread-duplicates)
138 (make-variable-buffer-local 'mh-thread-history) 138 (make-variable-buffer-local 'mh-thread-history)
139 139
140 ;;;###mh-autoload
140 (defun mh-delete-seq (sequence) 141 (defun mh-delete-seq (sequence)
141 "Delete the SEQUENCE." 142 "Delete the SEQUENCE."
142 (interactive (list (mh-read-seq-default "Delete" t))) 143 (interactive (list (mh-read-seq-default "Delete" t)))
143 (mh-map-to-seq-msgs 'mh-notate-if-in-one-seq sequence ? (1+ mh-cmd-note) 144 (mh-map-to-seq-msgs 'mh-notate-if-in-one-seq sequence ? (1+ mh-cmd-note)
144 sequence) 145 sequence)
145 (mh-undefine-sequence sequence '("all")) 146 (mh-undefine-sequence sequence '("all"))
146 (mh-delete-seq-locally sequence)) 147 (mh-delete-seq-locally sequence))
147 148
148 ;; Avoid compiler warnings 149 ;; Avoid compiler warnings
149 (defvar view-exit-action) 150 (defvar view-exit-action)
150 151
151 (defun mh-list-sequences (folder) 152 ;;;###mh-autoload
152 "List the sequences defined in FOLDER." 153 (defun mh-list-sequences ()
153 (interactive (list (mh-prompt-for-folder "List sequences in" 154 "List the sequences defined in the folder being visited."
154 mh-current-folder t))) 155 (interactive)
155 (let ((temp-buffer mh-temp-sequences-buffer) 156 (let ((folder mh-current-folder)
156 (seq-list mh-seq-list)) 157 (temp-buffer mh-temp-sequences-buffer)
158 (seq-list mh-seq-list)
159 (max-len 0))
157 (with-output-to-temp-buffer temp-buffer 160 (with-output-to-temp-buffer temp-buffer
158 (save-excursion 161 (save-excursion
159 (set-buffer temp-buffer) 162 (set-buffer temp-buffer)
160 (erase-buffer) 163 (erase-buffer)
161 (message "Listing sequences ...") 164 (message "Listing sequences ...")
162 (insert "Sequences in folder " folder ":\n") 165 (insert "Sequences in folder " folder ":\n")
163 (while seq-list 166 (let ((seq-list seq-list))
164 (let ((name (mh-seq-name (car seq-list))) 167 (while seq-list
165 (sorted-seq-msgs 168 (setq max-len
166 (sort (copy-sequence (mh-seq-msgs (car seq-list))) '<)) 169 (max (length (symbol-name (mh-seq-name (pop seq-list))))
167 (last-col (- (window-width) 4)) 170 max-len)))
168 name-spec) 171 (setq max-len (+ 2 max-len)))
169 (insert (setq name-spec (format "%20s:" name))) 172 (while seq-list
170 (while sorted-seq-msgs 173 (let ((name (mh-seq-name (car seq-list)))
171 (if (> (current-column) last-col) 174 (sorted-seq-msgs
172 (progn 175 (mh-coalesce-msg-list
173 (insert "\n") 176 (sort (copy-sequence (mh-seq-msgs (car seq-list))) '<)))
174 (move-to-column (length name-spec)))) 177 name-spec)
175 (insert (format " %s" (car sorted-seq-msgs))) 178 (insert (setq name-spec (format (format "%%%ss:" max-len) name)))
176 (setq sorted-seq-msgs (cdr sorted-seq-msgs))) 179 (while sorted-seq-msgs
177 (insert "\n")) 180 (let ((next-element (format " %s" (pop sorted-seq-msgs))))
178 (setq seq-list (cdr seq-list))) 181 (when (>= (+ (current-column) (length next-element))
179 (goto-char (point-min)) 182 (window-width))
180 (view-mode 1) 183 (insert "\n")
181 (setq view-exit-action 'kill-buffer) 184 (insert (format (format "%%%ss" (length name-spec)) "")))
182 (message "Listing sequences...done"))))) 185 (insert next-element)))
183 186 (insert "\n"))
187 (setq seq-list (cdr seq-list)))
188 (goto-char (point-min))
189 (view-mode 1)
190 (setq view-exit-action 'kill-buffer)
191 (message "Listing sequences...done")))))
192
193 ;;;###mh-autoload
184 (defun mh-msg-is-in-seq (message) 194 (defun mh-msg-is-in-seq (message)
185 "Display the sequences that contain MESSAGE (default: current message)." 195 "Display the sequences that contain MESSAGE (default: current message)."
186 (interactive (list (mh-get-msg-num t))) 196 (interactive (list (mh-get-msg-num t)))
187 (let* ((dest-folder (loop for seq in mh-refile-list 197 (let* ((dest-folder (loop for seq in mh-refile-list
188 when (member message (cdr seq)) 198 when (member message (cdr seq)) return (car seq)))
189 return (car seq)))
190 (deleted-flag (unless dest-folder (member message mh-delete-list)))) 199 (deleted-flag (unless dest-folder (member message mh-delete-list))))
191 (message "Message %d%s is in sequences: %s" 200 (message "Message %d%s is in sequences: %s"
192 message 201 message
193 (cond (dest-folder (format " (to be refiled to %s)" dest-folder)) 202 (cond (dest-folder (format " (to be refiled to %s)" dest-folder))
194 (deleted-flag (format " (to be deleted)")) 203 (deleted-flag (format " (to be deleted)"))
195 (t "")) 204 (t ""))
196 (mapconcat 'concat 205 (mapconcat 'concat
197 (mh-list-to-string (mh-seq-containing-msg message t)) 206 (mh-list-to-string (mh-seq-containing-msg message t))
198 " ")))) 207 " "))))
199 208
209 ;;;###mh-autoload
200 (defun mh-narrow-to-seq (sequence) 210 (defun mh-narrow-to-seq (sequence)
201 "Restrict display of this folder to just messages in SEQUENCE. 211 "Restrict display of this folder to just messages in SEQUENCE.
202 Use \\<mh-folder-mode-map>\\[mh-widen] to undo this command." 212 Use \\<mh-folder-mode-map>\\[mh-widen] to undo this command."
203 (interactive (list (mh-read-seq "Narrow to" t))) 213 (interactive (list (mh-read-seq "Narrow to" t)))
204 (with-mh-folder-updating (t) 214 (with-mh-folder-updating (t)
205 (cond ((mh-seq-to-msgs sequence) 215 (cond ((mh-seq-to-msgs sequence)
206 (mh-widen) 216 (mh-widen)
207 (mh-remove-all-notation) 217 (mh-remove-all-notation)
208 (let ((eob (point-max)) 218 (let ((eob (point-max))
209 (msg-at-cursor (mh-get-msg-num nil))) 219 (msg-at-cursor (mh-get-msg-num nil)))
210 (setq mh-thread-old-scan-line-map mh-thread-scan-line-map) 220 (setq mh-thread-old-scan-line-map mh-thread-scan-line-map)
211 (setq mh-thread-scan-line-map (make-hash-table :test #'eql)) 221 (setq mh-thread-scan-line-map (make-hash-table :test #'eql))
212 (mh-copy-seq-to-eob sequence) 222 (mh-copy-seq-to-eob sequence)
213 (narrow-to-region eob (point-max)) 223 (narrow-to-region eob (point-max))
214 (mh-notate-user-sequences) 224 (mh-notate-user-sequences)
215 (mh-notate-deleted-and-refiled) 225 (mh-notate-deleted-and-refiled)
216 (mh-notate-seq 'cur mh-note-cur mh-cmd-note) 226 (mh-notate-seq 'cur mh-note-cur mh-cmd-note)
217 (when msg-at-cursor (mh-goto-msg msg-at-cursor t t)) 227 (when msg-at-cursor (mh-goto-msg msg-at-cursor t t))
218 (make-variable-buffer-local 'mh-non-seq-mode-line-annotation) 228 (make-variable-buffer-local 'mh-non-seq-mode-line-annotation)
219 (setq mh-non-seq-mode-line-annotation mh-mode-line-annotation) 229 (setq mh-non-seq-mode-line-annotation mh-mode-line-annotation)
220 (setq mh-mode-line-annotation (symbol-name sequence)) 230 (setq mh-mode-line-annotation (symbol-name sequence))
221 (mh-make-folder-mode-line) 231 (mh-make-folder-mode-line)
222 (mh-recenter nil) 232 (mh-recenter nil)
223 (if (and (boundp 'tool-bar-mode) tool-bar-mode) 233 (if (and (boundp 'tool-bar-mode) tool-bar-mode)
224 (set (make-local-variable 'tool-bar-map) 234 (set (make-local-variable 'tool-bar-map)
225 mh-folder-seq-tool-bar-map)) 235 mh-folder-seq-tool-bar-map))
226 (setq mh-narrowed-to-seq sequence) 236 (setq mh-narrowed-to-seq sequence)
227 (push 'widen mh-view-ops))) 237 (push 'widen mh-view-ops)))
228 (t 238 (t
229 (error "No messages in sequence `%s'" (symbol-name sequence)))))) 239 (error "No messages in sequence `%s'" (symbol-name sequence))))))
230 240
241 ;;;###mh-autoload
231 (defun mh-put-msg-in-seq (msg-or-seq sequence) 242 (defun mh-put-msg-in-seq (msg-or-seq sequence)
232 "Add MSG-OR-SEQ (default: displayed message) to SEQUENCE. 243 "Add MSG-OR-SEQ (default: displayed message) to SEQUENCE.
233 If optional prefix argument provided, then prompt for the message sequence. 244 If optional prefix argument provided, then prompt for the message sequence.
234 If variable `transient-mark-mode' is non-nil and the mark is active, then 245 If variable `transient-mark-mode' is non-nil and the mark is active, then
235 the selected region is added to the sequence." 246 the selected region is added to the sequence."
236 (interactive (list (cond 247 (interactive (list (cond
237 ((mh-mark-active-p t) 248 ((mh-mark-active-p t)
238 (mh-region-to-sequence (region-beginning) (region-end)) 249 (mh-region-to-msg-list (region-beginning) (region-end)))
239 'region)
240 (current-prefix-arg 250 (current-prefix-arg
241 (mh-read-seq-default "Add messages from" t)) 251 (mh-read-seq-default "Add messages from" t))
242 (t 252 (t
243 (mh-get-msg-num t))) 253 (mh-get-msg-num t)))
244 (mh-read-seq-default "Add to" nil))) 254 (mh-read-seq-default "Add to" nil)))
245 (if (not (mh-internal-seq sequence)) 255 (if (not (mh-internal-seq sequence))
246 (setq mh-last-seq-used sequence)) 256 (setq mh-last-seq-used sequence))
247 (mh-add-msgs-to-seq (if (numberp msg-or-seq) 257 (mh-add-msgs-to-seq (cond ((numberp msg-or-seq) (list msg-or-seq))
248 msg-or-seq 258 ((listp msg-or-seq) msg-or-seq)
249 (mh-seq-to-msgs msg-or-seq)) 259 (t (mh-seq-to-msgs msg-or-seq)))
250 sequence)) 260 sequence))
251 261
252 (defun mh-valid-view-change-operation-p (op) 262 (defun mh-valid-view-change-operation-p (op)
253 "Check if the view change operation can be performed. 263 "Check if the view change operation can be performed.
254 OP is one of 'widen and 'unthread." 264 OP is one of 'widen and 'unthread."
255 (cond ((eq (car mh-view-ops) op) 265 (cond ((eq (car mh-view-ops) op)
256 (pop mh-view-ops)) 266 (pop mh-view-ops))
257 (t nil))) 267 (t nil)))
258 268
269 ;;;###mh-autoload
259 (defun mh-widen () 270 (defun mh-widen ()
260 "Remove restrictions from current folder, thereby showing all messages." 271 "Remove restrictions from current folder, thereby showing all messages."
261 (interactive) 272 (interactive)
262 (let ((msg (mh-get-msg-num nil))) 273 (let ((msg (mh-get-msg-num nil)))
263 (when mh-narrowed-to-seq 274 (when mh-narrowed-to-seq
302 313
303 314
304 315
305 ;;; Commands to manipulate sequences. Sequences are stored in an alist 316 ;;; Commands to manipulate sequences. Sequences are stored in an alist
306 ;;; of the form: 317 ;;; of the form:
307 ;;; ((seq-name msgs ...) (seq-name msgs ...) ...) 318 ;;; ((seq-name msgs ...) (seq-name msgs ...) ...)
308 319
309 (defun mh-read-seq-default (prompt not-empty) 320 (defun mh-read-seq-default (prompt not-empty)
310 "Read and return sequence name with default narrowed or previous sequence. 321 "Read and return sequence name with default narrowed or previous sequence.
311 PROMPT is the prompt to use when reading. If NOT-EMPTY is non-nil then a 322 PROMPT is the prompt to use when reading. If NOT-EMPTY is non-nil then a
312 non-empty sequence is read." 323 non-empty sequence is read."
313 (mh-read-seq prompt not-empty 324 (mh-read-seq prompt not-empty
314 (or mh-narrowed-to-seq 325 (or mh-narrowed-to-seq
315 mh-last-seq-used 326 mh-last-seq-used
316 (car (mh-seq-containing-msg (mh-get-msg-num nil) nil))))) 327 (car (mh-seq-containing-msg (mh-get-msg-num nil) nil)))))
317 328
318 (defun mh-read-seq (prompt not-empty &optional default) 329 (defun mh-read-seq (prompt not-empty &optional default)
319 "Read and return a sequence name. 330 "Read and return a sequence name.
320 Prompt with PROMPT, raise an error if the sequence is empty and the NOT-EMPTY 331 Prompt with PROMPT, raise an error if the sequence is empty and the NOT-EMPTY
321 flag is non-nil, and supply an optional DEFAULT sequence. A reply of '%' 332 flag is non-nil, and supply an optional DEFAULT sequence. A reply of '%'
322 defaults to the first sequence containing the current message." 333 defaults to the first sequence containing the current message."
323 (let* ((input (completing-read (format "%s %s %s" prompt "sequence:" 334 (let* ((input (completing-read (format "%s %s %s" prompt "sequence:"
324 (if default 335 (if default
325 (format "[%s] " default) 336 (format "[%s] " default)
326 "")) 337 ""))
327 (mh-seq-names mh-seq-list))) 338 (mh-seq-names mh-seq-list)))
328 (seq (cond ((equal input "%") 339 (seq (cond ((equal input "%")
329 (car (mh-seq-containing-msg (mh-get-msg-num t) nil))) 340 (car (mh-seq-containing-msg (mh-get-msg-num t) nil)))
330 ((equal input "") default) 341 ((equal input "") default)
331 (t (intern input)))) 342 (t (intern input))))
332 (msgs (mh-seq-to-msgs seq))) 343 (msgs (mh-seq-to-msgs seq)))
333 (if (and (null msgs) not-empty) 344 (if (and (null msgs) not-empty)
334 (error "No messages in sequence `%s'" seq)) 345 (error "No messages in sequence `%s'" seq))
335 seq)) 346 seq))
336 347
337 (defun mh-seq-names (seq-list) 348 (defun mh-seq-names (seq-list)
338 "Return an alist containing the names of the SEQ-LIST." 349 "Return an alist containing the names of the SEQ-LIST."
339 (mapcar (lambda (entry) (list (symbol-name (mh-seq-name entry)))) 350 (mapcar (lambda (entry) (list (symbol-name (mh-seq-name entry))))
340 seq-list)) 351 seq-list))
341 352
353 ;;;###mh-autoload
342 (defun mh-rename-seq (sequence new-name) 354 (defun mh-rename-seq (sequence new-name)
343 "Rename SEQUENCE to have NEW-NAME." 355 "Rename SEQUENCE to have NEW-NAME."
344 (interactive (list (mh-read-seq "Old" t) 356 (interactive (list (mh-read-seq "Old" t)
345 (intern (read-string "New sequence name: ")))) 357 (intern (read-string "New sequence name: "))))
346 (let ((old-seq (mh-find-seq sequence))) 358 (let ((old-seq (mh-find-seq sequence)))
347 (or old-seq 359 (or old-seq
348 (error "Sequence %s does not exist" sequence)) 360 (error "Sequence %s does not exist" sequence))
349 ;; create new sequence first, since it might raise an error. 361 ;; create new sequence first, since it might raise an error.
350 (mh-define-sequence new-name (mh-seq-msgs old-seq)) 362 (mh-define-sequence new-name (mh-seq-msgs old-seq))
351 (mh-undefine-sequence sequence (mh-seq-msgs old-seq)) 363 (mh-undefine-sequence sequence (mh-seq-msgs old-seq))
352 (rplaca old-seq new-name))) 364 (rplaca old-seq new-name)))
353 365
366 ;;;###mh-autoload
354 (defun mh-map-to-seq-msgs (func seq &rest args) 367 (defun mh-map-to-seq-msgs (func seq &rest args)
355 "Invoke the FUNC at each message in the SEQ. 368 "Invoke the FUNC at each message in the SEQ.
356 The remaining ARGS are passed as arguments to FUNC." 369 SEQ can either be a list of messages or a MH sequence. The remaining ARGS are
370 passed as arguments to FUNC."
357 (save-excursion 371 (save-excursion
358 (let ((msgs (mh-seq-to-msgs seq))) 372 (let ((msgs (if (listp seq) seq (mh-seq-to-msgs seq))))
359 (while msgs 373 (while msgs
360 (if (mh-goto-msg (car msgs) t t) 374 (if (mh-goto-msg (car msgs) t t)
361 (apply func (car msgs) args)) 375 (apply func (car msgs) args))
362 (setq msgs (cdr msgs)))))) 376 (setq msgs (cdr msgs))))))
363 377
378 ;;;###mh-autoload
364 (defun mh-notate-seq (seq notation offset) 379 (defun mh-notate-seq (seq notation offset)
365 "Mark the scan listing. 380 "Mark the scan listing.
366 All messages in SEQ are marked with NOTATION at OFFSET from the beginning of 381 All messages in SEQ are marked with NOTATION at OFFSET from the beginning of
367 the line." 382 the line."
368 (mh-map-to-seq-msgs 'mh-notate seq notation offset)) 383 (mh-map-to-seq-msgs 'mh-notate seq notation offset))
369 384
385 ;;;###mh-autoload
370 (defun mh-add-to-sequence (seq msgs) 386 (defun mh-add-to-sequence (seq msgs)
371 "The sequence SEQ is augmented with the messages in MSGS." 387 "The sequence SEQ is augmented with the messages in MSGS."
372 ;; Add to a SEQUENCE each message the list of MSGS. 388 ;; Add to a SEQUENCE each message the list of MSGS.
373 (if (not (mh-folder-name-p seq)) 389 (if (not (mh-folder-name-p seq))
374 (if msgs 390 (if msgs
375 (apply 'mh-exec-cmd "mark" mh-current-folder "-add" 391 (apply 'mh-exec-cmd "mark" mh-current-folder "-add"
376 "-sequence" (symbol-name seq) 392 "-sequence" (symbol-name seq)
377 (mh-coalesce-msg-list msgs))))) 393 (mh-coalesce-msg-list msgs)))))
378 394
379 ;; This has a tricky bug. mh-map-to-seq-msgs uses mh-goto-msg, which assumes 395 ;; This has a tricky bug. mh-map-to-seq-msgs uses mh-goto-msg, which assumes
380 ;; that the folder buffer is sorted. However in this case that assumption 396 ;; that the folder buffer is sorted. However in this case that assumption
381 ;; doesn't hold. So we will do this the dumb way. 397 ;; doesn't hold. So we will do this the dumb way.
382 ;(defun mh-copy-seq-to-point (seq location) 398 ;(defun mh-copy-seq-to-point (seq location)
395 (coalesced-msgs (mh-coalesce-msg-list msgs))) 411 (coalesced-msgs (mh-coalesce-msg-list msgs)))
396 (goto-char (point-max)) 412 (goto-char (point-max))
397 (save-restriction 413 (save-restriction
398 (narrow-to-region (point) (point)) 414 (narrow-to-region (point) (point))
399 (mh-regenerate-headers coalesced-msgs t) 415 (mh-regenerate-headers coalesced-msgs t)
400 (when (memq 'unthread mh-view-ops) 416 (cond ((memq 'unthread mh-view-ops)
401 ;; Populate restricted scan-line map 417 ;; Populate restricted scan-line map
402 (goto-char (point-min)) 418 (goto-char (point-min))
403 (while (not (eobp)) 419 (while (not (eobp))
404 (setf (gethash (mh-get-msg-num nil) mh-thread-scan-line-map) 420 (let ((msg (mh-get-msg-num nil)))
405 (mh-thread-parse-scan-line)) 421 (when (numberp msg)
406 (forward-line)) 422 (setf (gethash msg mh-thread-scan-line-map)
407 ;; Remove scan lines and read results from pre-computed thread tree 423 (mh-thread-parse-scan-line))))
408 (delete-region (point-min) (point-max)) 424 (forward-line))
409 (let ((thread-tree (mh-thread-generate mh-current-folder ())) 425 ;; Remove scan lines and read results from pre-computed tree
410 (mh-thread-body-width 426 (delete-region (point-min) (point-max))
411 (- (window-width) mh-cmd-note 427 (let ((thread-tree (mh-thread-generate mh-current-folder ()))
412 (1- mh-scan-field-subject-start-offset)))) 428 (mh-thread-body-width
413 (mh-thread-generate-scan-lines thread-tree -2))))))) 429 (- (window-width) mh-cmd-note
430 (1- mh-scan-field-subject-start-offset)))
431 (mh-thread-last-ancestor nil))
432 (mh-thread-generate-scan-lines thread-tree -2)))
433 (mh-index-data
434 (mh-index-insert-folder-headers)))))))
414 435
415 (defun mh-copy-line-to-point (msg location) 436 (defun mh-copy-line-to-point (msg location)
416 "Copy current message line to a specific location. 437 "Copy current message line to a specific location.
417 The argument MSG is not used. The message in the current line is copied to 438 The argument MSG is not used. The message in the current line is copied to
418 LOCATION." 439 LOCATION."
419 ;; msg is not used? 440 ;; msg is not used?
420 ;; Copy the current line to the LOCATION in the current buffer. 441 ;; Copy the current line to the LOCATION in the current buffer.
421 (beginning-of-line) 442 (beginning-of-line)
422 (save-excursion 443 (save-excursion
423 (let ((beginning-of-line (point)) 444 (let ((beginning-of-line (point))
424 end) 445 end)
425 (forward-line 1) 446 (forward-line 1)
426 (setq end (point)) 447 (setq end (point))
427 (goto-char location) 448 (goto-char location)
428 (insert-buffer-substring (current-buffer) beginning-of-line end)))) 449 (insert-buffer-substring (current-buffer) beginning-of-line end))))
429 450
430 (defun mh-region-to-sequence (begin end) 451 ;;;###mh-autoload
431 "Define sequence 'region as the messages between point and mark. 452 (defun mh-region-to-msg-list (begin end)
432 When called programmatically, use arguments BEGIN and END to define region." 453 "Return a list of messages within the region between BEGIN and END."
433 (interactive "r")
434 (mh-delete-seq-locally 'region)
435 (save-excursion 454 (save-excursion
436 ;; If end is end of buffer back up one position 455 ;; If end is end of buffer back up one position
437 (setq end (if (equal end (point-max)) (1- end) end)) 456 (setq end (if (equal end (point-max)) (1- end) end))
438 (goto-char begin) 457 (goto-char begin)
439 (while (<= (point) end) 458 (let ((result ()))
440 (mh-add-msgs-to-seq (mh-get-msg-num t) 'region t) 459 (while (<= (point) end)
441 (forward-line 1)))) 460 (let ((index (mh-get-msg-num nil)))
461 (when (numberp index) (push index result)))
462 (forward-line 1))
463 result)))
442 464
443 465
444 466
445 ;;; Commands to handle new 'subject sequence. 467 ;;; Commands to handle new 'subject sequence.
446 ;;; Or "Poor man's threading" by psg. 468 ;;; Or "Poor man's threading" by psg.
491 (setq sorted-list (cdr sorted-list))) 513 (setq sorted-list (cdr sorted-list)))
492 (safe-length list))) 514 (safe-length list)))
493 (t 515 (t
494 0)))))) 516 0))))))
495 517
518 ;;;###mh-autoload
496 (defun mh-narrow-to-subject () 519 (defun mh-narrow-to-subject ()
497 "Narrow to a sequence containing all following messages with same subject." 520 "Narrow to a sequence containing all following messages with same subject."
498 (interactive) 521 (interactive)
499 (let ((num (mh-get-msg-num nil)) 522 (let ((num (mh-get-msg-num nil))
500 (count (mh-subject-to-sequence t))) 523 (count (mh-subject-to-sequence t)))
508 (message "Found %d messages for subject sequence." count) 531 (message "Found %d messages for subject sequence." count)
509 (mh-narrow-to-seq 'subject) 532 (mh-narrow-to-seq 'subject)
510 (if (numberp num) 533 (if (numberp num)
511 (mh-goto-msg num t t)))))) 534 (mh-goto-msg num t t))))))
512 535
536 ;;;###mh-autoload
513 (defun mh-delete-subject () 537 (defun mh-delete-subject ()
514 "Mark all following messages with same subject to be deleted. 538 "Mark all following messages with same subject to be deleted.
515 This puts the messages in a sequence named subject. You can undo the last 539 This puts the messages in a sequence named subject. You can undo the last
516 deletion marks using `mh-undo' with a prefix argument and then specifying the 540 deletion marks using `mh-undo' with a prefix argument and then specifying the
517 subject sequence." 541 subject sequence."
525 (mh-delete-msg (mh-get-msg-num t))) 549 (mh-delete-msg (mh-get-msg-num t)))
526 (t ; We have a subject sequence. 550 (t ; We have a subject sequence.
527 (message "Marked %d messages for deletion" count) 551 (message "Marked %d messages for deletion" count)
528 (mh-delete-msg 'subject))))) 552 (mh-delete-msg 'subject)))))
529 553
554 ;;;###mh-autoload
555 (defun mh-delete-subject-or-thread ()
556 "Mark messages for deletion intelligently.
557 If the folder is threaded then `mh-thread-delete' is used to mark the current
558 message and all its descendants for deletion. Otherwise `mh-delete-subject' is
559 used to mark the current message and all messages following it with the same
560 subject for deletion."
561 (interactive)
562 (if (memq 'unthread mh-view-ops)
563 (mh-thread-delete)
564 (mh-delete-subject)))
565
530 ;;; Message threading: 566 ;;; Message threading:
531 567
532 (defun mh-thread-initialize () 568 (defun mh-thread-initialize ()
533 "Make hash tables, otherwise clear them." 569 "Make hash tables, otherwise clear them."
534 (cond 570 (cond
535 (mh-thread-id-hash 571 (mh-thread-id-hash
536 (clrhash mh-thread-id-hash) 572 (clrhash mh-thread-id-hash)
537 (clrhash mh-thread-subject-hash) 573 (clrhash mh-thread-subject-hash)
538 (clrhash mh-thread-id-table) 574 (clrhash mh-thread-id-table)
539 (clrhash mh-thread-id-index-map) 575 (clrhash mh-thread-id-index-map)
540 (clrhash mh-thread-index-id-map) 576 (clrhash mh-thread-index-id-map)
541 (clrhash mh-thread-scan-line-map) 577 (clrhash mh-thread-scan-line-map)
542 (clrhash mh-thread-subject-container-hash) 578 (clrhash mh-thread-subject-container-hash)
543 (clrhash mh-thread-duplicates) 579 (clrhash mh-thread-duplicates)
544 (setq mh-thread-history ())) 580 (setq mh-thread-history ()))
545 (t (setq mh-thread-id-hash (make-hash-table :test #'equal)) 581 (t (setq mh-thread-id-hash (make-hash-table :test #'equal))
546 (setq mh-thread-subject-hash (make-hash-table :test #'equal)) 582 (setq mh-thread-subject-hash (make-hash-table :test #'equal))
547 (setq mh-thread-id-table (make-hash-table :test #'eq)) 583 (setq mh-thread-id-table (make-hash-table :test #'eq))
548 (setq mh-thread-id-index-map (make-hash-table :test #'eq)) 584 (setq mh-thread-id-index-map (make-hash-table :test #'eq))
549 (setq mh-thread-index-id-map (make-hash-table :test #'eql)) 585 (setq mh-thread-index-id-map (make-hash-table :test #'eql))
550 (setq mh-thread-scan-line-map (make-hash-table :test #'eql)) 586 (setq mh-thread-scan-line-map (make-hash-table :test #'eql))
551 (setq mh-thread-subject-container-hash (make-hash-table :test #'eq)) 587 (setq mh-thread-subject-container-hash (make-hash-table :test #'eq))
552 (setq mh-thread-duplicates (make-hash-table :test #'eq)) 588 (setq mh-thread-duplicates (make-hash-table :test #'eq))
553 (setq mh-thread-history ())))) 589 (setq mh-thread-history ()))))
554 590
555 (defsubst mh-thread-id-container (id) 591 (defsubst mh-thread-id-container (id)
556 "Given ID, return the corresponding container in `mh-thread-id-table'. 592 "Given ID, return the corresponding container in `mh-thread-id-table'.
557 If no container exists then a suitable container is created and the id-table 593 If no container exists then a suitable container is created and the id-table
558 is updated." 594 is updated."
568 (let* ((child-container (if (mh-thread-container-p child) 604 (let* ((child-container (if (mh-thread-container-p child)
569 child (mh-thread-id-container child))) 605 child (mh-thread-id-container child)))
570 (parent-container (mh-container-parent child-container))) 606 (parent-container (mh-container-parent child-container)))
571 (when parent-container 607 (when parent-container
572 (setf (mh-container-children parent-container) 608 (setf (mh-container-children parent-container)
573 (remove* child-container (mh-container-children parent-container) 609 (loop for elem in (mh-container-children parent-container)
574 :test #'eq)) 610 unless (eq child-container elem) collect elem))
575 (setf (mh-container-parent child-container) nil)))) 611 (setf (mh-container-parent child-container) nil))))
576 612
577 (defsubst mh-thread-add-link (parent child &optional at-end-p) 613 (defsubst mh-thread-add-link (parent child &optional at-end-p)
578 "Add links so that PARENT becomes a parent of CHILD. 614 "Add links so that PARENT becomes a parent of CHILD.
579 Doesn't make any changes if CHILD is already an ancestor of PARENT. If 615 Doesn't make any changes if CHILD is already an ancestor of PARENT. If
709 (let ((node (cadr action))) 745 (let ((node (cadr action)))
710 (mh-thread-remove-parent-link node) 746 (mh-thread-remove-parent-link node)
711 (setf (mh-container-real-child-p node) t))))))) 747 (setf (mh-container-real-child-p node) t)))))))
712 748
713 (defun mh-thread-prune-containers (roots) 749 (defun mh-thread-prune-containers (roots)
714 "Prune empty containers in the containers ROOTS." 750 "Prune empty containers in the containers ROOTS."
715 (let ((dfs-ordered-nodes ()) 751 (let ((dfs-ordered-nodes ())
716 (work-list roots)) 752 (work-list roots))
717 (while work-list 753 (while work-list
718 (let ((node (pop work-list))) 754 (let ((node (pop work-list)))
719 (dolist (child (mh-container-children node)) 755 (dolist (child (mh-container-children node))
802 (defsubst mh-thread-process-in-reply-to (reply-to-header) 838 (defsubst mh-thread-process-in-reply-to (reply-to-header)
803 "Extract message id's from REPLY-TO-HEADER. 839 "Extract message id's from REPLY-TO-HEADER.
804 Ideally this should have some regexp which will try to guess if a string 840 Ideally this should have some regexp which will try to guess if a string
805 between < and > is a message id and not an email address. For now it will 841 between < and > is a message id and not an email address. For now it will
806 take the last string inside angles." 842 take the last string inside angles."
807 (let ((end (search ">" reply-to-header :from-end t))) 843 (let ((end (mh-search-from-end ?> reply-to-header)))
808 (when (numberp end) 844 (when (numberp end)
809 (let ((begin (search "<" reply-to-header :from-end t :end2 end))) 845 (let ((begin (mh-search-from-end ?< (substring reply-to-header 0 end))))
810 (when (numberp begin) 846 (when (numberp begin)
811 (list (substring reply-to-header begin (1+ end)))))))) 847 (list (substring reply-to-header begin (1+ end))))))))
812 848
813 (defun mh-thread-set-tables (folder) 849 (defun mh-thread-set-tables (folder)
814 "Use the tables of FOLDER in current buffer." 850 "Use the tables of FOLDER in current buffer."
815 (flet ((mh-get-table (symbol) 851 (flet ((mh-get-table (symbol)
816 (save-excursion (set-buffer folder) (symbol-value symbol)))) 852 (save-excursion
853 (set-buffer folder)
854 (symbol-value symbol))))
817 (setq mh-thread-id-hash (mh-get-table 'mh-thread-id-hash)) 855 (setq mh-thread-id-hash (mh-get-table 'mh-thread-id-hash))
818 (setq mh-thread-subject-hash (mh-get-table 'mh-thread-subject-hash)) 856 (setq mh-thread-subject-hash (mh-get-table 'mh-thread-subject-hash))
819 (setq mh-thread-id-table (mh-get-table 'mh-thread-id-table)) 857 (setq mh-thread-id-table (mh-get-table 'mh-thread-id-table))
820 (setq mh-thread-id-index-map (mh-get-table 'mh-thread-id-index-map)) 858 (setq mh-thread-id-index-map (mh-get-table 'mh-thread-id-index-map))
821 (setq mh-thread-index-id-map (mh-get-table 'mh-thread-index-id-map)) 859 (setq mh-thread-index-id-map (mh-get-table 'mh-thread-index-id-map))
849 (when msg-list 887 (when msg-list
850 (apply 888 (apply
851 #'call-process (expand-file-name mh-scan-prog mh-progs) nil '(t nil) nil 889 #'call-process (expand-file-name mh-scan-prog mh-progs) nil '(t nil) nil
852 "-width" "10000" "-format" 890 "-width" "10000" "-format"
853 "%(msg)\n%{message-id}\n%{references}\n%{in-reply-to}\n%{subject}\n" 891 "%(msg)\n%{message-id}\n%{references}\n%{in-reply-to}\n%{subject}\n"
854 (mapcar #'(lambda (x) (format "%s" x)) msg-list))) 892 folder (mapcar #'(lambda (x) (format "%s" x)) msg-list)))
855 (goto-char (point-min)) 893 (goto-char (point-min))
856 (let ((roots ()) 894 (let ((roots ())
857 (case-fold-search t)) 895 (case-fold-search t))
858 (block nil 896 (block nil
859 (while (not (eobp)) 897 (while (not (eobp))
860 (block process-message 898 (block process-message
861 (let* ((index-line 899 (let* ((index-line
862 (prog1 (buffer-substring (point) (line-end-position)) 900 (prog1 (buffer-substring (point) (line-end-position))
863 (forward-line))) 901 (forward-line)))
864 (index (car (read-from-string index-line))) 902 (index (car (read-from-string index-line)))
865 (id (prog1 (buffer-substring (point) (line-end-position)) 903 (id (prog1 (buffer-substring (point) (line-end-position))
866 (forward-line))) 904 (forward-line)))
867 (refs (prog1 (buffer-substring (point) (line-end-position)) 905 (refs (prog1 (buffer-substring (point) (line-end-position))
868 (forward-line))) 906 (forward-line)))
899 (prog1 (setq roots (mh-thread-group-by-subject roots)) 937 (prog1 (setq roots (mh-thread-group-by-subject roots))
900 (let ((history mh-thread-history)) 938 (let ((history mh-thread-history))
901 (set-buffer folder) 939 (set-buffer folder)
902 (setq mh-thread-history history)))))) 940 (setq mh-thread-history history))))))
903 941
942 ;;;###mh-autoload
904 (defun mh-thread-inc (folder start-point) 943 (defun mh-thread-inc (folder start-point)
905 "Update thread tree for FOLDER. 944 "Update thread tree for FOLDER.
906 All messages after START-POINT are added to the thread tree." 945 All messages after START-POINT are added to the thread tree."
907 (mh-thread-rewind-pruning) 946 (mh-thread-rewind-pruning)
908 (goto-char start-point) 947 (goto-char start-point)
909 (let ((msg-list ())) 948 (let ((msg-list ()))
910 (while (not (eobp)) 949 (while (not (eobp))
911 (let ((index (mh-get-msg-num nil))) 950 (let ((index (mh-get-msg-num nil)))
912 (push index msg-list) 951 (when (numberp index)
913 (setf (gethash index mh-thread-scan-line-map) 952 (push index msg-list)
914 (mh-thread-parse-scan-line)) 953 (setf (gethash index mh-thread-scan-line-map)
954 (mh-thread-parse-scan-line)))
915 (forward-line))) 955 (forward-line)))
916 (let ((thread-tree (mh-thread-generate folder msg-list)) 956 (let ((thread-tree (mh-thread-generate folder msg-list))
917 (buffer-read-only nil) 957 (buffer-read-only nil)
918 (old-buffer-modified-flag (buffer-modified-p))) 958 (old-buffer-modified-flag (buffer-modified-p)))
919 (delete-region (point-min) (point-max)) 959 (delete-region (point-min) (point-max))
920 (let ((mh-thread-body-width (- (window-width) mh-cmd-note 960 (let ((mh-thread-body-width (- (window-width) mh-cmd-note
921 (1- mh-scan-field-subject-start-offset)))) 961 (1- mh-scan-field-subject-start-offset)))
962 (mh-thread-last-ancestor nil))
922 (mh-thread-generate-scan-lines thread-tree -2)) 963 (mh-thread-generate-scan-lines thread-tree -2))
923 (mh-notate-user-sequences) 964 (mh-notate-user-sequences)
924 (mh-notate-deleted-and-refiled) 965 (mh-notate-deleted-and-refiled)
925 (mh-notate-seq 'cur mh-note-cur mh-cmd-note) 966 (mh-notate-seq 'cur mh-note-cur mh-cmd-note)
926 (set-buffer-modified-p old-buffer-modified-flag)))) 967 (set-buffer-modified-p old-buffer-modified-flag))))
968
969 (defvar mh-thread-last-ancestor)
927 970
928 (defun mh-thread-generate-scan-lines (tree level) 971 (defun mh-thread-generate-scan-lines (tree level)
929 "Generate scan lines. 972 "Generate scan lines.
930 TREE is the hierarchical tree of messages, SCAN-LINE-MAP maps message indices 973 TREE is the hierarchical tree of messages, SCAN-LINE-MAP maps message indices
931 to the corresponding scan lines and LEVEL used to determine indentation of 974 to the corresponding scan lines and LEVEL used to determine indentation of
936 (id (mh-message-id message)) 979 (id (mh-message-id message))
937 (index (gethash id mh-thread-id-index-map)) 980 (index (gethash id mh-thread-id-index-map))
938 (duplicates (gethash id mh-thread-duplicates)) 981 (duplicates (gethash id mh-thread-duplicates))
939 (new-level (+ level 2)) 982 (new-level (+ level 2))
940 (dupl-flag t) 983 (dupl-flag t)
984 (force-angle-flag nil)
941 (increment-level-flag nil)) 985 (increment-level-flag nil))
942 (dolist (scan-line (mapcar (lambda (x) 986 (dolist (scan-line (mapcar (lambda (x)
943 (gethash x mh-thread-scan-line-map)) 987 (gethash x mh-thread-scan-line-map))
944 (reverse (cons index duplicates)))) 988 (reverse (cons index duplicates))))
945 (when scan-line 989 (when scan-line
990 (when (and dupl-flag (equal level 0)
991 (mh-thread-ancestor-p mh-thread-last-ancestor tree))
992 (setq level (+ level 2)
993 new-level (+ new-level 2)
994 force-angle-flag t))
995 (when (equal level 0)
996 (setq mh-thread-last-ancestor tree)
997 (while (mh-container-parent mh-thread-last-ancestor)
998 (setq mh-thread-last-ancestor
999 (mh-container-parent mh-thread-last-ancestor))))
946 (insert (car scan-line) 1000 (insert (car scan-line)
947 (format (format "%%%ss" 1001 (format (format "%%%ss"
948 (if dupl-flag level new-level)) "") 1002 (if dupl-flag level new-level)) "")
949 (if (and (mh-container-real-child-p tree) dupl-flag) 1003 (if (and (mh-container-real-child-p tree) dupl-flag
1004 (not force-angle-flag))
950 "[" "<") 1005 "[" "<")
951 (cadr scan-line) 1006 (cadr scan-line)
952 (if (and (mh-container-real-child-p tree) dupl-flag) 1007 (if (and (mh-container-real-child-p tree) dupl-flag
1008 (not force-angle-flag))
953 "]" ">") 1009 "]" ">")
954 (truncate-string-to-width 1010 (truncate-string-to-width
955 (caddr scan-line) (- mh-thread-body-width 1011 (caddr scan-line) (- mh-thread-body-width
956 (if dupl-flag level new-level))) 1012 (if dupl-flag level new-level)))
957 "\n") 1013 "\n")
982 (+ mh-cmd-note mh-scan-field-from-start-offset) 1038 (+ mh-cmd-note mh-scan-field-from-start-offset)
983 (+ mh-cmd-note mh-scan-field-from-end-offset -2)) 1039 (+ mh-cmd-note mh-scan-field-from-end-offset -2))
984 (substring string (+ mh-cmd-note mh-scan-field-from-end-offset)) 1040 (substring string (+ mh-cmd-note mh-scan-field-from-end-offset))
985 string))) 1041 string)))
986 1042
1043 ;;;###mh-autoload
987 (defun mh-thread-add-spaces (count) 1044 (defun mh-thread-add-spaces (count)
988 "Add COUNT spaces to each scan line in `mh-thread-scan-line-map'." 1045 "Add COUNT spaces to each scan line in `mh-thread-scan-line-map'."
989 (let ((spaces (format (format "%%%ss" count) ""))) 1046 (let ((spaces (format (format "%%%ss" count) "")))
990 (while (not (eobp)) 1047 (while (not (eobp))
991 (let* ((msg-num (mh-get-msg-num nil)) 1048 (let* ((msg-num (mh-get-msg-num nil))
992 (old-line (nth 3 (gethash msg-num mh-thread-scan-line-map)))) 1049 (old-line (nth 3 (gethash msg-num mh-thread-scan-line-map))))
993 (setf (gethash msg-num mh-thread-scan-line-map) 1050 (when (numberp msg-num)
994 (mh-thread-parse-scan-line (format "%s%s" spaces old-line)))) 1051 (setf (gethash msg-num mh-thread-scan-line-map)
1052 (mh-thread-parse-scan-line (format "%s%s" spaces old-line)))))
995 (forward-line 1)))) 1053 (forward-line 1))))
996 1054
997 (defun mh-thread-folder () 1055 (defun mh-thread-folder ()
998 "Generate thread view of folder." 1056 "Generate thread view of folder."
999 (message "Threading %s..." (buffer-name)) 1057 (message "Threading %s..." (buffer-name))
1000 (mh-thread-initialize) 1058 (mh-thread-initialize)
1001 (goto-char (point-min)) 1059 (goto-char (point-min))
1002 (while (not (eobp)) 1060 (while (not (eobp))
1003 (setf (gethash (mh-get-msg-num nil) mh-thread-scan-line-map) 1061 (let ((index (mh-get-msg-num nil)))
1004 (mh-thread-parse-scan-line)) 1062 (when (numberp index)
1063 (setf (gethash index mh-thread-scan-line-map)
1064 (mh-thread-parse-scan-line))))
1005 (forward-line)) 1065 (forward-line))
1006 (let* ((range (format "%s-%s" mh-first-msg-num mh-last-msg-num)) 1066 (let* ((range (format "%s-%s" mh-first-msg-num mh-last-msg-num))
1007 (thread-tree (mh-thread-generate (buffer-name) (list range))) 1067 (thread-tree (mh-thread-generate (buffer-name) (list range))))
1008 (buffer-read-only nil)
1009 (old-buffer-modified-p (buffer-modified-p)))
1010 (delete-region (point-min) (point-max)) 1068 (delete-region (point-min) (point-max))
1011 (let ((mh-thread-body-width (- (window-width) mh-cmd-note 1069 (let ((mh-thread-body-width (- (window-width) mh-cmd-note
1012 (1- mh-scan-field-subject-start-offset)))) 1070 (1- mh-scan-field-subject-start-offset)))
1071 (mh-thread-last-ancestor nil))
1013 (mh-thread-generate-scan-lines thread-tree -2)) 1072 (mh-thread-generate-scan-lines thread-tree -2))
1014 (mh-notate-user-sequences) 1073 (mh-notate-user-sequences)
1015 (mh-notate-deleted-and-refiled) 1074 (mh-notate-deleted-and-refiled)
1016 (mh-notate-seq 'cur mh-note-cur mh-cmd-note) 1075 (mh-notate-seq 'cur mh-note-cur mh-cmd-note)
1017 (set-buffer-modified-p old-buffer-modified-p)
1018 (message "Threading %s...done" (buffer-name)))) 1076 (message "Threading %s...done" (buffer-name))))
1019 1077
1078 ;;;###mh-autoload
1020 (defun mh-toggle-threads () 1079 (defun mh-toggle-threads ()
1021 "Toggle threaded view of folder. 1080 "Toggle threaded view of folder.
1022 The conversion of normal view to threaded view is exact, that is the same 1081 The conversion of normal view to threaded view is exact, that is the same
1023 messages are displayed in the folder buffer before and after threading. However 1082 messages are displayed in the folder buffer before and after threading. However
1024 the conversion from threaded view to normal view is inexact. So more messages 1083 the conversion from threaded view to normal view is inexact. So more messages
1025 than were originally present may be shown as a result." 1084 than were originally present may be shown as a result."
1026 (interactive) 1085 (interactive)
1027 (let ((msg-at-point (mh-get-msg-num nil))) 1086 (let ((msg-at-point (mh-get-msg-num nil))
1087 (old-buffer-modified-flag (buffer-modified-p))
1088 (buffer-read-only nil))
1028 (cond ((and (memq 'unthread mh-view-ops) mh-narrowed-to-seq) 1089 (cond ((and (memq 'unthread mh-view-ops) mh-narrowed-to-seq)
1029 (unless (mh-valid-view-change-operation-p 'unthread) 1090 (unless (mh-valid-view-change-operation-p 'unthread)
1030 (error "Can't unthread folder")) 1091 (error "Can't unthread folder"))
1031 (mh-scan-folder mh-current-folder 1092 (mh-scan-folder mh-current-folder
1032 (format "%s" mh-narrowed-to-seq) 1093 (format "%s" mh-narrowed-to-seq)
1033 t)) 1094 t)
1095 (when mh-index-data
1096 (mh-index-insert-folder-headers)))
1034 ((memq 'unthread mh-view-ops) 1097 ((memq 'unthread mh-view-ops)
1035 (unless (mh-valid-view-change-operation-p 'unthread) 1098 (unless (mh-valid-view-change-operation-p 'unthread)
1036 (error "Can't unthread folder")) 1099 (error "Can't unthread folder"))
1037 (mh-scan-folder mh-current-folder 1100 (mh-scan-folder mh-current-folder
1038 (format "%s-%s" mh-first-msg-num mh-last-msg-num) 1101 (format "%s-%s" mh-first-msg-num mh-last-msg-num)
1039 t)) 1102 t)
1103 (when mh-index-data
1104 (mh-index-insert-folder-headers)))
1040 (t (mh-thread-folder) 1105 (t (mh-thread-folder)
1041 (push 'unthread mh-view-ops))) 1106 (push 'unthread mh-view-ops)))
1042 (when msg-at-point (mh-goto-msg msg-at-point t t)) 1107 (when msg-at-point (mh-goto-msg msg-at-point t t))
1108 (set-buffer-modified-p old-buffer-modified-flag)
1043 (mh-recenter nil))) 1109 (mh-recenter nil)))
1044 1110
1111 ;;;###mh-autoload
1045 (defun mh-thread-forget-message (index) 1112 (defun mh-thread-forget-message (index)
1046 "Forget the message INDEX from the threading tables." 1113 "Forget the message INDEX from the threading tables."
1047 (let* ((id (gethash index mh-thread-index-id-map)) 1114 (let* ((id (gethash index mh-thread-index-id-map))
1048 (id-index (gethash id mh-thread-id-index-map)) 1115 (id-index (gethash id mh-thread-id-index-map))
1049 (duplicates (gethash id mh-thread-duplicates))) 1116 (duplicates (gethash id mh-thread-duplicates)))
1056 (setf (gethash id mh-thread-duplicates) (cdr duplicates))) 1123 (setf (gethash id mh-thread-duplicates) (cdr duplicates)))
1057 (t 1124 (t
1058 (setf (gethash id mh-thread-duplicates) 1125 (setf (gethash id mh-thread-duplicates)
1059 (remove index duplicates)))))) 1126 (remove index duplicates))))))
1060 1127
1128
1129
1130 ;;; Operations on threads
1131
1132 (defun mh-thread-current-indentation-level ()
1133 "Find the number of spaces by which current message is indented."
1134 (save-excursion
1135 (let ((address-start-offset (+ mh-cmd-note mh-scan-date-flag-width
1136 mh-scan-date-width 1))
1137 (level 0))
1138 (beginning-of-line)
1139 (forward-char address-start-offset)
1140 (while (char-equal (char-after) ? )
1141 (incf level)
1142 (forward-char))
1143 level)))
1144
1145 ;;;###mh-autoload
1146 (defun mh-thread-next-sibling (&optional previous-flag)
1147 "Jump to next sibling.
1148 With non-nil optional argument PREVIOUS-FLAG jump to the previous sibling."
1149 (interactive)
1150 (cond ((not (memq 'unthread mh-view-ops))
1151 (error "Folder isn't threaded"))
1152 ((eobp)
1153 (error "No message at point")))
1154 (beginning-of-line)
1155 (let ((point (point))
1156 (done nil)
1157 (my-level (mh-thread-current-indentation-level)))
1158 (while (and (not done)
1159 (equal (forward-line (if previous-flag -1 1)) 0)
1160 (not (eobp)))
1161 (let ((level (mh-thread-current-indentation-level)))
1162 (cond ((equal level my-level)
1163 (setq done 'success))
1164 ((< level my-level)
1165 (message "No %s sibling" (if previous-flag "previous" "next"))
1166 (setq done 'failure)))))
1167 (cond ((eq done 'success) (mh-maybe-show))
1168 ((eq done 'failure) (goto-char point))
1169 (t (message "No %s sibling" (if previous-flag "previous" "next"))
1170 (goto-char point)))))
1171
1172 ;;;###mh-autoload
1173 (defun mh-thread-previous-sibling ()
1174 "Jump to previous sibling."
1175 (interactive)
1176 (mh-thread-next-sibling t))
1177
1178 (defun mh-thread-immediate-ancestor ()
1179 "Jump to immediate ancestor in thread tree."
1180 (beginning-of-line)
1181 (let ((point (point))
1182 (ancestor-level (- (mh-thread-current-indentation-level) 2))
1183 (done nil))
1184 (if (< ancestor-level 0)
1185 nil
1186 (while (and (not done) (equal (forward-line -1) 0))
1187 (when (equal ancestor-level (mh-thread-current-indentation-level))
1188 (setq done t)))
1189 (unless done
1190 (goto-char point))
1191 done)))
1192
1193 ;;;###mh-autoload
1194 (defun mh-thread-ancestor (&optional thread-root-flag)
1195 "Jump to the ancestor of current message.
1196 If optional argument THREAD-ROOT-FLAG is non-nil then jump to the root of the
1197 thread tree the message belongs to."
1198 (interactive "P")
1199 (beginning-of-line)
1200 (cond ((not (memq 'unthread mh-view-ops))
1201 (error "Folder isn't threaded"))
1202 ((eobp)
1203 (error "No message at point")))
1204 (let ((current-level (mh-thread-current-indentation-level)))
1205 (cond (thread-root-flag
1206 (while (mh-thread-immediate-ancestor))
1207 (mh-maybe-show))
1208 ((equal current-level 1)
1209 (message "Message has no ancestor"))
1210 (t (mh-thread-immediate-ancestor)
1211 (mh-maybe-show)))))
1212
1213 (defun mh-thread-find-children ()
1214 "Return a region containing the current message and its children.
1215 The result is returned as a list of two elements. The first is the point at the
1216 start of the region and the second is the point at the end."
1217 (beginning-of-line)
1218 (if (eobp)
1219 nil
1220 (let ((address-start-offset (+ mh-cmd-note mh-scan-date-flag-width
1221 mh-scan-date-width 1))
1222 (level (mh-thread-current-indentation-level))
1223 spaces begin)
1224 (setq begin (point))
1225 (setq spaces (format (format "%%%ss" (1+ level)) ""))
1226 (forward-line)
1227 (block nil
1228 (while (not (eobp))
1229 (forward-char address-start-offset)
1230 (unless (equal (string-match spaces (buffer-substring-no-properties
1231 (point) (line-end-position)))
1232 0)
1233 (beginning-of-line)
1234 (backward-char)
1235 (return))
1236 (forward-line)))
1237 (list begin (point)))))
1238
1239 ;;;###mh-autoload
1240 (defun mh-thread-delete ()
1241 "Mark current message and all its children for subsequent deletion."
1242 (interactive)
1243 (cond ((not (memq 'unthread mh-view-ops))
1244 (error "Folder isn't threaded"))
1245 ((eobp)
1246 (error "No message at point"))
1247 (t (mh-delete-msg
1248 (apply #'mh-region-to-msg-list (mh-thread-find-children))))))
1249
1250 ;; This doesn't handle mh-default-folder-for-message-function. We should
1251 ;; refactor that code so that we don't copy it.
1252 ;;;###mh-autoload
1253 (defun mh-thread-refile (folder)
1254 "Mark current message and all its children for refiling to FOLDER."
1255 (interactive (list
1256 (intern (mh-prompt-for-folder
1257 "Destination"
1258 (cond ((eq 'refile (car mh-last-destination-folder))
1259 (symbol-name (cdr mh-last-destination-folder)))
1260 (t ""))
1261 t))))
1262 (cond ((not (memq 'unthread mh-view-ops))
1263 (error "Folder isn't threaded"))
1264 ((eobp)
1265 (error "No message at point"))
1266 (t (mh-refile-msg
1267 (apply #'mh-region-to-msg-list (mh-thread-find-children))
1268 folder))))
1269
1061 (provide 'mh-seq) 1270 (provide 'mh-seq)
1062 1271
1063 ;;; Local Variables: 1272 ;;; Local Variables:
1273 ;;; indent-tabs-mode: nil
1064 ;;; sentence-end-double-space: nil 1274 ;;; sentence-end-double-space: nil
1065 ;;; End: 1275 ;;; End:
1066 1276
1067 ;;; mh-seq.el ends here 1277 ;;; mh-seq.el ends here