T-gnus 6.15.18 revision 00.
[elisp/gnus.git-] / lisp / gnus-namazu.el
index 1f42383..5ef95eb 100644 (file)
@@ -1,12 +1,10 @@
-;;; gnus-namazu.el --- Search mail with Namazu.
+;;; gnus-namazu.el --- Search mail with Namazu -*- coding: iso-2022-7bit; -*-
 
-;; Copyright (C) 2000,2001 Tsuchiya Masatoshi <tsuchiya@namazu.org>
+;; Copyright (C) 2000,2001,2002 TSUCHIYA Masatoshi <tsuchiya@namazu.org>
 
-;; Author: Tsuchiya Masatoshi <tsuchiya@namazu.org>
+;; Author: TSUCHIYA Masatoshi <tsuchiya@namazu.org>
 ;; Keywords: mail searching namazu
 
-;;; Copyright:
-
 ;; This file is a part of Semi-Gnus.
 
 ;; This program is free software; you can redistribute it and/or modify
 
 ;;; Commentary:
 
-;; This file defines the command to search mails with Namazu and
-;; browse its results with Gnus.  This module requires the external
-;; command Namazu.  Visit the following page for more information.
+;; This file defines the command to search mails and persistent
+;; articles with Namazu and to browse its results with Gnus.
+;;
+;; Namazu is a full-text search engine intended for easy use.  For
+;; more detail about Namazu, visit the following page:
 ;;
 ;;     http://namazu.org/
 
 
+;;; Quick Start:
+
+;; If this module has already been installed, only 3 steps are
+;; required to search articles with this module.
+;;
+;;   (1) Install Namazu.
+;;   (2) Start Gnus and type M-x gnus-namazu-create-index RET to make
+;;       index of articles.
+;;   (3) In group buffer or in summary buffer, type C-c C-n query RET.
+
+
 ;;; Install:
 
-;; Make index of articles with Namzu before using this module.
+;; Before installing this module, you must install Namazu.
 ;;
-;;      % mkdir ~/News/namazu
-;;       % mknmz -a -h -O ~/News/namazu ~/Mail
+;; This file is a part of T-gnus but is not *YET* a part of Gnus.
+;; When you would like to use this module in Gnus (not T-gnus), put
+;; this file into the lisp/ directory in the Gnus source tree and run
+;; `make install'.  And then, put the following expression into your
+;; ~/.gnus.
 ;;
-;; When you put index files of Namazu into the directory other than
-;; the default one (~/News/namazu), it is necessary to put this
-;; expression to your ~/.gnus, in order to set the path of index files
-;; to `gnus-namazu-index-directories'.
+;;      (require 'gnus-namazu)
+;;      (gnus-namazu-insinuate)
 ;;
-;;      (setq gnus-namazu-index-directories
-;;            (list (expand-file-name "~/namazu")))
+;; In order to make index of articles with Namazu before using this
+;; module, type M-x gnus-namazu-create-index RET.  Otherwise, you can
+;; create index by yourself with the following commands:
 ;;
-;; If you would like to use this module in Gnus (not T-gnus), put this
-;; file into the lisp/ directory in the Gnus source tree and run `make
-;; install'.  And then, put the following expressions into your ~/.gnus.
+;;      % mkdir ~/News/namazu
+;;      % mknmz -a -h -O ~/News/namazu ~/Mail ~/News/cache
 ;;
-;;      (require 'gnus-namazu)
-;;      (gnus-namazu-insinuate)
-
-
-;;; Usage:
-
-;; In group buffer or in summary buffer, type C-c C-n query RET.
+;; The first command makes the directory for index files, and the
+;; second command generates index files of mails and persistent
+;; articles.
+;;
+;; In order to update indices for incoming articles, this module
+;; automatically runs mknmz, the indexer of Namazu, at an interval of
+;; 3 days; this period is set to `gnus-namazu-index-update-interval'.
+;;
+;; Indices will be updated when `gnus-namazu-search' is called.  If
+;; you want to update indices everywhen Gnus is started, you can put
+;; the following expression to your ~/.gnus.
+;;
+;;      (add-hook 'gnus-startup-hook 'gnus-namazu-update-all-indices)
+;;
+;; In order to control mknmz closely, disable the automatic updating
+;; feature and run mknmz by yourself.  In this case, set nil to the
+;; above option.
+;;
+;;      (setq gnus-namazu-index-update-interval nil)
+;;
+;; When your index is put into the directory other than the default
+;; one (~/News/namazu), it is necessary to set its place to
+;; `gnus-namazu-index-directories' as follows:
+;;
+;;      (setq gnus-namazu-index-directories
+;;            (list (expand-file-name "~/namazu")))
 
 
 ;;; Code:
 ;; To suppress byte-compile warning.
 (eval-when-compile
   (defvar nnml-directory)
-  (defvar nnml-group-alist)
-  (defvar nnmh-directory)
-  (defvar nnmh-group-alist))
+  (defvar nnmh-directory))
 
 
 (defgroup gnus-namazu nil
   :group 'gnus
   :prefix "gnus-namazu-")
 
