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