diff options
| author | Bill Wohler | 2004-07-13 03:06:25 +0000 |
|---|---|---|
| committer | Bill Wohler | 2004-07-13 03:06:25 +0000 |
| commit | a66894d8b489dfdfafc2058cd181fefbb894fbf0 (patch) | |
| tree | 39c692b4da2f58c1f9830381b0befa1ec3d56b87 /lisp/mh-e/mh-seq.el | |
| parent | 0117451de7e30adf240f369f26b7667dbf3788bf (diff) | |
| download | emacs-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.el | 616 |
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. |
| 115 | This is the original map that is stored when the folder is narrowed.") | 116 | This 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. |
| 257 | Default is the displayed message. | 262 | |
| 258 | If optional prefix argument is provided, then prompt for the message sequence. | 263 | Check the documentation of `mh-interactive-range' to see how RANGE is read in |
| 259 | If variable `transient-mark-mode' is non-nil and the mark is active, then the | 264 | interactive use." |
| 260 | selected region is added to the sequence. | 265 | (interactive (list (mh-interactive-range "Add messages from") |
| 261 | In a program, MSG-OR-SEQ can be a message number, a list of message numbers, a | ||
| 262 | region 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) | 296 | If optional prefix argument ALL-FLAG is non-nil, then unwind to the beginning |
| 297 | of the view stack thereby showing all messages that the buffer originally | ||
| 298 | contained." | ||
| 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. |
| 324 | Messages to be deleted are given by `mh-delete-list' while messages to be | 345 | Messages 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. |
| 347 | PROMPT is the prompt to use when reading. If NOT-EMPTY is non-nil then a | 371 | PROMPT is the prompt to use when reading. If NOT-EMPTY is non-nil then a |
| 348 | non-empty sequence is read." | 372 | non-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. | ||
| 405 | STRING is the user input that is to be completed. PREDICATE if non-nil is a | ||
| 406 | function used to filter the possible choices and FLAG determines whether the | ||
| 407 | completion 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 | |||
| 432 | If FOLDER is non-nil then a range is read from that folder, otherwise use | ||
| 433 | `mh-current-folder'. | ||
| 434 | |||
| 435 | If DEFAULT is a string then use that as default range to return. If DEFAULT is | ||
| 436 | nil then ask user with default answer a range based on the sequences that seem | ||
| 437 | relevant. Finally if DEFAULT is t, try to avoid prompting the user. Unseen | ||
| 438 | messages, if present, are returned. If the folder has fewer than | ||
| 439 | `mh-large-folder' messages then \"all\" messages are returned. Finally as a | ||
| 440 | last resort prompt the user. | ||
| 441 | |||
| 442 | If EXPAND-FLAG is non-nil then a list of message numbers corresponding to the | ||
| 443 | input is returned. If this list is empty then an error is raised. If | ||
| 444 | EXPAND-FLAG is nil just return the input string. In this case we don't check | ||
| 445 | if the range is empty. | ||
| 446 | |||
| 447 | If ASK-FLAG is non-nil, then the user is always queried for a range of | ||
| 448 | messages. If ASK-FLAG is nil, then the function checks if the unseen sequence | ||
| 449 | is non-empty. If that is the case, `mh-unseen-seq', or the list of messages in | ||
| 450 | it depending on the value of EXPAND, is returned. Otherwise if the folder has | ||
| 451 | fewer than `mh-large-folder' messages then the list of messages corresponding | ||
| 452 | to \"all\" is returned. If neither of the above holds then as a last resort | ||
| 453 | the user is queried for a range of messages. | ||
| 454 | |||
| 455 | If NUMBER-AS-RANGE-FLAG is non-nil, then if a number, N is read as input, it | ||
| 456 | is interpreted as the range \"last:N\". | ||
| 457 | |||
| 458 | This function replaces the existing function `mh-read-msg-range'. Calls to: | ||
| 459 | (mh-read-msg-range folder flag) | ||
| 460 | should 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 | ||
| 515 | VAR is bound to each message in turn in a loop over MSG-OR-SEQ, which can be a | 657 | VAR is bound to each message in turn in a loop over RANGE, which can be a |
| 516 | message number, a list of message numbers, a sequence, or a region in a cons | 658 | message number, a list of message numbers, a sequence, a region in a cons |
| 517 | cell. In each iteration, BODY is executed. | 659 | cell, or a MH range (something like last:20) in a string. In each iteration, |
| 660 | BODY is executed. | ||
| 518 | 661 | ||
| 519 | The parameter MSG-OR-SEQ is usually created with `mh-interactive-msg-or-seq' | 662 | The parameter RANGE is usually created with `mh-interactive-range' |
| 520 | in order to provide a uniform interface to MH-E functions." | 663 | in 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. |
| 551 | MSG-OR-SEQ can be a message number, a list of message numbers, a sequence, or | 697 | RANGE can be a message number, a list of message numbers, a sequence, or |
| 552 | a region in a cons cell." | 698 | a 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. |
| 561 | By convention, the name of this argument is msg-or-seq. | 707 | By convention, the name of this argument is RANGE. |
| 562 | 708 | ||
| 563 | If variable `transient-mark-mode' is non-nil and the mark is active, then this | 709 | If variable `transient-mark-mode' is non-nil and the mark is active, then this |
| 564 | function returns a cons-cell of the region. | 710 | function returns a cons-cell of the region. |
| 565 | If optional prefix argument provided, then prompt for message sequence with | 711 | |
| 566 | SEQUENCE-PROMPT and return sequence. | 712 | If optional prefix argument is provided, then prompt for message range with |
| 713 | RANGE-PROMPT. A list of messages in that range is returned. | ||
| 714 | |||
| 715 | If a MH range is given, say something like last:20, then a list containing | ||
| 716 | the messages in that range is returned. | ||
| 717 | |||
| 567 | Otherwise, the message number at point is returned. | 718 | Otherwise, the message number at point is returned. |
| 568 | 719 | ||
| 569 | This function is usually used with `mh-iterate-on-msg-or-seq' in order to | 720 | This function is usually used with `mh-iterate-on-range' in order to provide |
| 570 | provide a uniform interface to MH-E functions." | 721 | a 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. |
| 596 | If arg ALL is t, move to beginning of folder buffer to collect all messages. | 745 | If arg ALL is t, move to beginning of folder buffer to collect all 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. | ||
| 759 | This function only works with an unthreaded folder. If arg ALL is t, move to | ||
| 760 | beginning of folder buffer to collect all messages. If arg ALL is nil, collect | ||
| 761 | only messages fron current one on forward. | ||
| 762 | |||
| 763 | Return 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. | ||
| 807 | This function works when the folder is threaded. In this situation the subject | ||
| 808 | could get truncated and so the normal matching doesn't work. | ||
| 809 | |||
| 810 | The parameter ALL is non-nil then all the messages in the buffer are | ||
| 811 | considered, otherwise only the messages after the current one are taken into | ||
| 812 | account." | ||
| 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. | ||
| 832 | This 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. | ||
| 858 | If 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. | ||
| 867 | With a prefix argument, prompt for the regular expression, REGEXP given to | ||
| 868 | pick." | ||
| 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. | ||
| 876 | With a prefix argument, prompt for the regular expression, REGEXP given to | ||
| 877 | pick." | ||
| 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. | ||
| 885 | With a prefix argument, prompt for the regular expression, REGEXP given to | ||
| 886 | pick." | ||
| 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. | ||
| 893 | The 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 | |||
| 941 | Check the documentation of `mh-interactive-range' to see how RANGE is read in | ||
| 942 | interactive 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. | ||
| 983 | TEST 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. |
| 964 | Ideally this should have some regexp which will try to guess if a string | 1248 | Ideally this should have some regexp which will try to guess if a string |
| 965 | between < and > is a message id and not an email address. For now it will | 1249 | between < 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. |
| 1072 | All messages after START-POINT are added to the thread tree." | 1356 | All 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'. | ||
| 1448 | MSG 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. | ||
| 1430 | If optional argument IGNORE-NARROWING is non-nil then highlighting is carried | ||
| 1431 | out 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 () |