nndoc.el (nndoc-dissect-mime-parts-sub): Fix last change
[gnus] / lisp / nnir.el
index 2a264d1..4dd123b 100644 (file)
@@ -1,9 +1,8 @@
-;;; nnir.el --- search mail with various search engines -*- coding: iso-8859-1 -*-
+;;; nnir.el --- search mail with various search engines -*- coding: utf-8 -*-
 
-;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
-;;   2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+;; Copyright (C) 1998-2013 Free Software Foundation, Inc.
 
-;; Author: Kai Großjohann <grossjohann@ls6.cs.uni-dortmund.de>
+;; Author: Kai Großjohann <grossjohann@ls6.cs.uni-dortmund.de>
 ;; Swish-e and Swish++ backends by:
 ;;   Christoph Conrad <christoph.conrad@gmx.de>.
 ;; IMAP backend by: Simon Josefsson <jas@pdc.kth.se>.
 
 ;;; Commentary:
 
-;; TODO: Documentation in the Gnus manual
-
-;; From: Reiner Steib
-;; Subject: Re: Including nnir.el
-;; Newsgroups: gmane.emacs.gnus.general
-;; Message-ID: <v9d5dnp6aq.fsf@marauder.physik.uni-ulm.de>
-;; Date: 2006-06-05 22:49:01 GMT
-;;
-;; On Sun, Jun 04 2006, Sascha Wilde wrote:
-;;
-;; > The one thing most hackers like to forget: Documentation.  By now the
-;; > documentation is only in the comments at the head of the source, I
-;; > would use it as basis to cook up some minimal texinfo docs.
-;; >
-;; > Where in the existing gnus manual would this fit best?
-
-;; Maybe (info "(gnus)Combined Groups") for a general description.
-;; `gnus-group-make-nnir-group' might be described in (info
-;; "(gnus)Foreign Groups") as well.
-
-
-;; The most recent version of this can always be fetched from the Gnus
-;; repository.  See http://www.gnus.org/ for more information.
-
-;; This code is still in the development stage but I'd like other
-;; people to have a look at it.  Please do not hesitate to contact me
-;; with your ideas.
-
-;; What does it do?  Well, it allows you to index your mail using some
-;; search engine (freeWAIS-sf, swish-e and others -- see later),
-;; then type `G G' in the Group buffer and issue a query to the search
-;; engine.  You will then get a buffer which shows all articles
-;; matching the query, sorted by Retrieval Status Value (score).
+;; What does it do?  Well, it allows you to search your mail using
+;; some search engine (imap, namazu, swish-e, gmane and others -- see
+;; later) by typing `G G' in the Group buffer.  You will then get a
+;; buffer which shows all articles matching the query, sorted by
+;; Retrieval Status Value (score).
 
 ;; When looking at the retrieval result (in the Summary buffer) you
-;; can type `G T' (aka M-x gnus-summary-nnir-goto-thread RET) on an
-;; article.  You will be teleported into the group this article came
-;; from, showing the thread this article is part of.  (See below for
-;; restrictions.)
-
-;; The Lisp installation is simple: just put this file on your
-;; load-path, byte-compile it, and load it from ~/.gnus or something.
-;; This will install a new command `G G' in your Group buffer for
-;; searching your mail.  Note that you also need to configure a number
-;; of variables, as described below.
-
-;; Restrictions:
-;;
-;; * If you don't use HyREX as your search engine, this expects that
-;;   you use nnml or another one-file-per-message backend, because the
-;;   others doesn't support nnfolder.
-;; * It can only search the mail backend's which are supported by one
-;;   search engine, because of different query languages.
-;; * There are restrictions to the Wais setup.
-;; * There are restrictions to the imap setup.
-;; * gnus-summary-nnir-goto-thread: Fetches whole group first, before
-;;   limiting to the right articles.  This is much too slow, of
-;;   course.  May issue a query for number of articles to fetch; you
-;;   must accept the default of all articles at this point or things
-;;   may break.
-
-;; The Lisp setup involves setting a few variables and setting up the
+;; can type `A W' (aka M-x gnus-warp-to-article RET) on an article.  You
+;; will be warped into the group this article came from. Typing `A T'
+;; (aka M-x gnus-summary-refer-thread RET) will warp to the group and
+;; also show the thread this article is part of.
+
+;; The Lisp setup may involve setting a few variables and setting up the
 ;; search engine. You can define the variables in the server definition
 ;; like this :
 ;;   (setq gnus-secondary-select-methods '(
 ;;       (nnimap "" (nnimap-address "localhost")
-;;                  (nnir-search-engine hyrex)
-;;                  (nnir-hyrex-additional-switches ("-d" "ddl-nnimap.xml"))
+;;                  (nnir-search-engine namazu)
 ;;       )))
-;; Or you can define the global ones. The variables set in the mailer-
-;; definition will be used first.
-;; The variable to set is `nnir-search-engine'.  Choose one of the engines
-;; listed in `nnir-engines'.  (Actually `nnir-engines' is an alist,
-;; type `C-h v nnir-engines RET' for more information; this includes
-;; examples for setting `nnir-search-engine', too.)
-;;
-;; The variable nnir-mail-backend isn't used anymore.
-;;
+;; The main variable to set is `nnir-search-engine'.  Choose one of
+;; the engines listed in `nnir-engines'.  (Actually `nnir-engines' is
+;; an alist, type `C-h v nnir-engines RET' for more information; this
+;; includes examples for setting `nnir-search-engine', too.)
 
-;; You must also set up a search engine.  I'll tell you about the two
-;; search engines currently supported:
+;; If you use one of the local indices (namazu, find-grep, swish) you
+;; must also set up a search engine backend.
 
-;; 1. freeWAIS-sf
-;;
-;; As always with freeWAIS-sf, you need a so-called `format file'.  I
-;; use the following file:
-;;
-;; ,-----
-;; | # Kai's format file for freeWAIS-sf for indexing mails.
-;; | # Each mail is in a file, much like the MH format.
-;; |
-;; | # Document separator should never match -- each file is a document.
-;; | record-sep: /^@this regex should never match@$/
-;; |
-;; | # Searchable fields specification.
-;; |
-;; | region: /^[sS]ubject:/ /^[sS]ubject: */
-;; |         subject "Subject header" stemming TEXT BOTH
-;; | end: /^[^ \t]/
-;; |
-;; | region: /^([tT][oO]|[cC][cC]):/ /^([tT][oO]|[cC][cC]): */
-;; |         to "To and Cc headers" SOUNDEX BOTH
-;; | end: /^[^ \t]/
-;; |
-;; | region: /^[fF][rR][oO][mM]:/ /^[fF][rR][oO][mM]: */
-;; |         from "From header" SOUNDEX BOTH
-;; | end: /^[^ \t]/
-;; |
-;; | region: /^$/
-;; |         stemming TEXT GLOBAL
-;; | end: /^@this regex should never match@$/
-;; `-----
-;;
-;; 1998-07-22: waisindex would dump core on me for large articles with
-;; the above settings.  I used /^$/ as the end regex for the global
-;; field.  That seemed to work okay.
-
-;; There is a Perl module called `WAIS.pm' which is available from
-;; CPAN as well as ls6-ftp.cs.uni-dortmund.de:/pub/wais/Perl.  This
-;; module comes with a nifty tool called `makedb', which I use for
-;; indexing.  Here's my `makedb.conf':
-;;
-;; ,-----
-;; | # Config file for makedb
-;; |
-;; | # Global options
-;; | waisindex = /usr/local/bin/waisindex
-;; | wais_opt  = -stem -t fields
-;; | # `-stem' option necessary when `stemming' is specified for the
-;; | # global field in the *.fmt file
-;; |
-;; | # Own variables
-;; | homedir = /home/kai
-;; |
-;; | # The mail database.
-;; | database        = mail
-;; | files           = `find $homedir/Mail -name \*[0-9] -print`
-;; | dbdir           = $homedir/.wais
-;; | limit           = 100
-;; `-----
-;;
-;; The Lisp setup involves the `nnir-wais-*' variables.  The most
-;; difficult to understand variable is probably
-;; `nnir-wais-remove-prefix'.  Here's what it does: the output of
-;; `waissearch' basically contains the file name and the (full)
-;; directory name.  As Gnus works with group names rather than
-;; directory names, the directory name is transformed into a group
-;; name as follows: first, a prefix is removed from the (full)
-;; directory name, then all `/' are replaced with `.'.  The variable
-;; `nnir-wais-remove-prefix' should contain a regex matching exactly
-;; this prefix.  It defaults to `$HOME/Mail/' (note the trailing
-;; slash).
-
-;; 2. Namazu
+;; 1. Namazu
 ;;
 ;; The Namazu backend requires you to have one directory containing all
 ;; index files, this is controlled by the `nnir-namazu-index-directory'
 ;; variable.  To function the `nnir-namazu-remove-prefix' variable must
-;; also be correct, see the documentation for `nnir-wais-remove-prefix'
+;; also be correct, see the documentation for `nnir-namazu-remove-prefix'
 ;; above.
 ;;
 ;; It is particularly important not to pass any any switches to namazu
 ;; For maximum searching efficiency I have a cron job set to run this
 ;; command every four hours.
 
-;; 3. HyREX
-;;
-;; The HyREX backend requires you to have one directory from where all
-;; your relative paths are to, if you use them. This directory must be
-;; set in the `nnir-hyrex-index-directory' variable, which defaults to
-;; your home directory. You must also pass the base, class and
-;; directory options or simply your dll to the `nnir-hyrex-programm' by
-;; setting the `nnir-hyrex-additional-switches' variable accordently.
-;; To function the `nnir-hyrex-remove-prefix' variable must also be
-;; correct, see the documentation for `nnir-wais-remove-prefix' above.
-
-;; 4. find-grep
+;; 2. find-grep
 ;;
 ;; The find-grep engine simply runs find(1) to locate eligible
 ;; articles and searches them with grep(1).  This, of course, is much
 ;; other backend.
 
 ;; The interface between the two layers consists of the single
-;; function `nnir-run-query', which just selects the appropriate
-;; function for the search engine one is using.  The input to
-;; `nnir-run-query' is a string, representing the query as input by
-;; the user.  The output of `nnir-run-query' is supposed to be a
-;; vector, each element of which should in turn be a three-element
-;; vector.  The first element should be full group name of the article,
-;; the second element should be the article number, and the third
-;; element should be the Retrieval Status Value (RSV) as returned from
-;; the search engine.  An RSV is the score assigned to the document by
-;; the search engine.  For Boolean search engines, the
-;; RSV is always 1000 (or 1 or 100, or whatever you like).
+;; function `nnir-run-query', which dispatches the search to the
+;; proper search function.  The argument of `nnir-run-query' is an
+;; alist with two keys: 'nnir-query-spec and 'nnir-group-spec. The
+;; value for 'nnir-query-spec is an alist. The only required key/value
+;; pair is (query . "query") specifying the search string to pass to
+;; the query engine. Individual engines may have other elements. The
+;; value of 'nnir-group-spec is a list with the specification of the
+;; groups/servers to search.  The format of the 'nnir-group-spec is
+;; (("server1" ("group11" "group12")) ("server2" ("group21"
+;; "group22"))). If any of the group lists is absent then all groups
+;; on that server are searched.
+
+;; The output of `nnir-run-query' is supposed to be a vector, each
+;; element of which should in turn be a three-element vector.  The
+;; first element should be full group name of the article, the second
+;; element should be the article number, and the third element should
+;; be the Retrieval Status Value (RSV) as returned from the search
+;; engine.  An RSV is the score assigned to the document by the search
+;; engine.  For Boolean search engines, the RSV is always 1000 (or 1
+;; or 100, or whatever you like).
 
 ;; The sorting order of the articles in the summary buffer created by
 ;; nnir is based on the order of the articles in the above mentioned
 ;; function should return the list of articles as a vector, as
 ;; described above.  Then, you need to register this backend in
 ;; `nnir-engines'.  Then, users can choose the backend by setting
