Mercurial > emacs
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 |