comparison lisp/mh-e/mh-index.el @ 50702:7dd3d5eae9c7

Upgraded to MH-E version 7.3. See etc/MH-E-NEWS and lisp/mh-e/ChangeLog for details.
author Bill Wohler <wohler@newt.com>
date Fri, 25 Apr 2003 05:52:00 +0000
parents 0d8b17d428b5
children 695cf19ef79e
comparison
equal deleted inserted replaced
50701:cb5f0a5d5b36 50702:7dd3d5eae9c7
1 ;;; mh-index -- MH-E interface to indexing programs 1 ;;; mh-index -- MH-E interface to indexing programs
2 2
3 ;; Copyright (C) 2002 Free Software Foundation, Inc. 3 ;; Copyright (C) 2002, 2003 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
27 ;;; Commentary: 27 ;;; Commentary:
28 28
29 ;;; (1) The following search engines are supported: 29 ;;; (1) The following search engines are supported:
30 ;;; swish++ 30 ;;; swish++
31 ;;; swish-e 31 ;;; swish-e
32 ;;; mairix
32 ;;; namazu 33 ;;; namazu
33 ;;; glimpse 34 ;;; glimpse
34 ;;; grep 35 ;;; grep
35 ;;; 36 ;;;
36 ;;; (2) To use this package, you first have to build an index. Please read 37 ;;; (2) To use this package, you first have to build an index. Please read
37 ;;; the documentation for `mh-index-search' to get started. That 38 ;;; the documentation for `mh-index-search' to get started. That
38 ;;; documentation will direct you to the specific instructions for your 39 ;;; documentation will direct you to the specific instructions for your
39 ;;; particular indexer. 40 ;;; particular indexer.
40 41
41 ;;; Change Log: 42 ;;; Change Log:
42
43 ;; $Id: mh-index.el,v 1.2 2003/02/03 20:55:30 wohler Exp $
44 43
45 ;;; Code: 44 ;;; Code:
46 45
47 (require 'cl) 46 (require 'cl)
48 (require 'mh-e) 47 (require 'mh-e)
163 "Partial imitation of xargs. 162 "Partial imitation of xargs.
164 The current buffer contains a list of strings, one on each line. The function 163 The current buffer contains a list of strings, one on each line. The function
165 will execute CMD with ARGS and pass the first `mh-index-max-cmdline-args' 164 will execute CMD with ARGS and pass the first `mh-index-max-cmdline-args'
166 strings to it. This is repeated till all the strings have been used." 165 strings to it. This is repeated till all the strings have been used."
167 (goto-char (point-min)) 166 (goto-char (point-min))
168 (let ((out (get-buffer-create " *mh-xargs-output*"))) 167 (let ((current-buffer (current-buffer)))
169 (save-excursion 168 (with-temp-buffer
170 (set-buffer out) 169 (let ((out (current-buffer)))
171 (erase-buffer)) 170 (set-buffer current-buffer)
172 (while (not (eobp)) 171 (while (not (eobp))
173 (let ((arg-list (reverse args)) 172 (let ((arg-list (reverse args))
174 (count 0)) 173 (count 0))
175 (while (and (not (eobp)) (< count mh-index-max-cmdline-args)) 174 (while (and (not (eobp)) (< count mh-index-max-cmdline-args))
176 (push (buffer-substring-no-properties (point) (line-end-position)) 175 (push (buffer-substring-no-properties (point) (line-end-position))
177 arg-list) 176 arg-list)
178 (incf count) 177 (incf count)
179 (forward-line)) 178 (forward-line))
180 (apply #'call-process cmd nil (list out nil) nil (nreverse arg-list)))) 179 (apply #'call-process cmd nil (list out nil) nil
181 (erase-buffer) 180 (nreverse arg-list))))
182 (insert-buffer-substring out))) 181 (erase-buffer)
182 (insert-buffer-substring out)))))
183 183
184 184
185 185
186 (defun mh-index-update-single-msg (msg checksum origin-map) 186 (defun mh-index-update-single-msg (msg checksum origin-map)
187 "Update various maps for one message. 187 "Update various maps for one message.
228 (while (not (eobp)) 228 (while (not (eobp))
229 (setq msg (buffer-substring-no-properties 229 (setq msg (buffer-substring-no-properties
230 (point) (line-end-position))) 230 (point) (line-end-position)))
231 (forward-line) 231 (forward-line)
232 (save-excursion 232 (save-excursion
233 (cond ((eolp) 233 (cond ((not (string-match "^[0-9]*$" msg)))
234 ((eolp)
234 ;; need to compute checksum 235 ;; need to compute checksum
235 (set-buffer mh-checksum-buffer) 236 (set-buffer mh-checksum-buffer)
236 (insert mh-user-path (substring folder 1) "/" msg "\n")) 237 (insert mh-user-path (substring folder 1) "/" msg "\n"))
237 (t 238 (t
238 ;; update maps 239 ;; update maps
258 (save-excursion 259 (save-excursion
259 (set-buffer folder) 260 (set-buffer folder)
260 (mh-index-update-single-msg msg checksum origin-map))) 261 (mh-index-update-single-msg msg checksum origin-map)))
261 (forward-line)))))) 262 (forward-line))))))
262 263
264 (defvar mh-flists-results-folder "new"
265 "Subfolder for `mh-index-folder' where flists output is placed.")
266
263 (defun mh-index-generate-pretty-name (string) 267 (defun mh-index-generate-pretty-name (string)
264 "Given STRING generate a name which is suitable for use as a folder name. 268 "Given STRING generate a name which is suitable for use as a folder name.
265 White space from the beginning and end are removed. All spaces in the name are 269 White space from the beginning and end are removed. All spaces in the name are
266 replaced with underscores and all / are replaced with $. If STRING is longer 270 replaced with underscores and all / are replaced with $. If STRING is longer
267 than 20 it is truncated too. STRING could be a list of strings in which case 271 than 20 it is truncated too. STRING could be a list of strings in which case
286 (subst-char-in-region (point-min) (point-max) ? ?_ t) 290 (subst-char-in-region (point-min) (point-max) ? ?_ t)
287 (subst-char-in-region (point-min) (point-max) ?\t ?_ t) 291 (subst-char-in-region (point-min) (point-max) ?\t ?_ t)
288 (subst-char-in-region (point-min) (point-max) ?\n ?_ t) 292 (subst-char-in-region (point-min) (point-max) ?\n ?_ t)
289 (subst-char-in-region (point-min) (point-max) ?\r ?_ t) 293 (subst-char-in-region (point-min) (point-max) ?\r ?_ t)
290 (subst-char-in-region (point-min) (point-max) ?/ ?$ t) 294 (subst-char-in-region (point-min) (point-max) ?/ ?$ t)
291 (truncate-string-to-width (buffer-substring (point-min) (point-max)) 20))) 295 (let ((out (truncate-string-to-width (buffer-string) 20)))
296 (cond ((eq mh-indexer 'flists) mh-flists-results-folder)
297 ((equal out mh-flists-results-folder) (concat out "1"))
298 (t out)))))
292 299
293 ;;;###mh-autoload 300 ;;;###mh-autoload
294 (defun* mh-index-search (redo-search-flag folder search-regexp 301 (defun* mh-index-search (redo-search-flag folder search-regexp
295 &optional window-config) 302 &optional window-config unseen-flag)
296 "Perform an indexed search in an MH mail folder. 303 "Perform an indexed search in an MH mail folder.
304 Use a prefix argument to repeat the search, as in REDO-SEARCH-FLAG below.
297 305
298 If REDO-SEARCH-FLAG is non-nil and the current folder buffer was generated by a 306 If REDO-SEARCH-FLAG is non-nil and the current folder buffer was generated by a
299 index search, then the search is repeated. Otherwise, FOLDER is searched with 307 index search, then the search is repeated. Otherwise, FOLDER is searched with
300 SEARCH-REGEXP and the results are presented in an MH-E folder. If FOLDER is 308 SEARCH-REGEXP and the results are presented in an MH-E folder. If FOLDER is
301 \"+\" then mail in all folders are searched. Optional argument WINDOW-CONFIG 309 \"+\" then mail in all folders are searched. Optional argument WINDOW-CONFIG
302 stores the window configuration that will be restored after the user quits the 310 stores the window configuration that will be restored after the user quits the
303 folder containing the index search results. 311 folder containing the index search results. If optional argument UNSEEN-FLAG
312 is non-nil, then all the messages are marked as unseen.
304 313
305 Four indexing programs are supported; if none of these are present, then grep 314 Four indexing programs are supported; if none of these are present, then grep
306 is used. This function picks the first program that is available on your 315 is used. This function picks the first program that is available on your
307 system. If you would prefer to use a different program, set the customization 316 system. If you would prefer to use a different program, set the customization
308 variable `mh-index-program' accordingly. 317 variable `mh-index-program' accordingly.
379 388
380 ;; Parse indexer output 389 ;; Parse indexer output
381 (message "Processing %s output... " mh-indexer) 390 (message "Processing %s output... " mh-indexer)
382 (goto-char (point-min)) 391 (goto-char (point-min))
383 (loop for next-result = (funcall mh-index-next-result-function) 392 (loop for next-result = (funcall mh-index-next-result-function)
384 when (null next-result) return nil 393 while next-result
385 do (unless (eq next-result 'error) 394 do (unless (eq next-result 'error)
386 (unless (gethash (car next-result) folder-results-map) 395 (unless (gethash (car next-result) folder-results-map)
387 (setf (gethash (car next-result) folder-results-map) 396 (setf (gethash (car next-result) folder-results-map)
388 (make-hash-table :test #'equal))) 397 (make-hash-table :test #'equal)))
389 (setf (gethash (cadr next-result) 398 (setf (gethash (cadr next-result)
401 do (incf result-count) 410 do (incf result-count)
402 (setf (gethash result-count origin-map) 411 (setf (gethash result-count origin-map)
403 (cons folder msg))))) 412 (cons folder msg)))))
404 folder-results-map) 413 folder-results-map)
405 414
415 ;; Mark messages as unseen (if needed)
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
406 ;; Generate scan lines for the hits. 420 ;; Generate scan lines for the hits.
407 (let ((mh-show-threads-flag nil)) 421 (mh-visit-folder index-folder () (list folder-results-map origin-map))
408 (mh-visit-folder index-folder () (list folder-results-map origin-map)))
409 422
410 (goto-char (point-min)) 423 (goto-char (point-min))
411 (forward-line) 424 (forward-line)
412 (mh-update-sequences) 425 (mh-update-sequences)
413 (mh-recenter nil) 426 (mh-recenter nil)
546 "Jump to the next folder marker. 559 "Jump to the next folder marker.
547 The function is only applicable to folders displaying index search results. 560 The function is only applicable to folders displaying index search results.
548 With non-nil optional argument BACKWARD-FLAG, jump to the previous group of 561 With non-nil optional argument BACKWARD-FLAG, jump to the previous group of
549 results." 562 results."
550 (interactive "P") 563 (interactive "P")
551 (if (or (null mh-index-data) 564 (if (null mh-index-data)
552 (memq 'unthread mh-view-ops)) 565 (message "Only applicable in an MH-E index search buffer")
553 (message "Only applicable in an unthreaded MH-E index search buffer")
554 (let ((point (point))) 566 (let ((point (point)))
555 (forward-line (if backward-flag -1 1)) 567 (forward-line (if backward-flag -1 1))
556 (cond ((if backward-flag 568 (cond ((if backward-flag
557 (re-search-backward "^+" (point-min) t) 569 (re-search-backward "^+" (point-min) t)
558 (re-search-forward "^+" (point-max) t)) 570 (re-search-forward "^+" (point-max) t))
626 (forward-line)) 638 (forward-line))
627 (when cur-msg (mh-goto-msg cur-msg t)) 639 (when cur-msg (mh-goto-msg cur-msg t))
628 (set-buffer-modified-p old-buffer-modified-flag))) 640 (set-buffer-modified-p old-buffer-modified-flag)))
629 641
630 ;;;###mh-autoload 642 ;;;###mh-autoload
643 (defun mh-index-group-by-folder ()
644 "Partition the messages based on source folder.
645 Returns an alist with the the folder names in the car and the cdr being the
646 list of messages originally from that folder."
647 (save-excursion
648 (goto-char (point-min))
649 (let ((result-table (make-hash-table)))
650 (loop for msg being hash-keys of mh-index-msg-checksum-map
651 do (push msg (gethash (car (gethash
652 (gethash msg mh-index-msg-checksum-map)
653 mh-index-checksum-origin-map))
654 result-table)))
655 (loop for x being the hash-keys of result-table
656 collect (cons x (nreverse (gethash x result-table)))))))
657
658 ;;;###mh-autoload
631 (defun mh-index-delete-folder-headers () 659 (defun mh-index-delete-folder-headers ()
632 "Delete the folder headers." 660 "Delete the folder headers."
633 (let ((cur-msg (mh-get-msg-num nil)) 661 (let ((cur-msg (mh-get-msg-num nil))
634 (old-buffer-modified-flag (buffer-modified-p)) 662 (old-buffer-modified-flag (buffer-modified-p))
635 (buffer-read-only nil)) 663 (buffer-read-only nil))
660 (line-beginning-position) (line-end-position)))) 688 (line-beginning-position) (line-end-position))))
661 (t (setq msg (mh-get-msg-num t))))) 689 (t (setq msg (mh-get-msg-num t)))))
662 (when (not folder) 690 (when (not folder)
663 (setq folder (car (gethash (gethash msg mh-index-msg-checksum-map) 691 (setq folder (car (gethash (gethash msg mh-index-msg-checksum-map)
664 mh-index-checksum-origin-map)))) 692 mh-index-checksum-origin-map))))
665 (mh-visit-folder 693 (when (or (not (get-buffer folder))
666 folder (loop for x being the hash-keys of (gethash folder mh-index-data) 694 (y-or-n-p (format "Reuse buffer displaying %s? " folder)))
667 when (mh-msg-exists-p x folder) collect x)))) 695 (mh-visit-folder
696 folder (loop for x being the hash-keys of (gethash folder mh-index-data)
697 when (mh-msg-exists-p x folder) collect x)))))
698
699 ;;;###mh-autoload
700 (defun mh-index-update-unseen (msg)
701 "Remove counterpart of MSG in source folder from `mh-unseen-seq'.
702 Also `mh-update-unseen' is called in the original folder, if we have it open."
703 (let* ((checksum (gethash msg mh-index-msg-checksum-map))
704 (folder-msg-pair (gethash checksum mh-index-checksum-origin-map))
705 (orig-folder (car folder-msg-pair))
706 (orig-msg (cdr folder-msg-pair)))
707 (when (mh-index-match-checksum orig-msg orig-folder checksum)
708 (when (get-buffer orig-folder)
709 (save-excursion
710 (set-buffer orig-folder)
711 (unless (member orig-msg mh-seen-list) (push orig-msg mh-seen-list))
712 (mh-update-unseen)))
713 (mh-exec-cmd-daemon "mark" #'ignore orig-folder (format "%s" orig-msg)
714 "-sequence" (symbol-name mh-unseen-seq) "-del"))))
668 715
669 (defun mh-index-match-checksum (msg folder checksum) 716 (defun mh-index-match-checksum (msg folder checksum)
670 "Check if MSG in FOLDER has X-MHE-Checksum header value of CHECKSUM." 717 "Check if MSG in FOLDER has X-MHE-Checksum header value of CHECKSUM."
671 (with-temp-buffer 718 (with-temp-buffer
672 (mh-exec-cmd-output mh-scan-prog nil "-width" "80" 719 (mh-exec-cmd-output mh-scan-prog nil "-width" "80"
916 (prog1 963 (prog1
917 (block nil 964 (block nil
918 (when (or (eobp) (and (bolp) (eolp))) 965 (when (or (eobp) (and (bolp) (eolp)))
919 (return nil)) 966 (return nil))
920 (unless (eq (char-after) ?/) 967 (unless (eq (char-after) ?/)
921 (return error)) 968 (return 'error))
922 (let ((start (point)) 969 (let ((start (point))
923 end msg-start) 970 end msg-start)
924 (setq end (line-end-position)) 971 (setq end (line-end-position))
925 (unless (search-forward mh-mairix-folder end t) 972 (unless (search-forward mh-mairix-folder end t)
926 (return 'error)) 973 (return 'error))
995 (cdadr expr))))) 1042 (cdadr expr)))))
996 ((eq (caadr expr) 'or) (mh-mairix-convert-to-sop* 1043 ((eq (caadr expr) 'or) (mh-mairix-convert-to-sop*
997 `(and ,@(mapcar #'(lambda (x) `(not ,x)) 1044 `(and ,@(mapcar #'(lambda (x) `(not ,x))
998 (cdadr expr))))) 1045 (cdadr expr)))))
999 (t (error "Unreachable: %s" expr)))) 1046 (t (error "Unreachable: %s" expr))))
1047
1048
1049
1050 ;; Interface to unseen messages script
1051
1052 (defvar mh-flists-search-folders)
1053
1054 (defun mh-flists-execute (&rest args)
1055 "Search for unseen messages in `mh-flists-search-folders'.
1056 If `mh-recursive-folders-flag' is t, then the folders are searched
1057 recursively. All parameters ARGS are ignored."
1058 (set-buffer (get-buffer-create mh-index-temp-buffer))
1059 (erase-buffer)
1060 (unless (executable-find "sh")
1061 (error "Didn't find sh"))
1062 (with-temp-buffer
1063 (let ((unseen (symbol-name mh-unseen-seq)))
1064 (insert "for folder in `flists "
1065 (cond ((eq mh-flists-search-folders t) mh-inbox)
1066 ((eq mh-flists-search-folders nil) "")
1067 ((listp mh-flists-search-folders)
1068 (loop for folder in mh-flists-search-folders
1069 concat (concat " " folder))))
1070 (if mh-recursive-folders-flag " -recurse" "")
1071 " -sequence " unseen " -noshowzero -fast` ; do\n"
1072 "mhpath \"+$folder\" " unseen "\n" "done\n"))
1073 (call-process-region
1074 (point-min) (point-max) "sh" nil (get-buffer mh-index-temp-buffer))))
1075
1076 ;;;###mh-autoload
1077 (defun mh-index-new-messages (folders)
1078 "Display new messages.
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
1081 searched. With a prefix argument, enter a space-separated list of folders, or
1082 nothing to search all folders."
1083 (interactive
1084 (list (if current-prefix-arg
1085 (split-string (read-string "Folders to search: "))
1086 mh-index-new-messages-folders)))
1087 (let* ((mh-flists-search-folders folders)
1088 (mh-indexer 'flists)
1089 (mh-index-execute-search-function 'mh-flists-execute)
1090 (mh-index-next-result-function 'mh-mairix-next-result)
1091 (mh-mairix-folder mh-user-path)
1092 (mh-index-regexp-builder nil)
1093 (new-folder (format "%s/%s" mh-index-folder mh-flists-results-folder))
1094 (window-config (if (equal new-folder mh-current-folder)
1095 mh-previous-window-config
1096 (current-window-configuration)))
1097 (redo-flag nil))
1098 (cond ((buffer-live-p (get-buffer new-folder))
1099 ;; The destination folder is being visited. Trick `mh-index-search'
1100 ;; into thinking that the folder was the result of a previous search.
1101 (set-buffer new-folder)
1102 (setq mh-index-previous-search (list "+" mh-flists-results-folder))
1103 (setq redo-flag t))
1104 ((mh-folder-exists-p new-folder)
1105 ;; Folder exists but we don't have it open. That means they are
1106 ;; stale results from a old flists search. Clear it out.
1107 (mh-exec-cmd-quiet nil "rmf" new-folder)))
1108 (mh-index-search redo-flag "+" mh-flists-results-folder window-config t)))
1000 1109
1001 1110
1002 1111
1003 ;; Swish interface 1112 ;; Swish interface
1004 1113
1161 (defalias 'mh-swish++-next-result 'mh-swish-next-result) 1270 (defalias 'mh-swish++-next-result 'mh-swish-next-result)
1162 1271
1163 (defun mh-swish++-regexp-builder (regexp-list) 1272 (defun mh-swish++-regexp-builder (regexp-list)
1164 "Generate query for swish++. 1273 "Generate query for swish++.
1165 REGEXP-LIST is an alist of fields and values." 1274 REGEXP-LIST is an alist of fields and values."
1166 (let ((regexp "") meta) 1275 (let ((regexp ""))
1167 (dolist (elem regexp-list) 1276 (dolist (elem regexp-list)
1168 (when (cdr elem) 1277 (when (cdr elem)
1169 (setq regexp (concat regexp " and " 1278 (setq regexp (concat regexp " and "
1170 (if (car elem) "(" "") 1279 (if (car elem) "(" "")
1171 (if (car elem) (symbol-name (car elem)) "") 1280 (if (car elem) (symbol-name (car elem)) "")
1262 nil)))) 1371 nil))))
1263 (forward-line))) 1372 (forward-line)))
1264 1373
1265 1374
1266 1375
1376 ;;;###mh-autoload
1267 (defun mh-index-choose () 1377 (defun mh-index-choose ()
1268 "Choose an indexing function. 1378 "Choose an indexing function.
1269 The side-effects of this function are that the variables `mh-indexer', 1379 The side-effects of this function are that the variables `mh-indexer',
1270 `mh-index-execute-search-function', and `mh-index-next-result-function' are 1380 `mh-index-execute-search-function', and `mh-index-next-result-function' are
1271 set according to the first indexer in `mh-indexer-choices' present on the 1381 set according to the first indexer in `mh-indexer-choices' present on the