-;; `nnir-search-engine'.
-
-;; Todo, or future ideas:
+;; `nnir-search-engine' as a server variable.
 
-;; * It should be possible to restrict search to certain groups.
-;;
-;; * There is currently no error checking.
-;;
-;; * The summary buffer display is currently really ugly, with all the
-;;   added information in the subjects.  How could I make this
-;;   prettier?
-;;
-;; * A function which can be called from an nnir summary buffer which
-;;   teleports you into the group the current article came from and
-;;   shows you the whole thread this article is part of.
-;;   Implementation suggestions?
-;;   (1998-07-24: There is now a preliminary implementation, but
-;;   it is much too slow and quite fragile.)
-;;
-;; * Support other mail backends.  In particular, probably quite a few
-;;   people use nnfolder.  How would one go about searching nnfolders
-;;   and producing the right data needed?  The group name and the RSV
-;;   are simple, but what about the article number?
-;;   - The article number is encoded in the `X-Gnus-Article-Number'
-;;     header of each mail.
-;;   - The HyREX engine supports nnfolder.
-;;
-;; * Support compressed mail files.  Probably, just stripping off the
-;;   `.gz' or `.Z' file name extension is sufficient.
-;;
-;; * At least for imap, the query is performed twice.
-;;
+;;; Code:
 
-;; Have you got other ideas?
+;;; Setup:
 
