1 ;;; mh-index -- MH-E interface to indexing programs
3 ;; Copyright (C) 2002 Free Software Foundation, Inc.
5 ;; Author: Bill Wohler <wohler@newt.com>
6 ;; Maintainer: Bill Wohler <wohler@newt.com>
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
29 ;;; (1) The following search engines are supported:
36 ;;; (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 ;;; documentation will direct you to the specific instructions for your
39 ;;; particular indexer.
41 ;;; (3) Right now only viewing messages and moving between messages works in
42 ;;; the index buffer. With a little bit of work more stuff like
43 ;;; replying or forwarding messages can be done.
47 ;; $Id: mh-index.el,v 1.51 2002/11/13 18:43:57 satyaki Exp $
55 ;; Shush the byte-compiler
56 (defvar font-lock-defaults)
58 (autoload 'gnus-local-map-property "gnus-util")
59 (autoload 'gnus-eval-format "gnus-spec")
60 (autoload 'widget-convert-button "wid-edit")
61 (autoload 'executable-find "executable")
64 (defcustom mh-index-program nil
65 "Indexing program that MH-E shall use.
66 The possible choices are swish++, swish-e, namazu, glimpse and grep. By
67 default this variable is nil which means that the programs are tried in order
68 and the first one found is used."
70 :type '(choice (const :tag "auto-detect" nil)
71 (const :tag "swish++" swish++)
72 (const :tag "swish-e" swish)
73 (const :tag "namazu" namazu)
74 (const :tag "glimpse" glimpse)
75 (const :tag "grep" grep)))
78 (defcustom mh-index-show-hook nil
79 "Invoked after the message has been displayed."
83 ;; Support different indexing programs
84 (defvar mh-indexer-choices
86 mh-swish++-binary mh-swish++-execute-search mh-swish++-next-result)
88 mh-swish-binary mh-swish-execute-search mh-swish-next-result)
90 mh-namazu-binary mh-namazu-execute-search mh-namazu-next-result)
92 mh-glimpse-binary mh-glimpse-execute-search mh-glimpse-next-result)
94 mh-grep-binary mh-grep-execute-search mh-grep-next-result))
95 "List of possible indexer choices.")
96 (defvar mh-indexer nil
97 "Chosen index program.")
98 (defvar mh-index-execute-search-function nil
99 "Function which executes the search program.")
100 (defvar mh-index-next-result-function nil
101 "Function to parse the next line of output.")
103 ;; Names for the default mh-index-buffers...
104 (defvar mh-index-buffer "*mh-index*")
105 (defvar mh-index-show-buffer "*mh-index-show*")
107 ;; For use with adaptive size setting...
108 (defvar mh-index-max-msg-index 0)
110 ;; Buffer locals to allow multiple concurrent search folders.
111 (defvar mh-index-other-buffer nil
112 "Keeps track of other buffer associated with current buffer.
113 The value is the show buffer or the folder-buffer depending on whether we are
114 in a folder buffer or show buffer respectively.")
115 (defvar mh-index-matches nil
116 "Map of folder to messages which match.")
117 (defvar mh-index-previous-window-configuration nil
118 "Keep track of previous window configuration that is restored on exit.")
119 (defvar mh-index-current-msg nil
120 "Message index of message being shown.")
122 ;; Make variables buffer local ...
123 (make-variable-buffer-local 'mh-index-other-buffer)
124 (make-variable-buffer-local 'mh-index-matches)
125 (make-variable-buffer-local 'mh-index-previous-window-configuration)
126 (make-variable-buffer-local 'mh-current-folder)
127 (make-variable-buffer-local 'mh-index-current-msg)
129 ;; ... and arrange for them to not get slaughtered by a call to text-mode
130 ;; (text-mode is called by mh-show-mode and mh-folder-mode).
131 (put 'mh-index-other-buffer 'permanent-local t)
132 (put 'mh-index-matches 'permanent-local t)
133 (put 'mh-index-previous-window-configuration 'permanent-local t)
134 (put 'mh-index-current-msg 'permanent-local t)
135 (put 'mh-current-folder 'permanent-local t)
136 (put 'mh-cmd-note 'permanent-local t)
138 ;; Temporary buffer where search results are output.
139 (defvar mh-index-temp-buffer " *mh-index-temp*")
143 ;; N.B. If this map were named mh-index-folder-mode-map, it would inherit the
144 ;; keymap from mh-folder-mode. Since we want our own keymap, we tweak the name
145 ;; to avoid this unwanted inheritance.
146 (defvar mh-index-folder-mode-keymap (make-sparse-keymap)
147 "Keymap for MH index folder.")
148 (suppress-keymap mh-index-folder-mode-keymap)
149 (gnus-define-keys mh-index-folder-mode-keymap
150 " " mh-index-page-msg
151 "," mh-index-header-display
153 [mouse-2] mh-index-show
155 "\177" mh-index-previous-page
156 "\M-\t" mh-index-prev-button
157 [backtab] mh-index-prev-button
159 "\t" mh-index-next-button
167 (gnus-define-keys (mh-index-folder-map "F" mh-index-folder-mode-keymap)
169 "f" mh-visit-folder ;alias
170 "i" mh-index-search-again
171 "o" mh-visit-folder ;alias
174 (defvar mh-index-button-map (make-sparse-keymap))
175 (gnus-define-keys mh-index-button-map
176 "\r" mh-index-press-button)
182 ;;; If you add a new prefix, add appropriate text to the nil key.
184 ;;; In general, messages are grouped logically. Taking the main commands for
185 ;;; example, the first line is "ways to view messages," the second line is
186 ;;; "things you can do with messages", and the third is "composing" messages.
188 ;;; When adding a new prefix, ensure that the help message contains "what" the
189 ;;; prefix is for. For example, if the word "folder" were not present in the
190 ;;; `F' entry, it would not be clear what these commands operated upon.
191 (defvar mh-index-folder-mode-help-messages
192 '((nil "[i]nc, [.]show, [,]show all, [n]ext, [p]revious,\n"
194 (?F "[v]isit folder; [i]ndexed search"))
195 "Key binding cheat sheet.
197 This is an associative array which is used to show the most common commands.
198 The key is a prefix char. The value is one or more strings which are
199 concatenated together and displayed in the minibuffer if ? is pressed after
200 the prefix character. The special key nil is used to display the
201 non-prefixed commands.
203 The substitutions described in `substitute-command-keys' are performed as
208 (defun mh-index-search (folder search-regexp &optional new-buffer-flag)
209 "Perform an indexed search in an MH mail folder.
211 FOLDER is searched with SEARCH-REGEXP and the results are presented in an MH-E
212 folder. If FOLDER is \"+\" then mail in all folders are searched. Optional
213 prefix argument NEW-BUFFER-FLAG decides whether the results are presented in a
214 new buffer. This allows multiple search results to coexist.
216 Four indexing programs are supported; if none of these are present, then grep
217 is used. This function picks the first program that is available on your
218 system. If you would prefer to use a different program, set the customization
219 variable `mh-index-program' accordingly.
221 The documentation for the following functions describes how to generate the
222 index for each program:
224 - `mh-swish++-execute-search'
225 - `mh-swish-execute-search'
226 - `mh-namazu-execute-search'
227 - `mh-glimpse-execute-search'"
230 (unless mh-find-path-run (mh-find-path))
231 (mh-prompt-for-folder "Search" "+" nil "all"))
233 ;; Yes, we do want to call mh-index-choose every time in case the
234 ;; user has switched the indexer manually.
235 (unless (mh-index-choose) (error "No indexing program found"))
236 (read-string (format "%s regexp: "
237 (upcase-initials (symbol-name mh-indexer)))))
239 (setq mh-index-max-msg-index 0)
240 (let ((config (current-window-configuration))
242 (cond (new-buffer-flag
243 (buffer-name (generate-new-buffer mh-index-buffer)))
244 ((and (eq major-mode 'mh-index-folder-mode))
245 (buffer-name (current-buffer)))
246 (t mh-index-buffer)))
247 (mh-index-show-buffer
248 (cond (new-buffer-flag
249 (buffer-name (generate-new-buffer mh-index-show-buffer)))
250 ((eq major-mode 'mh-index-folder-mode)
251 mh-index-other-buffer)
252 (t mh-index-show-buffer))))
253 (when (buffer-live-p (get-buffer mh-index-show-buffer))
254 (kill-buffer (get-buffer mh-index-show-buffer)))
255 (get-buffer-create mh-index-buffer)
256 (get-buffer-create mh-index-show-buffer)
258 (set-buffer mh-index-buffer)
259 (setq mh-index-other-buffer mh-index-show-buffer))
261 (set-buffer mh-index-show-buffer)
262 (setq mh-index-other-buffer mh-index-buffer))
263 (set-buffer mh-index-buffer)
264 (setq buffer-read-only nil)
266 (let* ((folder-path (format "%s%s" mh-user-path (substring folder 1)))
269 cur-folder last-folder cur-index last-index
270 parse-results button-start button-end)
271 (setq mh-index-matches (make-hash-table :test #'equal))
273 ;; Run search program...
274 (message "%s searching... " (upcase-initials (symbol-name mh-indexer)))
275 (funcall mh-index-execute-search-function folder-path search-regexp)
277 ;; Parse output and generate folder view
278 (message "Processing %s output... " mh-indexer)
279 (goto-char (point-min))
280 (while (setq parse-results (funcall mh-index-next-result-function))
281 (unless (eq parse-results 'error)
282 (setq cur-folder (car parse-results)
283 cur-index (cadr parse-results))
284 (setq mh-index-max-msg-index (max mh-index-max-msg-index cur-index))
285 (cond ((and (equal cur-folder last-folder)
286 (= cur-index last-index))
288 ((equal cur-folder last-folder)
290 (set-buffer mh-index-buffer)
291 (push cur-index (gethash cur-folder mh-index-matches))))
294 (set-buffer mh-index-buffer)
295 (unless (gethash cur-folder mh-index-matches)
296 (setq button-start (point))
297 (gnus-eval-format "%T\n" '((?T cur-folder ?s))
298 `(,@(gnus-local-map-property
300 mh-callback mh-index-callback
301 mh-data ,cur-folder))
302 (setq button-end (point))
303 (widget-convert-button
304 'link button-start button-end
305 :button-keymap mh-index-button-map
306 :action 'mh-index-callback)
308 (push cur-index (gethash cur-folder mh-index-matches)))))
309 (setq last-folder cur-folder)
310 (setq last-index cur-index)))
312 ;; Get rid of extra line at end of the buffer if there were any hits.
313 (set-buffer mh-index-buffer)
314 (goto-char (point-max))
315 (when (and (= (forward-line -1) 0) (bolp) (eolp))
318 ;; Set mh-cmd-note to a large enough value...
319 (when mh-adaptive-cmd-note-flag
320 (mh-set-cmd-note (mh-index-find-max-width mh-index-max-msg-index)))
322 ;; Generate scan lines for the hits.
323 (message "Generating scan lines... ")
324 (goto-char (point-min))
326 (let ((folder (get-text-property (point) 'mh-data)))
330 (incf count (mh-index-insert-scan folder))))
333 ;; Go to the first hit (if any).
334 (goto-char (point-min))
337 ;; Remember old window configuration
338 (setq mh-index-previous-window-configuration config)
340 ;; Setup folder buffer mode
341 (when mh-decode-mime-flag
342 (add-hook 'kill-buffer-hook 'mh-mime-cleanup))
343 (mh-index-folder-mode)
344 (setq mh-show-buffer mh-index-show-buffer)
345 (setq buffer-read-only t)
346 (set-buffer-modified-p nil)
347 (mh-index-configure-one-window)
348 (setq mh-current-folder nil mh-index-current-msg nil)
349 (message "%s found %s matches in %s folders"
350 (upcase-initials (symbol-name mh-indexer))
351 count folder-count))))
353 (defun mh-index-find-max-width (max-index)
354 "Given MAX-INDEX find the number of digits necessary to print it."
357 (while (< max-int max-index)
359 (setq max-int (+ (* 10 max-int) 9)))
362 (defun mh-index-search-again ()
363 "Call `mh-index-search' from index search buffer."
365 (cond ((eq major-mode 'mh-index-show-mode)
366 (set-buffer mh-index-other-buffer))
367 ((not (eq major-mode 'mh-index-folder-mode))
368 (error "Should be called from one of the index buffers")))
369 (let ((old-buffer (current-buffer))
370 (window-config mh-index-previous-window-configuration))
371 (unwind-protect (call-interactively 'mh-index-search)
372 (when (eq old-buffer (current-buffer))
373 (setq mh-index-previous-window-configuration window-config)))))
375 (defun mh-index-insert-scan (folder)
376 "Insert scan lines for hits in FOLDER that the indexing program found.
377 The only twist is to replace the subject/body field with the match (if
380 (apply #'mh-exec-cmd-output
381 mh-scan-prog nil (mh-scan-format)
382 "-noclear" "-noheader" "-width" (window-width)
383 folder (mh-coalesce-msg-list (gethash folder mh-index-matches))))
385 (let ((window-width (window-width))
387 (while (not (or (get-text-property (point) 'mh-data) (eobp)))
389 (unless (and (eolp) (bolp))
391 (forward-char mh-cmd-note)
397 (defun mh-index-callback ()
398 "Callback function for buttons in the index buffer."
399 (let* ((folder (save-excursion
400 (buffer-substring-no-properties
401 (progn (beginning-of-line) (point))
402 (progn (end-of-line) (point)))))
403 (data (get-text-property (point) 'mh-data))
404 (msg-list (gethash data mh-index-matches)))
406 (mh-visit-folder folder msg-list))))
408 (defmacro mh-defun-index (func args &rest body)
409 "Macro to generate a function callable both from index and show buffer.
410 FUNC is the function name, ARGS the argument list and BODY the function
413 interactive-spec doc-string)
414 (when (stringp (car body))
415 (setq doc-string (car body))
416 (setq body (cdr body)))
417 (when (and (listp (car body)) (eq (caar body) 'interactive))
418 (setq interactive-spec (car body))
419 (setq body (cdr body)))
421 ,@(if doc-string (list doc-string) ())
423 (let* ((mh-index-buffer (if (eq major-mode 'mh-index-folder-mode)
424 (buffer-name (current-buffer))
425 mh-index-other-buffer))
426 (mh-index-show-buffer (if (eq major-mode 'mh-index-show-mode)
427 (buffer-name (current-buffer))
428 mh-index-other-buffer))
429 (,cur (cond ((eq (get-buffer mh-index-buffer)
432 ((eq (get-buffer mh-index-show-buffer)
434 mh-index-show-buffer)
435 (t (error "Not called from mh-index buffer")))))
436 (flet ((mh-msg-folder (folder) mh-index-buffer)
437 (mh-msg-filename (msg-num folder)
438 (format "%s%s/%s" mh-user-path (subseq folder 1) msg-num)))
439 (cond ((eq ,cur mh-index-buffer)
440 (mh-index-goto-nearest-msg)
441 (when (and mh-current-folder mh-index-current-msg)
442 (mh-index-notate mh-current-folder
443 mh-index-current-msg " " mh-cmd-note))
444 (setq mh-current-folder (mh-index-parse-folder))
445 (setq mh-index-current-msg (mh-index-parse-msg-number)))
446 ((eq ,cur mh-index-show-buffer)
447 (set-buffer mh-index-buffer)
448 (mh-index-goto-msg mh-current-folder
449 mh-index-current-msg)
450 (mh-index-notate nil nil " " mh-cmd-note))
451 (t (error "This can't happen!")))
455 (set-buffer mh-index-buffer)
456 (mh-index-goto-msg mh-current-folder mh-index-current-msg)
458 (mh-index-configure-windows)
459 (pop-to-buffer ,cur)))))))
461 (defun mh-index-advance (steps)
462 "Advance STEPS messages in the folder buffer.
463 If there are less than STEPS messages left then an error message is printed."
464 (let* ((backward-flag (< steps 0))
465 (steps (if backward-flag (- steps) steps))
470 (unless (= (forward-line (if backward-flag -1 1)) 0)
472 (cond ((and (eolp) (bolp) (not backward-flag))
473 (unless (= (forward-line 2) 0) (return-from body)))
474 ((and (get-text-property (point) 'mh-data) backward-flag)
475 (unless (= (forward-line -2) 0) (return-from body)))
476 ((or (and (eolp) (bolp))
477 (get-text-property (point) 'mh-data))
478 (error "Mh-index-buffer is inconsistent")))
480 (setq point (point))))
481 (cond (point (goto-char point) t)
484 ;; Details about message at point. These functions assume that we are on a
485 ;; line which contains a message scan line and not on a blank line or a line
486 ;; with a folder name.
487 (defun mh-index-parse-msg-number ()
488 "Parse message number of message at point."
492 (e (progn (forward-char mh-cmd-note) (point)))
494 (read-from-string (buffer-substring-no-properties b e)))))
495 (unless (and (consp data) (integerp (car data)))
496 (error "Didn't find message number"))
499 (defun mh-index-parse-folder ()
500 "Parse folder of message at point."
502 (while (not (get-text-property (point) 'mh-data))
503 (unless (eql (forward-line -1) 0)
504 (error "Reached beginning of buffer without seeing a folder")))
505 (buffer-substring-no-properties (progn (beginning-of-line) (point))
506 (progn (end-of-line) (point)))))
508 (defun mh-index-goto-nearest-msg ()
509 "If point is not at a message go to the closest line with a message on it."
511 (cond ((and (eolp) (bolp)) (forward-line -1))
512 ((get-text-property (point) 'mh-data) (forward-line 1))))
514 ;; Window configuration for mh-index... There should be similar functions
515 ;; in MH-E but I couldn't find them. I got the idea of using next-window,
516 ;; previous-window and minibuffer-window from MH-E code.
517 (defun mh-index-configure-windows ()
519 (cond ((and (buffer-live-p (get-buffer mh-index-show-buffer))
520 (buffer-live-p (get-buffer mh-index-buffer))
521 (eq (save-excursion (set-buffer mh-index-show-buffer) major-mode)
522 'mh-index-show-mode))
523 (mh-index-configure-two-windows))
524 ((buffer-live-p (get-buffer mh-index-buffer))
525 (mh-index-configure-one-window))))
527 (defun mh-count-windows ()
528 "Count the number of windows in the current frame.
529 The minibuffer window is excluded from the count."
530 (let* ((start-window (next-window nil t))
531 (current-window (next-window start-window t))
533 (while (not (eq current-window start-window))
535 (setq current-window (next-window current-window t)))
538 (defun mh-index-configure-two-windows ()
539 "Force a split view like that of MH-E."
541 (unless (and (get-buffer mh-index-show-buffer)
542 (get-buffer mh-index-buffer))
543 (error "We don't have both index buffers"))
544 (let ((window-count (mh-count-windows)))
545 (unless (and (= window-count 2)
546 (eq (window-buffer (next-window (minibuffer-window)))
547 (get-buffer mh-index-buffer))
548 (eq (window-buffer (previous-window (minibuffer-window)))
549 (get-buffer mh-index-show-buffer)))
550 (unless (= window-count 2)
551 (delete-other-windows)
552 (split-window-vertically))
553 (set-window-buffer (next-window (minibuffer-window))
555 (set-window-buffer (previous-window (minibuffer-window))
556 mh-index-show-buffer))
557 (unless (and (get-buffer-window mh-index-buffer)
558 (= (window-height (get-buffer-window mh-index-buffer))
560 (pop-to-buffer mh-index-buffer)
561 (shrink-window (- (window-height) mh-summary-height))))
562 (set-window-point (previous-window (minibuffer-window))
563 (progn (set-buffer mh-index-show-buffer) (point)))
564 (set-window-point (next-window (minibuffer-window))
565 (progn (set-buffer mh-index-buffer) (point)))))
567 (defun mh-index-configure-one-window ()
568 "Single window view."
570 (unless (buffer-live-p (get-buffer mh-index-buffer))
571 (error "Should have mh-index-buffer"))
572 (switch-to-buffer mh-index-buffer)
573 (delete-other-windows)
574 (set-window-point (next-window (minibuffer-window))
575 (progn (set-buffer mh-index-buffer) (point)))))
577 ;; This is slightly more involved than normal MH-E since we may have multiple
578 ;; folders in the same buffer.
579 (defun mh-index-goto-msg (folder msg)
580 "Move the cursor to the message specified by FOLDER and MSG."
582 (unless (buffer-live-p (get-buffer mh-index-buffer))
583 (error "No index buffer to go to"))
584 (set-buffer mh-index-buffer)
585 (goto-char (point-min))
586 (while (re-search-forward (format "^%s$" folder) nil t)
589 (when (= (mh-index-parse-msg-number) msg)
592 (error "Folder: %s, msg: %s doesn't exist" folder msg)))
594 ;; Can't use mh-notate directly since we could have more than one folder in
596 (defun mh-index-notate (folder msg notation offset)
597 "Add notation to scan line.
598 FOLDER is the message folder and MSG the message index. These arguments
599 specify the message to be notated. NOTATION is the character to be used to
600 notate and OFFSET is the number of chars from start of the line where
601 notation is to be placed."
603 (set-buffer mh-index-buffer)
604 (let ((buffer-read-only nil)
605 (modified-p (buffer-modified-p))
608 (when (and (stringp folder) (numberp msg))
610 (goto-char (point-min))
611 (re-search-forward (format "^%s$" folder))
614 (when (= (mh-index-parse-msg-number) msg)
620 (forward-char offset)
623 (unless modified-p (set-buffer-modified-p nil))))))
629 (mh-defun-index mh-index-show (display-headers-flag)
630 "Display message at point.
631 If there are no messages at point then display the closest message.
632 The value of `mh-index-show-hook' is a list of functions to be called,
633 with no arguments, after the message has been displayed.
634 If DISPLAY-HEADERS-FLAG is non-nil then the raw message is shown."
635 (interactive (list nil))
636 (when (or (and (bolp) (eolp)) (get-text-property (point) 'mh-data))
637 (error "No message at point"))
638 (setq mh-current-folder (mh-index-parse-folder))
639 (setq mh-index-current-msg (mh-index-parse-msg-number))
641 (when (and mh-current-folder mh-index-current-msg)
642 (mh-index-notate mh-current-folder mh-index-current-msg
643 mh-note-cur mh-cmd-note))
644 (let ((mh-decode-mime-flag (and (not display-headers-flag) mh-decode-mime-flag))
645 (mh-clean-message-header-flag
646 (and (not display-headers-flag) mh-clean-message-header-flag))
647 (mhl-formfile (if display-headers-flag nil mhl-formfile))
648 (msg mh-index-current-msg)
649 (folder mh-current-folder))
650 (when (not (eq display-headers-flag mh-showing-with-headers))
651 (mh-invalidate-show-buffer))
652 (mh-in-show-buffer (mh-index-show-buffer)
653 (mh-display-msg msg folder))
654 ;; Search for match in shown message
655 (select-window (get-buffer-window mh-index-show-buffer))
656 (set-buffer mh-index-show-buffer)
657 (mh-index-show-mode))
658 (run-hooks 'mh-index-show-hook))
660 (defun mh-index-header-display ()
661 "Show the message with full headers."
664 (setq mh-showing-with-headers t))
666 (mh-defun-index mh-index-next (steps)
667 "Display next message.
668 Prefix argument STEPS specifies the number of messages to skip ahead."
670 (mh-index-goto-nearest-msg)
671 (if (mh-index-advance steps)
674 (message "Not enough messages")))
676 (mh-defun-index mh-index-prev (steps)
677 "Display previous message.
678 Prefix argument STEPS specifies the number of messages to skip backward."
680 (mh-index-goto-nearest-msg)
681 (if (mh-index-advance (- steps))
684 (message "Not enough messages")))
686 (defun mh-index-page-msg (arg)
687 "Scroll the displayed message upward ARG lines."
690 (let* ((show-buffer (cond ((eq major-mode 'mh-index-folder-mode)
691 mh-index-other-buffer)
692 ((eq major-mode 'mh-index-show-mode)
693 (buffer-name (current-buffer)))
694 (t (error "Don't use mh-index-page-msg"))))
695 (window (get-buffer-window show-buffer))
696 (current-window (selected-window)))
697 (when (window-live-p window)
698 (select-window window)
699 (unwind-protect (scroll-up arg)
700 (select-window current-window))))))
702 (defun mh-index-previous-page (arg)
703 "Scroll the displayed message downward ARG lines."
706 (let* ((show-buffer (cond ((eq major-mode 'mh-index-folder-mode)
707 mh-index-other-buffer)
708 ((eq major-mode 'mh-index-show-mode)
709 (buffer-name (current-buffer)))
710 (t (error "Don't use mh-index-previous-page"))))
711 (window (get-buffer-window show-buffer))
712 (current-window (selected-window)))
713 (when (window-live-p window)
714 (select-window window)
715 (unwind-protect (scroll-down arg)
716 (select-window current-window))))))
718 (defun mh-index-press-button ()
719 "Press index button."
721 (let ((function (get-text-property (point) 'mh-callback)))
723 (funcall function))))
725 (defun mh-index-quit ()
726 "Quit the index folder.
727 Restore the previous window configuration, if one exists.
728 The value of `mh-before-quit-hook' is a list of functions to be called, with
729 no arguments, immediately upon entry to this function.
730 The value of `mh-quit-hook' is a list of functions to be called, with no
731 arguments, upon exit of this function."
733 (cond ((eq major-mode 'mh-index-show-mode)
734 (set-buffer mh-index-other-buffer))
735 ((not (eq major-mode 'mh-index-folder-mode))
736 (error "The function mh-index-quit shouldn't be called")))
737 (run-hooks 'mh-before-quit-hook)
738 (let ((mh-index-buffer (buffer-name (current-buffer)))
739 (mh-index-show-buffer mh-index-other-buffer)
740 (window-config mh-index-previous-window-configuration))
741 (when (buffer-live-p (get-buffer mh-index-buffer))
742 (bury-buffer (get-buffer mh-index-buffer)))
743 (when (buffer-live-p (get-buffer mh-index-show-buffer))
744 (bury-buffer (get-buffer mh-index-show-buffer)))
746 (set-window-configuration window-config)))
747 (run-hooks 'mh-quit-hook))
749 ;; Can't quite use mh-next-button... This buffer has no concept of
750 ;; folder-buffer or show-buffer. Maybe refactor mh-next-button?
751 (defun mh-index-next-button (&optional backward-flag)
752 "Go to the next button.
753 Advance point to the next button in the show buffer. If the end of buffer is
754 reached then the search wraps over to the start of the buffer. With optional
755 argument BACKWARD-FLAG the point will move to the previous button."
756 (interactive current-prefix-arg)
757 (mh-goto-next-button backward-flag))
759 (defun mh-index-prev-button ()
760 "Go to the next button.
761 Move point to the previous button in the show buffer. If the beginning of
762 the buffer is reached then the search wraps over to the end."
764 (mh-index-next-button t))
770 (defvar mh-glimpse-binary (executable-find "glimpse"))
771 (defvar mh-glimpse-directory ".glimpse")
773 (defun mh-glimpse-execute-search (folder-path search-regexp)
774 "Execute glimpse and read the results.
776 In the examples below, replace /home/user/Mail with the path to your MH
779 First create the directory /home/user/Mail/.glimpse. Then create the file
780 /home/user/Mail/.glimpse/.glimpse_exclude with the following contents:
786 ^/home/user/Mail/.glimpse
788 If there are any directories you would like to ignore, append lines like the
789 following to .glimpse_exclude:
791 ^/home/user/Mail/scripts
793 Use the following command line to generate the glimpse index. Run this
796 glimpseindex -H /home/user/Mail/.glimpse /home/user/Mail
798 FOLDER-PATH is the directory in which SEARCH-REGEXP is used to search."
799 (set-buffer (get-buffer-create mh-index-temp-buffer))
801 (call-process mh-glimpse-binary nil '(t nil) nil
804 "-H" (format "%s%s" mh-user-path mh-glimpse-directory)
805 "-F" (format "^%s" folder-path)
807 (goto-char (point-min)))
809 (defun mh-glimpse-next-result ()
810 "Read the next result.
811 Parse it and return the message folder, message index and the match. If no
812 other matches left then return nil. If the current record is invalid return
818 (let ((eol-pos (line-end-position))
819 (bol-pos (line-beginning-position))
820 folder-start msg-end)
822 (unless (search-forward mh-user-path eol-pos t)
824 (setq folder-start (point))
825 (unless (search-forward ": " eol-pos t)
827 (let ((match (buffer-substring-no-properties (point) eol-pos)))
829 (setq msg-end (point))
830 (unless (search-backward "/" folder-start t)
832 (list (format "+%s" (buffer-substring-no-properties
833 folder-start (point)))
834 (let ((val (ignore-errors (read-from-string
835 (buffer-substring-no-properties
836 (1+ (point)) msg-end)))))
837 (if (and (consp val) (integerp (car val)))
847 (defvar mh-grep-binary (executable-find "grep"))
849 (defun mh-grep-execute-search (folder-path search-regexp)
850 "Execute grep and read the results.
851 FOLDER-PATH is the directory in which SEARCH-REGEXP is used to search."
852 (set-buffer (get-buffer-create mh-index-temp-buffer))
854 (call-process mh-grep-binary nil '(t nil) nil
855 "-i" "-r" search-regexp folder-path)
856 (goto-char (point-min)))
858 (defun mh-grep-next-result ()
859 "Read the next result.
860 Parse it and return the message folder, message index and the match. If no
861 other matches left then return nil. If the current record is invalid return
867 (let ((eol-pos (line-end-position))
868 (bol-pos (line-beginning-position))
869 folder-start msg-end)
871 (unless (search-forward mh-user-path eol-pos t)
873 (setq folder-start (point))
874 (unless (search-forward ":" eol-pos t)
876 (let ((match (buffer-substring-no-properties (point) eol-pos)))
878 (setq msg-end (point))
879 (unless (search-backward "/" folder-start t)
881 (list (format "+%s" (buffer-substring-no-properties
882 folder-start (point)))
883 (let ((val (ignore-errors (read-from-string
884 (buffer-substring-no-properties
885 (1+ (point)) msg-end)))))
886 (if (and (consp val) (integerp (car val)))
896 (defvar mh-swish-binary (executable-find "swish-e"))
897 (defvar mh-swish-directory ".swish")
898 (defvar mh-swish-folder nil)
900 (defun mh-swish-execute-search (folder-path search-regexp)
901 "Execute swish-e and read the results.
903 In the examples below, replace /home/user/Mail with the path to your MH
906 First create the directory /home/user/Mail/.swish. Then create the file
907 /home/user/Mail/.swish/config with the following contents:
909 IndexDir /home/user/Mail
910 IndexFile /home/user/Mail/.swish/index
911 IndexName \"Mail Index\"
912 IndexDescription \"Mail Index\"
913 IndexPointer \"http://nowhere\"
914 IndexAdmin \"nobody\"
919 IgnoreTotalWordCountWhenRanking yes
920 WordCharacters abcdefghijklmnopqrstuvwxyz0123456789-
921 BeginCharacters abcdefghijklmnopqrstuvwxyz
922 EndCharacters abcdefghijklmnopqrstuvwxyz0123456789
925 FileRules pathname contains /home/user/Mail/.swish
926 FileRules filename is index
927 FileRules filename is \..*
928 FileRules filename is #.*
929 FileRules filename is ,.*
930 FileRules filename is .*~
932 If there are any directories you would like to ignore, append lines like the
935 FileRules pathname contains /home/user/Mail/scripts
937 Use the following command line to generate the swish index. Run this
940 swish-e -c /home/user/Mail/.swish/config
942 FOLDER-PATH is the directory in which SEARCH-REGEXP is used to search."
943 (set-buffer (get-buffer-create mh-index-temp-buffer))
945 (unless mh-swish-binary
946 (error "Set mh-swish-binary appropriately"))
947 (call-process mh-swish-binary nil '(t nil) nil
949 "-f" (format "%s%s/index" mh-user-path mh-swish-directory))
950 (goto-char (point-min))
951 (setq mh-swish-folder
952 (let ((last-char (substring folder-path (1- (length folder-path)))))
953 (if (equal last-char "/")
955 (format "%s/" folder-path)))))
957 (defun mh-swish-next-result ()
958 "Get the next result from swish output."
961 (when (or (eobp) (equal (char-after (point)) ?.))
963 (when (equal (char-after (point)) ?#)
965 (let* ((start (search-forward " " (line-end-position) t))
966 (end (search-forward " " (line-end-position) t)))
967 (unless (and start end)
970 (unless (file-exists-p (buffer-substring-no-properties start end))
972 (unless (search-backward "/" start t)
974 (list (let* ((s (buffer-substring-no-properties start (1+ (point)))))
975 (unless (string-match mh-swish-folder s)
977 (if (string-match mh-user-path s)
979 (substring s (match-end 0) (1- (length s))))
981 (let* ((s (buffer-substring-no-properties (1+ (point)) end))
982 (val (ignore-errors (read-from-string s))))
983 (if (and (consp val) (numberp (car val)))
993 (defvar mh-swish++-binary (or (executable-find "search++")
994 (executable-find "search")))
995 (defvar mh-swish++-directory ".swish++")
997 (defun mh-swish++-execute-search (folder-path search-regexp)
998 "Execute swish++ and read the results.
1000 In the examples below, replace /home/user/Mail with the path to your MH
1003 First create the directory /home/user/Mail/.swish++. Then create the file
1004 /home/user/Mail/.swish++/swish++.conf with the following contents:
1006 IncludeMeta Bcc Cc Comments Content-Description From Keywords
1007 IncludeMeta Newsgroups Resent-To Subject To
1008 IncludeFile Mail [0-9]*
1009 IndexFile /home/user/Mail/.swish++/swish++.index
1011 Use the following command line to generate the swish index. Run this
1014 index -c /home/user/Mail/.swish++/swish++.conf /home/user/Mail
1016 On some systems (Debian GNU/Linux, for example), use index++ instead of index.
1018 FOLDER-PATH is the directory in which SEARCH-REGEXP is used to search."
1019 (set-buffer (get-buffer-create mh-index-temp-buffer))
1021 (unless mh-swish++-binary
1022 (error "Set mh-swish++-binary appropriately"))
1023 (call-process mh-swish++-binary nil '(t nil) nil
1025 (format "-i%s%s/swish++.index"
1026 mh-user-path mh-swish++-directory)
1028 (goto-char (point-min))
1029 (setq mh-swish-folder
1030 (let ((last-char (substring folder-path (1- (length folder-path)))))
1031 (if (equal last-char "/")
1033 (format "%s/" folder-path)))))
1035 (defalias 'mh-swish++-next-result 'mh-swish-next-result)
1041 (defvar mh-namazu-binary (executable-find "namazu"))
1042 (defvar mh-namazu-directory ".namazu")
1043 (defvar mh-namazu-folder nil)
1045 (defun mh-namazu-execute-search (folder-path search-regexp)
1046 "Execute namazu and read the results.
1048 In the examples below, replace /home/user/Mail with the path to your MH
1051 First create the directory /home/user/Mail/.namazu. Then create the file
1052 /home/user/Mail/.namazu/mknmzrc with the following contents:
1054 package conf; # Don't remove this line!
1055 $ADDRESS = 'user@localhost';
1056 $ALLOW_FILE = \"[0-9]*\";
1058 Use the following command line to generate the namazu index. Run this
1061 mknmz -f /home/user/Mail/.namazu/mknmzrc -O /home/user/Mail/.namazu \\
1064 FOLDER-PATH is the directory in which SEARCH-REGEXP is used to search."
1065 (let ((namazu-index-directory
1066 (format "%s%s" mh-user-path mh-namazu-directory)))
1067 (unless (file-exists-p namazu-index-directory)
1068 (error "Namazu directory %s not present" namazu-index-directory))
1069 (unless (executable-find mh-namazu-binary)
1070 (error "Set mh-namazu-binary appropriately"))
1071 (set-buffer (get-buffer-create mh-index-temp-buffer))
1073 (call-process mh-namazu-binary nil '(t nil) nil
1074 "-alR" search-regexp namazu-index-directory)
1075 (goto-char (point-min))
1076 (setq mh-namazu-folder
1077 (let ((last (substring folder-path (1- (length folder-path)))))
1078 (if (equal last "/")
1080 (format "%s/" folder-path))))))
1082 (defun mh-namazu-next-result ()
1083 "Get the next result from namazu output."
1086 (when (eobp) (return nil))
1087 (let ((file-name (buffer-substring-no-properties
1088 (point) (line-end-position))))
1089 (unless (equal (string-match mh-namazu-folder file-name) 0)
1091 (unless (file-exists-p file-name)
1093 (string-match mh-user-path file-name)
1094 (let* ((folder/msg (substring file-name (match-end 0)))
1095 (mark (search "/" folder/msg :from-end t)))
1096 (unless mark (return 'error))
1097 (list (format "+%s" (substring folder/msg 0 mark))
1098 (let ((n (ignore-errors (read-from-string
1099 (substring folder/msg (1+ mark))))))
1100 (if (and (consp n) (numberp (car n)))
1108 (defun mh-index-choose ()
1109 "Choose an indexing function.
1110 The side-effects of this function are that the variables `mh-indexer',
1111 `mh-index-execute-search-function', and `mh-index-next-result-function' are
1112 set according to the first indexer in `mh-indexer-choices' present on the
1115 ;; The following favors the user's preference; otherwise, the last
1116 ;; automatically chosen indexer is used for efficiency rather than going
1117 ;; through the list.
1118 (let ((program-alist (cond (mh-index-program
1120 (assoc mh-index-program mh-indexer-choices)))
1122 (list (assoc mh-indexer mh-indexer-choices)))
1123 (t mh-indexer-choices))))
1124 (while program-alist
1125 (let* ((current (pop program-alist))
1126 (executable (symbol-value (cadr current))))
1128 (setq mh-indexer (car current))
1129 (setq mh-index-execute-search-function (caddr current))
1130 (setq mh-index-next-result-function (cadddr current))
1131 (return mh-indexer))))
1136 ;;; Menu extracted from mh-menubar.el V1.1 (31 July 2001)
1137 ;;; Menus for folder mode: folder, message (in that order)
1138 ;;; folder-mode "Message" menu
1140 mh-index-folder-message-menu mh-index-folder-mode-keymap
1141 "Menu for MH-E folder-message."
1143 ["Show Message" mh-index-show (mh-get-msg-num nil)]
1144 ["Show Message with Header" mh-index-header-display (mh-get-msg-num nil)]
1145 ["Next Message" mh-index-next t]
1146 ["Previous Message" mh-index-prev t]
1148 ["Compose a New Message" mh-send t]))
1150 ;;; folder-mode "Folder" menu
1152 mh-index-folder-folder-menu mh-index-folder-mode-keymap
1153 "Menu for MH-E folder."
1155 ["Incorporate New Mail" mh-inc-folder t]
1157 ["Visit a Folder..." mh-visit-folder t]
1158 ["Indexed Search..." mh-index-search-again t]
1160 ["Quit Indexed Search" mh-index-quit t]))
1164 ;;; Support for emacs21 toolbar using gnus/message.el icons (and code).
1165 (eval-when-compile (defvar tool-bar-map))
1166 (defvar mh-index-folder-tool-bar-map nil)
1167 (when (fboundp 'tool-bar-add-item)
1168 (setq mh-index-folder-tool-bar-map
1169 (let ((tool-bar-map (make-sparse-keymap)))
1170 (tool-bar-add-item "mail" 'mh-inc-folder
1171 'mh-indexfoldertoolbar-inc-folder
1172 :help "Incorporate new mail in Inbox")
1173 (tool-bar-add-item "left_arrow" 'mh-index-prev
1174 'mh-indexfoldertoolbar-prev :help "Previous message")
1175 (tool-bar-add-item "page-down" 'mh-index-page-msg
1176 'mh-indexfoldertoolbar-page
1177 :help "Page this message")
1178 (tool-bar-add-item "right_arrow" 'mh-index-next
1179 'mh-indexfoldertoolbar-next :help "Next message")
1181 (tool-bar-add-item "mail_compose" 'mh-send 'mh-indexfoldertoolbar-compose
1182 :help "Compose new message")
1184 (tool-bar-add-item "search"
1185 (lambda (&optional arg)
1187 (call-interactively mh-tool-bar-search-function))
1188 'mh-indexfoldertoolbar-search :help "Search")
1189 (tool-bar-add-item "fld_open" 'mh-visit-folder
1190 'mh-indexfoldertoolbar-visit
1191 :help "Visit other folder")
1193 (tool-bar-add-item "preferences" (lambda ()
1195 (customize-group "mh"))
1196 'mh-indexfoldertoolbar-customize
1197 :help "MH-E preferences")
1198 (tool-bar-add-item "help" (lambda ()
1200 (Info-goto-node "(mh-e)Top"))
1201 'mh-indexfoldertoolbar-help :help "Help")
1204 ;; Modes for mh-index
1205 (define-derived-mode mh-index-folder-mode mh-folder-mode "MH-Index-Folder"
1206 "Major MH-E mode for displaying the results of searching.\\<mh-index-folder-mode-keymap>
1208 You can display the message the cursor is pointing to and step through the
1211 You can also jump to the folders narrowed to the search results by pressing
1212 RET on the folder name. Many operations, such as replying to a message,
1213 require that you do this first.
1215 \\{mh-index-folder-mode-keymap}"
1216 (make-local-variable 'font-lock-defaults)
1217 (setq font-lock-defaults '(mh-index-font-lock-keywords t))
1218 (use-local-map mh-index-folder-mode-keymap)
1219 (make-local-variable 'mh-help-messages)
1220 (easy-menu-add mh-index-folder-message-menu)
1221 (easy-menu-add mh-index-folder-folder-menu)
1222 (if (and (boundp 'tool-bar-mode) tool-bar-mode)
1223 (set (make-local-variable 'tool-bar-map) mh-index-folder-tool-bar-map))
1224 (setq mh-help-messages mh-index-folder-mode-help-messages))
1226 (define-derived-mode mh-index-show-mode mh-show-mode "MH-Index-Show"
1227 "Major mode for showing messages in MH-E index.\\<mh-index-folder-mode-keymap>
1228 \\{mh-index-folder-mode-keymap}"
1229 (use-local-map mh-index-folder-mode-keymap)
1230 (setq mh-help-messages mh-index-folder-mode-help-messages))
1232 ;; Font lock support for mh-index-folder. This is the same as mh-folder
1233 ;; except that the folder line needs to be recognized and highlighted.
1234 (defvar mh-index-folder-face 'mh-index-folder-face
1235 "Face for highlighting folders in MH-Index buffers.")
1236 (defface mh-index-folder-face
1237 '((((class color) (background light))
1238 (:foreground "dark green"))
1239 (((class color) (background dark))
1240 (:foreground "indian red"))
1243 "Face for highlighting folders in MH-Index buffers."
1246 (eval-after-load "font-lock"
1248 (defvar mh-index-folder-face 'mh-index-folder-face
1249 "Face for highlighting folders in MH-Index buffers.")
1251 (defvar mh-index-font-lock-keywords
1254 (list "^\\+.*" '(0 mh-index-folder-face))
1255 ;; Marked for deletion
1256 (list (concat mh-scan-deleted-msg-regexp ".*")
1257 '(0 mh-folder-deleted-face))
1258 ;; Marked for refile
1259 (list (concat mh-scan-refiled-msg-regexp ".*")
1260 '(0 mh-folder-refiled-face))
1262 (list mh-scan-body-regexp '(1 mh-folder-body-face nil t))
1263 '(mh-folder-font-lock-subject
1264 (1 mh-folder-followup-face append t)
1265 (2 mh-folder-subject-face append t))
1267 (list mh-scan-cur-msg-number-regexp
1268 '(1 mh-folder-cur-msg-number-face))
1269 (list mh-scan-good-msg-regexp
1270 '(1 mh-folder-msg-number-face)) ;; Msg number
1271 (list mh-scan-date-regexp '(1 mh-folder-date-face)) ;; Date
1272 (list mh-scan-rcpt-regexp
1273 '(1 mh-folder-to-face) ;; To:
1274 '(2 mh-folder-address-face)) ;; address
1275 ;; scan font-lock name
1276 (list mh-scan-format-regexp
1277 '(1 mh-folder-date-face)
1278 '(3 mh-folder-scan-format-face))
1279 ;; Current message line
1280 (list mh-scan-cur-msg-regexp
1281 '(1 mh-folder-cur-msg-face prepend t)))
1282 "Regexp keywords used to fontify the MH-Index-Folder buffer.")))
1286 ;;; Local Variables:
1287 ;;; sentence-end-double-space: nil
1290 ;;; mh-index ends here