aboutsummaryrefslogtreecommitdiffstats
path: root/lisp/mh-e/mh-seq.el
diff options
context:
space:
mode:
authorBill Wohler2004-07-13 03:06:25 +0000
committerBill Wohler2004-07-13 03:06:25 +0000
commita66894d8b489dfdfafc2058cd181fefbb894fbf0 (patch)
tree39c692b4da2f58c1f9830381b0befa1ec3d56b87 /lisp/mh-e/mh-seq.el
parent0117451de7e30adf240f369f26b7667dbf3788bf (diff)
downloademacs-a66894d8b489dfdfafc2058cd181fefbb894fbf0.tar.gz
emacs-a66894d8b489dfdfafc2058cd181fefbb894fbf0.zip
Upgraded to MH-E version 7.4.4.
See etc/MH-E-NEWS and lisp/mh-e/ChangeLog for details.
Diffstat (limited to 'lisp/mh-e/mh-seq.el')
-rw-r--r--lisp/mh-e/mh-seq.el616
1 files changed, 436 insertions, 180 deletions
diff --git a/lisp/mh-e/mh-seq.el b/lisp/mh-e/mh-seq.el
index e441466a7b4..20950d36c4c 100644
--- a/lisp/mh-e/mh-seq.el
+++ b/lisp/mh-e/mh-seq.el
@@ -1,6 +1,6 @@
1;;; mh-seq.el --- MH-E sequences support 1;;; mh-seq.el --- MH-E sequences support
2 2
3;; Copyright (C) 1993, 1995, 2001, 02, 2003 Free Software Foundation, Inc. 3;; Copyright (C) 1993, 1995, 2001, 02, 03, 2004 Free Software Foundation, Inc.
4 4
5;; Author: Bill Wohler <wohler@newt.com> 5;; Author: Bill Wohler <wohler@newt.com>
6;; Maintainer: Bill Wohler <wohler@newt.com> 6;; Maintainer: Bill Wohler <wohler@newt.com>
@@ -70,7 +70,8 @@
70 70
71;;; Code: 71;;; Code:
72 72
73(require 'cl) 73(require 'mh-utils)
74(mh-require-cl)
74(require 'mh-e) 75(require 'mh-e)
75 76
76;; Shush the byte-compiler 77;; Shush the byte-compiler
@@ -110,7 +111,7 @@
110 "Table to look up message identifier from message index.") 111 "Table to look up message identifier from message index.")
111(defvar mh-thread-scan-line-map nil 112(defvar mh-thread-scan-line-map nil
112 "Map of message index to various parts of the scan line.") 113 "Map of message index to various parts of the scan line.")
113(defvar mh-thread-old-scan-line-map nil 114(defvar mh-thread-scan-line-map-stack nil
114 "Old map of message index to various parts of the scan line. 115 "Old map of message index to various parts of the scan line.
115This is the original map that is stored when the folder is narrowed.") 116This is the original map that is stored when the folder is narrowed.")
116(defvar mh-thread-subject-container-hash nil 117(defvar mh-thread-subject-container-hash nil
@@ -131,7 +132,7 @@ redone to get the new thread tree. This makes incremental threading easier.")
131(make-variable-buffer-local 'mh-thread-id-index-map) 132(make-variable-buffer-local 'mh-thread-id-index-map)
132(make-variable-buffer-local 'mh-thread-index-id-map) 133(make-variable-buffer-local 'mh-thread-index-id-map)
133(make-variable-buffer-local 'mh-thread-scan-line-map) 134(make-variable-buffer-local 'mh-thread-scan-line-map)
134(make-variable-buffer-local 'mh-thread-old-scan-line-map) 135(make-variable-buffer-local 'mh-thread-scan-line-map-stack)
135(make-variable-buffer-local 'mh-thread-subject-container-hash) 136(make-variable-buffer-local 'mh-thread-subject-container-hash)
136(make-variable-buffer-local 'mh-thread-duplicates) 137(make-variable-buffer-local 'mh-thread-duplicates)
137(make-variable-buffer-local 'mh-thread-history) 138(make-variable-buffer-local 'mh-thread-history)
@@ -140,14 +141,19 @@ redone to get the new thread tree. This makes incremental threading easier.")
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 (let ((msg-list (mh-seq-to-msgs sequence))) 144 (let ((msg-list (mh-seq-to-msgs sequence))
145 (internal-flag (mh-internal-seq sequence))
146 (folders-changed (list mh-current-folder)))
147 (mh-iterate-on-range msg sequence
148 (mh-remove-sequence-notation msg internal-flag))
144 (mh-undefine-sequence sequence '("all")) 149 (mh-undefine-sequence sequence '("all"))
145 (mh-delete-seq-locally sequence) 150 (mh-delete-seq-locally sequence)
146 (mh-iterate-on-messages-in-region msg (point-min) (point-max) 151 (when mh-index-data
147 (cond ((and mh-tick-seq (eq sequence mh-tick-seq)) 152 (setq folders-changed
148 (mh-notate-tick msg ())) 153 (append folders-changed
149 ((and (member msg msg-list) (not (mh-seq-containing-msg msg nil))) 154 (mh-index-delete-from-sequence sequence msg-list))))
150 (mh-notate nil ? (1+ mh-cmd-note))))))) 155 (when (and (eq sequence mh-unseen-seq) (mh-speed-flists-active-p))
156 (apply #'mh-speed-flists t folders-changed))))
151 157
152;; Avoid compiler warnings 158;; Avoid compiler warnings
153(defvar view-exit-action) 159(defvar view-exit-action)
@@ -221,16 +227,15 @@ Use \\<mh-folder-mode-map>\\[mh-widen] to undo this command."
221 (interactive (list (mh-read-seq "Narrow to" t))) 227 (interactive (list (mh-read-seq "Narrow to" t)))
222 (with-mh-folder-updating (t) 228 (with-mh-folder-updating (t)
223 (cond ((mh-seq-to-msgs sequence) 229 (cond ((mh-seq-to-msgs sequence)
224 (mh-widen)
225 (mh-remove-all-notation) 230 (mh-remove-all-notation)
226 (let ((eob (point-max)) 231 (let ((eob (point-max))
227 (msg-at-cursor (mh-get-msg-num nil))) 232 (msg-at-cursor (mh-get-msg-num nil)))
228 (setq mh-thread-old-scan-line-map mh-thread-scan-line-map) 233 (push mh-thread-scan-line-map mh-thread-scan-line-map-stack)
229 (setq mh-thread-scan-line-map (make-hash-table :test #'eql)) 234 (setq mh-thread-scan-line-map (make-hash-table :test #'eql))
230 (mh-copy-seq-to-eob sequence) 235 (mh-copy-seq-to-eob sequence)
231 (narrow-to-region eob (point-max)) 236 (push (buffer-substring-no-properties (point-min) eob)
232 (setq mh-narrowed-to-seq sequence) 237 mh-folder-view-stack)
233 (mh-notate-user-sequences) 238 (delete-region (point-min) eob)
234 (mh-notate-deleted-and-refiled) 239 (mh-notate-deleted-and-refiled)
235 (mh-notate-cur) 240 (mh-notate-cur)
236 (when msg-at-cursor (mh-goto-msg msg-at-cursor t t)) 241 (when msg-at-cursor (mh-goto-msg msg-at-cursor t t))
@@ -252,29 +257,31 @@ Use \\<mh-folder-mode-map>\\[mh-widen] to undo this command."
252 (error "No messages in sequence `%s'" (symbol-name sequence)))))) 257 (error "No messages in sequence `%s'" (symbol-name sequence))))))
253 258
254;;;###mh-autoload 259;;;###mh-autoload
255(defun mh-put-msg-in-seq (msg-or-seq sequence) 260(defun mh-put-msg-in-seq (range sequence)
256 "Add MSG-OR-SEQ to SEQUENCE. 261 "Add RANGE to SEQUENCE.
257Default is the displayed message. 262
258If optional prefix argument is provided, then prompt for the message sequence. 263Check the documentation of `mh-interactive-range' to see how RANGE is read in
259If variable `transient-mark-mode' is non-nil and the mark is active, then the 264interactive use."
260selected region is added to the sequence. 265 (interactive (list (mh-interactive-range "Add messages from")
261In a program, MSG-OR-SEQ can be a message number, a list of message numbers, a
262region in a cons cell, or a sequence."
263 (interactive (list (mh-interactive-msg-or-seq "Add messages from")
264 (mh-read-seq-default "Add to" nil))) 266 (mh-read-seq-default "Add to" nil)))
265 (when (and (interactive-p) mh-tick-seq (eq sequence mh-tick-seq)) 267 (unless (mh-valid-seq-p sequence)
266 (error "Use `mh-toggle-tick' to add messages to %s" mh-tick-seq)) 268 (error "Can't put message in invalid sequence `%s'" sequence))
267 (let* ((internal-seq-flag (mh-internal-seq sequence)) 269 (let* ((internal-seq-flag (mh-internal-seq sequence))
268 (note-seq (if internal-seq-flag nil mh-note-seq)) 270 (original-msgs (mh-seq-msgs (mh-find-seq sequence)))
271 (folders (list mh-current-folder))
269 (msg-list ())) 272 (msg-list ()))
270 (mh-iterate-on-msg-or-seq m msg-or-seq 273 (mh-iterate-on-range m range
271 (push m msg-list) 274 (push m msg-list)
272 (mh-notate nil note-seq (1+ mh-cmd-note))) 275 (unless (memq m original-msgs)
276 (mh-add-sequence-notation m internal-seq-flag)))
273 (mh-add-msgs-to-seq msg-list sequence nil t) 277 (mh-add-msgs-to-seq msg-list sequence nil t)
274 (if (not internal-seq-flag) 278 (if (not internal-seq-flag)
275 (setq mh-last-seq-used sequence)) 279 (setq mh-last-seq-used sequence))
280 (when mh-index-data
281 (setq folders
282 (append folders (mh-index-add-to-sequence sequence msg-list))))
276 (when (and (eq sequence mh-unseen-seq) (mh-speed-flists-active-p)) 283 (when (and (eq sequence mh-unseen-seq) (mh-speed-flists-active-p))
277 (mh-speed-flists t mh-current-folder)))) 284 (apply #'mh-speed-flists t folders))))
278 285
279(defun mh-valid-view-change-operation-p (op) 286(defun mh-valid-view-change-operation-p (op)
280 "Check if the view change operation can be performed. 287 "Check if the view change operation can be performed.
@@ -284,33 +291,46 @@ OP is one of 'widen and 'unthread."
284 (t nil))) 291 (t nil)))
285 292
286;;;###mh-autoload 293;;;###mh-autoload
287(defun mh-widen () 294(defun mh-widen (&optional all-flag)
288 "Remove restrictions from current folder, thereby showing all messages." 295 "Remove last restriction from current folder.
289 (interactive) 296If optional prefix argument ALL-FLAG is non-nil, then unwind to the beginning
297of the view stack thereby showing all messages that the buffer originally
298contained."
299 (interactive "P")
290 (let ((msg (mh-get-msg-num nil))) 300 (let ((msg (mh-get-msg-num nil)))
291 (when mh-narrowed-to-seq 301 (when mh-folder-view-stack
292 (cond ((mh-valid-view-change-operation-p 'widen) nil) 302 (cond (all-flag
303 (while (cdr mh-view-ops)
304 (setq mh-view-ops (cdr mh-view-ops)))
305 (when (eq (car mh-view-ops) 'widen)
306 (setq mh-view-ops (cdr mh-view-ops))))
307 ((mh-valid-view-change-operation-p 'widen) nil)
293 ((memq 'widen mh-view-ops) 308 ((memq 'widen mh-view-ops)
294 (while (not (eq (car mh-view-ops) 'widen)) 309 (while (not (eq (car mh-view-ops) 'widen))
295 (setq mh-view-ops (cdr mh-view-ops))) 310 (setq mh-view-ops (cdr mh-view-ops)))
296 (pop mh-view-ops)) 311 (setq mh-view-ops (cdr mh-view-ops)))
297 (t (error "Widening is not applicable"))) 312 (t (error "Widening is not applicable")))
298 (when (memq 'unthread mh-view-ops) 313 ;; If ALL-FLAG is non-nil then rewind stacks
299 (setq mh-thread-scan-line-map mh-thread-old-scan-line-map)) 314 (when all-flag
315 (while (cdr mh-thread-scan-line-map-stack)
316 (setq mh-thread-scan-line-map-stack
317 (cdr mh-thread-scan-line-map-stack)))
318 (while (cdr mh-folder-view-stack)
319 (setq mh-folder-view-stack (cdr mh-folder-view-stack))))
320 (setq mh-thread-scan-line-map (pop mh-thread-scan-line-map-stack))
300 (with-mh-folder-updating (t) 321 (with-mh-folder-updating (t)
301 (delete-region (point-min) (point-max)) 322 (delete-region (point-min) (point-max))
302 (widen) 323 (insert (pop mh-folder-view-stack))
324 (mh-remove-all-notation)
303 (setq mh-mode-line-annotation mh-non-seq-mode-line-annotation) 325 (setq mh-mode-line-annotation mh-non-seq-mode-line-annotation)
304 (mh-make-folder-mode-line)) 326 (mh-make-folder-mode-line))
305 (if msg 327 (if msg
306 (mh-goto-msg msg t t)) 328 (mh-goto-msg msg t t))
307 (setq mh-narrowed-to-seq nil)
308 (setq mh-tick-seq-changed-when-narrowed-flag nil)
309 (mh-notate-deleted-and-refiled) 329 (mh-notate-deleted-and-refiled)
310 (mh-notate-user-sequences) 330 (mh-notate-user-sequences)
311 (mh-notate-cur) 331 (mh-notate-cur)
312 (mh-recenter nil))) 332 (mh-recenter nil)))
313 (when (and (boundp 'tool-bar-mode) tool-bar-mode) 333 (when (and (null mh-folder-view-stack) (boundp 'tool-bar-mode) tool-bar-mode)
314 (set (make-local-variable 'tool-bar-map) mh-folder-tool-bar-map) 334 (set (make-local-variable 'tool-bar-map) mh-folder-tool-bar-map)
315 (when (buffer-live-p (get-buffer mh-show-buffer)) 335 (when (buffer-live-p (get-buffer mh-show-buffer))
316 (save-excursion 336 (save-excursion
@@ -319,6 +339,7 @@ OP is one of 'widen and 'unthread."
319 339
320;; FIXME? We may want to clear all notations and add one for current-message 340;; FIXME? We may want to clear all notations and add one for current-message
321;; and process user sequences. 341;; and process user sequences.
342;;;###mh-autoload
322(defun mh-notate-deleted-and-refiled () 343(defun mh-notate-deleted-and-refiled ()
323 "Notate messages marked for deletion or refiling. 344 "Notate messages marked for deletion or refiling.
324Messages to be deleted are given by `mh-delete-list' while messages to be 345Messages to be deleted are given by `mh-delete-list' while messages to be
@@ -342,13 +363,15 @@ refiled are present in `mh-refile-list'."
342;;; of the form: 363;;; of the form:
343;;; ((seq-name msgs ...) (seq-name msgs ...) ...) 364;;; ((seq-name msgs ...) (seq-name msgs ...) ...)
344 365
366(defvar mh-sequence-history ())
367
368;;;###mh-autoload
345(defun mh-read-seq-default (prompt not-empty) 369(defun mh-read-seq-default (prompt not-empty)
346 "Read and return sequence name with default narrowed or previous sequence. 370 "Read and return sequence name with default narrowed or previous sequence.
347PROMPT is the prompt to use when reading. If NOT-EMPTY is non-nil then a 371PROMPT is the prompt to use when reading. If NOT-EMPTY is non-nil then a
348non-empty sequence is read." 372non-empty sequence is read."
349 (mh-read-seq prompt not-empty 373 (mh-read-seq prompt not-empty
350 (or mh-narrowed-to-seq 374 (or mh-last-seq-used
351 mh-last-seq-used
352 (car (mh-seq-containing-msg (mh-get-msg-num nil) nil))))) 375 (car (mh-seq-containing-msg (mh-get-msg-num nil) nil)))))
353 376
354(defun mh-read-seq (prompt not-empty &optional default) 377(defun mh-read-seq (prompt not-empty &optional default)
@@ -360,7 +383,8 @@ defaults to the first sequence containing the current message."
360 (if default 383 (if default
361 (format "[%s] " default) 384 (format "[%s] " default)
362 "")) 385 ""))
363 (mh-seq-names mh-seq-list))) 386 (mh-seq-names mh-seq-list)
387 nil nil nil 'mh-sequence-history))
364 (seq (cond ((equal input "%") 388 (seq (cond ((equal input "%")
365 (car (mh-seq-containing-msg (mh-get-msg-num t) nil))) 389 (car (mh-seq-containing-msg (mh-get-msg-num t) nil)))
366 ((equal input "") default) 390 ((equal input "") default)
@@ -370,6 +394,126 @@ defaults to the first sequence containing the current message."
370 (error "No messages in sequence `%s'" seq)) 394 (error "No messages in sequence `%s'" seq))
371 seq)) 395 seq))
372 396
397;;; Functions to read ranges with completion...
398(defvar mh-range-seq-names)
399(defvar mh-range-history ())
400(defvar mh-range-completion-map (copy-keymap minibuffer-local-completion-map))
401(define-key mh-range-completion-map " " 'self-insert-command)
402
403(defun mh-range-completion-function (string predicate flag)
404 "Programmable completion of message ranges.
405STRING is the user input that is to be completed. PREDICATE if non-nil is a
406function used to filter the possible choices and FLAG determines whether the
407completion is over."
408 (let* ((candidates mh-range-seq-names)
409 (last-char (and (not (equal string ""))
410 (aref string (1- (length string)))))
411 (last-word (cond ((null last-char) "")
412 ((memq last-char '(? ?- ?:)) "")
413 (t (car (last (split-string string "[ -:]+"))))))
414 (prefix (substring string 0 (- (length string) (length last-word)))))
415 (cond ((eq flag nil)
416 (let ((res (try-completion last-word candidates predicate)))
417 (cond ((null res) nil)
418 ((eq res t) t)
419 (t (concat prefix res)))))
420 ((eq flag t)
421 (all-completions last-word candidates predicate))
422 ((eq flag 'lambda)
423 (loop for x in candidates
424 when (equal x last-word) return t
425 finally return nil)))))
426
427;;;###mh-autoload
428(defun mh-read-range (prompt &optional folder default
429 expand-flag ask-flag number-as-range-flag)
430 "Read a message range with PROMPT.
431
432If FOLDER is non-nil then a range is read from that folder, otherwise use
433`mh-current-folder'.
434
435If DEFAULT is a string then use that as default range to return. If DEFAULT is
436nil then ask user with default answer a range based on the sequences that seem
437relevant. Finally if DEFAULT is t, try to avoid prompting the user. Unseen
438messages, if present, are returned. If the folder has fewer than
439`mh-large-folder' messages then \"all\" messages are returned. Finally as a
440last resort prompt the user.
441
442If EXPAND-FLAG is non-nil then a list of message numbers corresponding to the
443input is returned. If this list is empty then an error is raised. If
444EXPAND-FLAG is nil just return the input string. In this case we don't check
445if the range is empty.
446
447If ASK-FLAG is non-nil, then the user is always queried for a range of
448messages. If ASK-FLAG is nil, then the function checks if the unseen sequence
449is non-empty. If that is the case, `mh-unseen-seq', or the list of messages in
450it depending on the value of EXPAND, is returned. Otherwise if the folder has
451fewer than `mh-large-folder' messages then the list of messages corresponding
452to \"all\" is returned. If neither of the above holds then as a last resort
453the user is queried for a range of messages.
454
455If NUMBER-AS-RANGE-FLAG is non-nil, then if a number, N is read as input, it
456is interpreted as the range \"last:N\".
457
458This function replaces the existing function `mh-read-msg-range'. Calls to:
459 (mh-read-msg-range folder flag)
460should be replaced with:
461 (mh-read-range \"Suitable prompt\" folder t nil flag
462 mh-interpret-number-as-range-flag)"
463 (setq default (or default mh-last-seq-used
464 (car (mh-seq-containing-msg (mh-get-msg-num nil) t)))
465 prompt (format "%s range" prompt))
466 (let* ((folder (or folder mh-current-folder))
467 (default (cond ((or (eq default t) (stringp default)) default)
468 ((symbolp default) (symbol-name default))))
469 (guess (eq default t))
470 (counts (and guess (mh-folder-size folder)))
471 (unseen (and counts (> (cadr counts) 0)))
472 (large (and counts mh-large-folder (> (car counts) mh-large-folder)))
473 (str (cond ((and guess large
474 (setq default (format "last:%s" mh-large-folder)
475 prompt (format "%s (folder has %s messages)"
476 prompt (car counts)))
477 nil))
478 ((and guess (not large) (setq default "all") nil))
479 ((eq default nil) "")
480 (t (format "[%s] " default))))
481 (minibuffer-local-completion-map mh-range-completion-map)
482 (seq-list (if (eq folder mh-current-folder)
483 mh-seq-list
484 (mh-read-folder-sequences folder nil)))
485 (mh-range-seq-names
486 (append '(("first") ("last") ("all") ("prev") ("next"))
487 (mh-seq-names seq-list)))
488 (input (cond ((and (not ask-flag) unseen) (symbol-name mh-unseen-seq))
489 ((and (not ask-flag) (not large)) "all")
490 (t (completing-read (format "%s: %s" prompt str)
491 'mh-range-completion-function nil nil
492 nil 'mh-range-history default))))
493 msg-list)
494 (when (and number-as-range-flag
495 (string-match "^[ \t]*\\([0-9]+\\)[ \t]*$" input))
496 (setq input (concat "last:" (match-string 1 input))))
497 (cond ((not expand-flag) input)
498 ((assoc (intern input) seq-list)
499 (cdr (assoc (intern input) seq-list)))
500 ((setq msg-list (mh-translate-range folder input)) msg-list)
501 (t (error "No messages in range `%s'" input)))))
502
503;;;###mh-autoload
504(defun mh-translate-range (folder expr)
505 "In FOLDER, translate the string EXPR to a list of messages numbers."
506 (save-excursion
507 (let ((strings (delete "" (split-string expr "[ \t\n]")))
508 (result ()))
509 (ignore-errors
510 (apply #'mh-exec-cmd-quiet nil "mhpath" folder strings)
511 (set-buffer mh-temp-buffer)
512 (goto-char (point-min))
513 (while (re-search-forward "/\\([0-9]*\\)$" nil t)
514 (push (car (read-from-string (match-string 1))) result))
515 (nreverse result)))))
516
373(defun mh-seq-names (seq-list) 517(defun mh-seq-names (seq-list)
374 "Return an alist containing the names of the SEQ-LIST." 518 "Return an alist containing the names of the SEQ-LIST."
375 (mapcar (lambda (entry) (list (symbol-name (mh-seq-name entry)))) 519 (mapcar (lambda (entry) (list (symbol-name (mh-seq-name entry))))
@@ -427,7 +571,7 @@ uses `overlay-arrow-position' to put a marker in the fringe."
427(defun mh-add-to-sequence (seq msgs) 571(defun mh-add-to-sequence (seq msgs)
428 "The sequence SEQ is augmented with the messages in MSGS." 572 "The sequence SEQ is augmented with the messages in MSGS."
429 ;; Add to a SEQUENCE each message the list of MSGS. 573 ;; Add to a SEQUENCE each message the list of MSGS.
430 (if (not (mh-folder-name-p seq)) 574 (if (and (mh-valid-seq-p seq) (not (mh-folder-name-p seq)))
431 (if msgs 575 (if msgs
432 (apply 'mh-exec-cmd "mark" mh-current-folder "-add" 576 (apply 'mh-exec-cmd "mark" mh-current-folder "-add"
433 "-sequence" (symbol-name seq) 577 "-sequence" (symbol-name seq)
@@ -458,17 +602,15 @@ uses `overlay-arrow-position' to put a marker in the fringe."
458 (mh-regenerate-headers coalesced-msgs t) 602 (mh-regenerate-headers coalesced-msgs t)
459 (cond ((memq 'unthread mh-view-ops) 603 (cond ((memq 'unthread mh-view-ops)
460 ;; Populate restricted scan-line map 604 ;; Populate restricted scan-line map
461 (goto-char (point-min)) 605 (mh-remove-all-notation)
462 (while (not (eobp)) 606 (mh-iterate-on-range msg (cons (point-min) (point-max))
463 (let ((msg (mh-get-msg-num nil))) 607 (setf (gethash msg mh-thread-scan-line-map)
464 (when (numberp msg) 608 (mh-thread-parse-scan-line)))
465 (setf (gethash msg mh-thread-scan-line-map)
466 (mh-thread-parse-scan-line))))
467 (forward-line))
468 ;; Remove scan lines and read results from pre-computed tree 609 ;; Remove scan lines and read results from pre-computed tree
469 (delete-region (point-min) (point-max)) 610 (delete-region (point-min) (point-max))
470 (mh-thread-print-scan-lines 611 (mh-thread-print-scan-lines
471 (mh-thread-generate mh-current-folder ()))) 612 (mh-thread-generate mh-current-folder ()))
613 (mh-notate-user-sequences))
472 (mh-index-data 614 (mh-index-data
473 (mh-index-insert-folder-headers))))))) 615 (mh-index-insert-folder-headers)))))))
474 616
@@ -509,32 +651,36 @@ If VAR is nil then the loop is executed without any binding."
509(put 'mh-iterate-on-messages-in-region 'lisp-indent-hook 'defun) 651(put 'mh-iterate-on-messages-in-region 'lisp-indent-hook 'defun)
510 652
511;;;###mh-autoload 653;;;###mh-autoload
512(defmacro mh-iterate-on-msg-or-seq (var msg-or-seq &rest body) 654(defmacro mh-iterate-on-range (var range &rest body)
513 "Iterate an operation over a region or sequence. 655 "Iterate an operation over a region or sequence.
514 656
515VAR is bound to each message in turn in a loop over MSG-OR-SEQ, which can be a 657VAR is bound to each message in turn in a loop over RANGE, which can be a
516message number, a list of message numbers, a sequence, or a region in a cons 658message number, a list of message numbers, a sequence, a region in a cons
517cell. In each iteration, BODY is executed. 659cell, or a MH range (something like last:20) in a string. In each iteration,
660BODY is executed.
518 661
519The parameter MSG-OR-SEQ is usually created with `mh-interactive-msg-or-seq' 662The parameter RANGE is usually created with `mh-interactive-range'
520in order to provide a uniform interface to MH-E functions." 663in order to provide a uniform interface to MH-E functions."
521 (unless (symbolp var) 664 (unless (symbolp var)
522 (error "Can not bind the non-symbol %s" var)) 665 (error "Can not bind the non-symbol %s" var))
523 (let ((binding-needed-flag var) 666 (let ((binding-needed-flag var)
524 (msgs (make-symbol "msgs")) 667 (msgs (make-symbol "msgs"))
525 (seq-hash-table (make-symbol "seq-hash-table"))) 668 (seq-hash-table (make-symbol "seq-hash-table")))
526 `(cond ((numberp ,msg-or-seq) 669 `(cond ((numberp ,range)
527 (when (mh-goto-msg ,msg-or-seq t t) 670 (when (mh-goto-msg ,range t t)
528 (let ,(if binding-needed-flag `((,var ,msg-or-seq)) ()) 671 (let ,(if binding-needed-flag `((,var ,range)) ())
529 ,@body))) 672 ,@body)))
530 ((and (consp ,msg-or-seq) 673 ((and (consp ,range)
531 (numberp (car ,msg-or-seq)) (numberp (cdr ,msg-or-seq))) 674 (numberp (car ,range)) (numberp (cdr ,range)))
532 (mh-iterate-on-messages-in-region ,var 675 (mh-iterate-on-messages-in-region ,var
533 (car ,msg-or-seq) (cdr ,msg-or-seq) 676 (car ,range) (cdr ,range)
534 ,@body)) 677 ,@body))
535 (t (let ((,msgs (if (and ,msg-or-seq (symbolp ,msg-or-seq)) 678 (t (let ((,msgs (cond ((and ,range (symbolp ,range))
536 (mh-seq-to-msgs ,msg-or-seq) 679 (mh-seq-to-msgs ,range))
537 ,msg-or-seq)) 680 ((stringp ,range)
681 (mh-translate-range mh-current-folder
682 ,range))
683 (t ,range)))
538 (,seq-hash-table (make-hash-table))) 684 (,seq-hash-table (make-hash-table)))
539 (dolist (msg ,msgs) 685 (dolist (msg ,msgs)
540 (setf (gethash msg ,seq-hash-table) t)) 686 (setf (gethash msg ,seq-hash-table) t))
@@ -543,38 +689,39 @@ in order to provide a uniform interface to MH-E functions."
543 (let ,(if binding-needed-flag `((,var v)) ()) 689 (let ,(if binding-needed-flag `((,var v)) ())
544 ,@body)))))))) 690 ,@body))))))))
545 691
546(put 'mh-iterate-on-msg-or-seq 'lisp-indent-hook 'defun) 692(put 'mh-iterate-on-range 'lisp-indent-hook 'defun)
547 693
548;;;###mh-autoload 694;;;###mh-autoload
549(defun mh-msg-or-seq-to-msg-list (msg-or-seq) 695(defun mh-range-to-msg-list (range)
550 "Return a list of messages for MSG-OR-SEQ. 696 "Return a list of messages for RANGE.
551MSG-OR-SEQ can be a message number, a list of message numbers, a sequence, or 697RANGE can be a message number, a list of message numbers, a sequence, or
552a region in a cons cell." 698a region in a cons cell."
553 (let (msg-list) 699 (let (msg-list)
554 (mh-iterate-on-msg-or-seq msg msg-or-seq 700 (mh-iterate-on-range msg range
555 (push msg msg-list)) 701 (push msg msg-list))
556 (nreverse msg-list))) 702 (nreverse msg-list)))
557 703
558;;;###mh-autoload 704;;;###mh-autoload
559(defun mh-interactive-msg-or-seq (sequence-prompt) 705(defun mh-interactive-range (range-prompt)
560 "Return interactive specification for message, sequence, or region. 706 "Return interactive specification for message, sequence, range or region.
561By convention, the name of this argument is msg-or-seq. 707By convention, the name of this argument is RANGE.
562 708
563If variable `transient-mark-mode' is non-nil and the mark is active, then this 709If variable `transient-mark-mode' is non-nil and the mark is active, then this
564function returns a cons-cell of the region. 710function returns a cons-cell of the region.
565If optional prefix argument provided, then prompt for message sequence with 711
566SEQUENCE-PROMPT and return sequence. 712If optional prefix argument is provided, then prompt for message range with
713RANGE-PROMPT. A list of messages in that range is returned.
714
715If a MH range is given, say something like last:20, then a list containing
716the messages in that range is returned.
717
567Otherwise, the message number at point is returned. 718Otherwise, the message number at point is returned.
568 719
569This function is usually used with `mh-iterate-on-msg-or-seq' in order to 720This function is usually used with `mh-iterate-on-range' in order to provide
570provide a uniform interface to MH-E functions." 721a uniform interface to MH-E functions."
571 (cond 722 (cond ((mh-mark-active-p t) (cons (region-beginning) (region-end)))
572 ((mh-mark-active-p t) 723 (current-prefix-arg (mh-read-range range-prompt nil nil t t))
573 (cons (region-beginning) (region-end))) 724 (t (mh-get-msg-num t))))
574 (current-prefix-arg
575 (mh-read-seq-default sequence-prompt t))
576 (t
577 (mh-get-msg-num t))))
578 725
579;;;###mh-autoload 726;;;###mh-autoload
580(defun mh-region-to-msg-list (begin end) 727(defun mh-region-to-msg-list (begin end)
@@ -591,6 +738,8 @@ provide a uniform interface to MH-E functions."
591;;; Commands to handle new 'subject sequence. 738;;; Commands to handle new 'subject sequence.
592;;; Or "Poor man's threading" by psg. 739;;; Or "Poor man's threading" by psg.
593 740
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.
594(defun mh-subject-to-sequence (all) 743(defun mh-subject-to-sequence (all)
595 "Put all following messages with same subject in sequence 'subject. 744 "Put all following messages with same subject in sequence 'subject.
596If arg ALL is t, move to beginning of folder buffer to collect all messages. 745If arg ALL is t, move to beginning of folder buffer to collect all messages.
@@ -601,6 +750,21 @@ Return number of messages put in the sequence:
601 nil -> there was no subject line. 750 nil -> there was no subject line.
602 0 -> there were no later messages with the same subject (sequence not made) 751 0 -> there were no later messages with the same subject (sequence not made)
603 >1 -> the total number of messages including current one." 752 >1 -> the total number of messages including current one."
753 (if (memq 'unthread mh-view-ops)
754 (mh-subject-to-sequence-threaded all)
755 (mh-subject-to-sequence-unthreaded all)))
756
757(defun mh-subject-to-sequence-unthreaded (all)
758 "Put all following messages with same subject in sequence 'subject.
759This function only works with an unthreaded folder. If arg ALL is t, move to
760beginning of folder buffer to collect all messages. If arg ALL is nil, collect
761only messages fron current one on forward.
762
763Return number of messages put in the sequence:
764
765 nil -> there was no subject line.
766 0 -> there were no later messages with the same subject (sequence not made)
767 >1 -> the total number of messages including current one."
604 (if (not (eq major-mode 'mh-folder-mode)) 768 (if (not (eq major-mode 'mh-folder-mode))
605 (error "Not in a folder buffer")) 769 (error "Not in a folder buffer"))
606 (save-excursion 770 (save-excursion
@@ -628,8 +792,7 @@ Return number of messages put in the sequence:
628 ;; If we created a new sequence, add the initial message to it too. 792 ;; If we created a new sequence, add the initial message to it too.
629 (if (not (member (mh-get-msg-num t) list)) 793 (if (not (member (mh-get-msg-num t) list))
630 (setq list (cons (mh-get-msg-num t) list))) 794 (setq list (cons (mh-get-msg-num t) list)))
631 (if (member '("subject") (mh-seq-names mh-seq-list)) 795 (if (assoc 'subject mh-seq-list) (mh-delete-seq 'subject))
632 (mh-delete-seq 'subject))
633 ;; sort the result into a sequence 796 ;; sort the result into a sequence
634 (let ((sorted-list (sort (copy-sequence list) 'mh-lessp))) 797 (let ((sorted-list (sort (copy-sequence list) 'mh-lessp)))
635 (while sorted-list 798 (while sorted-list
@@ -639,6 +802,39 @@ Return number of messages put in the sequence:
639 (t 802 (t
640 0)))))) 803 0))))))
641 804
805(defun mh-subject-to-sequence-threaded (all)
806 "Put all messages with the same subject in the 'subject sequence.
807This function works when the folder is threaded. In this situation the subject
808could get truncated and so the normal matching doesn't work.
809
810The parameter ALL is non-nil then all the messages in the buffer are
811considered, otherwise only the messages after the current one are taken into
812account."
813 (let* ((cur (mh-get-msg-num nil))
814 (subject (mh-thread-find-msg-subject cur))
815 region msgs)
816 (if (null subject)
817 (and (message "No subject line") nil)
818 (setq region (cons (if all (point-min) (point)) (point-max)))
819 (mh-iterate-on-range msg region
820 (when (eq (mh-thread-find-msg-subject msg) subject)
821 (push msg msgs)))
822 (setq msgs (sort msgs #'mh-lessp))
823 (if (null msgs)
824 0
825 (when (assoc 'subject mh-seq-list)
826 (mh-delete-seq 'subject))
827 (mh-add-msgs-to-seq msgs 'subject)
828 (length msgs)))))
829
830(defun mh-thread-find-msg-subject (msg)
831 "Find canonicalized subject of MSG.
832This function can only be used the folder is threaded."
833 (ignore-errors
834 (mh-message-subject
835 (mh-container-message (gethash (gethash msg mh-thread-index-id-map)
836 mh-thread-id-table)))))
837
642;;;###mh-autoload 838;;;###mh-autoload
643(defun mh-narrow-to-subject () 839(defun mh-narrow-to-subject ()
644 "Narrow to a sequence containing all following messages with same subject." 840 "Narrow to a sequence containing all following messages with same subject."
@@ -657,6 +853,99 @@ Return number of messages put in the sequence:
657 (if (numberp num) 853 (if (numberp num)
658 (mh-goto-msg num t t)))))) 854 (mh-goto-msg num t t))))))
659 855
856(defun mh-read-pick-regexp (default)
857 "With prefix arg read a pick regexp.
858If no prefix arg is given, then return DEFAULT."
859 (let ((default-string (loop for x in default concat (format " %s" x))))
860 (if (or current-prefix-arg (equal default-string ""))
861 (delete "" (split-string (read-string "Pick regexp: " default-string)))
862 default)))
863
864;;;###mh-autoload
865(defun mh-narrow-to-from (&optional regexp)
866 "Limit to messages with the same From header field as the message at point.
867With a prefix argument, prompt for the regular expression, REGEXP given to
868pick."
869 (interactive
870 (list (mh-read-pick-regexp (mh-current-message-header-field 'from))))
871 (mh-narrow-to-header-field 'from regexp))
872
873;;;###mh-autoload
874(defun mh-narrow-to-cc (&optional regexp)
875 "Limit to messages with the same Cc header field as the message at point.
876With a prefix argument, prompt for the regular expression, REGEXP given to
877pick."
878 (interactive
879 (list (mh-read-pick-regexp (mh-current-message-header-field 'cc))))
880 (mh-narrow-to-header-field 'cc regexp))
881
882;;;###mh-autoload
883(defun mh-narrow-to-to (&optional regexp)
884 "Limit to messages with the same To header field as the message at point.
885With a prefix argument, prompt for the regular expression, REGEXP given to
886pick."
887 (interactive
888 (list (mh-read-pick-regexp (mh-current-message-header-field 'to))))
889 (mh-narrow-to-header-field 'to regexp))
890
891(defun mh-narrow-to-header-field (header-field regexp)
892 "Limit to messages whose HEADER-FIELD match REGEXP.
893The MH command pick is used to do the match."
894 (let ((folder mh-current-folder)
895 (original (mh-coalesce-msg-list
896 (mh-range-to-msg-list (cons (point-min) (point-max)))))
897 (msg-list ()))
898 (with-temp-buffer
899 (apply #'mh-exec-cmd-output "pick" nil folder
900 (append original (list "-list") regexp))
901 (goto-char (point-min))
902 (while (not (eobp))
903 (let ((num (read-from-string
904 (buffer-substring (point) (line-end-position)))))
905 (when (numberp (car num)) (push (car num) msg-list))
906 (forward-line))))
907 (if (null msg-list)
908 (message "No matches")
909 (when (assoc 'header mh-seq-list) (mh-delete-seq 'header))
910 (mh-add-msgs-to-seq msg-list 'header)
911 (mh-narrow-to-seq 'header))))
912
913(defun mh-current-message-header-field (header-field)
914 "Return a pick regexp to match HEADER-FIELD of the message at point."
915 (let ((num (mh-get-msg-num nil)))
916 (when num
917 (let ((folder mh-current-folder))
918 (with-temp-buffer
919 (insert-file-contents-literally (mh-msg-filename num folder))
920 (goto-char (point-min))
921 (when (search-forward "\n\n" nil t)
922 (narrow-to-region (point-min) (point)))
923 (let* ((field (or (message-fetch-field (format "%s" header-field))
924 ""))
925 (field-option (format "-%s" header-field))
926 (patterns (loop for x in (split-string field "[ ]*,[ ]*")
927 unless (equal x "")
928 collect (if (string-match "<\\(.*@.*\\)>" x)
929 (match-string 1 x)
930 x))))
931 (when patterns
932 (loop with accum = `(,field-option ,(car patterns))
933 for e in (cdr patterns)
934 do (setq accum `(,field-option ,e "-or" ,@accum))
935 finally return accum))))))))
936
937;;;###mh-autoload
938(defun mh-narrow-to-range (range)
939 "Limit to messages in RANGE.
940
941Check the documentation of `mh-interactive-range' to see how RANGE is read in
942interactive use."
943 (interactive (list (mh-interactive-range "Narrow to")))
944 (when (assoc 'range mh-seq-list) (mh-delete-seq 'range))
945 (mh-add-msgs-to-seq (mh-range-to-msg-list range) 'range)
946 (mh-narrow-to-seq 'range))
947
948
660;;;###mh-autoload 949;;;###mh-autoload
661(defun mh-delete-subject () 950(defun mh-delete-subject ()
662 "Mark all following messages with same subject to be deleted. 951 "Mark all following messages with same subject to be deleted.
@@ -689,28 +978,23 @@ subject for deletion."
689 978
690;;; Message threading: 979;;; Message threading:
691 980
981(defmacro mh-thread-initialize-hash (var test)
982 "Initialize the hash table in VAR.
983TEST is the test to use when creating a new hash table."
984 (unless (symbolp var) (error "Expected a symbol: %s" var))
985 `(if ,var (clrhash ,var) (setq ,var (make-hash-table :test ,test))))
986
692(defun mh-thread-initialize () 987(defun mh-thread-initialize ()
693 "Make hash tables, otherwise clear them." 988 "Make new hash tables, or clear them if already present."
694 (cond 989 (mh-thread-initialize-hash mh-thread-id-hash #'equal)
695 (mh-thread-id-hash 990 (mh-thread-initialize-hash mh-thread-subject-hash #'equal)
696 (clrhash mh-thread-id-hash) 991 (mh-thread-initialize-hash mh-thread-id-table #'eq)
697 (clrhash mh-thread-subject-hash) 992 (mh-thread-initialize-hash mh-thread-id-index-map #'eq)
698 (clrhash mh-thread-id-table) 993 (mh-thread-initialize-hash mh-thread-index-id-map #'eql)
699 (clrhash mh-thread-id-index-map) 994 (mh-thread-initialize-hash mh-thread-scan-line-map #'eql)
700 (clrhash mh-thread-index-id-map) 995 (mh-thread-initialize-hash mh-thread-subject-container-hash #'eq)
701 (clrhash mh-thread-scan-line-map) 996 (mh-thread-initialize-hash mh-thread-duplicates #'eq)
702 (clrhash mh-thread-subject-container-hash) 997 (setq mh-thread-history ()))
703 (clrhash mh-thread-duplicates)
704 (setq mh-thread-history ()))
705 (t (setq mh-thread-id-hash (make-hash-table :test #'equal))
706 (setq mh-thread-subject-hash (make-hash-table :test #'equal))
707 (setq mh-thread-id-table (make-hash-table :test #'eq))
708 (setq mh-thread-id-index-map (make-hash-table :test #'eq))
709 (setq mh-thread-index-id-map (make-hash-table :test #'eql))
710 (setq mh-thread-scan-line-map (make-hash-table :test #'eql))
711 (setq mh-thread-subject-container-hash (make-hash-table :test #'eq))
712 (setq mh-thread-duplicates (make-hash-table :test #'eq))
713 (setq mh-thread-history ()))))
714 998
715(defsubst mh-thread-id-container (id) 999(defsubst mh-thread-id-container (id)
716 "Given ID, return the corresponding container in `mh-thread-id-table'. 1000 "Given ID, return the corresponding container in `mh-thread-id-table'.
@@ -959,7 +1243,7 @@ preference to something that has it."
959 (push root results))))) 1243 (push root results)))))
960 (nreverse results))) 1244 (nreverse results)))
961 1245
962(defsubst mh-thread-process-in-reply-to (reply-to-header) 1246(defun mh-thread-process-in-reply-to (reply-to-header)
963 "Extract message id's from REPLY-TO-HEADER. 1247 "Extract message id's from REPLY-TO-HEADER.
964Ideally this should have some regexp which will try to guess if a string 1248Ideally this should have some regexp which will try to guess if a string
965between < and > is a message id and not an email address. For now it will 1249between < and > is a message id and not an email address. For now it will
@@ -1071,6 +1355,7 @@ Only information about messages in MSG-LIST are added to the tree."
1071 "Update thread tree for FOLDER. 1355 "Update thread tree for FOLDER.
1072All messages after START-POINT are added to the thread tree." 1356All messages after START-POINT are added to the thread tree."
1073 (mh-thread-rewind-pruning) 1357 (mh-thread-rewind-pruning)
1358 (mh-remove-all-notation)
1074 (goto-char start-point) 1359 (goto-char start-point)
1075 (let ((msg-list ())) 1360 (let ((msg-list ()))
1076 (while (not (eobp)) 1361 (while (not (eobp))
@@ -1085,7 +1370,6 @@ All messages after START-POINT are added to the thread tree."
1085 (old-buffer-modified-flag (buffer-modified-p))) 1370 (old-buffer-modified-flag (buffer-modified-p)))
1086 (delete-region (point-min) (point-max)) 1371 (delete-region (point-min) (point-max))
1087 (mh-thread-print-scan-lines thread-tree) 1372 (mh-thread-print-scan-lines thread-tree)
1088 (mh-notate-user-sequences)
1089 (mh-notate-deleted-and-refiled) 1373 (mh-notate-deleted-and-refiled)
1090 (mh-notate-cur) 1374 (mh-notate-cur)
1091 (set-buffer-modified-p old-buffer-modified-flag)))) 1375 (set-buffer-modified-p old-buffer-modified-flag))))
@@ -1150,18 +1434,30 @@ Otherwise uses the line at point as the scan line to parse."
1150 (let* ((string (or string 1434 (let* ((string (or string
1151 (buffer-substring-no-properties (line-beginning-position) 1435 (buffer-substring-no-properties (line-beginning-position)
1152 (line-end-position)))) 1436 (line-end-position))))
1153 (first-string (substring string 0 (+ mh-cmd-note 8)))) 1437 (address-start (+ mh-cmd-note mh-scan-field-from-start-offset))
1154 (setf (elt first-string mh-cmd-note) ? ) 1438 (body-start (+ mh-cmd-note mh-scan-field-from-end-offset))
1155 (when (equal (elt first-string (1+ mh-cmd-note)) (elt mh-note-seq 0)) 1439 (first-string (substring string 0 address-start)))
1156 (setf (elt first-string (1+ mh-cmd-note)) ? ))
1157 (list first-string 1440 (list first-string
1158 (substring string 1441 (substring string address-start (- body-start 2))
1159 (+ mh-cmd-note mh-scan-field-from-start-offset) 1442 (substring string body-start)
1160 (+ mh-cmd-note mh-scan-field-from-end-offset -2))
1161 (substring string (+ mh-cmd-note mh-scan-field-from-end-offset))
1162 string))) 1443 string)))
1163 1444
1164;;;###mh-autoload 1445;;;###mh-autoload
1446(defun mh-thread-update-scan-line-map (msg notation offset)
1447 "In threaded view update `mh-thread-scan-line-map'.
1448MSG is the message being notated with NOTATION at OFFSET."
1449 (let* ((msg (or msg (mh-get-msg-num nil)))
1450 (cur-scan-line (and mh-thread-scan-line-map
1451 (gethash msg mh-thread-scan-line-map)))
1452 (old-scan-lines (loop for map in mh-thread-scan-line-map-stack
1453 collect (and map (gethash msg map))))
1454 (notation (if (stringp notation) (aref notation 0) notation)))
1455 (when cur-scan-line
1456 (setf (aref (car cur-scan-line) offset) notation))
1457 (dolist (line old-scan-lines)
1458 (when line (setf (aref (car line) offset) notation)))))
1459
1460;;;###mh-autoload
1165(defun mh-thread-add-spaces (count) 1461(defun mh-thread-add-spaces (count)
1166 "Add COUNT spaces to each scan line in `mh-thread-scan-line-map'." 1462 "Add COUNT spaces to each scan line in `mh-thread-scan-line-map'."
1167 (let ((spaces (format (format "%%%ss" count) ""))) 1463 (let ((spaces (format (format "%%%ss" count) "")))
@@ -1197,14 +1493,11 @@ Otherwise uses the line at point as the scan line to parse."
1197 (message "Threading %s..." (buffer-name)) 1493 (message "Threading %s..." (buffer-name))
1198 (mh-thread-initialize) 1494 (mh-thread-initialize)
1199 (goto-char (point-min)) 1495 (goto-char (point-min))
1496 (mh-remove-all-notation)
1200 (let ((msg-list ())) 1497 (let ((msg-list ()))
1201 (while (not (eobp)) 1498 (mh-iterate-on-range msg (cons (point-min) (point-max))
1202 (let ((index (mh-get-msg-num nil))) 1499 (setf (gethash msg mh-thread-scan-line-map) (mh-thread-parse-scan-line))
1203 (when (numberp index) 1500 (push msg msg-list))
1204 (push index msg-list)
1205 (setf (gethash index mh-thread-scan-line-map)
1206 (mh-thread-parse-scan-line))))
1207 (forward-line))
1208 (let* ((range (mh-coalesce-msg-list msg-list)) 1501 (let* ((range (mh-coalesce-msg-list msg-list))
1209 (thread-tree (mh-thread-generate (buffer-name) range))) 1502 (thread-tree (mh-thread-generate (buffer-name) range)))
1210 (delete-region (point-min) (point-max)) 1503 (delete-region (point-min) (point-max))
@@ -1403,68 +1696,31 @@ start of the region and the second is the point at the end."
1403 1696
1404;; Tick mark handling 1697;; Tick mark handling
1405 1698
1406;; Functions to highlight and unhighlight ticked messages.
1407(defun mh-tick-add-overlay ()
1408 "Add tick overlay to current line."
1409 (with-mh-folder-updating (t)
1410 (let ((overlay
1411 (or (mh-funcall-if-exists make-overlay (point) (line-end-position))
1412 (mh-funcall-if-exists make-extent (point) (line-end-position)))))
1413 (or (mh-funcall-if-exists overlay-put overlay 'face 'mh-folder-tick-face)
1414 (mh-funcall-if-exists set-extent-face overlay 'mh-folder-tick-face))
1415 (mh-funcall-if-exists set-extent-priority overlay 10)
1416 (add-text-properties (point) (line-end-position) `(mh-tick ,overlay)))))
1417
1418(defun mh-tick-remove-overlay ()
1419 "Remove tick overlay from current line."
1420 (let ((overlay (get-text-property (point) 'mh-tick)))
1421 (when overlay
1422 (with-mh-folder-updating (t)
1423 (or (mh-funcall-if-exists delete-overlay overlay)
1424 (mh-funcall-if-exists delete-extent overlay))
1425 (remove-text-properties (point) (line-end-position) `(mh-tick nil))))))
1426
1427;;;###mh-autoload
1428(defun mh-notate-tick (msg ticked-msgs &optional ignore-narrowing)
1429 "Highlight current line if MSG is in TICKED-MSGS.
1430If optional argument IGNORE-NARROWING is non-nil then highlighting is carried
1431out even if folder is narrowed to `mh-tick-seq'."
1432 (when mh-tick-seq
1433 (let ((narrowed-to-tick (and (not ignore-narrowing)
1434 (eq mh-narrowed-to-seq mh-tick-seq)))
1435 (overlay (get-text-property (point) 'mh-tick))
1436 (in-tick (member msg ticked-msgs)))
1437 (cond (narrowed-to-tick (mh-tick-remove-overlay))
1438 ((and (not overlay) in-tick) (mh-tick-add-overlay))
1439 ((and overlay (not in-tick)) (mh-tick-remove-overlay))))))
1440
1441;; Interactive function to toggle tick.
1442;;;###mh-autoload 1699;;;###mh-autoload
1443(defun mh-toggle-tick (begin end) 1700(defun mh-toggle-tick (range)
1444 "Toggle tick mark of all messages in region BEGIN to END." 1701 "Toggle tick mark of all messages in RANGE."
1445 (interactive (cond ((mh-mark-active-p t) 1702 (interactive (list (mh-interactive-range "Tick")))
1446 (list (region-beginning) (region-end)))
1447 (t (list (line-beginning-position) (line-end-position)))))
1448 (unless mh-tick-seq 1703 (unless mh-tick-seq
1449 (error "Enable ticking by customizing `mh-tick-seq'")) 1704 (error "Enable ticking by customizing `mh-tick-seq'"))
1450 (let* ((tick-seq (mh-find-seq mh-tick-seq)) 1705 (let* ((tick-seq (mh-find-seq mh-tick-seq))
1451 (tick-seq-msgs (mh-seq-msgs tick-seq))) 1706 (tick-seq-msgs (mh-seq-msgs tick-seq))
1452 (mh-iterate-on-messages-in-region msg begin end 1707 (ticked ())
1708 (unticked ()))
1709 (mh-iterate-on-range msg range
1453 (cond ((member msg tick-seq-msgs) 1710 (cond ((member msg tick-seq-msgs)
1454 (mh-undefine-sequence mh-tick-seq (list msg)) 1711 (push msg unticked)
1455 (setcdr tick-seq (delq msg (cdr tick-seq))) 1712 (setcdr tick-seq (delq msg (cdr tick-seq)))
1456 (when (null (cdr tick-seq)) (setq mh-last-seq-used nil)) 1713 (when (null (cdr tick-seq)) (setq mh-last-seq-used nil))
1457 (mh-tick-remove-overlay)) 1714 (mh-remove-sequence-notation msg t))
1458 (t 1715 (t
1459 (mh-add-msgs-to-seq (list msg) mh-tick-seq nil t) 1716 (push msg ticked)
1460 (setq mh-last-seq-used mh-tick-seq) 1717 (setq mh-last-seq-used mh-tick-seq)
1461 (mh-tick-add-overlay)))) 1718 (mh-add-sequence-notation msg t))))
1462 (when (and (eq mh-tick-seq mh-narrowed-to-seq) 1719 (mh-add-msgs-to-seq ticked mh-tick-seq nil t)
1463 (not mh-tick-seq-changed-when-narrowed-flag)) 1720 (mh-undefine-sequence mh-tick-seq unticked)
1464 (setq mh-tick-seq-changed-when-narrowed-flag t) 1721 (when mh-index-data
1465 (let ((ticked-msgs (mh-seq-msgs (mh-find-seq mh-tick-seq)))) 1722 (mh-index-add-to-sequence mh-tick-seq ticked)
1466 (mh-iterate-on-messages-in-region msg (point-min) (point-max) 1723 (mh-index-delete-from-sequence mh-tick-seq unticked))))
1467 (mh-notate-tick msg ticked-msgs t))))))
1468 1724
1469;;;###mh-autoload 1725;;;###mh-autoload
1470(defun mh-narrow-to-tick () 1726(defun mh-narrow-to-tick ()