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