-;;; Setup Code:
+;; For Emacs <22.2 and XEmacs.
+(eval-and-compile
+  (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
 
 (require 'nnoo)
 (require 'gnus-group)
-(require 'gnus-sum)
 (require 'message)
 (require 'gnus-util)
 (eval-when-compile
   (require 'cl))
 
+;;; Internal Variables:
 
-(eval-when-compile
-  (autoload 'nnimap-buffer "nnimap")
-  (autoload 'nnimap-command "nnimap")
-  (autoload 'nnimap-possibly-change-group "nnimap"))
+(defvar nnir-memo-query nil
+  "Internal: stores current query.")
 
-(nnoo-declare nnir)
-(nnoo-define-basics nnir)
+(defvar nnir-memo-server nil
+  "Internal: stores current server.")
 
-(gnus-declare-backend "nnir" 'mail)
+(defvar nnir-artlist nil
+  "Internal: stores search result.")
 
-(defvar nnir-imap-default-search-key "Whole message"
-  "The default IMAP search key for an nnir search. Must be one of
-  the keys in nnir-imap-search-arguments. To use raw imap queries
-  by default set this to \"Imap\"")
+(defvar nnir-search-history ()
+  "Internal: the history for querying search options in nnir")
+
+(defconst nnir-tmp-buffer " *nnir*"
+  "Internal: temporary buffer.")
+
+
+;; Imap variables
 
 (defvar nnir-imap-search-arguments
-  '(("Whole message" . "TEXT")
-    ("Subject" . "SUBJECT")
-    ("To" . "TO")
-    ("From" . "FROM")
-    ("Imap" . ""))
+  '(("whole message" . "TEXT")
+    ("subject" . "SUBJECT")
+    ("to" . "TO")
+    ("from" . "FROM")
+    ("body" . "BODY")
+    ("imap" . ""))
   "Mapping from user readable keys to IMAP search items for use in nnir")
 
 (defvar nnir-imap-search-other "HEADER %S"
   "The IMAP search item to use for anything other than
-  nnir-imap-search-arguments. By default this is the name of an
+  `nnir-imap-search-arguments'. By default this is the name of an
   email header field")
 
 (defvar nnir-imap-search-argument-history ()
   "The history for querying search options in nnir")
 
-(defvar nnir-get-article-nov-override-function nil
-  "If non-nil, a function that will be passed each search result.  This
-should return a message's headers in NOV format.
+;;; Helper macros
 
-If this variable is nil, or if the provided function returns nil for a search
-result, `gnus-retrieve-headers' will be called instead.")
+;; Data type article list.
 
+(defmacro nnir-artlist-length (artlist)
+  "Returns number of articles in artlist."
+  `(length ,artlist))
 
-;;; Developer Extension Variable:
+(defmacro nnir-artlist-article (artlist n)
+  "Returns from ARTLIST the Nth artitem (counting starting at 1)."
+  `(when (> ,n 0)
+     (elt ,artlist (1- ,n))))
 
-(defvar nnir-engines
-  `((wais    nnir-run-waissearch
-             ())
-    (imap    nnir-run-imap
-             ((criteria
-              "Search in"                        ; Prompt
-              ,(mapcar 'car nnir-imap-search-arguments) ; alist for completing
-              nil                                ; allow any user input
-              nil                                ; initial value
-              nnir-imap-search-argument-history  ; the history to use
-              ,nnir-imap-default-search-key      ; default
-              )))
-    (swish++ nnir-run-swish++
-             ((group . "Group spec: ")))
-    (swish-e nnir-run-swish-e
-             ((group . "Group spec: ")))
-    (namazu  nnir-run-namazu
-             ())
-    (hyrex   nnir-run-hyrex
-            ((group . "Group spec: ")))
-  (find-grep nnir-run-find-grep
-            ((grep-options . "Grep options: "))))
-  "Alist of supported search engines.
-Each element in the alist is a three-element list (ENGINE FUNCTION ARGS).
-ENGINE is a symbol designating the searching engine.  FUNCTION is also
-a symbol, giving the function that does the search.  The third element
-ARGS is a list of cons pairs (PARAM . PROMPT).  When issuing a query,
-the FUNCTION will issue a query for each of the PARAMs, using PROMPT.
+(defmacro nnir-artitem-group (artitem)
+  "Returns the group from the ARTITEM."
+  `(elt ,artitem 0))
 
-The value of `nnir-search-engine' must be one of the ENGINE symbols.
-For example, use the following line for searching using freeWAIS-sf:
-    (setq nnir-search-engine 'wais)
-Use the following line if you read your mail via IMAP and your IMAP
-server supports searching:
-    (setq nnir-search-engine 'imap)
-Note that you have to set additional variables for most backends.  For
-example, the `wais' backend needs the variables `nnir-wais-program',
-`nnir-wais-database' and `nnir-wais-remove-prefix'.
+(defmacro nnir-artitem-number (artitem)
+  "Returns the number from the ARTITEM."
+  `(elt ,artitem 1))
+
+(defmacro nnir-artitem-rsv (artitem)
+  "Returns the Retrieval Status Value (RSV, score) from the ARTITEM."
+  `(elt ,artitem 2))
+
+(defmacro nnir-article-group (article)
+  "Returns the group for ARTICLE"
+  `(nnir-artitem-group (nnir-artlist-article nnir-artlist ,article)))
+
+(defmacro nnir-article-number (article)
+  "Returns the number for ARTICLE"
+  `(nnir-artitem-number (nnir-artlist-article nnir-artlist ,article)))
+
+(defmacro nnir-article-rsv (article)
+  "Returns the rsv for ARTICLE"
+  `(nnir-artitem-rsv (nnir-artlist-article nnir-artlist ,article)))
+
+(defsubst nnir-article-ids (article)
+  "Returns the pair `(nnir id . real id)' of ARTICLE"
+  (cons article (nnir-article-number article)))
+
+(defmacro nnir-categorize (sequence keyfunc &optional valuefunc)
+  "Sorts a sequence into categories and returns a list of the form
+`((key1 (element11 element12)) (key2 (element21 element22))'.
+The category key for a member of the sequence is obtained
+as `(keyfunc member)' and the corresponding element is just
+`member'. If `valuefunc' is non-nil, the element of the list
+is `(valuefunc member)'."
+  `(unless (null ,sequence)
+     (let (value)
+       (mapc
+       (lambda (member)
+         (let ((y (,keyfunc member))
+               (x ,(if valuefunc
+                       `(,valuefunc member)
+                     'member)))
+           (if (assoc y value)
+               (push x (cadr (assoc y value)))
+             (push (list y (list x)) value))))
+       ,sequence)
+       value)))
+
+;;; Finish setup:
+
+(require 'gnus-sum)
+
+(eval-when-compile
+  (autoload 'nnimap-buffer "nnimap")
+  (autoload 'nnimap-command "nnimap")
+  (autoload 'nnimap-change-group "nnimap")
+  (autoload 'nnimap-make-thread-query "nnimap")
+  (autoload 'gnus-registry-action "gnus-registry")
+  (autoload 'gnus-registry-get-id-key "gnus-registry")
+  (autoload 'gnus-group-topic-name "gnus-topic"))
+
+
+(nnoo-declare nnir)
+(nnoo-define-basics nnir)
+
+(gnus-declare-backend "nnir" 'mail 'virtual)
 
-Add an entry here when adding a new search engine.")
 
 ;;; User Customizable Variables:
 
 (defgroup nnir nil
-  "Search nnmh and nnml groups in Gnus with swish-e, freeWAIS-sf, or EWS."
+  "Search groups in Gnus with assorted search engines."
   :group 'gnus)
 
-;; Mail backend.
-
-;; TODO:
-;; If `nil', use server parameters to find out which server to search. CCC
-;;
-(defcustom nnir-mail-backend '(nnml "")
-  "*Specifies which backend should be searched.
-More precisely, this is used to determine from which backend to fetch the
-messages found.
-
-This must be equal to an existing server, so maybe it is best to use
-something like the following:
-    (setq nnir-mail-backend (nth 0 gnus-secondary-select-methods))
-The above line works fine if the mail backend you want to search is
-the first element of gnus-secondary-select-methods (`nth' starts counting
-at zero)."
-  :type '(sexp)
+(defcustom nnir-ignored-newsgroups ""
+  "*A regexp to match newsgroups in the active file that should
+  be skipped when searching."
+  :version "24.1"
+  :type '(regexp)
   :group 'nnir)
 
-;; Search engine to use.
+(defcustom nnir-summary-line-format nil
+  "*The format specification of the lines in an nnir summary buffer.
 
-(defcustom nnir-search-engine 'wais
-  "*The search engine to use.  Must be a symbol.
-See `nnir-engines' for a list of supported engines, and for example
-settings of `nnir-search-engine'."
-  :type '(sexp)
-  :group 'nnir)
+All the items from `gnus-summary-line-format' are available, along
+with three items unique to nnir summary buffers:
 
-;; freeWAIS-sf.
+%Z    Search retrieval score value (integer)
+%G    Article original full group name (string)
+%g    Article original short group name (string)
 
-(defcustom nnir-wais-program "waissearch"
-  "*Name of waissearch executable."
-  :type '(string)
+If nil this will use `gnus-summary-line-format'."
+  :version "24.1"
+  :type '(choice (const :tag "gnus-summary-line-format" nil) string)
   :group 'nnir)
 
-(defcustom nnir-wais-database (expand-file-name "~/.wais/mail")
-  "*Name of Wais database containing the mail.
+(defcustom nnir-retrieve-headers-override-function nil
+  "*If non-nil, a function that accepts an article list and group
+and populates the `nntp-server-buffer' with the retrieved
+headers. Must return either 'nov or 'headers indicating the
+retrieved header format.
 
-Note that this should be a file name without extension.  For example,
-if you have a file /home/john/.wais/mail.fmt, use this:
-    (setq nnir-wais-database \"/home/john/.wais/mail\")
-The string given here is passed to `waissearch -d' as-is."
-  :type '(file)
+If this variable is nil, or if the provided function returns nil for a search
+result, `gnus-retrieve-headers' will be called instead."
+  :version "24.1"
+  :type '(choice (const :tag "gnus-retrieve-headers" nil) function)
   :group 'nnir)
 
-(defcustom nnir-wais-remove-prefix (concat (getenv "HOME") "/Mail/")
-  "*The prefix to remove from each directory name returned by waissearch
-in order to get a group name (albeit with / instead of .).  This is a
-regular expression.
-
-For example, suppose that Wais returns file names such as
-\"/home/john/Mail/mail/misc/42\".  For this example, use the following
-setting:  (setq nnir-wais-remove-prefix \"/home/john/Mail/\")
-Note the trailing slash.  Removing this prefix gives \"mail/misc/42\".
-`nnir' knows to remove the \"/42\" and to replace \"/\" with \".\" to
-arrive at the correct group name, \"mail.misc\"."
-  :type '(regexp)
+(defcustom nnir-imap-default-search-key "whole message"
+  "*The default IMAP search key for an nnir search. Must be one of
+  the keys in `nnir-imap-search-arguments'. To use raw imap queries
+  by default set this to \"imap\"."
+  :version "24.1"
+  :type `(choice ,@(mapcar (lambda (elem) (list 'const (car elem)))
+                          nnir-imap-search-arguments))
   :group 'nnir)
 
 (defcustom nnir-swish++-configuration-file
@@ -513,8 +376,8 @@ Instead, use this:
 in order to get a group name (albeit with / instead of .).  This is a
 regular expression.
 
-This variable is very similar to `nnir-wais-remove-prefix', except
-that it is for swish++, not Wais."
+This variable is very similar to `nnir-namazu-remove-prefix', except
+that it is for swish++, not Namazu."
   :type '(regexp)
   :group 'nnir)
 
@@ -564,8 +427,8 @@ This could be a server parameter."
 in order to get a group name (albeit with / instead of .).  This is a
 regular expression.
 
-This variable is very similar to `nnir-wais-remove-prefix', except
-that it is for swish-e, not Wais.
+This variable is very similar to `nnir-namazu-remove-prefix', except
+that it is for swish-e, not Namazu.
 
 This could be a server parameter."
   :type '(regexp)
@@ -633,240 +496,417 @@ Instead, use this:
   "*The prefix to remove from each file name returned by Namazu
 in order to get a group name (albeit with / instead of .).
 
-This variable is very similar to `nnir-wais-remove-prefix', except
-that it is for Namazu, not Wais."
+For example, suppose that Namazu returns file names such as
+\"/home/john/Mail/mail/misc/42\".  For this example, use the following
+setting:  (setq nnir-namazu-remove-prefix \"/home/john/Mail/\")
+Note the trailing slash.  Removing this prefix gives \"mail/misc/42\".
+`nnir' knows to remove the \"/42\" and to replace \"/\" with \".\" to
+arrive at the correct group name, \"mail.misc\"."
   :type '(directory)
   :group 'nnir)
 
-;;; Internal Variables:
+(defcustom nnir-notmuch-program "notmuch"
+  "*Name of notmuch search executable."
+  :version "24.1"
+  :type '(string)
+  :group 'nnir)
 
-(defvar nnir-current-query nil
-  "Internal: stores current query (= group name).")
+(defcustom nnir-notmuch-additional-switches '()
+  "*A list of strings, to be given as additional arguments to notmuch.
 
-(defvar nnir-current-server nil
-  "Internal: stores current server (does it ever change?).")
+Note that this should be a list.  Ie, do NOT use the following:
+    (setq nnir-notmuch-additional-switches \"-i -w\") ; wrong
+Instead, use this:
+    (setq nnir-notmuch-additional-switches '(\"-i\" \"-w\"))"
+  :version "24.1"
+  :type '(repeat (string))
+  :group 'nnir)
 
-(defvar nnir-current-group-marked nil
-  "Internal: stores current list of process-marked groups.")
+(defcustom nnir-notmuch-remove-prefix (concat (getenv "HOME") "/Mail/")
+  "*The prefix to remove from each file name returned by notmuch
+in order to get a group name (albeit with / instead of .).  This is a
+regular expression.
 
-(defvar nnir-artlist nil
-  "Internal: stores search result.")
+This variable is very similar to `nnir-namazu-remove-prefix', except
+that it is for notmuch, not Namazu."
+  :version "24.1"
+  :type '(regexp)
+  :group 'nnir)
 
-(defvar nnir-tmp-buffer " *nnir*"
-  "Internal: temporary buffer.")
+;;; Developer Extension Variable:
 
-;;; Code:
+(defvar nnir-engines
+  `((imap    nnir-run-imap
+             ((criteria
+              "Imap Search in"                   ; Prompt
+              ,(mapcar 'car nnir-imap-search-arguments) ; alist for completing
+              nil                                ; allow any user input
+              nil                                ; initial value
+              nnir-imap-search-argument-history  ; the history to use
+              ,nnir-imap-default-search-key      ; default
+              )))
+    (gmane   nnir-run-gmane
+            ((gmane-author . "Gmane Author: ")))
+    (swish++ nnir-run-swish++
+             ((swish++-group . "Swish++ Group spec (regexp): ")))
+    (swish-e nnir-run-swish-e
+             ((swish-e-group . "Swish-e Group spec (regexp): ")))
+    (namazu  nnir-run-namazu
+             ())
+    (notmuch nnir-run-notmuch
+             ())
+    (hyrex   nnir-run-hyrex
+            ((hyrex-group . "Hyrex Group spec (regexp): ")))
+    (find-grep nnir-run-find-grep
+              ((grep-options . "Grep options: "))))
+  "Alist of supported search engines.
+Each element in the alist is a three-element list (ENGINE FUNCTION ARGS).
+ENGINE is a symbol designating the searching engine.  FUNCTION is also
+a symbol, giving the function that does the search.  The third element
+ARGS is a list of cons pairs (PARAM . PROMPT).  When issuing a query,
+the FUNCTION will issue a query for each of the PARAMs, using PROMPT.
 
-;; Gnus glue.
+The value of `nnir-search-engine' must be one of the ENGINE symbols.
+For example, for searching a server using namazu include
+    (nnir-search-engine namazu)
+in the server definition.  Note that you have to set additional
+variables for most backends.  For example, the `namazu' backend
+needs the variables `nnir-namazu-program',
+`nnir-namazu-index-directory' and `nnir-namazu-remove-prefix'.
 
-(defun gnus-group-make-nnir-group (extra-parms query)
-  "Create an nnir group.  Asks for query."
-  (interactive "P\nsQuery: ")
-  (setq nnir-current-query nil
-       nnir-current-server nil
-       nnir-current-group-marked nil
-       nnir-artlist nil)
-  (let ((parms nil))
-    (if extra-parms
-        (setq parms (nnir-read-parms query))
-      (setq parms (list (cons 'query query))))
-    (add-to-list 'parms (cons 'unique-id (message-unique-id)) t)
-    (gnus-group-read-ephemeral-group
-     (concat "nnir:" (prin1-to-string parms)) '(nnir "") t
-     (cons (current-buffer)
-           gnus-current-window-configuration)
-     nil)))
+Add an entry here when adding a new search engine.")
 
-(eval-when-compile
-  (when (featurep 'xemacs)
-    ;; The `kbd' macro requires that the `read-kbd-macro' macro is available.
-    (require 'edmacro)))
-
-(defun nnir-group-mode-hook ()
-  (define-key gnus-group-mode-map (kbd "G G")
-    'gnus-group-make-nnir-group))
-(add-hook 'gnus-group-mode-hook 'nnir-group-mode-hook)
-
-;; Why is this needed? Is this for compatibility with old/new gnusae? Using
-;; gnus-group-server instead works for me.  -- Justus Piater
-(defmacro nnir-group-server (group)
-  "Return the server for a newsgroup GROUP.
-The returned format is as `gnus-server-to-method' needs it.  See
-`gnus-group-real-prefix' and `gnus-group-real-name'."
-  `(let ((gname ,group))
-     (if (string-match "^\\([^:]+\\):" gname)
-        (progn
-          (setq gname (match-string 1 gname))
-          (if (string-match "^\\([^+]+\\)\\+\\(.+\\)$" gname)
-              (format "%s:%s" (match-string 1 gname) (match-string 2 gname))
-            (concat gname ":")))
-       (format "%s:%s" (car gnus-select-method) (cadr gnus-select-method)))))
-
-;; Summary mode commands.
-
-(defun gnus-summary-nnir-goto-thread ()
-  "Only applies to nnir groups.  Go to group this article came from
-and show thread that contains this article."
-  (interactive)
-  (unless (eq 'nnir (car (gnus-find-method-for-group gnus-newsgroup-name)))
-    (error "Can't execute this command unless in nnir group"))
-  (let* ((cur (gnus-summary-article-number))
-         (group (nnir-artlist-artitem-group nnir-artlist cur))
-         (backend-number (nnir-artlist-artitem-number nnir-artlist cur))
-        (id (mail-header-id (gnus-summary-article-header)))
-        (refs (split-string
-               (mail-header-references (gnus-summary-article-header)))))
-    (if (string= (car (gnus-group-method group)) "nnimap")
-       (with-current-buffer (nnimap-buffer)
-         (let* ((cmd (let ((value
-                            (format
-                             "(OR HEADER REFERENCES %s HEADER Message-Id %s)"
-                             id id)))
-                       (dolist (refid refs value)
-                         (setq value (format
-                                      "(OR (OR HEADER Message-Id %s HEADER REFERENCES %s) %s)"
-                                      refid refid value)))))
-                (result (nnimap-command
-                         "UID SEARCH %s" cmd)))
-           (gnus-summary-read-group-1 group t t gnus-summary-buffer nil
-                                      (and (car result)
-                                           (delete 0 (mapcar #'string-to-number
-                                                             (cdr (assoc "SEARCH" (cdr result)))))))))
-      (gnus-summary-read-group-1 group t t gnus-summary-buffer
-                                nil (list backend-number))
-      (gnus-summary-limit (list backend-number))
-      (gnus-summary-refer-thread))))
-
-
-(if (fboundp 'eval-after-load)
-    (eval-after-load "gnus-sum"
-      '(define-key gnus-summary-goto-map
-         "T" 'gnus-summary-nnir-goto-thread))
-  (add-hook 'gnus-summary-mode-hook
-            (function (lambda ()
-                        (define-key gnus-summary-goto-map
-                          "T" 'gnus-summary-nnir-goto-thread)))))
+(defcustom nnir-method-default-engines  '((nnimap . imap) (nntp . gmane))
+  "*Alist of default search engines keyed by server method."
+  :version "24.1"
+  :group 'nnir
+  :type `(repeat (cons (choice (const nnimap) (const nntp) (const nnspool)
+                              (const nneething) (const nndir) (const nnmbox)
+                              (const nnml) (const nnmh) (const nndraft)
+                              (const nnfolder) (const nnmaildir))
+                      (choice
+                       ,@(mapcar (lambda (elem) (list 'const (car elem)))
+                                 nnir-engines)))))
 
+;; Gnus glue.
+
+(defun gnus-group-make-nnir-group (nnir-extra-parms &optional specs)
+  "Create an nnir group.  Prompt for a search query and determine
+the groups to search as follows: if called from the *Server*
+buffer search all groups belonging to the server on the current
+line; if called from the *Group* buffer search any marked groups,
+or the group on the current line, or all the groups under the
+current topic. Calling with a prefix-arg prompts for additional
+search-engine specific constraints. A non-nil `specs' arg must be
+an alist with `nnir-query-spec' and `nnir-group-spec' keys, and
+skips all prompting."
+  (interactive "P")
+  (let* ((group-spec
+         (or (cdr (assq 'nnir-group-spec specs))
+           (if (gnus-server-server-name)
+               (list (list (gnus-server-server-name)))
+             (nnir-categorize
+              (or gnus-group-marked
+                  (if (gnus-group-group-name)
+                      (list (gnus-group-group-name))
+                    (cdr (assoc (gnus-group-topic-name) gnus-topic-alist))))
+              gnus-group-server))))
+        (query-spec
+         (or (cdr (assq 'nnir-query-spec specs))
+           (apply
+            'append
+            (list (cons 'query
+                        (read-string "Query: " nil 'nnir-search-history)))
+            (when nnir-extra-parms
+              (mapcar
+               (lambda (x)
+                 (nnir-read-parms (nnir-server-to-search-engine (car x))))
+               group-spec))))))
+    (gnus-group-read-ephemeral-group
+     (concat "nnir-" (message-unique-id))
+     (list 'nnir "nnir")
+     nil
+;     (cons (current-buffer) gnus-current-window-configuration)
+     nil
+     nil nil
+     (list
+      (cons 'nnir-specs (list (cons 'nnir-query-spec query-spec)
+                             (cons 'nnir-group-spec group-spec)))
+      (cons 'nnir-artlist nil)))))
+
+(defun gnus-summary-make-nnir-group (nnir-extra-parms)
+  "Search a group from the summary buffer."
+  (interactive "P")
+  (gnus-warp-to-article)
+  (let ((spec
+        (list
+         (cons 'nnir-group-spec
+               (list (list
+                      (gnus-group-server gnus-newsgroup-name)
+                      (list gnus-newsgroup-name)))))))
+    (gnus-group-make-nnir-group nnir-extra-parms spec)))
 
 
 ;; Gnus backend interface functions.
 
 (deffoo nnir-open-server (server &optional definitions)
   ;; Just set the server variables appropriately.
-  (nnoo-change-server 'nnir server definitions))
-
-(deffoo nnir-request-group (group &optional server fast info)
-  "GROUP is the query string."
-  (nnir-possibly-change-server server)
-  ;; Check for cache and return that if appropriate.
-  (if (and (equal group nnir-current-query)
-           (equal gnus-group-marked nnir-current-group-marked)
-           (or (null server)
-               (equal server nnir-current-server)))
-      nnir-artlist
-    ;; Cache miss.
-    (setq nnir-artlist (nnir-run-query group)))
-  (with-current-buffer nntp-server-buffer
-    (if (zerop (length nnir-artlist))
-       (progn
-         (setq nnir-current-query nil
-               nnir-current-server nil
-               nnir-current-group-marked nil
-               nnir-artlist nil)
-         (nnheader-report 'nnir "Search produced empty results."))
-      ;; Remember data for cache.
-      (setq nnir-current-query group)
-      (when server (setq nnir-current-server server))
-      (setq nnir-current-group-marked gnus-group-marked)
-      (nnheader-insert "211 %d %d %d %s\n"
-                      (nnir-artlist-length nnir-artlist) ; total #
-                      1              ; first #
-                      (nnir-artlist-length nnir-artlist) ; last #
-                      group))))     ; group name
+  (let ((backend (car (gnus-server-to-method server))))
+    (if backend
+       (nnoo-change-server backend server definitions)
+      (add-hook 'gnus-summary-mode-hook 'nnir-mode)
+      (nnoo-change-server 'nnir server definitions))))
+
+(deffoo nnir-request-group (group &optional server dont-check info)
+  (nnir-possibly-change-group group server)
+  (let ((pgroup (gnus-group-guess-full-name-from-command-method group))
+       length)
+    ;; Check for cached search result or run the query and cache the
+    ;; result.
+    (unless (and nnir-artlist dont-check)
+      (gnus-group-set-parameter
+       pgroup 'nnir-artlist
+       (setq nnir-artlist
+            (nnir-run-query
+             (gnus-group-get-parameter pgroup 'nnir-specs t))))
+      (nnir-request-update-info pgroup (gnus-get-info pgroup)))
+    (with-current-buffer nntp-server-buffer
+      (if (zerop (setq length (nnir-artlist-length nnir-artlist)))
+          (progn
+            (nnir-close-group group)
+            (nnheader-report 'nnir "Search produced empty results."))
+        (nnheader-insert "211 %d %d %d %s\n"
+                         length    ; total #
+                         1         ; first #
+                         length    ; last #
+                         group)))) ; group name
+  nnir-artlist)
 
 (deffoo nnir-retrieve-headers (articles &optional group server fetch-old)
-  (save-excursion
-    (let ((artlist (copy-sequence articles))
-          art artitem artgroup artno artrsv artfullgroup
-          novitem novdata foo server)
-      (while (not (null artlist))
-        (setq art (car artlist))
-        (or (numberp art)
-            (nnheader-report
-             'nnir
-             "nnir-retrieve-headers doesn't grok message ids: %s"
-             art))
-        (setq artitem (nnir-artlist-article nnir-artlist art))
-        (setq artrsv (nnir-artitem-rsv artitem))
-        (setq artfullgroup (nnir-artitem-group artitem))
-        (setq artno (nnir-artitem-number artitem))
-        (setq artgroup (gnus-group-real-name artfullgroup))
-       (setq server (nnir-group-server artfullgroup))
-        ;; retrieve NOV or HEAD data for this article, transform into
-        ;; NOV data and prepend to `novdata'
-        (set-buffer nntp-server-buffer)
-       (nnir-possibly-change-server server)
-        (let ((gnus-override-method
-              (gnus-server-to-method server)))
-         ;; if nnir-get-article-nov-override-function is set, use it
-         (if nnir-get-article-nov-override-function
-             (setq novitem (funcall nnir-get-article-nov-override-function
-                                    artitem))
-           ;; else, set novitem through nnheader-parse-nov/nnheader-parse-head
-           (case (setq foo (gnus-retrieve-headers (list artno)
-                                                  artfullgroup nil))
-             (nov
-              (goto-char (point-min))
-              (setq novitem (nnheader-parse-nov)))
-             (headers
-              (goto-char (point-min))
-              (setq novitem (nnheader-parse-head)))
-             (t (error "Unknown header type %s while requesting article %s of group %s"
-                       foo artno artfullgroup)))))
-       ;; replace article number in original group with article number
-        ;; in nnir group
-       (when novitem
-         (mail-header-set-number novitem art)
-         (mail-header-set-from novitem
-                               (mail-header-from novitem))
-         (mail-header-set-subject
-          novitem
-          (format "[%d: %s/%d] %s"
-                  artrsv artgroup artno
-                  (mail-header-subject novitem)))
-         (push novitem novdata)
-         (setq artlist (cdr artlist))))
-      (setq novdata (nreverse novdata))
-      (set-buffer nntp-server-buffer) (erase-buffer)
-      (mapc 'nnheader-insert-nov novdata)
+  (with-current-buffer nntp-server-buffer
+    (let ((gnus-inhibit-demon t)
+         (articles-by-group (nnir-categorize
+                             articles nnir-article-group nnir-article-ids))
+         headers)
+      (while (not (null articles-by-group))
+       (let* ((group-articles (pop articles-by-group))
+              (artgroup (car group-articles))
+              (articleids (cadr group-articles))
+              (artlist (sort (mapcar 'cdr articleids) '<))
+              (server (gnus-group-server artgroup))
+              (gnus-override-method (gnus-server-to-method server))
+              parsefunc)
+         ;; (nnir-possibly-change-group nil server)
+         (erase-buffer)
+         (case (setq gnus-headers-retrieved-by
+                     (or
+                      (and
+                       nnir-retrieve-headers-override-function
+                       (funcall nnir-retrieve-headers-override-function
+                                artlist artgroup))
+                      (gnus-retrieve-headers artlist artgroup nil)))
+           (nov
+            (setq parsefunc 'nnheader-parse-nov))
+           (headers
+            (setq parsefunc 'nnheader-parse-head))
+           (t (error "Unknown header type %s while requesting articles \
+                    of group %s" gnus-headers-retrieved-by artgroup)))
+         (goto-char (point-min))
+         (while (not (eobp))
+           (let* ((novitem (funcall parsefunc))
+                  (artno (and novitem
+                              (mail-header-number novitem)))
+                  (art (car (rassq artno articleids))))
+             (when art
+               (mail-header-set-number novitem art)
+               (push novitem headers))
+             (forward-line 1)))))
+      (setq headers
+           (sort headers
+                 (lambda (x y)
+                   (< (mail-header-number x) (mail-header-number y)))))
+      (erase-buffer)
+      (mapc 'nnheader-insert-nov headers)
       'nov)))
 
-(deffoo nnir-request-article (article
-                              &optional group server to-buffer)
-  (if (stringp article)
+(deffoo nnir-request-article (article &optional group server to-buffer)
+  (nnir-possibly-change-group group server)
+  (if (and (stringp article)
+          (not (eq 'nnimap (car (gnus-server-to-method server)))))
       (nnheader-report
        'nnir
-       "nnir-retrieve-headers doesn't grok message ids: %s"
-       article)
+       "nnir-request-article only groks message ids for nnimap servers: %s"
+       server)
     (save-excursion
-      (let* ((artitem (nnir-artlist-article nnir-artlist
-                                           article))
-            (artfullgroup (nnir-artitem-group artitem))
-            (artno (nnir-artitem-number artitem))
-            ;; Bug?
-            ;; Why must we bind nntp-server-buffer here?  It won't
-            ;; work if `buf' is used, say.  (Of course, the set-buffer
-            ;; line below must then be updated, too.)
-            (nntp-server-buffer (or to-buffer nntp-server-buffer)))
-       (set-buffer nntp-server-buffer)
-       (erase-buffer)
-       (message "Requesting article %d from group %s"
-                artno artfullgroup)
-       (gnus-request-article artno artfullgroup nntp-server-buffer)
-       (cons artfullgroup artno)))))
+      (let ((article article)
+            query)
+        (when (stringp article)
+          (setq gnus-override-method (gnus-server-to-method server))
+          (setq query
+                (list
+                 (cons 'query (format "HEADER Message-ID %s" article))
+                 (cons 'criteria "")
+                 (cons 'shortcut t)))
+          (unless (and nnir-artlist (equal query nnir-memo-query)
+                       (equal server nnir-memo-server))
+            (setq nnir-artlist (nnir-run-imap query server)
+                 nnir-memo-query query
+                 nnir-memo-server server))
+          (setq article 1))
+        (unless (zerop (nnir-artlist-length nnir-artlist))
+          (let ((artfullgroup (nnir-article-group article))
+                (artno (nnir-article-number article)))
+            (message "Requesting article %d from group %s"
+                     artno artfullgroup)
+            (if to-buffer
+                (with-current-buffer to-buffer
+                  (let ((gnus-article-decode-hook nil))
+                    (gnus-request-article-this-buffer artno artfullgroup)))
+              (gnus-request-article artno artfullgroup))
+            (cons artfullgroup artno)))))))
+
+(deffoo nnir-request-move-article (article group server accept-form
+                                          &optional last internal-move-group)
+  (nnir-possibly-change-group group server)
+  (let* ((artfullgroup (nnir-article-group article))
+        (artno (nnir-article-number article))
+        (to-newsgroup (nth 1 accept-form))
+        (to-method (gnus-find-method-for-group to-newsgroup))
+        (from-method (gnus-find-method-for-group artfullgroup))
+        (move-is-internal (gnus-server-equal from-method to-method)))
+    (unless (gnus-check-backend-function
+            'request-move-article artfullgroup)
+      (error "The group %s does not support article moving" artfullgroup))
+    (gnus-request-move-article
+     artno
+     artfullgroup
+     (nth 1 from-method)
+     accept-form
+     last
+     (and move-is-internal
+         to-newsgroup          ; Not respooling
+         (gnus-group-real-name to-newsgroup)))))
+
+(deffoo nnir-request-expire-articles (articles group &optional server force)
+  (nnir-possibly-change-group group server)
+  (if force
+    (let ((articles-by-group (nnir-categorize
+                             articles nnir-article-group nnir-article-ids))
+         not-deleted)
+      (while (not (null articles-by-group))
+       (let* ((group-articles (pop articles-by-group))
+              (artgroup (car group-articles))
+              (articleids (cadr group-articles))
+              (artlist (sort (mapcar 'cdr articleids) '<)))
+         (unless (gnus-check-backend-function 'request-expire-articles
+                                              artgroup)
+           (error "The group %s does not support article deletion" artgroup))
+         (unless (gnus-check-server (gnus-find-method-for-group artgroup))
+           (error "Couldn't open server for group %s" artgroup))
+         (push (gnus-request-expire-articles
+                artlist artgroup force)
+               not-deleted)))
+      (sort (delq nil not-deleted) '<))
+    articles))
+
+(deffoo nnir-warp-to-article ()
+  (nnir-possibly-change-group gnus-newsgroup-name)
+  (let* ((cur (if (> (gnus-summary-article-number) 0)
+                 (gnus-summary-article-number)
+               (error "Can't warp to a pseudo-article")))
+        (backend-article-group (nnir-article-group cur))
+         (backend-article-number (nnir-article-number cur))
+        (quit-config (gnus-ephemeral-group-p gnus-newsgroup-name)))
+
+    ;; what should we do here? we could leave all the buffers around
+    ;; and assume that we have to exit from them one by one. or we can
+    ;; try to clean up directly
+
+    ;;first exit from the nnir summary buffer.
+;    (gnus-summary-exit)
+    ;; and if the nnir summary buffer in turn came from another
+    ;; summary buffer we have to clean that summary up too.
+ ;   (when (not (eq (cdr quit-config) 'group))
+;      (gnus-summary-exit))
+    (gnus-summary-read-group-1 backend-article-group t t  nil
+                               nil (list backend-article-number))))
+
+(deffoo nnir-request-update-mark (group article mark)
+  (let ((artgroup (nnir-article-group article))
+       (artnumber (nnir-article-number article)))
+    (gnus-request-update-mark artgroup artnumber mark)))
+
+(deffoo nnir-request-set-mark (group actions &optional server)
+  (nnir-possibly-change-group group server)
+  (let (mlist)
+    (dolist (action actions)
+      (destructuring-bind (range action marks) action
+        (let ((articles-by-group (nnir-categorize
+                                  (gnus-uncompress-range range)
+                                  nnir-article-group nnir-article-number)))
+          (dolist (artgroup articles-by-group)
+            (push (list
+                  (car artgroup)
+                  (list (gnus-compress-sequence
+                         (sort (cadr artgroup) '<)) action marks)) mlist)))))
+    (dolist (request (nnir-categorize  mlist car cadr))
+      (gnus-request-set-mark (car request) (cadr request)))))
+
+
+(deffoo nnir-request-update-info (group info &optional server)
+  (nnir-possibly-change-group group server)
+  ;; clear out all existing marks.
+  (gnus-info-set-marks info nil)
+  (gnus-info-set-read info nil)
+  (let ((group (gnus-group-guess-full-name-from-command-method group))
+       (articles-by-group
+        (nnir-categorize
+         (gnus-uncompress-range (cons 1 (nnir-artlist-length nnir-artlist)))
+         nnir-article-group nnir-article-ids)))
+    (gnus-set-active group
+                    (cons 1 (nnir-artlist-length nnir-artlist)))
+    (while (not (null articles-by-group))
+      (let* ((group-articles (pop articles-by-group))
+            (articleids (reverse (cadr group-articles)))
+            (group-info (gnus-get-info (car group-articles)))
+            (marks (gnus-info-marks group-info))
+            (read (gnus-info-read group-info)))
+       (gnus-info-set-read
+        info
+        (gnus-add-to-range
+         (gnus-info-read info)
+         (delq nil
+                 (mapcar
+                  #'(lambda (art)
+                    (when (gnus-member-of-range (cdr art) read) (car art)))
+                  articleids))))
+       (dolist (mark marks)
+         (destructuring-bind (type . range) mark
+           (gnus-add-marked-articles
+            group type
+            (delq nil
+                    (mapcar
+                     #'(lambda (art)
+                       (when (gnus-member-of-range (cdr art) range) (car art)))
+                     articleids)))))))))
+
+
+(deffoo nnir-close-group (group &optional server)
+  (nnir-possibly-change-group group server)
+  (let ((pgroup (gnus-group-guess-full-name-from-command-method group)))
+    (when (and nnir-artlist (not (gnus-ephemeral-group-p pgroup)))
+      (gnus-group-set-parameter  pgroup 'nnir-artlist nnir-artlist))
+    (setq nnir-artlist nil)
+    (when (gnus-ephemeral-group-p pgroup)
+      (gnus-kill-ephemeral-group pgroup)
+      (setq gnus-ephemeral-servers
+           (delq (assq 'nnir gnus-ephemeral-servers)
+                 gnus-ephemeral-servers)))))
+;; (gnus-opened-servers-remove
+;;  (car (assoc  '(nnir "nnir-ephemeral" (nnir-address "nnir"))
+;;             gnus-opened-servers))))
 
 
-(nnoo-define-skeleton nnir)
 
 
 (defmacro nnir-add-result (dirnam artno score prefix server artlist)
@@ -892,7 +932,7 @@ ready to be added to the list of search results."
     ;; remove trailing slash and, for nnmaildir, cur/new/tmp
     (setq dirnam
          (substring dirnam 0
-                    (if (string= (gnus-group-server server) "nnmaildir")
+                    (if (string-match "\\`nnmaildir:" (gnus-group-server server))
                         -5 -1)))
 
     ;; Set group to dirnam without any leading dots or slashes,
@@ -901,8 +941,8 @@ ready to be added to the list of search results."
                  (gnus-replace-in-string dirnam "^[./\\]" "" t)
                  "[/\\]" "." t)))
 
-    (vector (nnir-group-full-name group server)
-           (if (string= (gnus-group-server server) "nnmaildir")
+    (vector (gnus-group-full-name group server)
+           (if (string-match "\\`nnmaildir:" (gnus-group-server server))
                (nnmaildir-base-name-to-article-number
                 (substring article 0 (string-match ":" article))
                 group nil)
@@ -911,94 +951,54 @@ ready to be added to the list of search results."
 
 ;;; Search Engine Interfaces:
 
-;; freeWAIS-sf interface.
-(defun nnir-run-waissearch (query server &optional group)
-  "Run given query agains waissearch.  Returns vector of (group name, file name)
-pairs (also vectors, actually)."
-  (when group
-    (error "The freeWAIS-sf backend cannot search specific groups"))
-  (save-excursion
-    (let ((qstring (cdr (assq 'query query)))
-         (prefix (nnir-read-server-parm 'nnir-wais-remove-prefix server))
-          artlist score artno dirnam)
-      (set-buffer (get-buffer-create nnir-tmp-buffer))
-      (erase-buffer)
-      (message "Doing WAIS query %s..." query)
-      (call-process nnir-wais-program
-                    nil                 ; input from /dev/null
-                    t                   ; output to current buffer
-                    nil                 ; don't redisplay
-                    "-d" (nnir-read-server-parm 'nnir-wais-database server) ; database to search
-                    qstring)
-      (message "Massaging waissearch output...")
-      ;; remove superfluous lines
-      (keep-lines "Score:")
-      ;; extract data from result lines
-      (goto-char (point-min))
-      (while (re-search-forward
-              "Score: +\\([0-9]+\\).*'\\([0-9]+\\) +\\([^']+\\)/'" nil t)
-        (setq score (match-string 1)
-              artno (match-string 2)
-              dirnam (match-string 3))
-        (unless (string-match prefix dirnam)
-          (nnheader-report 'nnir "Dir name %s doesn't contain prefix %s"
-                           dirnam prefix))
-        (setq group (gnus-replace-in-string
-                     (replace-match "" t t dirnam) "/" "."))
-        (push (vector (nnir-group-full-name group server)
-                      (string-to-number artno)
-                      (string-to-number score))
-              artlist))
-      (message "Massaging waissearch output...done")
-      (apply 'vector
-             (sort artlist
-                   (function (lambda (x y)
-                               (> (nnir-artitem-rsv x)
-                                  (nnir-artitem-rsv y)))))))))
-
-;; IMAP interface.
-;; todo:
-;; send queries as literals
-;; handle errors
-
-
-(defun nnir-run-imap (query srv &optional group-option)
+;; imap interface
+(defun nnir-run-imap (query srv &optional groups)
   "Run a search against an IMAP back-end server.
 This uses a custom query language parser; see `nnir-imap-make-query' for
-details on the language and supported extensions"
+details on the language and supported extensions."
   (save-excursion
     (let ((qstring (cdr (assq 'query query)))
-         (server (cadr (gnus-server-to-method srv)))
-         (group (or group-option (gnus-group-group-name)))
-         (defs (caddr (gnus-server-to-method srv)))
-         (criteria (or (cdr (assq 'criteria query))
-                       (cdr (assoc nnir-imap-default-search-key
-                                   nnir-imap-search-arguments))))
-         (gnus-inhibit-demon t)
-         artlist)
+          (server (cadr (gnus-server-to-method srv)))
+          (defs (caddr (gnus-server-to-method srv)))
+          (criteria (or (cdr (assq 'criteria query))
+                        (cdr (assoc nnir-imap-default-search-key
+                                    nnir-imap-search-arguments))))
+          (gnus-inhibit-demon t)
+         (groups (or groups (nnir-get-active srv))))
       (message "Opening server %s" server)
-      (condition-case ()
-         (when (nnimap-possibly-change-group (gnus-group-short-name group) server)
-           (with-current-buffer (nnimap-buffer)
-             (message "Searching %s..." group)
-             (let ((arts 0)
-                   (result
-                    (nnimap-command "UID SEARCH %s"
-                                    (if (string= criteria "")
-                                        qstring
-                                      (nnir-imap-make-query criteria qstring)
-                                      ))))
-               (mapc
-                (lambda (artnum)
-                  (push (vector group artnum 1) artlist)
-                  (setq arts (1+ arts)))
-                (and (car result)
-                     (delete 0 (mapcar #'string-to-number
-                                       (cdr (assoc "SEARCH" (cdr result)))))))
-               (message "Searching %s... %d matches" group arts)))
-           (message "Searching %s...done" group))
-       (quit nil))
-      (reverse artlist))))
+      (apply
+       'vconcat
+       (catch 'found
+         (mapcar
+          #'(lambda (group)
+            (let (artlist)
+              (condition-case ()
+                  (when (nnimap-change-group
+                         (gnus-group-short-name group) server)
+                    (with-current-buffer (nnimap-buffer)
+                      (message "Searching %s..." group)
+                      (let ((arts 0)
+                            (result (nnimap-command "UID SEARCH %s"
+                                                    (if (string= criteria "")
+                                                        qstring
+                                                      (nnir-imap-make-query
+                                                       criteria qstring)))))
+                        (mapc
+                         (lambda (artnum)
+                           (let ((artn (string-to-number artnum)))
+                             (when (> artn 0)
+                               (push (vector group artn 100)
+                                     artlist)
+                               (when (assq 'shortcut query)
+                                 (throw 'found (list artlist)))
+                               (setq arts (1+ arts)))))
+                         (and (car result)
+                             (cdr (assoc "SEARCH" (cdr result)))))
+                        (message "Searching %s... %d matches" group arts)))
+                    (message "Searching %s...done" group))
+                (quit nil))
+              (nreverse artlist)))
+          groups))))))
 
 (defun nnir-imap-make-query (criteria qstring)
   "Parse the query string and criteria into an appropriate IMAP search
@@ -1188,19 +1188,20 @@ actually).
 Tested with swish++ 4.7 on GNU/Linux and with swish++ 5.0b2 on
 Windows NT 4.0."
 
-  (when group
-    (error "The swish++ backend cannot search specific groups"))
+  ;; (when group
+  ;;   (error "The swish++ backend cannot search specific groups"))
 
   (save-excursion
     (let ( (qstring (cdr (assq 'query query)))
-          (groupspec (cdr (assq 'group query)))
+          (groupspec (cdr (assq 'swish++-group query)))
           (prefix (nnir-read-server-parm 'nnir-swish++-remove-prefix server))
            artlist
           ;; nnml-use-compressed-files might be any string, but probably this
           ;; is sufficient.  Note that we can't only use the value of
           ;; nnml-use-compressed-files because old articles might have been
           ;; saved with a different value.
-          (article-pattern (if (string= (gnus-group-server server) "nnmaildir")
+          (article-pattern (if (string-match "\\`nnmaildir:"
+                                             (gnus-group-server server))
                                ":[0-9]+"
                              "^[0-9]+\\(\\.[a-z0-9]+\\)?$"))
            score artno dirnam filenam)
@@ -1277,8 +1278,8 @@ actually).
 Tested with swish-e-2.0.1 on Windows NT 4.0."
 
   ;; swish-e crashes with empty parameter to "-w" on commandline...
-  (when group
-    (error "The swish-e backend cannot search specific groups"))
+  ;; (when group
+  ;;   (error "The swish-e backend cannot search specific groups"))
 
   (save-excursion
     (let ((qstring (cdr (assq 'query query)))
@@ -1348,7 +1349,7 @@ Tested with swish-e-2.0.1 on Windows NT 4.0."
             ;; Windows "\\" -> "."
             (setq group (gnus-replace-in-string group "\\\\" "."))
 
-            (push (vector (nnir-group-full-name group server)
+            (push (vector (gnus-group-full-name group server)
                           (string-to-number artno)
                           (string-to-number score))
                   artlist))))
@@ -1366,23 +1367,17 @@ Tested with swish-e-2.0.1 on Windows NT 4.0."
 (defun nnir-run-hyrex (query server &optional group)
   (save-excursion
     (let ((artlist nil)
-          (groupspec (cdr (assq 'group query)))
+          (groupspec (cdr (assq 'hyrex-group query)))
           (qstring (cdr (assq 'query query)))
          (prefix (nnir-read-server-parm 'nnir-hyrex-remove-prefix server))
          score artno dirnam)
-      (when (and group groupspec)
-        (error (concat "It does not make sense to use a group spec"
-                       " with process-marked groups.")))
-      (when group
-        (setq groupspec (gnus-group-real-name group)))
-      (when (and group (not (equal group (nnir-group-full-name groupspec server))))
-       (message "%s vs. %s" group (nnir-group-full-name groupspec server))
-       (error "Server with groupspec doesn't match group !"))
+      (when (and (not groupspec) group)
+        (setq groupspec
+             (regexp-opt
+              (mapcar (lambda (x) (gnus-group-real-name x)) group))))
       (set-buffer (get-buffer-create nnir-tmp-buffer))
       (erase-buffer)
-      (if groupspec
-          (message "Doing hyrex-search query %s on %s..." query groupspec)
-        (message "Doing hyrex-search query %s..." query))
+      (message "Doing hyrex-search query %s..." query)
       (let* ((cp-list
              `( ,nnir-hyrex-program
                 nil                    ; input from /dev/null
@@ -1403,17 +1398,15 @@ Tested with swish-e-2.0.1 on Windows NT 4.0."
           ;; nnir-search failure reason is in this buffer, show it if
           ;; the user wants it.
           (when (> gnus-verbose 6)
-            (display-buffer nnir-tmp-buffer)))) ;; FIXME: Dont clear buffer !
-      (if groupspec
-          (message "Doing hyrex-search query \"%s\" on %s...done" qstring groupspec)
-        (message "Doing hyrex-search query \"%s\"...done" qstring))
+            (display-buffer nnir-tmp-buffer)))) ;; FIXME: Don't clear buffer !
+      (message "Doing hyrex-search query \"%s\"...done" qstring)
       (sit-for 0)
       ;; nnir-search returns:
-      ;;   for nnml/nnfolder: "filename mailid weigth"
-      ;;   for nnimap:        "group mailid weigth"
+      ;;   for nnml/nnfolder: "filename mailid weight"
+      ;;   for nnimap:        "group mailid weight"
       (goto-char (point-min))
       (delete-non-matching-lines "^\\S + [0-9]+ [0-9]+$")
-      ;; HyREX couldn't search directly in groups -- so filter out here.
+      ;; HyREX doesn't search directly in groups -- so filter out here.
       (when groupspec
        (keep-lines groupspec))
       ;; extract data from result lines
@@ -1425,7 +1418,7 @@ Tested with swish-e-2.0.1 on Windows NT 4.0."
              score (match-string 3))
        (when (string-match prefix dirnam)
          (setq dirnam (replace-match "" t t dirnam)))
-       (push (vector (nnir-group-full-name
+       (push (vector (gnus-group-full-name
                        (gnus-replace-in-string dirnam "/" ".") server)
                      (string-to-number artno)
                      (string-to-number score))
@@ -1447,10 +1440,11 @@ Tested with swish-e-2.0.1 on Windows NT 4.0."
 pairs (also vectors, actually).
 
 Tested with Namazu 2.0.6 on a GNU/Linux system."
-  (when group
-    (error "The Namazu backend cannot search specific groups"))
+  ;; (when group
+  ;;   (error "The Namazu backend cannot search specific groups"))
   (save-excursion
-    (let ((article-pattern (if (string= (gnus-group-server server) "nnmaildir")
+    (let ((article-pattern (if (string-match "\\`nnmaildir:"
+                                            (gnus-group-server server))
                               ":[0-9]+"
                             "^[0-9]+$"))
           artlist
@@ -1510,7 +1504,82 @@ Tested with Namazu 2.0.6 on a GNU/Linux system."
                                (> (nnir-artitem-rsv x)
                                   (nnir-artitem-rsv y)))))))))
 
-(defun nnir-run-find-grep (query server &optional group)
+(defun nnir-run-notmuch (query server &optional group)
+  "Run QUERY against notmuch.
+Returns a vector of (group name, file name) pairs (also vectors,
+actually)."
+
+  ;; (when group
+  ;;   (error "The notmuch backend cannot search specific groups"))
+
+  (save-excursion
+    (let ( (qstring (cdr (assq 'query query)))
+          (groupspec (cdr (assq 'notmuch-group query)))
+          (prefix (nnir-read-server-parm 'nnir-notmuch-remove-prefix server))
+           artlist
+          (article-pattern (if (string-match "\\`nnmaildir:"
+                                             (gnus-group-server server))
+                              ":[0-9]+"
+                            "^[0-9]+$"))
+           artno dirnam filenam)
+
+      (when (equal "" qstring)
+        (error "notmuch: You didn't enter anything"))
+
+      (set-buffer (get-buffer-create nnir-tmp-buffer))
+      (erase-buffer)
+
+      (if groupspec
+          (message "Doing notmuch query %s on %s..." qstring groupspec)
+        (message "Doing notmuch query %s..." qstring))
+
+      (let* ((cp-list `( ,nnir-notmuch-program
+                         nil            ; input from /dev/null
+                         t              ; output
+                         nil            ; don't redisplay
+                         "search"
+                         "--format=text"
+                         "--output=files"
+                         ,@(nnir-read-server-parm 'nnir-notmuch-additional-switches server)
+                         ,qstring       ; the query, in notmuch format
+                         ))
+             (exitstatus
+              (progn
+                (message "%s args: %s" nnir-notmuch-program
+                         (mapconcat 'identity (cddddr cp-list) " ")) ;; ???
+                (apply 'call-process cp-list))))
+        (unless (or (null exitstatus)
+                    (zerop exitstatus))
+          (nnheader-report 'nnir "Couldn't run notmuch: %s" exitstatus)
+          ;; notmuch failure reason is in this buffer, show it if
+          ;; the user wants it.
+          (when (> gnus-verbose 6)
+            (display-buffer nnir-tmp-buffer))))
+
+      ;; The results are output in the format of:
+      ;; absolute-path-name
+      (goto-char (point-min))
+      (while (not (eobp))
+        (setq filenam (buffer-substring-no-properties (line-beginning-position)
+                                                      (line-end-position))
+              artno (file-name-nondirectory filenam)
+              dirnam (file-name-directory filenam))
+        (forward-line 1)
+
+        ;; don't match directories
+        (when (string-match article-pattern artno)
+          (when (not (null dirnam))
+
+           ;; maybe limit results to matching groups.
+           (when (or (not groupspec)
+                     (string-match groupspec dirnam))
+             (nnir-add-result dirnam artno "" prefix server artlist)))))
+
+      (message "Massaging notmuch output...done")
+
+      artlist)))
+
+(defun nnir-run-find-grep (query server &optional grouplist)
   "Run find and grep to obtain matching articles."
   (let* ((method (gnus-server-to-method server))
         (sym (intern
@@ -1518,69 +1587,143 @@ Tested with Namazu 2.0.6 on a GNU/Linux system."
         (directory (cadr (assoc sym (cddr method))))
         (regexp (cdr (assoc 'query query)))
         (grep-options (cdr (assoc 'grep-options query)))
-        artlist)
+        (grouplist (or grouplist (nnir-get-active server))))
     (unless directory
       (error "No directory found in method specification of server %s"
             server))
-    (message "Searching %s using find-grep..." (or group server))
-    (save-window-excursion
-      (set-buffer (get-buffer-create nnir-tmp-buffer))
-      (erase-buffer)
-      (if (> gnus-verbose 6)
-         (pop-to-buffer (current-buffer)))
-      (cd directory) ; Using relative paths simplifies postprocessing.
-      (let ((group
-            (if (not group)
-                "."
-              ;; Try accessing the group literally as well as
-              ;; interpreting dots as directory separators so the
-              ;; engine works with plain nnml as well as the Gnus Cache.
-               (let ((group (gnus-group-real-name group)))
-                 ;; Replace cl-func find-if.
-                 (if (file-directory-p group)
-                     group
-                   (if (file-directory-p
-                        (setq group (gnus-replace-in-string group "\\." "/" t)))
-                       group))))))
-       (unless group
-         (error "Cannot locate directory for group"))
-       (save-excursion
-         (apply
-          'call-process "find" nil t
-          "find" group "-type" "f" "-name" "[0-9]*" "-exec"
-          "grep"
-          `("-l" ,@(and grep-options
-                        (split-string grep-options "\\s-" t))
-            "-e" ,regexp "{}" "+"))))
-
-      ;; Translate relative paths to group names.
-      (while (not (eobp))
-       (let* ((path (split-string
-                     (buffer-substring (point) (line-end-position)) "/" t))
-              (art (string-to-number (car (last path)))))
-         (while (string= "." (car path))
-           (setq path (cdr path)))
-         (let ((group (mapconcat 'identity
-                                  ;; Replace cl-func: (subseq path 0 -1)
-                                  (let ((end (1- (length path)))
-                                        res)
-                                    (while (>= (setq end (1- end)) 0)
-                                      (push (pop path) res))
-                                    (nreverse res))
-                                  ".")))
-           (push (vector (nnir-group-full-name group server) art 0)
-                 artlist))
-         (forward-line 1)))
-      (message "Searching %s using find-grep...done" (or group server))
-      artlist)))
+    (apply
+     'vconcat
+     (mapcar (lambda (x)
+              (let ((group x)
+                    artlist)
+                (message "Searching %s using find-grep..."
+                         (or group server))
+                (save-window-excursion
+                  (set-buffer (get-buffer-create nnir-tmp-buffer))
+                  (if (> gnus-verbose 6)
+                      (pop-to-buffer (current-buffer)))
+                  (cd directory) ; Using relative paths simplifies
+                                       ; postprocessing.
+                  (let ((group
+                         (if (not group)
+                             "."
+                           ;; Try accessing the group literally as
+                           ;; well as interpreting dots as directory
+                           ;; separators so the engine works with
+                           ;; plain nnml as well as the Gnus Cache.
+                           (let ((group (gnus-group-real-name group)))
+                             ;; Replace cl-func find-if.
+                             (if (file-directory-p group)
+                                 group
+                               (if (file-directory-p
+                                    (setq group
+                                          (gnus-replace-in-string
+                                           group
+                                           "\\." "/" t)))
+                                   group))))))
+                    (unless group
+                      (error "Cannot locate directory for group"))
+                    (save-excursion
+                      (apply
+                       'call-process "find" nil t
+                       "find" group "-maxdepth" "1" "-type" "f"
+                       "-name" "[0-9]*" "-exec"
+                       "grep"
+                       `("-l" ,@(and grep-options
+                                     (split-string grep-options "\\s-" t))
+                         "-e" ,regexp "{}" "+"))))
+
+                  ;; Translate relative paths to group names.
+                  (while (not (eobp))
+                    (let* ((path (split-string
+                                  (buffer-substring
+                                   (point)
+                                   (line-end-position)) "/" t))
+                           (art (string-to-number (car (last path)))))
+                      (while (string= "." (car path))
+                        (setq path (cdr path)))
+                      (let ((group (mapconcat 'identity
+                                              ;; Replace cl-func:
+                                              ;; (subseq path 0 -1)
+                                              (let ((end (1- (length path)))
+                                                    res)
+                                                (while
+                                                    (>= (setq end (1- end)) 0)
+                                                  (push (pop path) res))
+                                                (nreverse res))
+                                              ".")))
+                        (push
+                         (vector (gnus-group-full-name group server) art 0)
+                         artlist))
+                      (forward-line 1)))
+                  (message "Searching %s using find-grep...done"
+                           (or group server))
+                  artlist)))
+     grouplist))))
+
+(declare-function mm-url-insert "mm-url" (url &optional follow-refresh))
+(declare-function mm-url-encode-www-form-urlencoded "mm-url" (pairs))
+
+;; gmane interface
+(defun nnir-run-gmane (query srv &optional groups)
+  "Run a search against a gmane back-end server."
+      (let* ((case-fold-search t)
+            (qstring (cdr (assq 'query query)))
+            (server (cadr (gnus-server-to-method srv)))
+            (groupspec (mapconcat
+                        (lambda (x)
+                          (if (gnus-string-match-p "gmane" x)
+                              (format "group:%s" (gnus-group-short-name x))
+                            (error "Can't search non-gmane groups: %s" x)))
+                          groups " "))
+            (authorspec
+             (if (assq 'gmane-author query)
+                 (format "author:%s" (cdr (assq 'gmane-author query))) ""))
+            (search (format "%s %s %s"
+                            qstring groupspec authorspec))
+            (gnus-inhibit-demon t)
+            artlist)
+       (require 'mm-url)
+       (with-current-buffer (get-buffer-create nnir-tmp-buffer)
+         (erase-buffer)
+         (mm-url-insert
+          (concat
+           "http://search.gmane.org/nov.php"
+           "?"
+           (mm-url-encode-www-form-urlencoded
+            `(("query" . ,search)
+              ("HITSPERPAGE" . "999")))))
+         (unless (featurep 'xemacs) (set-buffer-multibyte t))
+         (mm-decode-coding-region (point-min) (point-max) 'utf-8)
+         (goto-char (point-min))
+         (forward-line 1)
+         (while (not (eobp))
+           (unless (or (eolp) (looking-at "\x0d"))
+             (let ((header (nnheader-parse-nov)))
+               (let ((xref (mail-header-xref header))
+                     (xscore (string-to-number (cdr (assoc 'X-Score
+                              (mail-header-extra header))))))
+                 (when (string-match " \\([^:]+\\)[:/]\\([0-9]+\\)" xref)
+                   (push
+                    (vector
+                     (gnus-group-prefixed-name (match-string 1 xref) srv)
+                     (string-to-number (match-string 2 xref)) xscore)
+                    artlist)))))
+           (forward-line 1)))
+       (apply 'vector (nreverse (mm-delete-duplicates artlist)))))
 
 ;;; Util Code:
 
-(defun nnir-read-parms (query)
+(defun gnus-nnir-group-p (group)
+  "Say whether GROUP is nnir or not."
+  (if (gnus-group-prefixed-p group)
+      (eq 'nnir (car (gnus-find-method-for-group group)))
+    (and group (string-match "^nnir" group))))
+
+(defun nnir-read-parms (nnir-search-engine)
   "Reads additional search parameters according to `nnir-engines'."
   (let ((parmspec (caddr (assoc nnir-search-engine nnir-engines))))
-    (cons (cons 'query query)
-          (mapcar 'nnir-read-parm parmspec))))
+    (mapcar 'nnir-read-parm parmspec)))
 
 (defun nnir-read-parm (parmspec)
   "Reads a single search parameter.
@@ -1594,114 +1737,182 @@ Tested with Namazu 2.0.6 on a GNU/Linux system."
          (cons sym (format (cdr mapping) result)))
       (cons sym (read-string prompt)))))
 
-(defun nnir-run-query (query)
-  "Invoke appropriate search engine function (see `nnir-engines').
-If some groups were process-marked, run the query for each of the groups
-and concat the results."
-  (let ((q (car (read-from-string query))))
-    (if gnus-group-marked
-       (apply 'vconcat
-              (mapcar (lambda (x)
-                        (let ((server (nnir-group-server x))
-                              search-func)
-                          (setq search-func (cadr
-                                             (assoc
-                                              (nnir-read-server-parm 'nnir-search-engine server) nnir-engines)))
-                          (if search-func
-                              (funcall search-func q server x)
-                            nil)))
-                      gnus-group-marked)
-              )
-      (apply 'vconcat
-            (mapcar (lambda (x)
-                      (if (and (equal (cadr x) 'ok) (not (equal (cadar x) "-ephemeral")))
-                          (let ((server (format "%s:%s" (caar x) (cadar x)))
-                                search-func)
-                            (setq search-func (cadr
-                                               (assoc
-                                                (nnir-read-server-parm 'nnir-search-engine server) nnir-engines)))
-                            (if search-func
-                                (funcall search-func q server nil)
-                              nil))
-                        nil))
-                    gnus-opened-servers)
-            ))
-    ))
-
-(defun nnir-read-server-parm (key server)
-  "Returns the parameter value of for the given server, where server is of
-form 'backend:name'."
+(defun nnir-run-query (specs)
+  "Invoke appropriate search engine function (see `nnir-engines')."
+  (apply 'vconcat
+        (mapcar
+         (lambda (x)
+           (let* ((server (car x))
+                  (search-engine (nnir-server-to-search-engine server))
+                  (search-func (cadr (assoc search-engine nnir-engines))))
+             (and search-func
+                  (funcall search-func (cdr (assq 'nnir-query-spec specs))
+                           server (cadr x)))))
+         (cdr (assq 'nnir-group-spec specs)))))
+
+(defun nnir-server-to-search-engine (server)
+  (or (nnir-read-server-parm 'nnir-search-engine server t)
+      (cdr (assoc (car (gnus-server-to-method server))
+                 nnir-method-default-engines))))
+
+(defun nnir-read-server-parm (key server &optional not-global)
+  "Returns the parameter value corresponding to `key' for
+`server'. If no server-specific value is found consult the global
+environment unless `not-global' is non-nil."
   (let ((method (gnus-server-to-method server)))
     (cond ((and method (assq key (cddr method)))
-          (nth 1 (assq key (cddr method))))
-         ((and nnir-mail-backend
-               (gnus-server-equal method nnir-mail-backend))
-          (symbol-value key))
-         (t nil))))
-;;     (if method
-;;       (if (assq key (cddr method))
-;;       (nth 1 (assq key (cddr method)))
-;;     (symbol-value key))
-;;       (symbol-value key))
-;;     ))
-
-(defun nnir-group-full-name (shortname server)
-  "For the given group name, return a full Gnus group name.
-The Gnus backend/server information is added."
-  (gnus-group-prefixed-name shortname (gnus-server-to-method server)))
-
-(defun nnir-possibly-change-server (server)
-  (unless (and server (nnir-server-opened server))
-    (nnir-open-server server)))
-
-
-;; Data type article list.
-
-(defun nnir-artlist-length (artlist)
-  "Returns number of articles in artlist."
-  (length artlist))
-
-(defun nnir-artlist-article (artlist n)
-  "Returns from ARTLIST the Nth artitem (counting starting at 1)."
-  (elt artlist (1- n)))
-
-(defun nnir-artitem-group (artitem)
-  "Returns the group from the ARTITEM."
-  (elt artitem 0))
-
-(defun nnir-artlist-artitem-group (artlist n)
-  "Returns from ARTLIST the group of the Nth artitem (counting from 1)."
-  (nnir-artitem-group (nnir-artlist-article artlist n)))
-
-(defun nnir-artitem-number (artitem)
-  "Returns the number from the ARTITEM."
-  (elt artitem 1))
-
-(defun nnir-artlist-artitem-number (artlist n)
-  "Returns from ARTLIST the number of the Nth artitem (counting from 1)."
-  (nnir-artitem-number (nnir-artlist-article artlist n)))
-
-(defun nnir-artitem-rsv (artitem)
-  "Returns the Retrieval Status Value (RSV, score) from the ARTITEM."
-  (elt artitem 2))
-
-(defun nnir-artlist-artitem-rsv (artlist n)
-  "Returns from ARTLIST the Retrieval Status Value of the Nth artitem
-\(counting from 1)."
-  (nnir-artitem-rsv (nnir-artlist-article artlist n)))
-
-;; unused?
-(defun nnir-artlist-groups (artlist)
-  "Returns a list of all groups in the given ARTLIST."
-  (let ((res nil)
-        (with-dups nil))
-    ;; from each artitem, extract group component
-    (setq with-dups (mapcar 'nnir-artitem-group artlist))
-    ;; remove duplicates from above
-    (mapc (function (lambda (x) (add-to-list 'res x)))
-            with-dups)
-    res))
+           (nth 1 (assq key (cddr method))))
+          ((and (not not-global) (boundp key)) (symbol-value key))
+          (t nil))))
+
+(defun nnir-possibly-change-group (group &optional server)
+  (or (not server) (nnir-server-opened server) (nnir-open-server server))
+  (when (gnus-nnir-group-p group)
+    (setq nnir-artlist (gnus-group-get-parameter
+                       (gnus-group-prefixed-name
+                        (gnus-group-short-name group) '(nnir "nnir"))
+                       'nnir-artlist t))))
+
+(defun nnir-server-opened (&optional server)
+  (let ((backend (car (gnus-server-to-method server))))
+    (nnoo-current-server-p (or backend 'nnir) server)))
+
+(defun nnir-search-thread (header)
+  "Make an nnir group based on the thread containing the article
+header. The current server will be searched. If the registry is
+installed, the server that the registry reports the current
+article came from is also searched."
+  (let* ((query
+         (list (cons 'query (nnimap-make-thread-query header))
+               (cons 'criteria "")))
+        (server
+         (list (list (gnus-method-to-server
+          (gnus-find-method-for-group gnus-newsgroup-name)))))
+        (registry-group (and
+                         (gnus-bound-and-true-p 'gnus-registry-enabled)
+                         (car (gnus-registry-get-id-key
+                               (mail-header-id header) 'group))))
+        (registry-server
+         (and registry-group
+              (gnus-method-to-server
+               (gnus-find-method-for-group registry-group)))))
+    (when registry-server (add-to-list 'server (list registry-server)))
+    (gnus-group-make-nnir-group nil (list
+                                    (cons 'nnir-query-spec query)
+                                    (cons 'nnir-group-spec server)))
+    (gnus-summary-goto-subject (gnus-id-to-article (mail-header-id header)))))
+
+(defun nnir-get-active (srv)
+  (let ((method (gnus-server-to-method srv))
+       groups)
+    (gnus-request-list method)
+    (with-current-buffer nntp-server-buffer
+      (let ((cur (current-buffer))
+           name)
+       (goto-char (point-min))
+       (unless (or (null nnir-ignored-newsgroups)
+                   (string= nnir-ignored-newsgroups ""))
+         (delete-matching-lines nnir-ignored-newsgroups))
+       (if (eq (car method) 'nntp)
+           (while (not (eobp))
+             (ignore-errors
+               (push (mm-string-as-unibyte
+                      (gnus-group-full-name
+                       (buffer-substring
+                        (point)
+                        (progn
+                          (skip-chars-forward "^ \t")
+                          (point))) method))
+                     groups))
+             (forward-line))
+         (while (not (eobp))
+           (ignore-errors
+             (push (mm-string-as-unibyte
+                    (if (eq (char-after) ?\")
+                        (gnus-group-full-name (read cur) method)
+                      (let ((p (point)) (name ""))
+                        (skip-chars-forward "^ \t\\\\")
+                        (setq name (buffer-substring p (point)))
+                        (while (eq (char-after) ?\\)
+                          (setq p (1+ (point)))
+                          (forward-char 2)
+                          (skip-chars-forward "^ \t\\\\")
+                          (setq name (concat name (buffer-substring
+                                                   p (point)))))
+                        (gnus-group-full-name name method))))
+                   groups))
+           (forward-line)))))
+    groups))
+
+(defun nnir-registry-action (action data-header from &optional to method)
+  "Call `gnus-registry-action' with the original article group."
+  (gnus-registry-action
+   action
+   data-header
+   (nnir-article-group (mail-header-number data-header))
+   to
+   method))
+
+(defun nnir-mode ()
+  (when (eq (car (gnus-find-method-for-group gnus-newsgroup-name)) 'nnir)
+    (setq gnus-summary-line-format
+         (or nnir-summary-line-format gnus-summary-line-format))
+    (when (gnus-bound-and-true-p 'gnus-registry-enabled)
+      (remove-hook 'gnus-summary-article-delete-hook 'gnus-registry-action t)
+      (remove-hook 'gnus-summary-article-move-hook 'gnus-registry-action t)
+      (remove-hook 'gnus-summary-article-expire-hook 'gnus-registry-action t)
+      (add-hook 'gnus-summary-article-delete-hook 'nnir-registry-action t t)
+      (add-hook 'gnus-summary-article-move-hook 'nnir-registry-action t t)
+      (add-hook 'gnus-summary-article-expire-hook 'nnir-registry-action t t))))
+
+
+(defun gnus-summary-create-nnir-group ()
+  (interactive)
+  (or (nnir-server-opened "") (nnir-open-server "nnir"))
+  (let ((name (gnus-read-group "Group name: "))
+       (method '(nnir ""))
+       (pgroup
+        (gnus-group-guess-full-name-from-command-method gnus-newsgroup-name)))
+    (with-current-buffer gnus-group-buffer
+      (gnus-group-make-group
+       name method nil
+       (gnus-group-find-parameter pgroup)))))
+
+
+(deffoo nnir-request-create-group (group &optional server args)
+  (message "Creating nnir group %s" group)
+  (let* ((group (gnus-group-prefixed-name  group '(nnir "nnir")))
+         (specs (assq 'nnir-specs args))
+         (query-spec
+          (or (cdr (assq 'nnir-query-spec specs))
+              (list (cons 'query
+                          (read-string "Query: " nil 'nnir-search-history)))))
+         (group-spec
+          (or (cdr (assq 'nnir-group-spec specs))
+              (list (list (read-string "Server: " nil nil)))))
+         (nnir-specs (list (cons 'nnir-query-spec query-spec)
+                           (cons 'nnir-group-spec group-spec))))
+    (gnus-group-set-parameter group 'nnir-specs nnir-specs)
+    (gnus-group-set-parameter
+     group 'nnir-artlist
+     (or (cdr (assq 'nnir-artlist args))
+         (nnir-run-query nnir-specs)))
+    (nnir-request-update-info group (gnus-get-info group)))
+  t)
+
+(deffoo nnir-request-delete-group (group &optional force server)
+  t)
+
+(deffoo nnir-request-list (&optional server)
+  t)
+
+(deffoo nnir-request-scan (group method)
+  t)
+
+(deffoo nnir-request-close ()
+  t)
 
+(nnoo-define-skeleton nnir)
 
 ;; The end.
 (provide 'nnir)