diff lisp/mh-e/mh-search.el @ 68163:3b7532a82079

* mh-search.el: New file containing contents of mh-index.el and mh-pick.el. C-c C-c launches your mh-index-program; C-c C-p runs pick. Pick no longer sets the "search" sequence. Instead, it brings up a folder view that we're accustomed to. (closes SF #829207). (mh-index-search): Rename to mh-search. (mh-pick-menu): Rename menu from Pick to Search. Rename Execute the Search to Perform Search and call mh-do-search. Add Search with Pick menu item. (mh-do-search): Delete. (mh-search-mode): Rename from mh-pick-mode. (MH-Search): Rename mode from MH-Pick. (mh-search-mode-map): Rename from mh-pick-mode-map. (mh-search-mode-help-messages): Rename from mh-pick-mode-help-messages. (mh-index-choose): Don't reuse the last value of mh-indexer; when mh-pick-do-search sets it to 'pick, we don't necessarily want to stay with that choice! * mh-index.el: mh-pick.el: Merge into mh-search.el and delete. * mh-customize.el (mh-index-program): Change mh-index-search to mh-search in docstring. (mh-tool-bar-search-function): Change default from mh-search-folder to mh-search. Remove mh-search-folder as choice and rename mh-index-search choice to mh-search. Fix docstring. (mh-pick-mode-hook): Rename to mh-search-mode-hook and change mh-search-folder to mh-search in docstring. * mh-e.el (mh-folder-folder-menu): Delete Search a Folder. Change Indexed Search to Search. Use mh-search instead of mh-index-search. (mh-folder-map): Delete i (mh-index-search) keybinding. Change s from mh-show-search-folder to mh-search. * mh-seq.el (mh-put-msg-in-seq): Fix docstring now that mh-search-folder no longer creates the search sequence. * mh-utils.el (mh-show-search-folder): Delete. (mh-show-folder-map): Delete i (mh-index-search) keybinding. Change s from mh-show-search-folder to mh-search. (mh-show-folder-menu): Delete Search a Folder. Change Indexed Search to Search. Use mh-search instead of mh-index-search. (mh-index-max-cmdline-args, mh-xargs, mh-quote-for-shell): Move here from deleted mh-index.el.
author Bill Wohler <wohler@newt.com>
date Fri, 13 Jan 2006 06:40:14 +0000
parents
children dcf226991252
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/mh-e/mh-search.el	Fri Jan 13 06:40:14 2006 +0000
@@ -0,0 +1,1901 @@
+;;; mh-search  --  MH-E search
+
+;; Copyright (C) 1993, 1995,
+;;  2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
+
+;; Author: Indexed search by Satyaki Das <satyaki@theforce.stanford.edu>
+;; Maintainer: Bill Wohler <wohler@newt.com>
+;; Keywords: mail
+;; See: mh-e.el
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 2, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;;  (1) The following search engines are supported:
+;;        swish++
+;;        swish-e
+;;        mairix
+;;        namazu
+;;        pick
+;;        grep
+;;
+;;  (2) To use this package, you first have to build an index. Please
+;;      read the documentation for `mh-search' to get started. That
+;;      documentation will direct you to the specific instructions for
+;;      your particular indexer.
+
+;;; Change Log:
+
+;;; Code:
+
+(eval-when-compile (require 'mh-acros))
+(mh-require-cl)
+
+(require 'gnus-util)
+(require 'mh-buffers)
+(require 'mh-e)
+
+(defvar mh-indexer nil
+  "Cached value of chosen index program.")
+
+(defvar mh-index-execute-search-function nil
+  "Function which executes the search program.")
+
+(defvar mh-index-next-result-function nil
+  "Function to parse the next line of output.")
+
+(defvar mh-index-regexp-builder nil
+  "Function used to construct search regexp.")
+
+(defvar mh-index-folder "+mhe-index"
+  "Folder that contains the folders resulting from the index searches.")
+
+(defvar mh-flists-results-folder "sequence"
+  "Subfolder for `mh-index-folder' where flists output is placed.")
+
+(defvar mh-flists-sequence)
+
+(defvar mh-flists-called-flag nil)
+
+
+
+;;; MH-Search mode
+
+;;;###mh-autoload
+(defun* mh-search (redo-search-flag folder search-regexp
+                                    &optional window-config)
+  "Search your MH mail.
+
+With this command, you can search for messages to or from a
+particular person or about a particular subject. In fact, you can
+also search for messages containing selected strings in any
+arbitrary header field or any string found within the messages.
+
+Use a prefix argument REDO-SEARCH-FLAG to repeat the last search.
+
+You are prompted for the FOLDER to search. This can be \"all\" to
+search all folders. For most search methods, the search works
+recursively on the listed folder.
+
+Next, an MH-Search buffer appears where you can enter search
+criteria.
+
+     From:
+     To:
+     Cc:
+     Date:
+     Subject:
+     --------
+
+Edit this template by entering your search criteria in an
+appropriate header field that is already there, or create a new
+field yourself. If the string you're looking for could be
+anywhere in a message, then place the string underneath the row
+of dashes.
+
+It is all right to specify several search criteria. What happens
+then is that a logical _and_ of the various fields is performed.
+If you prefer a logical _or_ operation, run \\[mh-search]
+multiple times.
+
+As an example, let's say that we want to find messages from
+Ginnean about horseback riding in the Kosciusko National
+Park (Australia) during January, 1994. Normally we would start
+with a broad search and narrow it down if necessary to produce a
+manageable amount of data, but we'll cut to the chase and create
+a fairly restrictive set of criteria as follows:
+
+     From: ginnean
+     To:
+     Cc:
+     Date: Jan 1994
+     Subject:
+     --------
+     horse
+     kosciusko
+
+As with MH-Letter mode, MH-Search provides commands like
+\\<mh-search-mode-map>\\[mh-to-field] to help you fill in the
+blanks.
+
+If you find that you do the same thing over and over when editing
+the search template, you may wish to bind some shortcuts to keys.
+This can be done with the variable `mh-search-mode-hook', which is
+called when \\[mh-search] is run on a new pattern.
+
+To perform the search, type \\[mh-index-do-search].
+
+Sometimes you're searching for text that is either not indexed,
+or hasn't been indexed yet. In this case you can override the
+default method with the pick method by running the command
+\\[mh-pick-do-search].
+
+The messages that are found are put in a temporary sub-folder of
+\"+mhe-index\" and are displayed in an MH-Folder buffer. This
+buffer is special because it displays messages from multiple
+folders; each set of messages from a given folder has a heading
+with the folder name.\\<mh-folder-mode-map>
+
+The appearance of the heading can be modified by customizing the
+face `mh-index-folder'. You can jump back and forth between the
+headings using the commands \\[mh-index-next-folder] and
+\\[mh-index-previous-folder].
+
+In addition, the command \\[mh-index-visit-folder] can be used to
+visit the folder of the message at point. Initially, only the
+messages that matched the search criteria are displayed in the
+folder. While the temporary buffer has its own set of message
+numbers, the actual messages numbers are shown in the visited
+folder. Thus, the command \\[mh-index-visit-folder] is useful to
+find the actual message number of an interesting message, or to
+view surrounding messages with the command \\[mh-rescan-folder].
+
+Because this folder is temporary, you'll probably get in the
+habit of killing it when you're done with
+\\[mh-kill-folder].
+
+SEARCH METHODS
+
+The command \\[mh-search] runs the command defined by the option
+`mh-index-program'. The default value is \"Auto-detect\" which
+means that MH-E will automatically choose one of \"swish++\",
+\"swish-e\", \"mairix\", \"namazu\", \"pick\" and \"grep\" in
+that order. If, for example, you have both \"swish++\" and
+\"mairix\" installed and you want to use \"mairix\", then you can
+set this option to \"mairix\".
+
+                                *NOTE*
+
+     The \"pick\" and \"grep\" commands do not perform a
+     recursive search on the given folder.
+
+This command uses an \"X-MHE-Checksum:\" header field to cache
+the MD5 checksum of a message. This means that if an incoming
+message already contains an \"X-MHE-Checksum:\" field, that
+message might not be found by this command. The following
+\"procmail\" recipe avoids this problem by renaming the existing
+header field:
+
+     :0 wf
+     | formail -R \"X-MHE-Checksum\" \"X-Old-MHE-Checksum\"
+
+The documentation for the following commands describe how to set
+up the various indexing programs to use with MH-E. The \"pick\"
+and \"grep\" commands do not require additional configuration.
+
+    - `mh-swish++-execute-search'
+    - `mh-swish-execute-search'
+    - `mh-mairix-execute-search'
+    - `mh-namazu-execute-search'
+    - `mh-pick-execute-search'
+    - `mh-grep-execute-search'
+
+In a program, the folder is searched with SEARCH-REGEXP. If
+FOLDER is \"+\" then mail in all folders are searched. Optional
+argument WINDOW-CONFIG stores the window configuration that will
+be restored after the user quits the folder containing the index
+search results."
+  (interactive
+   (list current-prefix-arg
+         (progn
+           (mh-find-path)
+           ;; Yes, we do want to call mh-index-choose every time in case the
+           ;; user has switched the indexer manually.
+           (unless (mh-index-choose (and current-prefix-arg
+                                         mh-index-previous-search
+                                         (cadr mh-index-previous-search)))
+             (error "No indexing program found"))
+           (or (and current-prefix-arg mh-index-sequence-search-flag)
+               (and current-prefix-arg (car mh-index-previous-search))
+               (mh-prompt-for-folder "Search" "+" nil "all" t)))
+         (or (and current-prefix-arg (caddr mh-index-previous-search))
+             mh-index-regexp-builder
+             (read-string (format "%s regexp: "
+                                  (upcase-initials (symbol-name mh-indexer)))))
+         (if (and (not (and current-prefix-arg
+                            (caddr mh-index-previous-search)))
+                  mh-index-regexp-builder)
+             (current-window-configuration)
+           nil)))
+  ;; Redoing a sequence search?
+  (when (and redo-search-flag mh-index-data mh-index-sequence-search-flag
+             (not mh-flists-called-flag))
+    (let ((mh-flists-called-flag t))
+      (apply #'mh-index-sequenced-messages mh-index-previous-search))
+    (return-from mh-search))
+  ;; We have fancy query parsing.
+  (when (symbolp search-regexp)
+    (mh-search-folder folder window-config)
+    (return-from mh-search))
+  ;; Begin search proper.
+  (mh-checksum-choose)
+  (let ((result-count 0)
+        (old-window-config (or window-config mh-previous-window-config))
+        (previous-search mh-index-previous-search)
+        (index-folder (format "%s/%s" mh-index-folder
+                              (mh-index-generate-pretty-name search-regexp))))
+    ;; Create a new folder for the search results or recreate the old one...
+    (if (and redo-search-flag mh-index-previous-search)
+        (let ((buffer-name (buffer-name (current-buffer))))
+          (mh-process-or-undo-commands buffer-name)
+          (save-excursion (mh-exec-cmd-quiet nil "rmf" buffer-name))
+          (mh-exec-cmd-quiet nil "folder" "-create" "-fast" buffer-name)
+          (setq index-folder buffer-name))
+      (setq index-folder (mh-index-new-folder index-folder search-regexp)))
+
+    (let ((folder-path (format "%s%s" mh-user-path (substring folder 1)))
+          (folder-results-map (make-hash-table :test #'equal))
+          (origin-map (make-hash-table :test #'equal)))
+      ;; Run search program...
+      (message "Executing %s... " mh-indexer)
+      (funcall mh-index-execute-search-function folder-path search-regexp)
+
+      ;; Parse indexer output.
+      (message "Processing %s output... " mh-indexer)
+      (goto-char (point-min))
+      (loop for next-result = (funcall mh-index-next-result-function)
+            while next-result
+            do (unless (eq next-result 'error)
+                 (unless (gethash (car next-result) folder-results-map)
+                   (setf (gethash (car next-result) folder-results-map)
+                         (make-hash-table :test #'equal)))
+                 (setf (gethash (cadr next-result)
+                                (gethash (car next-result) folder-results-map))
+                       t)))
+
+      ;; Copy the search results over.
+      (maphash #'(lambda (folder msgs)
+                   (let ((cur (car (mh-translate-range folder "cur")))
+                         (msgs (sort (loop for msg being the hash-keys of msgs
+                                           collect msg)
+                                     #'<)))
+                     (mh-exec-cmd "refile" msgs "-src" folder
+                                  "-link" index-folder)
+                     ;; Restore cur to old value, that refile changed
+                     (when cur
+                       (mh-exec-cmd-quiet nil "mark" folder "-add" "-zero"
+                                          "-sequence" "cur" (format "%s" cur)))
+                     (loop for msg in msgs
+                           do (incf result-count)
+                           (setf (gethash result-count origin-map)
+                                 (cons folder msg)))))
+               folder-results-map)
+
+      ;; Vist the results folder.
+      (mh-visit-folder index-folder () (list folder-results-map origin-map))
+
+      (goto-char (point-min))
+      (forward-line)
+      (mh-update-sequences)
+      (mh-recenter nil)
+
+      ;; Update the speedbar, if needed.
+      (when (mh-speed-flists-active-p)
+        (mh-speed-flists t mh-current-folder))
+
+      ;; Maintain history.
+      (when (or (and redo-search-flag previous-search) window-config)
+        (setq mh-previous-window-config old-window-config))
+      (setq mh-index-previous-search (list folder mh-indexer search-regexp))
+
+      ;; Write out data to disk.
+      (unless mh-flists-called-flag (mh-index-write-data))
+
+      (message "%s found %s matches in %s folders"
+               (upcase-initials (symbol-name mh-indexer))
+               (loop for msg-hash being hash-values of mh-index-data
+                     sum (hash-table-count msg-hash))
+               (loop for msg-hash being hash-values of mh-index-data
+                     count (> (hash-table-count msg-hash) 0))))))
+
+(defun mh-search-folder (folder window-config)
+  "Search FOLDER for messages matching a pattern.
+
+In a program, argument WINDOW-CONFIG is the current window
+configuration and is used when the search folder is dismissed."
+  (interactive (list (mh-prompt-for-folder "Search" mh-current-folder nil nil t)
+                     (current-window-configuration)))
+  (let ((pick-folder (if (equal folder "+") mh-current-folder folder)))
+    (switch-to-buffer-other-window "search-pattern")
+    (if (or (zerop (buffer-size))
+            (not (y-or-n-p "Reuse pattern? ")))
+        (mh-make-pick-template)
+      (message ""))
+    (mh-make-local-vars 'mh-current-folder folder
+                        'mh-previous-window-config window-config)
+    (message "%s" (substitute-command-keys
+                   (concat "Type \\[mh-index-do-search] to search messages, "
+                           "\\[mh-pick-do-search] to use pick, "
+                           "\\[mh-help] for help")))))
+
+(defun mh-make-pick-template ()
+  "Initialize the current buffer with a template for a pick pattern."
+  (let ((inhibit-read-only t)) (erase-buffer))
+  (insert "From: \n"
+          "To: \n"
+          "Cc: \n"
+          "Date: \n"
+          "Subject: \n"
+          "---------\n")
+  (mh-search-mode)
+  (goto-char (point-min))
+  (dotimes (i 5)
+    (add-text-properties (point) (1+ (point)) '(front-sticky t))
+    (add-text-properties (- (line-end-position) 2) (1- (line-end-position))
+                         '(rear-nonsticky t))
+    (add-text-properties (point) (1- (line-end-position)) '(read-only t))
+    (forward-line))
+  (add-text-properties (point) (1+ (point)) '(front-sticky t))
+  (add-text-properties (point) (1- (line-end-position)) '(read-only t))
+  (goto-char (point-max)))
+
+(defvar mh-search-mode-map (make-sparse-keymap)
+  "Keymap for searching folder.")
+
+;; If this changes, modify mh-search-mode-help-messages accordingly, below.
+(gnus-define-keys  mh-search-mode-map
+  "\C-c?"               mh-help
+  "\C-c\C-c"            mh-index-do-search
+  "\C-c\C-p"            mh-pick-do-search
+  "\C-c\C-f\C-b"        mh-to-field
+  "\C-c\C-f\C-c"        mh-to-field
+  "\C-c\C-f\C-d"        mh-to-field
+  "\C-c\C-f\C-f"        mh-to-field
+  "\C-c\C-f\C-r"        mh-to-field
+  "\C-c\C-f\C-s"        mh-to-field
+  "\C-c\C-f\C-t"        mh-to-field
+  "\C-c\C-fb"           mh-to-field
+  "\C-c\C-fc"           mh-to-field
+  "\C-c\C-fd"           mh-to-field
+  "\C-c\C-ff"           mh-to-field
+  "\C-c\C-fr"           mh-to-field
+  "\C-c\C-fs"           mh-to-field
+  "\C-c\C-ft"           mh-to-field)
+
+(easy-menu-define
+  mh-pick-menu mh-search-mode-map "Menu for MH-E Search"
+  '("Search"
+    ["Perform Search"       mh-index-do-search t]
+    ["Search with pick"     mh-pick-do-search t]))
+
+;; Group messages logically, more or less.
+(defvar mh-search-mode-help-messages
+  '((nil
+     "Perform search:  \\[mh-index-do-search]\n"
+     "Search with pick:  \\[mh-pick-do-search]\n"
+     "Move to a field by typing C-c C-f C-<field>\n"
+     "where <field> is the first letter of the desired field\n"
+     "(except for From: which uses \"m\")."))
+  "Key binding cheat sheet.
+
+This is an associative array which is used to show the most common
+commands. The key is a prefix char. The value is one or more strings
+which are concatenated together and displayed in the minibuffer if ?
+is pressed after the prefix character. The special key nil is used to
+display the non-prefixed commands.
+
+The substitutions described in `substitute-command-keys' are performed
+as well.")
+
+(put 'mh-search-mode 'mode-class 'special)
+
+(define-derived-mode mh-search-mode fundamental-mode "MH-Search"
+  "Mode for creating search templates in MH-E.\\<mh-search-mode-map>
+
+After each field name, enter the pattern to search for. If a field's
+value does not matter for the search, leave it empty. To search the
+entire message, supply the pattern in the \"body\" of the template.
+Each non-empty field must be matched for a message to be selected. To
+effect a logical \"or\", use \\[mh-search-folder] multiple times. When
+you have finished, type \\[mh-pick-do-search] to do the search.
+
+The hook `mh-search-mode-hook' is called upon entry to this mode.
+
+\\{mh-search-mode-map}"
+
+  (make-local-variable 'mh-help-messages)
+  (easy-menu-add mh-pick-menu)
+  (setq mh-help-messages mh-search-mode-help-messages))
+
+;;;###mh-autoload
+(defun mh-index-do-search (&optional indexer)
+  "Find messages that match the qualifications in the current pattern buffer.
+If optional argument INDEXER is present, use it instead of
+`mh-index-program'."
+  (interactive)
+  (unless (mh-index-choose indexer) (error "No indexing program found"))
+  (let* ((regexp-list (mh-pick-parse-search-buffer))
+         (pattern (funcall mh-index-regexp-builder regexp-list)))
+    (if pattern
+        (mh-search nil mh-current-folder pattern mh-previous-window-config)
+      (error "No search terms"))))
+
+;;;###mh-autoload
+(defun mh-pick-do-search ()
+  "Find messages that match the qualifications in the current pattern buffer.
+
+Uses the pick method described in `mh-pick-execute-search'."
+  (interactive)
+  (mh-index-do-search 'pick))
+
+(defun mh-pick-parse-search-buffer ()
+  "Parse the search buffer contents.
+The function returns a alist. The car of each element is either
+the header name to search in or nil to search the whole message.
+The cdr of the element is the pattern to search."
+  (save-excursion
+    (let ((pattern-list ())
+          (in-body-flag nil)
+          start begin)
+      (goto-char (point-min))
+      (while (not (eobp))
+        (if (search-forward "--------" (line-end-position) t)
+            (setq in-body-flag t)
+          (beginning-of-line)
+          (setq begin (point))
+          (setq start (if in-body-flag
+                          (point)
+                        (search-forward ":" (line-end-position) t)
+                        (point)))
+          (push (cons (and (not in-body-flag)
+                           (intern (downcase
+                                    (buffer-substring-no-properties
+                                     begin (1- start)))))
+                      (mh-index-parse-search-regexp
+                       (buffer-substring-no-properties
+                        start (line-end-position))))
+                pattern-list))
+        (forward-line))
+      pattern-list)))
+
+;;;###mh-autoload
+(defun mh-index-parse-search-regexp (input-string)
+  "Construct parse tree for INPUT-STRING.
+All occurrences of &, |, ! and ~ in INPUT-STRING are replaced by
+AND, OR and NOT as appropriate. Then the resulting string is
+parsed."
+  (let (input)
+    (with-temp-buffer
+      (insert input-string)
+      ;; replace tabs
+      (mh-replace-string "\t" " ")
+      ;; synonyms of AND
+      (mh-replace-string " AND " " and ")
+      (mh-replace-string "&" " and ")
+      (mh-replace-string " -and " " and ")
+      ;; synonyms of OR
+      (mh-replace-string " OR " " or ")
+      (mh-replace-string "|" " or ")
+      (mh-replace-string " -or " " or ")
+      ;; synonyms of NOT
+      (mh-replace-string " NOT " " not ")
+      (mh-replace-string "!" " not ")
+      (mh-replace-string "~" " not ")
+      (mh-replace-string " -not " " not ")
+      ;; synonyms of left brace
+      (mh-replace-string "(" " ( ")
+      (mh-replace-string " -lbrace " " ( ")
+      ;; synonyms of right brace
+      (mh-replace-string ")" " ) ")
+      (mh-replace-string " -rbrace " " ) ")
+      ;; get the normalized input
+      (setq input (format "( %s )" (buffer-substring (point-min) (point-max)))))
+
+    (let ((tokens (mh-index-add-implicit-ops (split-string input)))
+          (op-stack ())
+          (operand-stack ())
+          oper1)
+      (dolist (token tokens)
+        (cond ((equal token "(") (push 'paren op-stack))
+              ((equal token "not") (push 'not op-stack))
+              ((equal token "or") (push 'or op-stack))
+              ((equal token "and") (push 'and op-stack))
+              ((equal token ")")
+               (multiple-value-setq (op-stack operand-stack)
+                 (mh-index-evaluate op-stack operand-stack))
+               (when (eq (car op-stack) 'not)
+                 (setq op-stack (cdr op-stack))
+                 (push `(not ,(pop operand-stack)) operand-stack))
+               (when (eq (car op-stack) 'and)
+                 (setq op-stack (cdr op-stack))
+                 (setq oper1 (pop operand-stack))
+                 (push `(and ,(pop operand-stack) ,oper1) operand-stack)))
+              ((eq (car op-stack) 'not)
+               (setq op-stack (cdr op-stack))
+               (push `(not ,token) operand-stack)
+               (when (eq (car op-stack) 'and)
+                 (setq op-stack (cdr op-stack))
+                 (setq oper1 (pop operand-stack))
+                 (push `(and ,(pop operand-stack) ,oper1) operand-stack)))
+              ((eq (car op-stack) 'and)
+               (setq op-stack (cdr op-stack))
+               (push `(and ,(pop operand-stack) ,token) operand-stack))
+              (t (push token operand-stack))))
+      (prog1 (pop operand-stack)
+        (when (or op-stack operand-stack)
+          (error "Invalid regexp: %s" input))))))
+
+(defun mh-index-add-implicit-ops (tokens)
+  "Add implicit operators in the list TOKENS."
+  (let ((result ())
+        (literal-seen nil)
+        current)
+    (while tokens
+      (setq current (pop tokens))
+      (cond ((or (equal current ")") (equal current "and") (equal current "or"))
+             (setq literal-seen nil)
+             (push current result))
+            ((and literal-seen
+                  (push "and" result)
+                  (setq literal-seen nil)
+                  nil))
+            (t
+             (push current result)
+             (unless (or (equal current "(") (equal current "not"))
+               (setq literal-seen t)))))
+    (nreverse result)))
+
+(defun mh-index-evaluate (op-stack operand-stack)
+  "Read expression till starting paren based on OP-STACK and OPERAND-STACK."
+  (block mh-index-evaluate
+    (let (op oper1)
+      (while op-stack
+        (setq op (pop op-stack))
+        (cond ((eq op 'paren)
+               (return-from mh-index-evaluate (values op-stack operand-stack)))
+              ((eq op 'not)
+               (push `(not ,(pop operand-stack)) operand-stack))
+              ((or (eq op 'and) (eq op 'or))
+               (setq oper1 (pop operand-stack))
+               (push `(,op ,(pop operand-stack) ,oper1) operand-stack))))
+      (error "Ran out of tokens"))))
+
+
+
+;;; Sequence browsing
+
+;;;###mh-autoload
+(defun mh-index-new-messages (folders)
+  "Display unseen messages.
+
+If you use a program such as \"procmail\" to use \"rcvstore\" to file
+your incoming mail automatically, you can display new, unseen,
+messages using this command. All messages in the \"unseen\"
+sequence from the folders in `mh-new-messages-folders' are
+listed.
+
+With a prefix argument, enter a space-separated list of FOLDERS,
+or nothing to search all folders."
+  (interactive
+   (list (if current-prefix-arg
+             (split-string (read-string "Search folder(s) (default all): "))
+           mh-new-messages-folders)))
+  (mh-index-sequenced-messages folders mh-unseen-seq))
+
+;;;###mh-autoload
+(defun mh-index-ticked-messages (folders)
+  "Display ticked messages.
+
+All messages in `mh-tick-seq' from the folders in
+`mh-ticked-messages-folders' are listed.
+
+With a prefix argument, enter a space-separated list of FOLDERS,
+or nothing to search all folders."
+  (interactive
+   (list (if current-prefix-arg
+             (split-string (read-string "Search folder(s) (default all): "))
+           mh-ticked-messages-folders)))
+  (mh-index-sequenced-messages folders mh-tick-seq))
+
+;;;###mh-autoload
+(defun mh-index-sequenced-messages (folders sequence)
+  "Display messages in any sequence.
+
+All messages from the FOLDERS in `mh-new-messages-folders' in the
+SEQUENCE you provide are listed. With a prefix argument, enter a
+space-separated list of folders at the prompt, or nothing to
+search all folders."
+  (interactive
+   (list (if current-prefix-arg
+             (split-string (read-string "Search folder(s) (default all): "))
+           mh-new-messages-folders)
+         (mh-read-seq-default "Search" nil)))
+  (unless sequence (setq sequence mh-unseen-seq))
+  (let* ((mh-flists-search-folders folders)
+         (mh-flists-sequence sequence)
+         (mh-flists-called-flag t)
+         (mh-indexer 'flists)
+         (mh-index-execute-search-function 'mh-flists-execute)
+         (mh-index-next-result-function 'mh-mairix-next-result)
+         (mh-mairix-folder mh-user-path)
+         (mh-index-regexp-builder nil)
+         (new-folder (format "%s/%s/%s" mh-index-folder
+                             mh-flists-results-folder sequence))
+         (window-config (if (equal new-folder mh-current-folder)
+                            mh-previous-window-config
+                          (current-window-configuration)))
+         (redo-flag nil)
+         message)
+    (cond ((buffer-live-p (get-buffer new-folder))
+           ;; The destination folder is being visited. Trick `mh-search'
+           ;; into thinking that the folder resulted from a previous search.
+           (set-buffer new-folder)
+           (setq mh-index-previous-search (list folders mh-indexer sequence))
+           (setq redo-flag t))
+          ((mh-folder-exists-p new-folder)
+           ;; Folder exists but we don't have it open. That means they are
+           ;; stale results from a old flists search. Clear it out.
+           (mh-exec-cmd-quiet nil "rmf" new-folder)))
+    (setq message (mh-search redo-flag "+" mh-flists-results-folder
+                             window-config)
+          mh-index-sequence-search-flag t
+          mh-index-previous-search (list folders mh-indexer sequence))
+    (mh-index-write-data)
+    (when (stringp message) (message "%s" message))))
+
+(defvar mh-flists-search-folders)
+
+(defun mh-flists-execute (&rest args)
+  "Execute flists.
+Search for messages belonging to `mh-flists-sequence' in the
+folders specified by `mh-flists-search-folders'. If
+`mh-recursive-folders-flag' is t, then the folders are searched
+recursively. All parameters ARGS are ignored."
+  (set-buffer (get-buffer-create mh-temp-index-buffer))
+  (erase-buffer)
+  (unless (executable-find "sh")
+    (error "Didn't find sh"))
+  (with-temp-buffer
+    (let ((seq (symbol-name mh-flists-sequence)))
+      (insert "for folder in `" (expand-file-name "flists" mh-progs) " "
+              (cond ((eq mh-flists-search-folders t)
+                     (mh-quote-for-shell mh-inbox))
+                    ((eq mh-flists-search-folders nil) "")
+                    ((listp mh-flists-search-folders)
+                     (loop for folder in mh-flists-search-folders
+                           concat
+                           (concat " " (mh-quote-for-shell folder)))))
+              (if mh-recursive-folders-flag " -recurse" "")
+              " -sequence " seq " -noshowzero -fast` ; do\n"
+              (expand-file-name "mhpath" mh-progs) " \"+$folder\" " seq "\n"
+              "done\n"))
+    (call-process-region
+     (point-min) (point-max) "sh" nil (get-buffer mh-temp-index-buffer))))
+
+
+
+;;; Folder navigation and utilities
+
+;;;###mh-autoload
+(defun mh-index-group-by-folder ()
+  "Partition the messages based on source folder.
+Returns an alist with the the folder names in the car and the cdr
+being the list of messages originally from that folder."
+  (save-excursion
+    (goto-char (point-min))
+    (let ((result-table (make-hash-table :test #'equal)))
+      (loop for msg being hash-keys of mh-index-msg-checksum-map
+            do (push msg (gethash (car (gethash
+                                        (gethash msg mh-index-msg-checksum-map)
+                                        mh-index-checksum-origin-map))
+                                  result-table)))
+      (loop for x being the hash-keys of result-table
+            collect (cons x (nreverse (gethash x result-table)))))))
+
+;;;###mh-autoload
+(defun mh-index-insert-folder-headers ()
+  "Annotate the search results with original folder names."
+  (let ((cur-msg (mh-get-msg-num nil))
+        (old-buffer-modified-flag (buffer-modified-p))
+        (buffer-read-only nil)
+        current-folder last-folder)
+    (goto-char (point-min))
+    (while (not (eobp))
+      (setq current-folder (car (gethash (gethash (mh-get-msg-num nil)
+                                                  mh-index-msg-checksum-map)
+                                         mh-index-checksum-origin-map)))
+      (when (and current-folder (not (equal current-folder last-folder)))
+        (insert (if last-folder "\n" "") current-folder "\n")
+        (setq last-folder current-folder))
+      (forward-line))
+    (when cur-msg
+      (mh-notate-cur)
+      (mh-goto-msg cur-msg t))
+    (set-buffer-modified-p old-buffer-modified-flag))
+  (mh-index-create-imenu-index))
+
+;;;###mh-autoload
+(defun mh-index-delete-folder-headers ()
+  "Delete the folder headers."
+  (let ((cur-msg (mh-get-msg-num nil))
+        (old-buffer-modified-flag (buffer-modified-p))
+        (buffer-read-only nil))
+    (while (and (not cur-msg) (not (eobp)))
+      (forward-line)
+      (setq cur-msg (mh-get-msg-num nil)))
+    (goto-char (point-min))
+    (while (not (eobp))
+      (if (or (char-equal (char-after) ?+) (char-equal (char-after) 10))
+          (delete-region (point) (progn (forward-line) (point)))
+        (forward-line)))
+    (when cur-msg (mh-goto-msg cur-msg t t))
+    (set-buffer-modified-p old-buffer-modified-flag)))
+
+;;;###mh-autoload
+(defun mh-index-create-imenu-index ()
+  "Create alist of folder names and positions in index folder buffers."
+  (save-excursion
+    (setq which-func-mode t)
+    (let ((alist ()))
+      (goto-char (point-min))
+      (while (re-search-forward "^+" nil t)
+        (save-excursion
+          (beginning-of-line)
+          (push (cons (buffer-substring-no-properties
+                       (point) (line-end-position))
+                      (set-marker (make-marker) (point)))
+                alist)))
+      (setq imenu--index-alist (nreverse alist)))))
+
+;;;###mh-autoload
+(defun mh-index-next-folder (&optional backward-flag)
+  "Jump to the next folder marker.
+
+With non-nil optional argument BACKWARD-FLAG, jump to the previous
+group of results."
+  (interactive "P")
+  (if (null mh-index-data)
+      (message "Only applicable in an MH-E index search buffer")
+    (let ((point (point)))
+      (forward-line (if backward-flag 0 1))
+      (cond ((if backward-flag
+                 (re-search-backward "^+" (point-min) t)
+               (re-search-forward "^+" (point-max) t))
+             (beginning-of-line))
+            ((and (if backward-flag
+                      (goto-char (point-max))
+                    (goto-char (point-min)))
+                  nil))
+            ((if backward-flag
+                 (re-search-backward "^+" (point-min) t)
+               (re-search-forward "^+" (point-max) t))
+             (beginning-of-line))
+            (t (goto-char point))))))
+
+;;;###mh-autoload
+(defun mh-index-previous-folder ()
+  "Jump to the previous folder marker."
+  (interactive)
+  (mh-index-next-folder t))
+
+;;;###mh-autoload
+(defun mh-index-visit-folder ()
+  "Visit original folder from where the message at point was found."
+  (interactive)
+  (unless mh-index-data
+    (error "Not in an index folder"))
+  (let (folder msg)
+    (save-excursion
+      (cond ((and (bolp) (eolp))
+             (ignore-errors (forward-line -1))
+             (setq msg (mh-get-msg-num t)))
+            ((equal (char-after (line-beginning-position)) ?+)
+             (setq folder (buffer-substring-no-properties
+                           (line-beginning-position) (line-end-position))))
+            (t (setq msg (mh-get-msg-num t)))))
+    (when (not folder)
+      (setq folder (car (gethash (gethash msg mh-index-msg-checksum-map)
+                                 mh-index-checksum-origin-map))))
+    (when (or (not (get-buffer folder))
+              (y-or-n-p (format "Reuse buffer displaying %s? " folder)))
+      (mh-visit-folder
+       folder (loop for x being the hash-keys of (gethash folder mh-index-data)
+                    when (mh-msg-exists-p x folder) collect x)))))
+
+;;;###mh-autoload
+(defun mh-index-p ()
+  "Non-nil means that this folder was generated by an index search."
+  mh-index-data)
+
+;;;###mh-autoload
+(defun mh-index-execute-commands ()
+  "Delete/refile the actual messages.
+The copies in the searched folder are then deleted/refiled to get
+the desired result. Before deleting the messages we make sure
+that the message being deleted is identical to the one that the
+user has marked in the index buffer."
+  (save-excursion
+    (let ((folders ())
+          (mh-speed-flists-inhibit-flag t))
+      (maphash
+       (lambda (folder msgs)
+         (push folder folders)
+         (if (not (get-buffer folder))
+             ;; If source folder not open, just delete the messages...
+             (apply #'mh-exec-cmd "rmm" folder (mh-coalesce-msg-list msgs))
+           ;; Otherwise delete the messages in the source buffer...
+           (save-excursion
+             (set-buffer folder)
+             (let ((old-refile-list mh-refile-list)
+                   (old-delete-list mh-delete-list))
+               (setq mh-refile-list nil
+                     mh-delete-list msgs)
+               (unwind-protect (mh-execute-commands)
+                 (setq mh-refile-list
+                       (mapcar (lambda (x)
+                                 (cons (car x)
+                                       (loop for y in (cdr x)
+                                             unless (memq y msgs) collect y)))
+                               old-refile-list)
+                       mh-delete-list
+                       (loop for x in old-delete-list
+                             unless (memq x msgs) collect x))
+                 (mh-set-folder-modified-p (mh-outstanding-commands-p))
+                 (when (mh-outstanding-commands-p)
+                   (mh-notate-deleted-and-refiled)))))))
+       (mh-index-matching-source-msgs (append (loop for x in mh-refile-list
+                                                    append (cdr x))
+                                              mh-delete-list)
+                                      t))
+      folders)))
+
+
+
+;;; Indexing functions
+
+;; Support different indexing programs
+(defvar mh-indexer-choices
+  '((swish++
+     mh-swish++-binary mh-swish++-execute-search mh-swish++-next-result
+     mh-swish++-regexp-builder)
+    (swish
+     mh-swish-binary mh-swish-execute-search mh-swish-next-result nil)
+    (mairix
+     mh-mairix-binary mh-mairix-execute-search mh-mairix-next-result
+     mh-mairix-regexp-builder)
+    (namazu
+     mh-namazu-binary mh-namazu-execute-search mh-namazu-next-result nil)
+    (pick
+     mh-pick-binary mh-pick-execute-search mh-pick-next-result
+     mh-pick-regexp-builder)
+    (grep
+     mh-grep-binary mh-grep-execute-search mh-grep-next-result nil))
+  "List of possible indexer choices.")
+
+(defun mh-index-choose (&optional indexer)
+  "Choose an indexing function.
+The side-effects of this function are that the variables
+`mh-indexer', `mh-index-execute-search-function', and
+`mh-index-next-result-function' are set according to the first
+indexer in `mh-indexer-choices' present on the system.
+If optional argument INDEXER is present, use it instead of
+`mh-index-program'."
+  (block nil
+    (let ((program-alist (cond (indexer
+                                (list (assoc indexer mh-indexer-choices)))
+                               (mh-index-program
+                                (list
+                                 (assoc mh-index-program mh-indexer-choices)))
+                               (t mh-indexer-choices))))
+      (while program-alist
+        (let* ((current (pop program-alist))
+               (executable (symbol-value (cadr current))))
+          (when executable
+            (setq mh-indexer (car current))
+            (setq mh-index-execute-search-function (nth 2 current))
+            (setq mh-index-next-result-function (nth 3 current))
+            (setq mh-index-regexp-builder (nth 4 current))
+            (return mh-indexer))))
+      nil)))
+
+;;; Swish++ interface
+
+(defvar mh-swish++-binary (or (executable-find "search++")
+                              (executable-find "search")))
+(defvar mh-swish++-directory ".swish++")
+(defvar mh-swish-folder nil)
+
+;;;###mh-autoload
+(defun mh-swish++-execute-search (folder-path search-regexp)
+  "Execute swish++ and read the results.
+
+In the examples below, replace \"/home/user/Mail\" with the path to
+your MH directory.
+
+First create the directory \"/home/user/Mail/.swish++\". Then create
+the file \"/home/user/Mail/.swish++/swish++.conf\" with the following
+contents:
+
+     IncludeMeta         Bcc Cc Comments Content-Description From Keywords
+     IncludeMeta         Newsgroups Resent-To Subject To
+     IncludeMeta         Message-Id References In-Reply-To
+     IncludeFile         Mail    *
+     IndexFile           /home/user/Mail/.swish++/swish++.index
+
+Use the following command line to generate the swish index. Run
+this daily from cron:
+
+     find /home/user/Mail -path /home/user/Mail/mhe-index -prune \\
+                          -o -path /home/user/Mail/.swish++ -prune \\
+                          -o -name \"[0-9]*\" -print \\
+         | index -c /home/user/Mail/.swish++/swish++.conf -
+
+This command does not index the folders that hold the results of your
+searches in \"+mhe-index\" since they tend to be ephemeral and the
+original messages are indexed anyway.
+
+On some systems (Debian GNU/Linux, for example), use \"index++\"
+instead of \"index\".
+
+In a program, FOLDER-PATH is the directory in which SEARCH-REGEXP is
+used to search."
+  (set-buffer (get-buffer-create mh-temp-index-buffer))
+  (erase-buffer)
+  (unless mh-swish++-binary
+    (error "Set `mh-swish++-binary' appropriately"))
+  (call-process mh-swish++-binary nil '(t nil) nil
+                "-m" "10000"
+                (format "-i%s%s/swish++.index"
+                        mh-user-path mh-swish++-directory)
+                search-regexp)
+  (goto-char (point-min))
+  (setq mh-swish-folder
+        (let ((last-char (substring folder-path (1- (length folder-path)))))
+          (if (equal last-char "/")
+              folder-path
+            (format "%s/" folder-path)))))
+
+(defalias 'mh-swish++-next-result 'mh-swish-next-result)
+
+(defun mh-swish++-regexp-builder (regexp-list)
+  "Generate query for swish++.
+REGEXP-LIST is an alist of fields and values."
+  (let ((regexp ""))
+    (dolist (elem regexp-list)
+      (when (cdr elem)
+        (setq regexp (concat regexp " and "
+                             (if (car elem) "(" "")
+                             (if (car elem) (symbol-name (car elem)) "")
+                             (if (car elem) " = " "")
+                             (mh-swish++-print-regexp (cdr elem))
+                             (if (car elem) ")" "")))))
+    (substring regexp 4)))
+
+(defun mh-swish++-print-regexp (expr)
+  "Return infix expression corresponding to EXPR."
+  (cond ((atom expr) (format "%s" expr))
+        ((eq (car expr) 'not)
+         (format "(not %s)" (mh-swish++-print-regexp (cadr expr))))
+        (t (format "(%s %s %s)" (mh-swish++-print-regexp (cadr expr))
+                   (symbol-name (car expr))
+                   (mh-swish++-print-regexp (caddr expr))))))
+
+;;; Swish interface
+
+(defvar mh-swish-binary (executable-find "swish-e"))
+(defvar mh-swish-directory ".swish")
+
+;;;###mh-autoload
+(defun mh-swish-execute-search (folder-path search-regexp)
+  "Execute swish-e and read the results.
+
+In the examples below, replace \"/home/user/Mail\" with the path
+to your MH directory.
+
+First create the directory \"/home/user/Mail/.swish\". Then
+create the file \"/home/user/Mail/.swish/config\" with the
+following contents:
+
+     DefaultContents TXT*
+     IndexDir /home/user/Mail
+     IndexFile /home/user/Mail/.swish/index
+     IndexName \"Mail Index\"
+     IndexDescription \"Mail Index\"
+     IndexPointer \"http://nowhere\"
+     IndexAdmin \"nobody\"
+     #MetaNames automatic
+     IndexReport 3
+     FollowSymLinks no
+     UseStemming no
+     IgnoreTotalWordCountWhenRanking yes
+     WordCharacters abcdefghijklmnopqrstuvwxyz0123456789-
+     BeginCharacters abcdefghijklmnopqrstuvwxyz
+     EndCharacters abcdefghijklmnopqrstuvwxyz0123456789
+     IgnoreLimit 50 1000
+     IndexComments 0
+     FileRules filename contains \\D
+     FileRules pathname contains /home/user/Mail/.swish
+     FileRules pathname contains /home/user/Mail/mhe-index
+
+This configuration does not index the folders that hold the
+results of your searches in \"+mhe-index\" since they tend to be
+ephemeral and the original messages are indexed anyway.
+
+If there are any directories you would like to ignore, append
+lines like the following to \"config\":
+
+     FileRules pathname contains /home/user/Mail/scripts
+
+Use the following command line to generate the swish index. Run
+this daily from cron:
+
+         swish-e -c /home/user/Mail/.swish/config
+
+In a program, FOLDER-PATH is the directory in which SEARCH-REGEXP
+is used to search."
+  (set-buffer (get-buffer-create mh-temp-index-buffer))
+  (erase-buffer)
+  (unless mh-swish-binary
+    (error "Set `mh-swish-binary' appropriately"))
+  (call-process mh-swish-binary nil '(t nil) nil
+                "-w" search-regexp
+                "-f" (format "%s%s/index" mh-user-path mh-swish-directory))
+  (goto-char (point-min))
+  (setq mh-swish-folder
+        (let ((last-char (substring folder-path (1- (length folder-path)))))
+          (if (equal last-char "/")
+              folder-path
+            (format "%s/" folder-path)))))
+
+(defun mh-swish-next-result ()
+  "Get the next result from swish output."
+  (prog1
+      (block nil
+        (when (or (eobp) (equal (char-after (point)) ?.))
+          (return nil))
+        (when (equal (char-after (point)) ?#)
+          (return 'error))
+        (let* ((start (search-forward " " (line-end-position) t))
+               (end (search-forward " " (line-end-position) t)))
+          (unless (and start end)
+            (return 'error))
+          (setq end (1- end))
+          (unless (file-exists-p (buffer-substring-no-properties start end))
+            (return 'error))
+          (unless (search-backward "/" start t)
+            (return 'error))
+          (list (let* ((s (buffer-substring-no-properties start (1+ (point)))))
+                  (unless (string-match mh-swish-folder s)
+                    (return 'error))
+                  (if (and (string-match mh-user-path s)
+                           (< (match-end 0) (1- (length s))))
+                      (format "+%s"
+                              (substring s (match-end 0) (1- (length s))))
+                    (return 'error)))
+                (let* ((s (buffer-substring-no-properties (1+ (point)) end))
+                       (val (ignore-errors (read-from-string s))))
+                  (if (and (consp val) (numberp (car val)))
+                      (car val)
+                    (return 'error)))
+                nil)))
+    (forward-line)))
+
+;;; Mairix interface
+
+(defvar mh-mairix-binary (executable-find "mairix"))
+(defvar mh-mairix-directory ".mairix")
+(defvar mh-mairix-folder nil)
+
+;;;###mh-autoload
+(defun mh-mairix-execute-search (folder-path search-regexp-list)
+  "Execute mairix and read the results.
+
+In the examples below, replace \"/home/user/Mail\" with the path
+to your MH directory.
+
+First create the directory \"/home/user/Mail/.mairix\". Then
+create the file \"/home/user/Mail/.mairix/config\" with the
+following contents:
+
+     base=/home/user/Mail
+
+     # List of folders that should be indexed. 3 dots at the end means there
+     # are subfolders within the folder
+     mh=archive...:inbox:drafts:news:sent:trash
+
+     vfolder_format=raw
+     database=/home/user/Mail/mairix/database
+
+Use the following command line to generate the mairix index. Run
+this daily from cron:
+
+     mairix -f /home/user/Mail/.mairix/config
+
+In a program, FOLDER-PATH is the directory in which
+SEARCH-REGEXP-LIST is used to search."
+  (set-buffer (get-buffer-create mh-temp-index-buffer))
+  (erase-buffer)
+  (unless mh-mairix-binary
+    (error "Set `mh-mairix-binary' appropriately"))
+  (apply #'call-process mh-mairix-binary nil '(t nil) nil
+         "-r" "-f" (format "%s%s/config" mh-user-path mh-mairix-directory)
+         search-regexp-list)
+  (goto-char (point-min))
+  (setq mh-mairix-folder
+        (let ((last-char (substring folder-path (1- (length folder-path)))))
+          (if (equal last-char "/")
+              folder-path
+            (format "%s/" folder-path)))))
+
+(defun mh-mairix-next-result ()
+  "Return next result from mairix output."
+  (prog1
+      (block nil
+        (when (or (eobp) (and (bolp) (eolp)))
+          (return nil))
+        (unless (eq (char-after) ?/)
+          (return 'error))
+        (let ((start (point))
+              end msg-start)
+          (setq end (line-end-position))
+          (unless (search-forward mh-mairix-folder end t)
+            (return 'error))
+          (goto-char (match-beginning 0))
+          (unless (equal (point) start)
+            (return 'error))
+          (goto-char end)
+          (unless (search-backward "/" start t)
+            (return 'error))
+          (setq msg-start (1+ (point)))
+          (goto-char start)
+          (unless (search-forward mh-user-path end t)
+            (return 'error))
+          (list (format "+%s" (buffer-substring-no-properties
+                               (point) (1- msg-start)))
+                (car (read-from-string
+                      (buffer-substring-no-properties msg-start end)))
+                ())))
+    (forward-line)))
+
+(defun mh-mairix-regexp-builder (regexp-list)
+  "Generate query for mairix.
+REGEXP-LIST is an alist of fields and values."
+  (let ((result ()))
+    (dolist (pair regexp-list)
+      (when (cdr pair)
+        (push
+         (concat
+          (cond ((eq (car pair) 'to) "t:")
+                ((eq (car pair) 'from) "f:")
+                ((eq (car pair) 'cc) "c:")
+                ((eq (car pair) 'subject) "s:")
+                ((eq (car pair) 'date) "d:")
+                (t ""))
+          (let ((sop (cdr (mh-mairix-convert-to-sop* (cdr pair))))
+                (final ""))
+            (dolist (conjunct sop)
+              (let ((expr-list (cdr conjunct))
+                    (expr-string ""))
+                (dolist (e expr-list)
+                  (setq expr-string (concat expr-string ","
+                                            (if (atom e) "" "~")
+                                            (if (atom e) e (cadr e)))))
+                (setq final (concat final "/" (substring expr-string 1)))))
+            (substring final 1)))
+         result)))
+    result))
+
+(defun mh-mairix-convert-to-sop* (expr)
+  "Convert EXPR to sum of product form."
+  (cond ((atom expr) `(or (and ,expr)))
+        ((eq (car expr) 'or)
+         (cons 'or
+               (loop for e in (mapcar #'mh-mairix-convert-to-sop* (cdr expr))
+                     append (cdr e))))
+        ((eq (car expr) 'and)
+         (let ((conjuncts (mapcar #'mh-mairix-convert-to-sop* (cdr expr)))
+               result next-factor)
+           (setq result (pop conjuncts))
+           (while conjuncts
+             (setq next-factor (pop conjuncts))
+             (setq result (let ((res ()))
+                            (dolist (t1 (cdr result))
+                              (dolist (t2 (cdr next-factor))
+                                (push `(and ,@(cdr t1) ,@(cdr t2)) res)))
+                            (cons 'or res))))
+           result))
+        ((atom (cadr expr)) `(or (and ,expr)))
+        ((eq (caadr expr) 'not) (mh-mairix-convert-to-sop* (cadadr expr)))
+        ((eq (caadr expr) 'and) (mh-mairix-convert-to-sop*
+                                 `(or ,@(mapcar #'(lambda (x) `(not ,x))
+                                                (cdadr expr)))))
+        ((eq (caadr expr) 'or) (mh-mairix-convert-to-sop*
+                                `(and ,@(mapcar #'(lambda (x) `(not ,x))
+                                                (cdadr expr)))))
+        (t (error "Unreachable: %s" expr))))
+
+;;; Namazu interface
+
+(defvar mh-namazu-binary (executable-find "namazu"))
+(defvar mh-namazu-directory ".namazu")
+(defvar mh-namazu-folder nil)
+
+;;;###mh-autoload
+(defun mh-namazu-execute-search (folder-path search-regexp)
+  "Execute namazu and read the results.
+
+In the examples below, replace \"/home/user/Mail\" with the path to
+your MH directory.
+
+First create the directory \"/home/user/Mail/.namazu\". Then create
+the file \"/home/user/Mail/.namazu/mknmzrc\" with the following
+contents:
+
+     package conf;  # Don't remove this line!
+     $ADDRESS = 'user@localhost';
+     $ALLOW_FILE = \"[0-9]*\";
+     $EXCLUDE_PATH = \"^/home/user/Mail/(mhe-index|spam)\";
+
+This configuration does not index the folders that hold the results of
+your searches in \"+mhe-index\" since they tend to be ephemeral and
+the original messages are indexed anyway.
+
+Use the following command line to generate the namazu index. Run this
+daily from cron:
+
+     mknmz -f /home/user/Mail/.namazu/mknmzrc -O /home/user/Mail/.namazu \\
+              /home/user/Mail
+
+In a program, FOLDER-PATH is the directory in which SEARCH-REGEXP
+is used to search."
+  (let ((namazu-index-directory
+         (format "%s%s" mh-user-path mh-namazu-directory)))
+    (unless (file-exists-p namazu-index-directory)
+      (error "Namazu directory %s not present" namazu-index-directory))
+    (unless (executable-find mh-namazu-binary)
+      (error "Set `mh-namazu-binary' appropriately"))
+    (set-buffer (get-buffer-create mh-temp-index-buffer))
+    (erase-buffer)
+    (call-process mh-namazu-binary nil '(t nil) nil
+                  "-alR" search-regexp namazu-index-directory)
+    (goto-char (point-min))
+    (setq mh-namazu-folder
+          (let ((last (substring folder-path (1- (length folder-path)))))
+            (if (equal last "/")
+                folder-path
+              (format "%s/" folder-path))))))
+
+(defun mh-namazu-next-result ()
+  "Get the next result from namazu output."
+  (prog1
+      (block nil
+        (when (eobp) (return nil))
+        (let ((file-name (buffer-substring-no-properties
+                          (point) (line-end-position))))
+          (unless (equal (string-match mh-namazu-folder file-name) 0)
+            (return 'error))
+          (unless (file-exists-p file-name)
+            (return 'error))
+          (string-match mh-user-path file-name)
+          (let* ((folder/msg (substring file-name (match-end 0)))
+                 (mark (mh-search-from-end ?/ folder/msg)))
+            (unless mark (return 'error))
+            (list (format "+%s" (substring folder/msg 0 mark))
+                  (let ((n (ignore-errors (read-from-string
+                                           (substring folder/msg (1+ mark))))))
+                    (if (and (consp n) (numberp (car n)))
+                        (car n)
+                      (return 'error)))
+                  nil))))
+    (forward-line)))
+
+;;; Pick interface
+
+(defvar mh-index-pick-folder)
+(defvar mh-pick-binary "pick")
+(defconst mh-pick-single-dash  '(cc date from subject to)
+  "Search components that are supported by single-dash option in pick.")
+
+;;;###mh-autoload
+(defun mh-pick-execute-search (folder-path search-regexp)
+  "Execute pick.
+
+There are no semantics associated with the search criteria--they
+are simply treated as strings. Case is ignored when all lowercase
+is used, and regular expressions (a la \"ed\") are available.
+
+Unlike the other index search programs, you must specify a
+folder. In addition, this command does not descend into any
+sub-folders that may be present.
+
+In a program, FOLDER-PATH is the directory in which SEARCH-REGEXP
+is used to search."
+  (set-buffer (get-buffer-create mh-temp-index-buffer))
+  (erase-buffer)
+  (setq mh-index-pick-folder
+        (concat "+" (substring folder-path (length mh-user-path))))
+  (apply #'call-process (expand-file-name "pick" mh-progs) nil '(t nil) nil
+         mh-index-pick-folder "-list" search-regexp)
+  (goto-char (point-min)))
+
+(defun mh-pick-next-result ()
+  "Return the next pick search result."
+  (prog1 (block nil
+           (when (eobp) (return nil))
+           (unless (re-search-forward "^[1-9][0-9]*$" (line-end-position) t)
+             (return 'error))
+           (list mh-index-pick-folder
+                 (car (read-from-string (buffer-substring-no-properties
+                                         (line-beginning-position)
+                                         (line-end-position))))
+                 nil))
+    (forward-line)))
+
+;; All implementations of pick have special options -cc, -date, -from and
+;; -subject that allow to search for corresponding components. Any other
+;; component is searched using option --COMPNAME, for example: `pick
+;; --x-mailer mh-e'. Mailutils "pick" supports this option using a certain
+;; kludge, but it prefers the following syntax for this purpose:
+;; "--component=COMPNAME --pattern=PATTERN".
+;;                                           -- Sergey Poznyakoff, Aug 2003
+(defun mh-pick-regexp-builder (pattern-list)
+  "Generate pick search expression from PATTERN-LIST."
+  (let ((result ()))
+    (dolist (pattern pattern-list)
+      (when (cdr pattern)
+        (setq result `(,@result "-and" "-lbrace"
+                       ,@(mh-pick-construct-regexp
+                          (if (and (mh-variant-p 'mu-mh) (car pattern))
+                              (format "--pattern=%s" (cdr pattern))
+                            (cdr pattern))
+                          (if (car pattern)
+                              (cond
+                               ((mh-variant-p 'mu-mh)
+                                (format "--component=%s" (car pattern)))
+                               ((member (car pattern) mh-pick-single-dash)
+                                (format "-%s" (car pattern)))
+                               (t
+                                (format "--%s" (car pattern))))
+                            "-search"))
+                       "-rbrace"))))
+    (cdr result)))
+
+(defun mh-pick-construct-regexp (expr component)
+  "Construct pick compatible expression corresponding to EXPR.
+COMPONENT is the component to search."
+  (cond ((atom expr) (list component expr))
+        ((eq (car expr) 'and)
+         `("-lbrace" ,@(mh-pick-construct-regexp (cadr expr) component) "-and"
+           ,@(mh-pick-construct-regexp (caddr expr) component) "-rbrace"))
+        ((eq (car expr) 'or)
+         `("-lbrace" ,@(mh-pick-construct-regexp (cadr expr) component) "-or"
+           ,@(mh-pick-construct-regexp (caddr expr) component) "-rbrace"))
+        ((eq (car expr) 'not)
+         `("-lbrace" "-not" ,@(mh-pick-construct-regexp (cadr expr) component)
+           "-rbrace"))
+        (t (error "Unknown operator %s seen" (car expr)))))
+
+;;; Grep interface
+
+(defvar mh-grep-binary (executable-find "grep"))
+
+;;;###mh-autoload
+(defun mh-grep-execute-search (folder-path search-regexp)
+  "Execute grep and read the results.
+
+In a program, FOLDER-PATH is the directory in which SEARCH-REGEXP
+is used to search."
+  (set-buffer (get-buffer-create mh-temp-index-buffer))
+  (erase-buffer)
+  (call-process mh-grep-binary nil '(t nil) nil
+                "-i" "-r" search-regexp folder-path)
+  (goto-char (point-min)))
+
+(defun mh-grep-next-result ()
+  "Read the next result.
+Parse it and return the message folder, message index and the
+match. If no other matches left then return nil. If the current
+record is invalid return 'error."
+  (prog1
+      (block nil
+        (when (eobp)
+          (return nil))
+        (let ((eol-pos (line-end-position))
+              (bol-pos (line-beginning-position))
+              folder-start msg-end)
+          (goto-char bol-pos)
+          (unless (search-forward mh-user-path eol-pos t)
+            (return 'error))
+          (setq folder-start (point))
+          (unless (search-forward ":" eol-pos t)
+            (return 'error))
+          (let ((match (buffer-substring-no-properties (point) eol-pos)))
+            (forward-char -1)
+            (setq msg-end (point))
+            (unless (search-backward "/" folder-start t)
+              (return 'error))
+            (list (format "+%s" (buffer-substring-no-properties
+                                 folder-start (point)))
+                  (let ((val (ignore-errors (read-from-string
+                                             (buffer-substring-no-properties
+                                              (1+ (point)) msg-end)))))
+                    (if (and (consp val) (integerp (car val)))
+                        (car val)
+                      (return 'error)))
+                  match))))
+    (forward-line)))
+
+
+
+;;; Folder support
+
+(defun mh-index-generate-pretty-name (string)
+  "Given STRING generate a name which is suitable for use as a folder name.
+White space from the beginning and end are removed. All spaces in
+the name are replaced with underscores and all / are replaced
+with $. If STRING is longer than 20 it is truncated too. STRING
+could be a list of strings in which case they are concatenated to
+construct the base name."
+  (with-temp-buffer
+    (if (stringp string)
+        (insert string)
+      (when (car string) (insert (car string)))
+      (dolist (s (cdr string))
+        (insert "_" s)))
+    (setq string (mh-replace-string "-lbrace" " "))
+    (setq string (mh-replace-string "-rbrace" " "))
+    (subst-char-in-region (point-min) (point-max) ?( ?  t)
+    (subst-char-in-region (point-min) (point-max) ?) ?  t)
+    (subst-char-in-region (point-min) (point-max) ?- ?  t)
+    (goto-char (point-min))
+    (while (and (not (eobp)) (memq (char-after) '(?  ?\t ?\n ?\r ?_)))
+      (delete-char 1))
+    (goto-char (point-max))
+    (while (and (not (bobp)) (memq (char-before) '(?  ?\t ?\n ?\r ?_)))
+      (delete-backward-char 1))
+    (subst-char-in-region (point-min) (point-max) ?  ?_ t)
+    (subst-char-in-region (point-min) (point-max) ?\t ?_ t)
+    (subst-char-in-region (point-min) (point-max) ?\n ?_ t)
+    (subst-char-in-region (point-min) (point-max) ?\r ?_ t)
+    (subst-char-in-region (point-min) (point-max) ?/ ?$ t)
+    (let ((out (truncate-string-to-width (buffer-string) 20)))
+      (cond ((eq mh-indexer 'flists)
+             (format "%s/%s" mh-flists-results-folder mh-flists-sequence))
+            ((equal out mh-flists-results-folder) (concat out "1"))
+            (t out)))))
+
+(defun mh-folder-exists-p (folder)
+  "Check if FOLDER exists."
+  (and (mh-folder-name-p folder)
+       (save-excursion
+         (with-temp-buffer
+           (mh-exec-cmd-output "folder" nil "-fast" "-nocreate" folder)
+           (goto-char (point-min))
+           (not (eobp))))))
+
+(defun mh-msg-exists-p (msg folder)
+  "Check if MSG exists in FOLDER."
+  (file-exists-p (format "%s%s/%s" mh-user-path (substring folder 1) msg)))
+
+(defun mh-index-new-folder (name search-regexp)
+  "Return a folder name based on NAME for search results of SEARCH-REGEXP.
+
+If folder NAME already exists and was generated for the same
+SEARCH-REGEXP then it is reused.
+
+Otherwise if the folder NAME was generated from a different
+search then check if NAME<2> can be used. Otherwise try NAME<3>.
+This is repeated till we find a new folder name.
+
+If the folder returned doesn't exist then it is created."
+  (unless (mh-folder-name-p name)
+    (error "The argument should be a valid MH folder name"))
+  (let ((chosen-name
+         (loop for i from 1
+               for candidate = (if (equal i 1) name (format "%s<%s>" name i))
+               when (or (not (mh-folder-exists-p candidate))
+                        (equal (mh-index-folder-search-regexp candidate)
+                               search-regexp))
+               return candidate)))
+    ;; Do pending refiles/deletes...
+    (when (get-buffer chosen-name)
+      (mh-process-or-undo-commands chosen-name))
+    ;; Recreate folder...
+    (save-excursion (mh-exec-cmd-quiet nil "rmf" chosen-name))
+    (mh-exec-cmd-quiet nil "folder" "-create" "-fast" chosen-name)
+    (mh-remove-from-sub-folders-cache chosen-name)
+    (when (boundp 'mh-speed-folder-map)
+      (mh-speed-add-folder chosen-name))
+    chosen-name))
+
+(defun mh-index-folder-search-regexp (folder)
+  "If FOLDER was created by a index search, return the search regexp.
+Return nil if FOLDER doesn't exist or the .mhe_index file is
+garbled."
+  (ignore-errors
+    (with-temp-buffer
+      (insert-file-contents
+       (format "%s%s/%s" mh-user-path (substring folder 1) mh-index-data-file))
+      (goto-char (point-min))
+      (forward-list 3)
+      (cadr (read (current-buffer))))))
+
+
+
+;;; Sequence support
+
+;;;###mh-autoload
+(defun mh-index-create-sequences ()
+  "Mirror sequences present in source folders in index folder."
+  (let ((seq-hash (make-hash-table :test #'equal))
+        (seq-list ()))
+    (loop for folder being the hash-keys of mh-index-data
+          do (setf (gethash folder seq-hash)
+                   (mh-create-sequence-map
+                    (mh-read-folder-sequences folder nil))))
+    (dolist (msg (mh-translate-range mh-current-folder "all"))
+      (let* ((checksum (gethash msg mh-index-msg-checksum-map))
+             (pair (gethash checksum mh-index-checksum-origin-map))
+             (ofolder (car pair))
+             (omsg (cdr pair)))
+        (loop for seq in (ignore-errors
+                           (gethash omsg (gethash ofolder seq-hash)))
+              do (if (assoc seq seq-list)
+                     (push msg (cdr (assoc seq seq-list)))
+                   (push (list seq msg) seq-list)))))
+    (loop for seq in seq-list
+          do (apply #'mh-exec-cmd "mark" mh-current-folder
+                    "-sequence" (symbol-name (car seq)) "-add"
+                    (mapcar #'(lambda (x) (format "%s" x)) (cdr seq))))))
+
+;;;###mh-autoload
+(defun mh-create-sequence-map (seq-list)
+  "Return a map from msg number to list of sequences in which it is present.
+SEQ-LIST is an assoc list whose keys are sequence names and whose
+cdr is the list of messages in that sequence."
+  (loop with map = (make-hash-table)
+        for seq in seq-list
+        when (and (not (memq (car seq) (mh-unpropagated-sequences)))
+                  (mh-valid-seq-p (car seq)))
+        do (loop for msg in (cdr seq)
+                 do (push (car seq) (gethash msg map)))
+        finally return map))
+
+;;;###mh-autoload
+(defun mh-index-add-to-sequence (seq msgs)
+  "Add to SEQ the messages in the list MSGS.
+This function updates the source folder sequences. Also makes an
+attempt to update the source folder buffer if we have it open."
+  ;; Don't need to do anything for cur
+  (save-excursion
+    (when (and (not (memq seq (mh-unpropagated-sequences)))
+               (mh-valid-seq-p seq))
+      (let ((folders ())
+            (mh-speed-flists-inhibit-flag t))
+        (maphash (lambda (folder msgs)
+                   (push folder folders)
+                   ;; Add messages to sequence in source folder...
+                   (apply #'mh-exec-cmd-quiet nil "mark" folder
+                          "-add" "-nozero" "-sequence" (symbol-name seq)
+                          (mapcar (lambda (x) (format "%s" x))
+                                  (mh-coalesce-msg-list msgs)))
+                   ;; Update source folder buffer if we have it open...
+                   (when (get-buffer folder)
+                     (save-excursion
+                       (set-buffer folder)
+                       (mh-put-msg-in-seq msgs seq))))
+                 (mh-index-matching-source-msgs msgs))
+        folders))))
+
+;;;###mh-autoload
+(defun mh-index-delete-from-sequence (seq msgs)
+  "Delete from SEQ the messages in MSGS.
+This function updates the source folder sequences. Also makes an
+attempt to update the source folder buffer if present."
+  (save-excursion
+    (when (and (not (memq seq (mh-unpropagated-sequences)))
+               (mh-valid-seq-p seq))
+      (let ((folders ())
+            (mh-speed-flists-inhibit-flag t))
+        (maphash (lambda (folder msgs)
+                   (push folder folders)
+                   ;; Remove messages from sequence in source folder...
+                   (apply #'mh-exec-cmd-quiet nil "mark" folder
+                          "-del" "-nozero" "-sequence" (symbol-name seq)
+                          (mapcar (lambda (x) (format "%s" x))
+                                  (mh-coalesce-msg-list msgs)))
+                   ;; Update source folder buffer if we have it open...
+                   (when (get-buffer folder)
+                     (save-excursion
+                       (set-buffer folder)
+                       (mh-delete-msg-from-seq msgs seq t))))
+                 (mh-index-matching-source-msgs msgs))
+        folders))))
+
+(defvar mh-unpropagated-sequences '(cur range subject search)
+  "List of sequences that aren't preserved.")
+
+(defun mh-unpropagated-sequences ()
+  "Return a list of sequences that aren't propagated to the source folders.
+It is just the sequences in the variable
+`mh-unpropagated-sequences' in addition to the
+Previous-Sequence (see mh-profile 5)."
+  (if mh-previous-seq
+      (cons mh-previous-seq mh-unpropagated-sequences)
+    mh-unpropagated-sequences))
+
+(defun mh-index-matching-source-msgs (msgs &optional delete-from-index-data)
+  "Return a table of original messages and folders for messages in MSGS.
+If optional argument DELETE-FROM-INDEX-DATA is non-nil, then each
+of the messages, whose counter-part is found in some source
+folder, is removed from `mh-index-data'."
+  (let ((table (make-hash-table :test #'equal)))
+    (dolist (msg msgs)
+      (let* ((checksum (gethash msg mh-index-msg-checksum-map))
+             (pair (gethash checksum mh-index-checksum-origin-map)))
+        (when (and checksum (car pair) (cdr pair)
+                   (mh-index-match-checksum (cdr pair) (car pair) checksum))
+          (push (cdr pair) (gethash (car pair) table))
+          (when delete-from-index-data
+            (remhash (cdr pair) (gethash (car pair) mh-index-data))))))
+    table))
+
+(defun mh-index-match-checksum (msg folder checksum)
+  "Check if MSG in FOLDER has X-MHE-Checksum header value of CHECKSUM."
+  (with-temp-buffer
+    (mh-exec-cmd-output mh-scan-prog nil "-width" "80"
+                        "-format" "%{x-mhe-checksum}\n" folder msg)
+    (goto-char (point-min))
+    (string-equal (buffer-substring-no-properties (point) (line-end-position))
+                  checksum)))
+
+
+
+;;; Serialization of index data
+
+(defun mh-index-write-data ()
+  "Write index data to file."
+  (ignore-errors
+    (unless (eq major-mode 'mh-folder-mode)
+      (error "Can't be called from folder in \"%s\"" major-mode))
+    (let ((data mh-index-data)
+          (msg-checksum-map mh-index-msg-checksum-map)
+          (checksum-origin-map mh-index-checksum-origin-map)
+          (previous-search mh-index-previous-search)
+          (sequence-search-flag mh-index-sequence-search-flag)
+          (outfile (concat buffer-file-name mh-index-data-file))
+          (print-length nil)
+          (print-level nil))
+      (with-temp-file outfile
+        (mh-index-write-hashtable
+         data (lambda (x) (loop for y being the hash-keys of x collect y)))
+        (mh-index-write-hashtable msg-checksum-map #'identity)
+        (mh-index-write-hashtable checksum-origin-map #'identity)
+        (pp previous-search (current-buffer)) (insert "\n")
+        (pp sequence-search-flag (current-buffer)) (insert "\n")))))
+
+(defun mh-index-write-hashtable (table proc)
+  "Write TABLE to `current-buffer'.
+PROC is used to serialize the values corresponding to the hash
+table keys."
+  (pp (loop for x being the hash-keys of table
+            collect (cons x (funcall proc (gethash x table))))
+      (current-buffer))
+  (insert "\n"))
+
+;;;###mh-autoload
+(defun mh-index-read-data ()
+  "Read index data from file."
+  (ignore-errors
+    (unless (eq major-mode 'mh-folder-mode)
+      (error "Can't be called from folder in \"%s\"" major-mode))
+    (let ((infile (concat buffer-file-name mh-index-data-file))
+          t1 t2 t3 t4 t5)
+      (with-temp-buffer
+        (insert-file-contents-literally infile)
+        (goto-char (point-min))
+        (setq t1 (mh-index-read-hashtable
+                  (lambda (data)
+                    (loop with table = (make-hash-table :test #'equal)
+                          for x in data do (setf (gethash x table) t)
+                          finally return table)))
+              t2 (mh-index-read-hashtable #'identity)
+              t3 (mh-index-read-hashtable #'identity)
+              t4 (read (current-buffer))
+              t5 (read (current-buffer))))
+      (setq mh-index-data t1
+            mh-index-msg-checksum-map t2
+            mh-index-checksum-origin-map t3
+            mh-index-previous-search t4
+            mh-index-sequence-search-flag t5))))
+
+(defun mh-index-read-hashtable (proc)
+  "From BUFFER read a hash table serialized as a list.
+PROC is used to convert the value to actual data."
+  (loop with table = (make-hash-table :test #'equal)
+        for pair in (read (current-buffer))
+        do (setf (gethash (car pair) table) (funcall proc (cdr pair)))
+        finally return table))
+
+
+
+;;; Checksum routines
+
+;; A few different checksum programs are supported. The supported programs
+;; are:
+;;
+;;   1. md5sum
+;;   2. md5
+;;   3. openssl
+;;
+;; To add support for your favorite checksum program add a clause to the cond
+;; statement in mh-checksum-choose. This should set the variable
+;; mh-checksum-cmd to the command line needed to run the checsum program and
+;; should set mh-checksum-parser to a function which returns a cons cell
+;; containing the message number and checksum string.
+
+(defvar mh-checksum-cmd)
+(defvar mh-checksum-parser)
+
+(defun mh-checksum-choose ()
+  "Check if a program to create a checksum is present."
+  (unless (boundp 'mh-checksum-cmd)
+    (let ((exec-path (append '("/sbin" "/usr/sbin") exec-path)))
+      (cond ((executable-find "md5sum")
+             (setq mh-checksum-cmd (list (executable-find "md5sum")))
+             (setq mh-checksum-parser #'mh-md5sum-parser))
+            ((executable-find "openssl")
+             (setq mh-checksum-cmd (list (executable-find "openssl") "md5"))
+             (setq mh-checksum-parser #'mh-openssl-parser))
+            ((executable-find "md5")
+             (setq mh-checksum-cmd (list (executable-find "md5")))
+             (setq mh-checksum-parser #'mh-md5-parser))
+            (t (error "No suitable checksum program"))))))
+
+(defun mh-md5sum-parser ()
+  "Parse md5sum output."
+  (let ((begin (line-beginning-position))
+        (end (line-end-position))
+        first-space last-slash)
+    (setq first-space (search-forward " " end t))
+    (goto-char end)
+    (setq last-slash (search-backward "/" begin t))
+    (cond ((and first-space last-slash)
+           (cons (car (read-from-string (buffer-substring-no-properties
+                                         (1+ last-slash) end)))
+                 (buffer-substring-no-properties begin (1- first-space))))
+          (t (cons nil nil)))))
+
+(defun mh-openssl-parser ()
+  "Parse openssl output."
+  (let ((begin (line-beginning-position))
+        (end (line-end-position))
+        last-space last-slash)
+    (goto-char end)
+    (setq last-space (search-backward " " begin t))
+    (setq last-slash (search-backward "/" begin t))
+    (cond ((and last-slash last-space)
+           (cons (car (read-from-string (buffer-substring-no-properties
+                                         (1+ last-slash) (1- last-space))))
+                 (buffer-substring-no-properties (1+ last-space) end))))))
+
+(defalias 'mh-md5-parser 'mh-openssl-parser)
+
+;;;###mh-autoload
+(defun mh-index-update-maps (folder &optional origin-map)
+  "Annotate all as yet unannotated messages in FOLDER with their MD5 hash.
+As a side effect msg -> checksum map is updated. Optional
+argument ORIGIN-MAP is a hashtable which maps each message in the
+index folder to the original folder and message from whence it
+was copied. If present the checksum -> (origin-folder,
+origin-index) map is updated too."
+  (clrhash mh-index-msg-checksum-map)
+  (save-excursion
+    ;; Clear temp buffer
+    (set-buffer (get-buffer-create mh-temp-checksum-buffer))
+    (erase-buffer)
+    ;; Run scan to check if any messages needs MD5 annotations at all
+    (with-temp-buffer
+      (mh-exec-cmd-output mh-scan-prog nil "-width" "80"
+                          "-format" "%(msg)\n%{x-mhe-checksum}\n"
+                          folder "all")
+      (goto-char (point-min))
+      (let (msg checksum)
+        (while (not (eobp))
+          (setq msg (buffer-substring-no-properties
+                     (point) (line-end-position)))
+          (forward-line)
+          (save-excursion
+            (cond ((not (string-match "^[0-9]*$" msg)))
+                  ((eolp)
+                   ;; need to compute checksum
+                   (set-buffer mh-temp-checksum-buffer)
+                   (insert mh-user-path (substring folder 1) "/" msg "\n"))
+                  (t
+                   ;; update maps
+                   (setq checksum (buffer-substring-no-properties
+                                   (point) (line-end-position)))
+                   (let ((msg (car (read-from-string msg))))
+                     (set-buffer folder)
+                     (mh-index-update-single-msg msg checksum origin-map)))))
+          (forward-line))))
+    ;; Run checksum program if needed
+    (unless (and (eobp) (bobp))
+      (apply #'mh-xargs mh-checksum-cmd)
+      (goto-char (point-min))
+      (while (not (eobp))
+        (let* ((intermediate (funcall mh-checksum-parser))
+               (msg (car intermediate))
+               (checksum (cdr intermediate)))
+          (when msg
+            ;; annotate
+            (mh-exec-cmd "anno" folder msg "-component" "X-MHE-Checksum"
+                         "-nodate" "-text" checksum "-inplace")
+            ;; update maps
+            (save-excursion
+              (set-buffer folder)
+              (mh-index-update-single-msg msg checksum origin-map)))
+          (forward-line)))))
+  (mh-index-write-data))
+
+(defun mh-index-update-single-msg (msg checksum origin-map)
+  "Update various maps for one message.
+MSG is a index folder message, CHECKSUM its MD5 hash and
+ORIGIN-MAP, if non-nil, a hashtable containing which maps each
+message in the index folder to the folder and message that it was
+copied from. The function updates the hash tables
+`mh-index-msg-checksum-map' and `mh-index-checksum-origin-map'.
+
+This function should only be called in the appropriate index
+folder buffer."
+  (cond ((and origin-map (gethash checksum mh-index-checksum-origin-map))
+         (let* ((intermediate (gethash msg origin-map))
+                (ofolder (car intermediate))
+                (omsg (cdr intermediate)))
+           ;; This is most probably a duplicate. So eliminate it.
+           (call-process "rm" nil nil nil
+                         (format "%s%s/%s" mh-user-path
+                                 (substring mh-current-folder 1) msg))
+           (when (gethash ofolder mh-index-data)
+             (remhash omsg (gethash ofolder mh-index-data)))))
+        (t
+         (setf (gethash msg mh-index-msg-checksum-map) checksum)
+         (when origin-map
+           (setf (gethash checksum mh-index-checksum-origin-map)
+                 (gethash msg origin-map))))))
+
+(provide 'mh-search)
+
+;; Local Variables:
+;; indent-tabs-mode: nil
+;; sentence-end-double-space: nil
+;; End:
+
+;; arch-tag: 607762ad-0dff-4fe1-a27e-6c0dde0dcc47
+;;; mh-search ends here