+(defconst gnus-namazu-default-index-directory
+  (expand-file-name "namazu" gnus-directory)
+  "Default place of Namazu index files.")
+
 (defcustom gnus-namazu-index-directories
   (list
    (or (and (boundp 'gnus-namazu-index-directory)
            (symbol-value 'gnus-namazu-index-directory))
        (and (boundp 'nnir-namazu-index-directory)
            (symbol-value 'nnir-namazu-index-directory))
-       (expand-file-name "namazu" gnus-directory)))
-  "*Index directory of Namazu."
+       gnus-namazu-default-index-directory))
+  "*Places of Namazu index files."
   :type '(repeat directory)
   :group 'gnus-namazu)
 
           (symbol-value 'nnir-namazu-program))
       "namazu")
   "*Name of the executable file of Namazu."
-  :group 'gnus-namazu
-  :type 'string)
+  :type 'string
+  :group 'gnus-namazu)
 
 (defcustom gnus-namazu-additional-arguments nil
   "*Additional arguments of Namazu.
@@ -116,6 +149,30 @@ options make any sense in this context."
   :type '(repeat string)
   :group 'gnus-namazu)
 
+(defcustom gnus-namazu-index-update-interval
+  259200                               ; 3 days == 259200 seconds.
+  "*Number of seconds between running the indexer of Namazu."
+  :type '(choice (const :tag "Never run the indexer" nil)
+                (integer :tag "Number of seconds"))
+  :group 'gnus-namazu)
+
+(defcustom gnus-namazu-make-index-command "mknmz"
+  "*Name of the executable file of the indexer of Namazu."
+  :type 'string
+  :group 'gnus-namazu)
+
+(defcustom gnus-namazu-make-index-arguments
+  (nconc
+   (list "--all" "--mailnews" "--deny=^.*[^0-9].*$")
+   (when (or (and (boundp 'current-language-environment)
+                 (string= "Japanese"
+                          (symbol-value 'current-language-environment)))
+            (boundp 'MULE))
+     (list "--indexing-lang=ja")))
+  "*Arguments of the indexer of Namazu."
+  :type '(repeat string)
+  :group 'gnus-namazu)
+
 (defcustom gnus-namazu-field-keywords
   '("date" "from" "newsgroups" "size" "subject" "summary" "to" "uri")
   "*List of keywords to do field-search."
@@ -131,8 +188,8 @@ options make any sense in this context."
   :group 'gnus-namazu)
 
 (defcustom gnus-namazu-need-path-normalization
-  (eq system-type 'windows-nt)
-  "*Non-nil means that outputs of namazu may contain a not normalized path."
+  (and (memq system-type '(windows-nt OS/2 emx)) t)
+  "*Non-nil means that outputs of namazu may contain drive letters."
   :type 'boolean
   :group 'gnus-namazu)
 
@@ -142,12 +199,43 @@ options make any sense in this context."
   :type 'boolean
   :group 'gnus-namazu)
 
+(defcustom gnus-namazu-query-highlight t
+  "Non-nil means that queried words is highlighted."
+  :type 'boolean
+  :group 'gnus-namazu)
+
+(defface gnus-namazu-query-highlight-face
+  '((((type tty pc) (class color))
+     (:background "magenta4" :foreground "cyan1"))
+    (((class color) (background light))
+     (:background "magenta4" :foreground "lightskyblue1"))
+    (((class color) (background dark))
+     (:background "palevioletred2" :foreground "brown4"))
+    (t (:inverse-video t)))
+  "Face used for namazu query matching words."
+  :group 'gnus-namazu)
 
 ;;; Internal Variable:
-(defvar gnus-namazu/group-alist nil
-  "Associative list to map groups in lower case to official names.")
 (defconst gnus-namazu/group-name-regexp "\\`nnvirtual:namazu-search\\?")
 
+;; Multibyte group name:
+(and
+ (fboundp 'gnus-group-decoded-name)
+ (let ((gnus-group-name-charset-group-alist
+       (list (cons gnus-namazu/group-name-regexp gnus-namazu-coding-system)))
+       (query (decode-coding-string
+              (string 27 36 66 52 65 59 122 27 40 66)
+              (if (boundp 'MULE) '*iso-2022-jp* 'iso-2022-7bit))))
+   (not (string-match query
+                     (gnus-summary-buffer-name
+                      (encode-coding-string
+                       (concat "nnvirtual:namazu-search?query=" query)
+                       gnus-namazu-coding-system)))))
+ (let (current-load-list)
+   (defadvice gnus-summary-buffer-name
+     (before gnus-namazu-summary-buffer-name activate compile)
+     "Advised by `gnus-namazu' to handle encoded group names."
+     (ad-set-arg 0 (gnus-group-decoded-name (ad-get-arg 0))))))
 
 (defmacro gnus-namazu/make-article (group number)
   `(cons ,group ,number))
@@ -160,33 +248,25 @@ options make any sense in this context."
    (gnus-servers-using-backend 'nnml)
    (gnus-servers-using-backend 'nnmh)))
 
+(defsubst gnus-namazu/default-index-directory ()
+  (if (member gnus-namazu-default-index-directory
+             gnus-namazu-index-directories)
+      gnus-namazu-default-index-directory
+    (car gnus-namazu-index-directories)))
+
 (defun gnus-namazu/setup ()
-  (add-to-list 'gnus-group-name-charset-group-alist
-              (cons gnus-namazu/group-name-regexp gnus-namazu-coding-system))
-  (unless gnus-namazu-case-sensitive-filesystem
-    ;; FIXME: The alist to map group names in lower case to real names
-    ;; is reconstructed every when gnus-namazu/setup() is called.
-    ;; This reconstruction make gnus-namazu-search() slow.
-    (setq gnus-namazu/group-alist nil)
-    (dolist (server (gnus-namazu/indexed-servers))
-      (dolist (group (gnus-namazu/request-list server))
-       (let ((name (gnus-group-prefixed-name group server)))
-         (unless (assoc name gnus-namazu/group-alist)
-           (push (cons (downcase name) name) gnus-namazu/group-alist)))))))
-
-(defun gnus-namazu/shutdown ()
-  (setq gnus-namazu/group-alist nil))
-(add-hook 'gnus-exit-gnus-hook 'gnus-namazu/shutdown)
-
-(defun gnus-namazu/request-list (server)
-  "Return groups of the server SERVER."
-  (and (memq (car server) '(nnml nnmh))
-       (nnoo-change-server (car server) (nth 1 server) (nthcdr 2 server))
-       (gnus-request-list server)
-       (mapcar (function car)
-              (if (eq 'nnml (car server))
-                  nnml-group-alist
-                nnmh-group-alist))))
+  (and (boundp 'gnus-group-name-charset-group-alist)
+       (not (member (cons gnus-namazu/group-name-regexp
+                         gnus-namazu-coding-system)
+                   gnus-group-name-charset-group-alist))
+       (let ((pair (assoc gnus-namazu/group-name-regexp
+                         gnus-group-name-charset-group-alist)))
+        (if pair
+            (setcdr pair gnus-namazu-coding-system)
+          (push (cons gnus-namazu/group-name-regexp
+                      gnus-namazu-coding-system)
+                gnus-group-name-charset-group-alist))))
+  (gnus-namazu-update-all-indices))
 
 (defun gnus-namazu/server-directory (server)
   "Return the top directory of the server SERVER."
@@ -202,6 +282,8 @@ options make any sense in this context."
   "Normalize file names returned by Namazu in this current buffer."
   (goto-char (point-min))
   (while (not (eobp))
+    (when (looking-at "file://")
+      (delete-region (point) (match-end 0)))
     (when (if gnus-namazu-need-path-normalization
              (or (not (looking-at "/\\(.\\)|/"))
                  (replace-match "\\1:/"))
@@ -214,8 +296,11 @@ options make any sense in this context."
 (defsubst gnus-namazu/call-namazu (query)
   (let ((coding-system-for-read gnus-namazu-coding-system)
        (coding-system-for-write gnus-namazu-coding-system)
+       (input-coding-system gnus-namazu-coding-system)
+       (output-coding-system gnus-namazu-coding-system)
        (default-process-coding-system
          (cons gnus-namazu-coding-system gnus-namazu-coding-system))
+       program-coding-system-alist
        (file-name-coding-system gnus-namazu-coding-system)
        (pathname-coding-system gnus-namazu-coding-system))
     (apply 'call-process
@@ -234,9 +319,49 @@ options make any sense in this context."
   "Return the whole name from GROUP and METHOD."
   (if gnus-namazu-case-sensitive-filesystem
       (gnus-group-prefixed-name group method)
-    (let ((name (gnus-group-prefixed-name group method)))
-      (or (cdr (assoc (downcase name) gnus-namazu/group-alist))
-         name))))
+    (let* ((orig (gnus-group-prefixed-name group method))
+          (name (downcase orig)))
+      (catch 'found-group
+       (mapatoms (lambda (sym)
+                   (when (string= name (downcase (symbol-name sym)))
+                     (throw 'found-group (symbol-name sym))))
+                 gnus-newsrc-hashtb)
+       orig))))
+
+(defun gnus-namazu/real-group-name (cond str)
+  "Generate the real group name from the partial path, STR."
+  (if cond
+      str
+    (catch 'found-group
+      (dolist (group (gnus-namazu/possible-real-groups
+                     (nnheader-replace-chars-in-string str ?/ ?.)))
+       (when (gnus-gethash group gnus-newsrc-hashtb)
+         (throw 'found-group group))))))
+
+(defun gnus-namazu/possible-real-groups (str)
+  "Regard the string STR as the partial path of the cached article and
+generate possible group names from it."
+  (if (string-match "_\\(_\\(_\\)?\\)?" str)
+      (let ((prefix (substring str 0 (match-beginning 0)))
+           (suffix (substring str (match-end 0))))
+       (cond
+        ((match-beginning 2) ;; The number of discoverd underscores = 3
+         (nconc
+          (gnus-namazu/possible-real-groups (concat prefix "/__" suffix))
+          (gnus-namazu/possible-real-groups (concat prefix ".._" suffix))))
+        ((match-beginning 1) ;; The number of discoverd underscores = 2
+         (nconc
+          (gnus-namazu/possible-real-groups (concat prefix "//" suffix))
+          (gnus-namazu/possible-real-groups (concat prefix ".." suffix))))
+        (t ;; The number of discoverd underscores = 1
+         (gnus-namazu/possible-real-groups (concat prefix "/" suffix)))))
+    (if (string-match "\\." str)
+       ;; Handle the first occurence of period.
+       (list (concat (substring str 0 (match-beginning 0))
+                     ":"
+                     (substring str (match-end 0)))
+             str)
+      (list str))))
 
 (defun gnus-namazu/search (groups query)
   (with-temp-buffer
@@ -252,31 +377,56 @@ options make any sense in this context."
                         (when (setq dir (gnus-namazu/server-directory s))
                           (cons (file-name-as-directory dir) s)))
                       (gnus-namazu/indexed-servers)))))
-            (topdir-regexp (regexp-opt (mapcar 'car server-alist))))
+            (topdir-regexp (regexp-opt (mapcar 'car server-alist)))
+            (cache-regexp (concat
+                           (regexp-quote
+                            (file-name-as-directory
+                             (expand-file-name gnus-cache-directory)))
+                           "\\(.*\\)/\\([0-9]+\\)$"))
+            (agent-regexp (concat
+                           (regexp-quote
+                            (file-name-as-directory
+                             (expand-file-name gnus-agent-directory)))
+                           "\\(.*\\)/\\([0-9]+\\)$")))
        (gnus-namazu/normalize-results)
        (goto-char (point-min))
        (while (not (eobp))
          (let (server group file)
-           (and (looking-at topdir-regexp)
-                ;; Check a discovered file is managed by Gnus servers.
-                (setq file (buffer-substring-no-properties
-                            (match-end 0) (gnus-point-at-eol))
-                      server (cdr (assoc (match-string-no-properties 0)
-                                         server-alist)))
-                ;; Check validity of the file name.
-                (string-match "/\\([0-9]+\\)\\'" file)
-                (progn
-                  (setq group (substring file 0 (match-beginning 0))
-                        file (match-string 1 file))
-                  (setq group
-                        (gnus-namazu/group-prefixed-name
-                         (nnheader-replace-chars-in-string group ?/ ?.)
-                         server))
-                  (when (or (not groups)
-                            (member group groups))
-                    (push (gnus-namazu/make-article
-                           group (string-to-number file))
-                          articles)))))
+           (and (or
+                 ;; Check the discoverd file is the persistent article.
+                 (and (looking-at cache-regexp)
+                      (setq file (match-string-no-properties 2)
+                            group (gnus-namazu/real-group-name
+                                   (gnus-use-long-file-name 'not-cache)
+                                   (match-string-no-properties 1))))
+                 ;; Check the discoverd file is covered by the agent.
+                 (and (looking-at agent-regexp)
+                      (setq file (match-string-no-properties 2)
+                            group (gnus-namazu/real-group-name
+                                   nnmail-use-long-file-names
+                                   (match-string-no-properties 1))))
+                 ;; Check the discovered file is managed by Gnus servers.
+                 (and (looking-at topdir-regexp)
+                      (setq file (buffer-substring-no-properties
+                                  (match-end 0) (gnus-point-at-eol))
+                            server (cdr (assoc (match-string-no-properties 0)
+                                               server-alist)))
+                      ;; Check validity of the file name.
+                      (string-match "/\\([0-9]+\\)\\'" file)
+                      (progn
+                        (setq group (substring file 0 (match-beginning 0))
+                              file (match-string 1 file))
+                        (setq group
+                              (gnus-namazu/group-prefixed-name
+                               (if nnmail-use-long-file-names
+                                   group
+                                 (nnheader-replace-chars-in-string group
+                                                                   ?/ ?.))
+                               server)))))
+                (or (not groups)
+                    (member group groups))
+                (push (gnus-namazu/make-article group (string-to-number file))
+                      articles)))
          (forward-line 1))
        (nreverse articles)))))
 
@@ -295,8 +445,9 @@ options make any sense in this context."
     ;; In Summary buffer.
     (if current-prefix-arg
        (list (gnus-read-group "Group: "))
-      (if (and (gnus-ephemeral-group-p gnus-newsgroup-name)
-              (string-match gnus-namazu/group-name-regexp gnus-newsgroup-name))
+      (if (and
+          (gnus-ephemeral-group-p gnus-newsgroup-name)
+          (string-match gnus-namazu/group-name-regexp gnus-newsgroup-name))
          (cadr (assq 'gnus-namazu-target-groups
                      (gnus-info-method (gnus-get-info gnus-newsgroup-name))))
        (list gnus-newsgroup-name))))))
@@ -338,6 +489,15 @@ options make any sense in this context."
                  (mail-header-from
                   (gnus-summary-article-header))))))))
 
+(defun gnus-namazu/get-current-to ()
+  (and gnus-namazu/read-query-original-buffer
+       (bufferp gnus-namazu/read-query-original-buffer)
+       (with-current-buffer gnus-namazu/read-query-original-buffer
+        (when (eq major-mode 'gnus-summary-mode)
+          (cadr (mail-extract-address-components
+                 (cdr (assq 'To (mail-header-extra
+                                 (gnus-summary-article-header))))))))))
+
 (defmacro gnus-namazu/minibuffer-prompt-end ()
   (if (fboundp 'minibuffer-prompt-end)
       '(minibuffer-prompt-end)
@@ -363,7 +523,7 @@ options make any sense in this context."
   (interactive)
   (let ((pos (point)))
     (cond
-     ((and (re-search-backward "\\+\\([a-z]*\\)" nil t)
+     ((and (re-search-backward "\\+\\([-a-z]*\\)" nil t)
           (= pos (match-end 0)))
       (let* ((partial (match-string 1))
             (completions
@@ -405,6 +565,13 @@ options make any sense in this context."
        (when f
          (goto-char pos)
          (insert "\"" f "\"")
+         (setq pos (point)))))
+     ((and (looking-at "\\+to:")
+          (= pos (match-end 0)))
+      (let ((to (gnus-namazu/get-current-to)))
+       (when to
+         (goto-char pos)
+         (insert "\"" to "\"")
          (setq pos (point))))))
     (goto-char pos)))
 
@@ -422,21 +589,60 @@ options make any sense in this context."
     (read-from-minibuffer prompt initial gnus-namazu/read-query-map nil
                          'gnus-namazu/read-query-history)))
 
+(defun gnus-namazu/highlight-words (query)
+  (with-temp-buffer
+    (insert " " query)
+    ;; Remove tokens for NOT search
+    (goto-char (point-min))
+    (while (re-search-forward "[\e$B!!\e(B \t\r\f\n]+not[\e$B!!\e(B \t\r\f\n]+\
+\\([^\e$B!!\e(B \t\r\f\n\"{(/]+\\|\"[^\"]+\"\\|{[^}]+}\\|([^)]+)\\|/[^/]+/\\)+" nil t)
+      (delete-region (match-beginning 0) (match-end 0)))
+    ;; Remove tokens for Field search
+    (goto-char (point-min))
+    (while (re-search-forward "[\e$B!!\e(B \t\r\f\n]+\\+[^\e$B!!\e(B \t\r\f\n:]+:\
+\\([^\e$B!!\e(B \t\r\f\n\"{(/]+\\|\"[^\"]+\"\\|{[^}]+}\\|([^)]+)\\|/[^/]+/\\)+" nil t)
+      (delete-region (match-beginning 0) (match-end 0)))
+    ;; Remove tokens for Regexp search
+    (goto-char (point-min))
+    (while (re-search-forward "/[^/]+/" nil t)
+      (delete-region (match-beginning 0) (match-end 0)))
+    ;; Remove brackets, double quote, asterisk and operators
+    (goto-char (point-min))
+    (while (re-search-forward "\\([(){}\"*]\\|\\b\\(and\\|or\\)\\b\\)" nil t)
+      (delete-region (match-beginning 0) (match-end 0)))
+    ;; Collect all keywords
+    (setq query nil)
+    (goto-char (point-min))
+    (while (re-search-forward "[^\e$B!!\e(B \t\r\f\n]+" nil t)
+      (push (match-string 0) query))
+    (when query
+      (let (en ja)
+       (dolist (q query)
+         (if (string-match "\\cj" q)
+             (push q ja)
+           (push q en)))
+       (append
+        (when en
+          (list (list (concat "\\b\\(" (regexp-opt en) "\\)\\b")
+                      0 0 'gnus-namazu-query-highlight-face)))
+        (when ja
+          (list (list (regexp-opt ja)
+                      0 0 'gnus-namazu-query-highlight-face))))))))
+
 (defun gnus-namazu/truncate-article-list (articles)
   (let ((hit (length articles)))
-    (when (> hit gnus-large-newsgroup)
+    (when (and gnus-large-newsgroup
+              (> hit gnus-large-newsgroup))
       (let* ((cursor-in-echo-area nil)
-            (input
-             (when (> hit gnus-large-newsgroup)
-               (read-from-minibuffer
-                (format
-                 "Too many articles were retrieved.  How many articles (max %d): "
-                 hit)
-                (cons (number-to-string gnus-large-newsgroup) 0)))))
+            (input (read-from-minibuffer
+                    (format "\
+Too many articles were retrieved.  How many articles (max %d): "
+                            hit)
+                    (cons (number-to-string gnus-large-newsgroup) 0))))
        (unless (string-match "\\`[ \t]*\\'" input)
          (setcdr (nthcdr (min (1- (string-to-number input)) hit) articles)
-                 nil))))
-    articles))
+                 nil)))))
+  articles)
 
 ;;;###autoload
 (defun gnus-namazu-search (groups query)
@@ -461,6 +667,9 @@ and make a virtual group contains its results."
            (dolist (a articles)
              (add-to-list 'real-groups (gnus-namazu/article-group a))))
          ;; Generate virtual group which includes all results.
+         (when (fboundp 'gnus-group-decoded-name)
+           (setq vgroup
+                 (encode-coding-string vgroup gnus-namazu-coding-system)))
          (setq vgroup
                (gnus-group-read-ephemeral-group
                 vgroup
@@ -469,6 +678,9 @@ and make a virtual group contains its results."
                             (gnus-namazu-target-groups ,groups)
                             (gnus-namazu-current-query ,query))
                 t (cons (current-buffer) (current-window-configuration)) t))
+         (when gnus-namazu-query-highlight
+           (gnus-group-set-parameter vgroup 'highlight-words
+                                     (gnus-namazu/highlight-words query)))
          ;; Generate new summary buffer which contains search results.
          (gnus-group-read-group
           t t vgroup
@@ -482,6 +694,204 @@ and make a virtual group contains its results."
                 '<)))
       (message "No entry."))))
 
+(defmacro gnus-namazu/lock-file-name (&optional directory)
+  `(expand-file-name "NMZ.lock2" ,directory))
+
+(defmacro gnus-namazu/status-file-name (&optional directory)
+  `(expand-file-name "NMZ.status" ,directory))
+
+(defmacro gnus-namazu/index-file-name (&optional directory)
+  `(expand-file-name "NMZ.i" ,directory))
+
+(defun gnus-namazu/mknmz-cleanup (directory)
+  (let ((lockfile (gnus-namazu/lock-file-name directory)))
+    (when (file-exists-p lockfile)
+      (delete-file lockfile)
+      (dolist (tmpfile (directory-files directory t "\\`NMZ\\..*\\.tmp\\'" t))
+       (delete-file tmpfile)))))
+
+;;;###autoload
+(defun gnus-namazu-create-index (directory &optional target-directories force)
+  "Create index under DIRECTORY."
+  (interactive
+   (list
+    (if (and current-prefix-arg (> (length gnus-namazu-index-directories) 1))
+       (completing-read "Directory: "
+                        (mapcar 'list gnus-namazu-index-directories) nil t)
+      (gnus-namazu/default-index-directory))
+    nil t))
+  (setq directory (file-name-as-directory (expand-file-name directory)))
+  (unless target-directories
+    (setq target-directories
+         (delq nil
+               (mapcar (lambda (dir)
+                         (when (file-directory-p dir) dir))
+                       (append
+                        (mapcar 'gnus-namazu/server-directory
+                                (gnus-namazu/indexed-servers))
+                        (list
+                         (expand-file-name gnus-cache-directory)
+                         (expand-file-name gnus-agent-directory)))))))
+  (if (file-exists-p (gnus-namazu/lock-file-name directory))
+      (when force
+       (error "Found lock file: %s" (gnus-namazu/lock-file-name directory)))
+    (with-current-buffer
+       (get-buffer-create (concat " *mknmz*" directory))
+      (erase-buffer)
+      (unless (file-directory-p directory)
+       (make-directory directory t))
+      (setq default-directory directory)
+      (let ((args (append gnus-namazu-make-index-arguments
+                         target-directories)))
+       (insert "% " gnus-namazu-make-index-command " "
+               (mapconcat 'identity args " ") "\n")
+       (goto-char (point-max))
+       (when force
+         (pop-to-buffer (current-buffer)))
+       (message "Make index at %s..." directory)
+       (unwind-protect
+           (apply 'call-process gnus-namazu-make-index-command nil t t args)
+         (gnus-namazu/mknmz-cleanup directory))
+       (message "Make index at %s...done" directory)
+       (unless force
+         (kill-buffer (current-buffer)))))))
+
+(defun gnus-namazu/lapse-seconds (start end)
+  "Return lapse seconds from START to END.
+START and END are lists which represent time in Emacs-style."
+  (+ (* (- (car end) (car start)) 65536)
+     (cadr end)
+     (- (cadr start))))
+
+(defun gnus-namazu/index-old-p (directory)
+  "Return non-nil value when the index under the DIRECTORY is older
+than the period that is set to `gnus-namazu-index-update-interval'"
+  (let ((file (gnus-namazu/index-file-name directory)))
+    (or (not (file-exists-p file))
+       (and (integerp gnus-namazu-index-update-interval)
+            (>= (gnus-namazu/lapse-seconds
+                 (nth 5 (file-attributes file))
+                 (current-time))
+                gnus-namazu-index-update-interval)))))
+
+(defvar gnus-namazu/update-directories nil)
+(defvar gnus-namazu/update-process nil)
+
+(defun gnus-namazu/update-p (directory &optional force)
+  "Return the DIRECTORY when the index undef the DIRECTORY should be updated."
+  (setq directory (file-name-as-directory (expand-file-name directory)))
+  (labels ((error-message (format &rest args)
+                         (apply (if force 'error 'message) format args)
+                         nil))
+    (if gnus-namazu/update-process
+       (error-message "%s" "Can not run two update processes simultaneously")
+      (and (or force
+              (gnus-namazu/index-old-p directory))
+          (let ((status-file (gnus-namazu/status-file-name directory)))
+            (or (file-exists-p status-file)
+                (error-message "Can not find status file: %s" status-file)))
+          (let ((lock-file (gnus-namazu/lock-file-name directory)))
+            (or (not (file-exists-p lock-file))
+                (error-message "Found lock file: %s" lock-file)))
+          directory))))
+
+;;;###autoload
+(defun gnus-namazu-update-index (directory &optional force)
+  "Update the index under the DIRECTORY."
+  (interactive
+   (list
+    (if (and current-prefix-arg (> (length gnus-namazu-index-directories) 1))
+       (completing-read "Directory: "
+                        (mapcar 'list gnus-namazu-index-directories) nil t)
+      (gnus-namazu/default-index-directory))
+    t))
+  (when (setq directory (gnus-namazu/update-p directory force))
+    (with-current-buffer (get-buffer-create (concat " *mknmz*" directory))
+      (buffer-disable-undo)
+      (erase-buffer)
+      (unless (file-directory-p directory)
+       (make-directory directory t))
+      (setq default-directory directory)
+      (let ((proc (start-process gnus-namazu-make-index-command
+                                (current-buffer)
+                                gnus-namazu-make-index-command
+                                (format "--update=%s" directory))))
+       (if (processp proc)
+           (prog1 (setq gnus-namazu/update-process proc)
+             (process-kill-without-query proc)
+             (set-process-sentinel proc 'gnus-namazu/update-sentinel)
+             (add-hook 'kill-emacs-hook 'gnus-namazu-stop-update)
+             (message "Update index at %s..." directory))
+         (goto-char (point-min))
+         (if (re-search-forward "^ERROR:.*$" nil t)
+             (progn
+               (pop-to-buffer (current-buffer))
+               (funcall (if force 'error 'message)
+                        "Update index at %s...%s" directory (match-string 0)))
+           (kill-buffer (current-buffer))
+           (funcall (if force 'error 'message)
+                    "Can not start %s" gnus-namazu-make-index-command))
+         nil)))))
+
+;;;###autoload
+(defun gnus-namazu-update-all-indices (&optional directories force)
+  "Update all indices which is set to `gnus-namazu-index-directories'."
+  (interactive (list nil t))
+  (when (setq directories
+             (delq nil (mapcar
+                        (lambda (d) (gnus-namazu/update-p d force))
+                        (or directories gnus-namazu-index-directories))))
+    (setq gnus-namazu/update-directories (cdr directories))
+    (gnus-namazu-update-index (car directories))))
+
+(defun gnus-namazu/update-sentinel (process event)
+  (let ((buffer (process-buffer process)))
+    (when (buffer-name buffer)
+      (with-current-buffer buffer
+       (gnus-namazu/mknmz-cleanup default-directory)
+       (goto-char (point-min))
+       (cond
+        ((re-search-forward "^ERROR:.*$" nil t)
+         (pop-to-buffer (current-buffer))
+         (message "Update index at %s...%s"
+                  default-directory (match-string 0))
+         (setq gnus-namazu/update-directories nil))
+        ((and (eq 'exit (process-status process))
+              (zerop (process-exit-status process)))
+         (message "Update index at %s...done" default-directory)
+         (unless (or debug-on-error debug-on-quit)
+           (kill-buffer buffer)))))))
+  (setq gnus-namazu/update-process nil)
+  (when gnus-namazu/update-directories
+    (gnus-namazu-update-all-indices gnus-namazu/update-directories)))
+
+;;;###autoload
+(defun gnus-namazu-stop-update ()
+  "Stop the running indexer of Namazu."
+  (interactive)
+  (setq gnus-namazu/update-directories nil)
+  (and gnus-namazu/update-process
+       (processp gnus-namazu/update-process)
+       (kill-process gnus-namazu/update-process)))
+
+(let (current-load-list)
+  (defadvice gnus-offer-save-summaries
+    (before gnus-namazu-kill-summary-buffers activate compile)
+    "Advised by `gnus-namazu'.
+In order to avoid annoying questions, kill summary buffers which
+generated by `gnus-namazu' itself before `gnus-offer-save-summaries'
+is called."
+    (let ((buffers (buffer-list)))
+      (while buffers
+       (when (with-current-buffer (car buffers)
+               (and (eq major-mode 'gnus-summary-mode)
+                    (gnus-ephemeral-group-p gnus-newsgroup-name)
+                    (string-match gnus-namazu/group-name-regexp
+                                  gnus-newsgroup-name)))
+         (kill-buffer (car buffers)))
+       (setq buffers (cdr buffers))))))
+
+;;;###autoload
 (defun gnus-namazu-insinuate ()
   (add-hook
    'gnus-group-mode-hook