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