Mercurial > emacs
comparison lisp/mh-e/mh-index.el @ 56406:d36b00b98db0
Upgraded to MH-E version 7.4.4.
See etc/MH-E-NEWS and lisp/mh-e/ChangeLog for details.
author | Bill Wohler <wohler@newt.com> |
---|---|
date | Tue, 13 Jul 2004 03:06:25 +0000 |
parents | 695cf19ef79e |
children | e9a6cbc8ca5e 97905c4f1a42 |
comparison
equal
deleted
inserted
replaced
56405:10b68aa88abe | 56406:d36b00b98db0 |
---|---|
1 ;;; mh-index -- MH-E interface to indexing programs | 1 ;;; mh-index -- MH-E interface to indexing programs |
2 | 2 |
3 ;; Copyright (C) 2002, 2003 Free Software Foundation, Inc. | 3 ;; Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc. |
4 | 4 |
5 ;; Author: Satyaki Das <satyaki@theforce.stanford.edu> | 5 ;; Author: Satyaki Das <satyaki@theforce.stanford.edu> |
6 ;; Maintainer: Bill Wohler <wohler@newt.com> | 6 ;; Maintainer: Bill Wohler <wohler@newt.com> |
7 ;; Keywords: mail | 7 ;; Keywords: mail |
8 ;; See: mh-e.el | 8 ;; See: mh-e.el |
41 | 41 |
42 ;;; Change Log: | 42 ;;; Change Log: |
43 | 43 |
44 ;;; Code: | 44 ;;; Code: |
45 | 45 |
46 (require 'cl) | 46 (require 'mh-utils) |
47 (mh-require-cl) | |
47 (require 'mh-e) | 48 (require 'mh-e) |
48 (require 'mh-mime) | 49 (require 'mh-mime) |
49 (require 'mh-pick) | 50 (require 'mh-pick) |
50 | 51 |
51 (autoload 'gnus-local-map-property "gnus-util") | 52 (autoload 'gnus-local-map-property "gnus-util") |
257 "-nodate" "-text" checksum "-inplace") | 258 "-nodate" "-text" checksum "-inplace") |
258 ;; update maps | 259 ;; update maps |
259 (save-excursion | 260 (save-excursion |
260 (set-buffer folder) | 261 (set-buffer folder) |
261 (mh-index-update-single-msg msg checksum origin-map))) | 262 (mh-index-update-single-msg msg checksum origin-map))) |
262 (forward-line)))))) | 263 (forward-line))))) |
263 | 264 (mh-index-write-data)) |
264 (defvar mh-flists-results-folder "new" | 265 |
266 (defvar mh-unpropagated-sequences '(cur range subject search) | |
267 "List of sequences that aren't preserved.") | |
268 | |
269 (defun mh-unpropagated-sequences () | |
270 "Return a list of sequences that aren't propagated to the source folders. | |
271 It is just the sequences in the variable `mh-unpropagated-sequences' in | |
272 addition to the Previous-Sequence (see mh-profile 5)." | |
273 (if mh-previous-seq | |
274 (cons mh-previous-seq mh-unpropagated-sequences) | |
275 mh-unpropagated-sequences)) | |
276 | |
277 ;;;###mh-autoload | |
278 (defun mh-create-sequence-map (seq-list) | |
279 "Return a map from msg number to list of sequences in which it is present. | |
280 SEQ-LIST is an assoc list whose keys are sequence names and whose cdr is the | |
281 list of messages in that sequence." | |
282 (loop with map = (make-hash-table) | |
283 for seq in seq-list | |
284 when (and (not (memq (car seq) (mh-unpropagated-sequences))) | |
285 (mh-valid-seq-p (car seq))) | |
286 do (loop for msg in (cdr seq) | |
287 do (push (car seq) (gethash msg map))) | |
288 finally return map)) | |
289 | |
290 ;;;###mh-autoload | |
291 (defun mh-index-create-sequences () | |
292 "Mirror sequences present in source folders in index folder." | |
293 (let ((seq-hash (make-hash-table :test #'equal)) | |
294 (seq-list ())) | |
295 (loop for folder being the hash-keys of mh-index-data | |
296 do (setf (gethash folder seq-hash) | |
297 (mh-create-sequence-map | |
298 (mh-read-folder-sequences folder nil)))) | |
299 (dolist (msg (mh-translate-range mh-current-folder "all")) | |
300 (let* ((checksum (gethash msg mh-index-msg-checksum-map)) | |
301 (pair (gethash checksum mh-index-checksum-origin-map)) | |
302 (ofolder (car pair)) | |
303 (omsg (cdr pair))) | |
304 (loop for seq in (gethash omsg (gethash ofolder seq-hash)) | |
305 do (if (assoc seq seq-list) | |
306 (push msg (cdr (assoc seq seq-list))) | |
307 (push (list seq msg) seq-list))))) | |
308 (loop for seq in seq-list | |
309 do (apply #'mh-exec-cmd "mark" mh-current-folder | |
310 "-sequence" (symbol-name (car seq)) "-add" | |
311 (mapcar #'(lambda (x) (format "%s" x)) (cdr seq)))))) | |
312 | |
313 (defvar mh-flists-results-folder "sequence" | |
265 "Subfolder for `mh-index-folder' where flists output is placed.") | 314 "Subfolder for `mh-index-folder' where flists output is placed.") |
315 (defvar mh-flists-sequence) | |
316 (defvar mh-flists-called-flag nil) | |
266 | 317 |
267 (defun mh-index-generate-pretty-name (string) | 318 (defun mh-index-generate-pretty-name (string) |
268 "Given STRING generate a name which is suitable for use as a folder name. | 319 "Given STRING generate a name which is suitable for use as a folder name. |
269 White space from the beginning and end are removed. All spaces in the name are | 320 White space from the beginning and end are removed. All spaces in the name are |
270 replaced with underscores and all / are replaced with $. If STRING is longer | 321 replaced with underscores and all / are replaced with $. If STRING is longer |
291 (subst-char-in-region (point-min) (point-max) ?\t ?_ t) | 342 (subst-char-in-region (point-min) (point-max) ?\t ?_ t) |
292 (subst-char-in-region (point-min) (point-max) ?\n ?_ t) | 343 (subst-char-in-region (point-min) (point-max) ?\n ?_ t) |
293 (subst-char-in-region (point-min) (point-max) ?\r ?_ t) | 344 (subst-char-in-region (point-min) (point-max) ?\r ?_ t) |
294 (subst-char-in-region (point-min) (point-max) ?/ ?$ t) | 345 (subst-char-in-region (point-min) (point-max) ?/ ?$ t) |
295 (let ((out (truncate-string-to-width (buffer-string) 20))) | 346 (let ((out (truncate-string-to-width (buffer-string) 20))) |
296 (cond ((eq mh-indexer 'flists) mh-flists-results-folder) | 347 (cond ((eq mh-indexer 'flists) |
348 (format "%s/%s" mh-flists-results-folder mh-flists-sequence)) | |
297 ((equal out mh-flists-results-folder) (concat out "1")) | 349 ((equal out mh-flists-results-folder) (concat out "1")) |
298 (t out))))) | 350 (t out))))) |
299 | 351 |
300 ;;;###mh-autoload | 352 ;;;###mh-autoload |
301 (defun* mh-index-search (redo-search-flag folder search-regexp | 353 (defun* mh-index-search (redo-search-flag folder search-regexp |
302 &optional window-config unseen-flag) | 354 &optional window-config) |
303 "Perform an indexed search in an MH mail folder. | 355 "Perform an indexed search in an MH mail folder. |
304 Use a prefix argument to repeat the search, as in REDO-SEARCH-FLAG below. | 356 Use a prefix argument to repeat the search, as in REDO-SEARCH-FLAG below. |
305 | 357 |
306 If REDO-SEARCH-FLAG is non-nil and the current folder buffer was generated by a | 358 If REDO-SEARCH-FLAG is non-nil and the current folder buffer was generated by a |
307 index search, then the search is repeated. Otherwise, FOLDER is searched with | 359 index search, then the search is repeated. Otherwise, FOLDER is searched with |
308 SEARCH-REGEXP and the results are presented in an MH-E folder. If FOLDER is | 360 SEARCH-REGEXP and the results are presented in an MH-E folder. If FOLDER is |
309 \"+\" then mail in all folders are searched. Optional argument WINDOW-CONFIG | 361 \"+\" then mail in all folders are searched. Optional argument WINDOW-CONFIG |
310 stores the window configuration that will be restored after the user quits the | 362 stores the window configuration that will be restored after the user quits the |
311 folder containing the index search results. If optional argument UNSEEN-FLAG | 363 folder containing the index search results. |
312 is non-nil, then all the messages are marked as unseen. | |
313 | 364 |
314 Four indexing programs are supported; if none of these are present, then grep | 365 Four indexing programs are supported; if none of these are present, then grep |
315 is used. This function picks the first program that is available on your | 366 is used. This function picks the first program that is available on your |
316 system. If you would prefer to use a different program, set the customization | 367 system. If you would prefer to use a different program, set the customization |
317 variable `mh-index-program' accordingly. | 368 variable `mh-index-program' accordingly. |
342 This has the effect of renaming already present X-MHE-Checksum headers." | 393 This has the effect of renaming already present X-MHE-Checksum headers." |
343 (interactive | 394 (interactive |
344 (list current-prefix-arg | 395 (list current-prefix-arg |
345 (progn | 396 (progn |
346 (unless mh-find-path-run (mh-find-path)) | 397 (unless mh-find-path-run (mh-find-path)) |
347 (or (and current-prefix-arg (car mh-index-previous-search)) | 398 (or (and current-prefix-arg mh-index-sequence-search-flag) |
399 (and current-prefix-arg (car mh-index-previous-search)) | |
348 (mh-prompt-for-folder "Search" "+" nil "all" t))) | 400 (mh-prompt-for-folder "Search" "+" nil "all" t))) |
349 (progn | 401 (progn |
350 ;; Yes, we do want to call mh-index-choose every time in case the | 402 ;; Yes, we do want to call mh-index-choose every time in case the |
351 ;; user has switched the indexer manually. | 403 ;; user has switched the indexer manually. |
352 (unless (mh-index-choose) (error "No indexing program found")) | 404 (unless (mh-index-choose) (error "No indexing program found")) |
358 (if (and (not | 410 (if (and (not |
359 (and current-prefix-arg (cadr mh-index-previous-search))) | 411 (and current-prefix-arg (cadr mh-index-previous-search))) |
360 mh-index-regexp-builder) | 412 mh-index-regexp-builder) |
361 (current-window-configuration) | 413 (current-window-configuration) |
362 nil))) | 414 nil))) |
415 ;; Redoing a sequence search? | |
416 (when (and redo-search-flag mh-index-data mh-index-sequence-search-flag | |
417 (not mh-flists-called-flag)) | |
418 (let ((mh-flists-called-flag t)) | |
419 (apply #'mh-index-sequenced-messages mh-index-previous-search)) | |
420 (return-from mh-index-search)) | |
421 ;; We have fancy query parsing | |
363 (when (symbolp search-regexp) | 422 (when (symbolp search-regexp) |
364 (mh-search-folder folder window-config) | 423 (mh-search-folder folder window-config) |
365 (setq mh-searching-function 'mh-index-do-search) | 424 (setq mh-searching-function 'mh-index-do-search) |
366 (return-from mh-index-search)) | 425 (return-from mh-index-search)) |
367 (mh-checksum-choose) | 426 (mh-checksum-choose) |
399 (gethash (car next-result) folder-results-map)) | 458 (gethash (car next-result) folder-results-map)) |
400 t))) | 459 t))) |
401 | 460 |
402 ;; Copy the search results over | 461 ;; Copy the search results over |
403 (maphash #'(lambda (folder msgs) | 462 (maphash #'(lambda (folder msgs) |
404 (let ((msgs (sort (loop for msg being the hash-keys of msgs | 463 (let ((cur (car (mh-translate-range folder "cur"))) |
464 (msgs (sort (loop for msg being the hash-keys of msgs | |
405 collect msg) | 465 collect msg) |
406 #'<))) | 466 #'<))) |
407 (mh-exec-cmd "refile" msgs "-src" folder | 467 (mh-exec-cmd "refile" msgs "-src" folder |
408 "-link" index-folder) | 468 "-link" index-folder) |
469 ;; Restore cur to old value, that refile changed | |
470 (when cur | |
471 (mh-exec-cmd-quiet nil "mark" folder "-add" "-zero" | |
472 "-sequence" "cur" (format "%s" cur))) | |
409 (loop for msg in msgs | 473 (loop for msg in msgs |
410 do (incf result-count) | 474 do (incf result-count) |
411 (setf (gethash result-count origin-map) | 475 (setf (gethash result-count origin-map) |
412 (cons folder msg))))) | 476 (cons folder msg))))) |
413 folder-results-map) | 477 folder-results-map) |
414 | 478 |
415 ;; Mark messages as unseen (if needed) | 479 ;; Vist the results folder |
416 (when (and unseen-flag (> result-count 0)) | |
417 (mh-exec-cmd "mark" index-folder "all" | |
418 "-sequence" (symbol-name mh-unseen-seq) "-add")) | |
419 | |
420 ;; Generate scan lines for the hits. | |
421 (mh-visit-folder index-folder () (list folder-results-map origin-map)) | 480 (mh-visit-folder index-folder () (list folder-results-map origin-map)) |
422 | 481 |
423 (goto-char (point-min)) | 482 (goto-char (point-min)) |
424 (forward-line) | 483 (forward-line) |
425 (mh-update-sequences) | 484 (mh-update-sequences) |
426 (mh-recenter nil) | 485 (mh-recenter nil) |
427 | 486 |
487 ;; Update the speedbar, if needed | |
488 (when (mh-speed-flists-active-p) | |
489 (mh-speed-flists t mh-current-folder)) | |
490 | |
428 ;; Maintain history | 491 ;; Maintain history |
429 (when (or (and redo-search-flag previous-search) window-config) | 492 (when (or (and redo-search-flag previous-search) window-config) |
430 (setq mh-previous-window-config old-window-config)) | 493 (setq mh-previous-window-config old-window-config)) |
431 (setq mh-index-previous-search (list folder search-regexp)) | 494 (setq mh-index-previous-search (list folder search-regexp)) |
495 | |
496 ;; Write out data to disk | |
497 (unless mh-flists-called-flag (mh-index-write-data)) | |
432 | 498 |
433 (message "%s found %s matches in %s folders" | 499 (message "%s found %s matches in %s folders" |
434 (upcase-initials (symbol-name mh-indexer)) | 500 (upcase-initials (symbol-name mh-indexer)) |
435 (loop for msg-hash being hash-values of mh-index-data | 501 (loop for msg-hash being hash-values of mh-index-data |
436 sum (hash-table-count msg-hash)) | 502 sum (hash-table-count msg-hash)) |
437 (loop for msg-hash being hash-values of mh-index-data | 503 (loop for msg-hash being hash-values of mh-index-data |
438 count (> (hash-table-count msg-hash) 0)))))) | 504 count (> (hash-table-count msg-hash) 0)))))) |
505 | |
506 | |
507 | |
508 ;;; Functions to serialize index data... | |
509 | |
510 (defun mh-index-write-data () | |
511 "Write index data to file." | |
512 (ignore-errors | |
513 (unless (eq major-mode 'mh-folder-mode) | |
514 (error "Can't be called from folder in `%s'" major-mode)) | |
515 (let ((data mh-index-data) | |
516 (msg-checksum-map mh-index-msg-checksum-map) | |
517 (checksum-origin-map mh-index-checksum-origin-map) | |
518 (previous-search mh-index-previous-search) | |
519 (sequence-search-flag mh-index-sequence-search-flag) | |
520 (outfile (concat buffer-file-name mh-index-data-file)) | |
521 (print-length nil) | |
522 (print-level nil)) | |
523 (with-temp-file outfile | |
524 (mh-index-write-hashtable | |
525 data (lambda (x) (loop for y being the hash-keys of x collect y))) | |
526 (mh-index-write-hashtable msg-checksum-map #'identity) | |
527 (mh-index-write-hashtable checksum-origin-map #'identity) | |
528 (pp previous-search (current-buffer)) (insert "\n") | |
529 (pp sequence-search-flag (current-buffer)) (insert "\n"))))) | |
530 | |
531 ;;;###mh-autoload | |
532 (defun mh-index-read-data () | |
533 "Read index data from file." | |
534 (ignore-errors | |
535 (unless (eq major-mode 'mh-folder-mode) | |
536 (error "Can't be called from folder in `%s'" major-mode)) | |
537 (let ((infile (concat buffer-file-name mh-index-data-file)) | |
538 t1 t2 t3 t4 t5) | |
539 (with-temp-buffer | |
540 (insert-file-contents-literally infile) | |
541 (goto-char (point-min)) | |
542 (setq t1 (mh-index-read-hashtable | |
543 (lambda (data) | |
544 (loop with table = (make-hash-table :test #'equal) | |
545 for x in data do (setf (gethash x table) t) | |
546 finally return table))) | |
547 t2 (mh-index-read-hashtable #'identity) | |
548 t3 (mh-index-read-hashtable #'identity) | |
549 t4 (read (current-buffer)) | |
550 t5 (read (current-buffer)))) | |
551 (setq mh-index-data t1 | |
552 mh-index-msg-checksum-map t2 | |
553 mh-index-checksum-origin-map t3 | |
554 mh-index-previous-search t4 | |
555 mh-index-sequence-search-flag t5)))) | |
556 | |
557 (defun mh-index-write-hashtable (table proc) | |
558 "Write TABLE to `current-buffer'. | |
559 PROC is used to serialize the values corresponding to the hash table keys." | |
560 (pp (loop for x being the hash-keys of table | |
561 collect (cons x (funcall proc (gethash x table)))) | |
562 (current-buffer)) | |
563 (insert "\n")) | |
564 | |
565 (defun mh-index-read-hashtable (proc) | |
566 "From BUFFER read a hash table serialized as a list. | |
567 PROC is used to convert the value to actual data." | |
568 (loop with table = (make-hash-table :test #'equal) | |
569 for pair in (read (current-buffer)) | |
570 do (setf (gethash (car pair) table) (funcall proc (cdr pair))) | |
571 finally return table)) | |
572 | |
573 ;;;###mh-autoload | |
574 (defun mh-index-p () | |
575 "Non-nil means that this folder was generated by an index search." | |
576 mh-index-data) | |
439 | 577 |
440 ;;;###mh-autoload | 578 ;;;###mh-autoload |
441 (defun mh-index-do-search () | 579 (defun mh-index-do-search () |
442 "Construct appropriate regexp and call `mh-index-search'." | 580 "Construct appropriate regexp and call `mh-index-search'." |
443 (interactive) | 581 (interactive) |
450 (error "No search terms")))) | 588 (error "No search terms")))) |
451 | 589 |
452 (defun mh-replace-string (old new) | 590 (defun mh-replace-string (old new) |
453 "Replace all occurrences of OLD with NEW in the current buffer." | 591 "Replace all occurrences of OLD with NEW in the current buffer." |
454 (goto-char (point-min)) | 592 (goto-char (point-min)) |
455 (while (search-forward old nil t) | 593 (let ((case-fold-search t)) |
456 (replace-match new))) | 594 (while (search-forward old nil t) |
595 (replace-match new t t)))) | |
457 | 596 |
458 ;;;###mh-autoload | 597 ;;;###mh-autoload |
459 (defun mh-index-parse-search-regexp (input-string) | 598 (defun mh-index-parse-search-regexp (input-string) |
460 "Construct parse tree for INPUT-STRING. | 599 "Construct parse tree for INPUT-STRING. |
461 All occurrences of &, |, ! and ~ in INPUT-STRING are replaced by AND, OR and | 600 All occurrences of &, |, ! and ~ in INPUT-STRING are replaced by AND, OR and |
462 NOT as appropriate. Then the resulting string is parsed." | 601 NOT as appropriate. Then the resulting string is parsed." |
463 (let (input) | 602 (let (input) |
464 (with-temp-buffer | 603 (with-temp-buffer |
465 (insert input-string) | 604 (insert input-string) |
466 (downcase-region (point-min) (point-max)) | |
467 ;; replace tabs | 605 ;; replace tabs |
468 (mh-replace-string "\t" " ") | 606 (mh-replace-string "\t" " ") |
469 ;; synonyms of AND | 607 ;; synonyms of AND |
608 (mh-replace-string " AND " " and ") | |
470 (mh-replace-string "&" " and ") | 609 (mh-replace-string "&" " and ") |
471 (mh-replace-string " -and " " and ") | 610 (mh-replace-string " -and " " and ") |
472 ;; synonyms of OR | 611 ;; synonyms of OR |
612 (mh-replace-string " OR " " or ") | |
473 (mh-replace-string "|" " or ") | 613 (mh-replace-string "|" " or ") |
474 (mh-replace-string " -or " " or ") | 614 (mh-replace-string " -or " " or ") |
475 ;; synonyms of NOT | 615 ;; synonyms of NOT |
616 (mh-replace-string " NOT " " not ") | |
476 (mh-replace-string "!" " not ") | 617 (mh-replace-string "!" " not ") |
477 (mh-replace-string "~" " not ") | 618 (mh-replace-string "~" " not ") |
478 (mh-replace-string " -not " " not ") | 619 (mh-replace-string " -not " " not ") |
479 ;; synonyms of left brace | 620 ;; synonyms of left brace |
480 (mh-replace-string "(" " ( ") | 621 (mh-replace-string "(" " ( ") |
496 ((equal token "and") (push 'and op-stack)) | 637 ((equal token "and") (push 'and op-stack)) |
497 ((equal token ")") | 638 ((equal token ")") |
498 (multiple-value-setq (op-stack operand-stack) | 639 (multiple-value-setq (op-stack operand-stack) |
499 (mh-index-evaluate op-stack operand-stack)) | 640 (mh-index-evaluate op-stack operand-stack)) |
500 (when (eq (car op-stack) 'not) | 641 (when (eq (car op-stack) 'not) |
501 (pop op-stack) | 642 (setq op-stack (cdr op-stack)) |
502 (push `(not ,(pop operand-stack)) operand-stack)) | 643 (push `(not ,(pop operand-stack)) operand-stack)) |
503 (when (eq (car op-stack) 'and) | 644 (when (eq (car op-stack) 'and) |
504 (pop op-stack) | 645 (setq op-stack (cdr op-stack)) |
505 (setq oper1 (pop operand-stack)) | 646 (setq oper1 (pop operand-stack)) |
506 (push `(and ,(pop operand-stack) ,oper1) operand-stack))) | 647 (push `(and ,(pop operand-stack) ,oper1) operand-stack))) |
507 ((eq (car op-stack) 'not) | 648 ((eq (car op-stack) 'not) |
508 (pop op-stack) | 649 (setq op-stack (cdr op-stack)) |
509 (push `(not ,token) operand-stack) | 650 (push `(not ,token) operand-stack) |
510 (when (eq (car op-stack) 'and) | 651 (when (eq (car op-stack) 'and) |
511 (pop op-stack) | 652 (setq op-stack (cdr op-stack)) |
512 (setq oper1 (pop operand-stack)) | 653 (setq oper1 (pop operand-stack)) |
513 (push `(and ,(pop operand-stack) ,oper1) operand-stack))) | 654 (push `(and ,(pop operand-stack) ,oper1) operand-stack))) |
514 ((eq (car op-stack) 'and) | 655 ((eq (car op-stack) 'and) |
515 (pop op-stack) | 656 (setq op-stack (cdr op-stack)) |
516 (push `(and ,(pop operand-stack) ,token) operand-stack)) | 657 (push `(and ,(pop operand-stack) ,token) operand-stack)) |
517 (t (push token operand-stack)))) | 658 (t (push token operand-stack)))) |
518 (prog1 (pop operand-stack) | 659 (prog1 (pop operand-stack) |
519 (when (or op-stack operand-stack) | 660 (when (or op-stack operand-stack) |
520 (error "Invalid regexp: %s" input)))))) | 661 (error "Invalid regexp: %s" input)))))) |
630 (goto-char (point-min)) | 771 (goto-char (point-min)) |
631 (while (not (eobp)) | 772 (while (not (eobp)) |
632 (setq current-folder (car (gethash (gethash (mh-get-msg-num nil) | 773 (setq current-folder (car (gethash (gethash (mh-get-msg-num nil) |
633 mh-index-msg-checksum-map) | 774 mh-index-msg-checksum-map) |
634 mh-index-checksum-origin-map))) | 775 mh-index-checksum-origin-map))) |
635 (when (and current-folder (not (eq current-folder last-folder))) | 776 (when (and current-folder (not (equal current-folder last-folder))) |
636 (insert (if last-folder "\n" "") current-folder "\n") | 777 (insert (if last-folder "\n" "") current-folder "\n") |
637 (setq last-folder current-folder)) | 778 (setq last-folder current-folder)) |
638 (forward-line)) | 779 (forward-line)) |
639 (when cur-msg (mh-goto-msg cur-msg t)) | 780 (when cur-msg (mh-goto-msg cur-msg t)) |
640 (set-buffer-modified-p old-buffer-modified-flag))) | 781 (set-buffer-modified-p old-buffer-modified-flag))) |
644 "Partition the messages based on source folder. | 785 "Partition the messages based on source folder. |
645 Returns an alist with the the folder names in the car and the cdr being the | 786 Returns an alist with the the folder names in the car and the cdr being the |
646 list of messages originally from that folder." | 787 list of messages originally from that folder." |
647 (save-excursion | 788 (save-excursion |
648 (goto-char (point-min)) | 789 (goto-char (point-min)) |
649 (let ((result-table (make-hash-table))) | 790 (let ((result-table (make-hash-table :test #'equal))) |
650 (loop for msg being hash-keys of mh-index-msg-checksum-map | 791 (loop for msg being hash-keys of mh-index-msg-checksum-map |
651 do (push msg (gethash (car (gethash | 792 do (push msg (gethash (car (gethash |
652 (gethash msg mh-index-msg-checksum-map) | 793 (gethash msg mh-index-msg-checksum-map) |
653 mh-index-checksum-origin-map)) | 794 mh-index-checksum-origin-map)) |
654 result-table))) | 795 result-table))) |
720 "-format" "%{x-mhe-checksum}\n" folder msg) | 861 "-format" "%{x-mhe-checksum}\n" folder msg) |
721 (goto-char (point-min)) | 862 (goto-char (point-min)) |
722 (string-equal (buffer-substring-no-properties (point) (line-end-position)) | 863 (string-equal (buffer-substring-no-properties (point) (line-end-position)) |
723 checksum))) | 864 checksum))) |
724 | 865 |
866 (defun mh-index-matching-source-msgs (msgs &optional delete-from-index-data) | |
867 "Return a table of original messages and folders for messages in MSGS. | |
868 If optional argument DELETE-FROM-INDEX-DATA is non-nil, then each of the | |
869 messages, whose counter-part is found in some source folder, is removed from | |
870 `mh-index-data'." | |
871 (let ((table (make-hash-table :test #'equal))) | |
872 (dolist (msg msgs) | |
873 (let* ((checksum (gethash msg mh-index-msg-checksum-map)) | |
874 (pair (gethash checksum mh-index-checksum-origin-map))) | |
875 (when (and checksum (car pair) (cdr pair) | |
876 (mh-index-match-checksum (cdr pair) (car pair) checksum)) | |
877 (push (cdr pair) (gethash (car pair) table)) | |
878 (when delete-from-index-data | |
879 (remhash (cdr pair) (gethash (car pair) mh-index-data)))))) | |
880 table)) | |
881 | |
725 ;;;###mh-autoload | 882 ;;;###mh-autoload |
726 (defun mh-index-execute-commands () | 883 (defun mh-index-execute-commands () |
727 "Delete/refile the actual messages. | 884 "Delete/refile the actual messages. |
728 The copies in the searched folder are then deleted/refiled to get the desired | 885 The copies in the searched folder are then deleted/refiled to get the desired |
729 result. Before deleting the messages we make sure that the message being | 886 result. Before deleting the messages we make sure that the message being |
730 deleted is identical to the one that the user has marked in the index buffer." | 887 deleted is identical to the one that the user has marked in the index buffer." |
731 (let ((message-table (make-hash-table :test #'equal))) | 888 (save-excursion |
732 (dolist (msg-list (cons mh-delete-list (mapcar #'cdr mh-refile-list))) | 889 (let ((folders ()) |
733 (dolist (msg msg-list) | 890 (mh-speed-flists-inhibit-flag t)) |
734 (let* ((checksum (gethash msg mh-index-msg-checksum-map)) | 891 (maphash |
735 (pair (gethash checksum mh-index-checksum-origin-map))) | 892 (lambda (folder msgs) |
736 (when (and checksum (car pair) (cdr pair) | 893 (push folder folders) |
737 (mh-index-match-checksum (cdr pair) (car pair) checksum)) | 894 (if (not (get-buffer folder)) |
738 (push (cdr pair) (gethash (car pair) message-table)) | 895 ;; If source folder not open, just delete the messages... |
739 (remhash (cdr pair) (gethash (car pair) mh-index-data)))))) | 896 (apply #'mh-exec-cmd "rmm" folder (mh-coalesce-msg-list msgs)) |
740 (maphash (lambda (folder msgs) | 897 ;; Otherwise delete the messages in the source buffer... |
741 (apply #'mh-exec-cmd "rmm" folder (mh-coalesce-msg-list msgs))) | 898 (save-excursion |
742 message-table))) | 899 (set-buffer folder) |
900 (let ((old-refile-list mh-refile-list) | |
901 (old-delete-list mh-delete-list)) | |
902 (setq mh-refile-list nil | |
903 mh-delete-list msgs) | |
904 (unwind-protect (mh-execute-commands) | |
905 (setq mh-refile-list | |
906 (mapcar (lambda (x) | |
907 (cons (car x) | |
908 (loop for y in (cdr x) | |
909 unless (memq y msgs) collect y))) | |
910 old-refile-list) | |
911 mh-delete-list | |
912 (loop for x in old-delete-list | |
913 unless (memq x msgs) collect x)) | |
914 (mh-set-folder-modified-p (mh-outstanding-commands-p)) | |
915 (when (mh-outstanding-commands-p) | |
916 (mh-notate-deleted-and-refiled))))))) | |
917 (mh-index-matching-source-msgs (append (loop for x in mh-refile-list | |
918 append (cdr x)) | |
919 mh-delete-list) | |
920 t)) | |
921 folders))) | |
922 | |
923 ;;;###mh-autoload | |
924 (defun mh-index-add-to-sequence (seq msgs) | |
925 "Add to SEQ the messages in the list MSGS. | |
926 This function updates the source folder sequences. Also makes an attempt to | |
927 update the source folder buffer if we have it open." | |
928 ;; Don't need to do anything for cur | |
929 (save-excursion | |
930 (when (and (not (memq seq (mh-unpropagated-sequences))) | |
931 (mh-valid-seq-p seq)) | |
932 (let ((folders ()) | |
933 (mh-speed-flists-inhibit-flag t)) | |
934 (maphash (lambda (folder msgs) | |
935 (push folder folders) | |
936 ;; Add messages to sequence in source folder... | |
937 (apply #'mh-exec-cmd-quiet nil "mark" folder | |
938 "-add" "-nozero" "-sequence" (symbol-name seq) | |
939 (mapcar (lambda (x) (format "%s" x)) | |
940 (mh-coalesce-msg-list msgs))) | |
941 ;; Update source folder buffer if we have it open... | |
942 (when (get-buffer folder) | |
943 (save-excursion | |
944 (set-buffer folder) | |
945 (mh-put-msg-in-seq msgs seq)))) | |
946 (mh-index-matching-source-msgs msgs)) | |
947 folders)))) | |
948 | |
949 ;;;###mh-autoload | |
950 (defun mh-index-delete-from-sequence (seq msgs) | |
951 "Delete from SEQ the messages in MSGS. | |
952 This function updates the source folder sequences. Also makes an attempt to | |
953 update the source folder buffer if present." | |
954 (save-excursion | |
955 (when (and (not (memq seq (mh-unpropagated-sequences))) | |
956 (mh-valid-seq-p seq)) | |
957 (let ((folders ()) | |
958 (mh-speed-flists-inhibit-flag t)) | |
959 (maphash (lambda (folder msgs) | |
960 (push folder folders) | |
961 ;; Remove messages from sequence in source folder... | |
962 (apply #'mh-exec-cmd-quiet nil "mark" folder | |
963 "-del" "-nozero" "-sequence" (symbol-name seq) | |
964 (mapcar (lambda (x) (format "%s" x)) | |
965 (mh-coalesce-msg-list msgs))) | |
966 ;; Update source folder buffer if we have it open... | |
967 (when (get-buffer folder) | |
968 (save-excursion | |
969 (set-buffer folder) | |
970 (mh-delete-msg-from-seq msgs seq t)))) | |
971 (mh-index-matching-source-msgs msgs)) | |
972 folders)))) | |
743 | 973 |
744 | 974 |
745 | 975 |
746 ;; Glimpse interface | 976 ;; Glimpse interface |
747 | 977 |
1049 | 1279 |
1050 ;; Interface to unseen messages script | 1280 ;; Interface to unseen messages script |
1051 | 1281 |
1052 (defvar mh-flists-search-folders) | 1282 (defvar mh-flists-search-folders) |
1053 | 1283 |
1284 ;; XXX: This should probably be in mh-utils.el and used in other places where | |
1285 ;; MH-E calls out to /bin/sh. | |
1286 (defun mh-index-quote-for-shell (string) | |
1287 "Quote STRING for /bin/sh." | |
1288 (concat "\"" | |
1289 (loop for x across string | |
1290 concat (format (if (memq x '(?\\ ?` ?$)) "\\%c" "%c") x)) | |
1291 "\"")) | |
1292 | |
1054 (defun mh-flists-execute (&rest args) | 1293 (defun mh-flists-execute (&rest args) |
1055 "Search for unseen messages in `mh-flists-search-folders'. | 1294 "Execute flists. |
1056 If `mh-recursive-folders-flag' is t, then the folders are searched | 1295 Search for messages belonging to `mh-flists-sequence' in the folders |
1057 recursively. All parameters ARGS are ignored." | 1296 specified by `mh-flists-search-folders'. If `mh-recursive-folders-flag' is t, |
1297 then the folders are searched recursively. All parameters ARGS are ignored." | |
1058 (set-buffer (get-buffer-create mh-index-temp-buffer)) | 1298 (set-buffer (get-buffer-create mh-index-temp-buffer)) |
1059 (erase-buffer) | 1299 (erase-buffer) |
1060 (unless (executable-find "sh") | 1300 (unless (executable-find "sh") |
1061 (error "Didn't find sh")) | 1301 (error "Didn't find sh")) |
1062 (with-temp-buffer | 1302 (with-temp-buffer |
1063 (let ((unseen (symbol-name mh-unseen-seq))) | 1303 (let ((seq (symbol-name mh-flists-sequence))) |
1064 (insert "for folder in `flists " | 1304 (insert "for folder in `" (expand-file-name "flists" mh-progs) " " |
1065 (cond ((eq mh-flists-search-folders t) mh-inbox) | 1305 (cond ((eq mh-flists-search-folders t) |
1306 (mh-index-quote-for-shell mh-inbox)) | |
1066 ((eq mh-flists-search-folders nil) "") | 1307 ((eq mh-flists-search-folders nil) "") |
1067 ((listp mh-flists-search-folders) | 1308 ((listp mh-flists-search-folders) |
1068 (loop for folder in mh-flists-search-folders | 1309 (loop for folder in mh-flists-search-folders |
1069 concat (concat " " folder)))) | 1310 concat |
1311 (concat " " (mh-index-quote-for-shell folder))))) | |
1070 (if mh-recursive-folders-flag " -recurse" "") | 1312 (if mh-recursive-folders-flag " -recurse" "") |
1071 " -sequence " unseen " -noshowzero -fast` ; do\n" | 1313 " -sequence " seq " -noshowzero -fast` ; do\n" |
1072 "mhpath \"+$folder\" " unseen "\n" "done\n")) | 1314 (expand-file-name "mhpath" mh-progs) " \"+$folder\" " seq "\n" |
1315 "done\n")) | |
1073 (call-process-region | 1316 (call-process-region |
1074 (point-min) (point-max) "sh" nil (get-buffer mh-index-temp-buffer)))) | 1317 (point-min) (point-max) "sh" nil (get-buffer mh-index-temp-buffer)))) |
1075 | 1318 |
1076 ;;;###mh-autoload | 1319 ;;;###mh-autoload |
1077 (defun mh-index-new-messages (folders) | 1320 (defun mh-index-sequenced-messages (folders sequence) |
1078 "Display new messages. | 1321 "Display messages from FOLDERS in SEQUENCE. |
1079 All messages in the `mh-unseen-seq' sequence from FOLDERS are displayed. | |
1080 By default the folders specified by `mh-index-new-messages-folders' are | 1322 By default the folders specified by `mh-index-new-messages-folders' are |
1081 searched. With a prefix argument, enter a space-separated list of folders, or | 1323 searched. With a prefix argument, enter a space-separated list of folders, or |
1082 nothing to search all folders." | 1324 nothing to search all folders. |
1325 | |
1326 Argument SEQUENCE defaults to `mh-unseen-seq' and is the sequence that the | |
1327 function searches for in each of the FOLDERS. With a prefix argument, enter a | |
1328 sequence to use." | |
1083 (interactive | 1329 (interactive |
1084 (list (if current-prefix-arg | 1330 (list (if current-prefix-arg |
1085 (split-string (read-string "Folders to search: ")) | 1331 (split-string (read-string "Search folder(s) [all]? ")) |
1086 mh-index-new-messages-folders))) | 1332 mh-index-new-messages-folders) |
1333 (mh-read-seq-default "Search" nil))) | |
1334 (unless sequence (setq sequence mh-unseen-seq)) | |
1087 (let* ((mh-flists-search-folders folders) | 1335 (let* ((mh-flists-search-folders folders) |
1336 (mh-flists-sequence sequence) | |
1337 (mh-flists-called-flag t) | |
1088 (mh-indexer 'flists) | 1338 (mh-indexer 'flists) |
1089 (mh-index-execute-search-function 'mh-flists-execute) | 1339 (mh-index-execute-search-function 'mh-flists-execute) |
1090 (mh-index-next-result-function 'mh-mairix-next-result) | 1340 (mh-index-next-result-function 'mh-mairix-next-result) |
1091 (mh-mairix-folder mh-user-path) | 1341 (mh-mairix-folder mh-user-path) |
1092 (mh-index-regexp-builder nil) | 1342 (mh-index-regexp-builder nil) |
1093 (new-folder (format "%s/%s" mh-index-folder mh-flists-results-folder)) | 1343 (new-folder (format "%s/%s/%s" mh-index-folder |
1344 mh-flists-results-folder sequence)) | |
1094 (window-config (if (equal new-folder mh-current-folder) | 1345 (window-config (if (equal new-folder mh-current-folder) |
1095 mh-previous-window-config | 1346 mh-previous-window-config |
1096 (current-window-configuration))) | 1347 (current-window-configuration))) |
1097 (redo-flag nil)) | 1348 (redo-flag nil) |
1349 message) | |
1098 (cond ((buffer-live-p (get-buffer new-folder)) | 1350 (cond ((buffer-live-p (get-buffer new-folder)) |
1099 ;; The destination folder is being visited. Trick `mh-index-search' | 1351 ;; The destination folder is being visited. Trick `mh-index-search' |
1100 ;; into thinking that the folder was the result of a previous search. | 1352 ;; into thinking that the folder resulted from a previous search. |
1101 (set-buffer new-folder) | 1353 (set-buffer new-folder) |
1102 (setq mh-index-previous-search (list "+" mh-flists-results-folder)) | 1354 (setq mh-index-previous-search (list folders sequence)) |
1103 (setq redo-flag t)) | 1355 (setq redo-flag t)) |
1104 ((mh-folder-exists-p new-folder) | 1356 ((mh-folder-exists-p new-folder) |
1105 ;; Folder exists but we don't have it open. That means they are | 1357 ;; Folder exists but we don't have it open. That means they are |
1106 ;; stale results from a old flists search. Clear it out. | 1358 ;; stale results from a old flists search. Clear it out. |
1107 (mh-exec-cmd-quiet nil "rmf" new-folder))) | 1359 (mh-exec-cmd-quiet nil "rmf" new-folder))) |
1108 (mh-index-search redo-flag "+" mh-flists-results-folder window-config t))) | 1360 (setq message (mh-index-search redo-flag "+" mh-flists-results-folder |
1361 window-config) | |
1362 mh-index-sequence-search-flag t | |
1363 mh-index-previous-search (list folders sequence)) | |
1364 (mh-index-write-data) | |
1365 (when (stringp message) (message message)))) | |
1366 | |
1367 ;;;###mh-autoload | |
1368 (defun mh-index-new-messages (folders) | |
1369 "Display unseen messages. | |
1370 All messages in the `unseen' sequence from FOLDERS are displayed. | |
1371 By default the folders specified by `mh-index-new-messages-folders' | |
1372 are searched. With a prefix argument, enter a space-separated list of | |
1373 folders, or nothing to search all folders." | |
1374 (interactive | |
1375 (list (if current-prefix-arg | |
1376 (split-string (read-string "Search folder(s) [all]? ")) | |
1377 mh-index-new-messages-folders))) | |
1378 (mh-index-sequenced-messages folders mh-unseen-seq)) | |
1379 | |
1380 ;;;###mh-autoload | |
1381 (defun mh-index-ticked-messages (folders) | |
1382 "Display ticked messages. | |
1383 All messages in the `tick' sequence from FOLDERS are displayed. | |
1384 By default the folders specified by `mh-index-ticked-messages-folders' | |
1385 are searched. With a prefix argument, enter a space-separated list of | |
1386 folders, or nothing to search all folders." | |
1387 (interactive | |
1388 (list (if current-prefix-arg | |
1389 (split-string (read-string "Search folder(s) [all]? ")) | |
1390 mh-index-ticked-messages-folders))) | |
1391 (mh-index-sequenced-messages folders mh-tick-seq)) | |
1109 | 1392 |
1110 | 1393 |
1111 | 1394 |
1112 ;; Swish interface | 1395 ;; Swish interface |
1113 | 1396 |