gnus-art.el (gnus-article-describe-bindings): Add a comment
[gnus] / lisp / nnir.el
index 87377e6..aba07cf 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
-
-;; Where in the existing gnus manual would this fit best?
-
 ;; 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
 ;; 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
 
 ;;; Internal Variables:
 
-(defvar nnir-current-query nil
-  "Internal: stores current query (= group name).")
-
-(defvar nnir-current-server nil
-  "Internal: stores current server (does it ever change?).")
+(defvar nnir-memo-query nil
+  "Internal: stores current query.")
 
-(defvar nnir-current-group-marked nil
-  "Internal: stores current list of process-marked groups.")
+(defvar nnir-memo-server nil
+  "Internal: stores current server.")
 
 (defvar nnir-artlist nil
   "Internal: stores search result.")
 
-(defvar nnir-tmp-buffer " *nnir*"
-  "Internal: temporary buffer.")
-
 (defvar nnir-search-history ()
   "Internal: the history for querying search options in nnir")
 
-(defvar nnir-extra-parms nil
-  "Internal: stores request for extra search parms")
+(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"
@@ -288,26 +288,29 @@ is `(valuefunc member)'."
 (eval-when-compile
   (autoload 'nnimap-buffer "nnimap")
   (autoload 'nnimap-command "nnimap")
-  (autoload 'nnimap-possibly-change-group "nnimap")
+  (autoload 'nnimap-change-group "nnimap")
+  (autoload 'nnimap-make-thread-query "nnimap")
   (autoload 'gnus-registry-action "gnus-registry")
-  (defvar gnus-registry-install))
+  (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)
+(gnus-declare-backend "nnir" 'mail 'virtual)
 
 
 ;;; User Customizable Variables:
 
 (defgroup nnir nil
-  "Search groups in Gnus with assorted seach engines."
+  "Search groups in Gnus with assorted search engines."
   :group 'gnus)
 
 (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)
 
@@ -322,6 +325,7 @@ with three items unique to nnir summary buffers:
 %g    Article original short group name (string)
 
 If nil this will use `gnus-summary-line-format'."
+  :version "24.1"
   :type '(string)
   :group 'nnir)
 
@@ -333,13 +337,15 @@ retrieved header format.
 
 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 '(function)
   :group 'nnir)
 
-(defcustom nnir-imap-default-search-key "Whole message"
+(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\"."
+  by default set this to \"imap\"."
+  :version "24.1"
   :type `(choice ,@(mapcar (lambda (elem) (list 'const (car elem)))
                           nnir-imap-search-arguments))
   :group 'nnir)
@@ -499,6 +505,34 @@ arrive at the correct group name, \"mail.misc\"."
   :type '(directory)
   :group 'nnir)
 
+(defcustom nnir-notmuch-program "notmuch"
+  "*Name of notmuch search executable."
+  :version "24.1"
+  :type '(string)
+  :group 'nnir)
+
+(defcustom nnir-notmuch-additional-switches '()
+  "*A list of strings, to be given as additional arguments to notmuch.
+
+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)
+
+(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.
+
+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)
+
 ;;; Developer Extension Variable:
 
 (defvar nnir-engines
@@ -512,15 +546,17 @@ arrive at the correct group name, \"mail.misc\"."
               ,nnir-imap-default-search-key      ; default
               )))
     (gmane   nnir-run-gmane
-            ((author . "Gmane Author: ")))
+            ((gmane-author . "Gmane Author: ")))
     (swish++ nnir-run-swish++
-             ((group . "Swish++ Group spec: ")))
+             ((swish++-group . "Swish++ Group spec: ")))
     (swish-e nnir-run-swish-e
-             ((group . "Swish-e Group spec: ")))
+             ((swish-e-group . "Swish-e Group spec: ")))
     (namazu  nnir-run-namazu
              ())
+    (notmuch nnir-run-notmuch
+             ())
     (hyrex   nnir-run-hyrex
-            ((group . "Hyrex Group spec: ")))
+            ((hyrex-group . "Hyrex Group spec: ")))
     (find-grep nnir-run-find-grep
               ((grep-options . "Grep options: "))))
   "Alist of supported search engines.
@@ -540,69 +576,111 @@ needs the variables `nnir-namazu-program',
 
 Add an entry here when adding a new search engine.")
 
-(defcustom nnir-method-default-engines
-  '((nnimap . imap)
-    (nntp . gmane))
+(defcustom nnir-method-default-engines  '((nnimap . imap) (nntp . gmane))
   "*Alist of default search engines keyed by server method."
-  :type `(repeat (cons (choice (const nnimap) (const nttp) (const nnspool)
+  :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))))
-  :group 'nnir)
+                                 nnir-engines)))))
 
 ;; Gnus glue.
 
-(defun gnus-group-make-nnir-group (nnir-extra-parms)
-  "Create an nnir group.  Asks for query."
+(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")
-  (setq nnir-current-query nil
-       nnir-current-server nil
-       nnir-current-group-marked nil
-       nnir-artlist nil)
-  (let* ((query (read-string "Query: " nil 'nnir-search-history))
-        (parms (list (cons 'query query)))
-        (srv (if (gnus-server-server-name)
-                 "all" "")))
-    (add-to-list 'parms (cons 'unique-id (message-unique-id)) t)
+  (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:" (prin1-to-string parms)) (list 'nnir srv) t
-     (cons (current-buffer) gnus-current-window-configuration)
-     nil)))
+     (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.
-  (add-hook 'gnus-summary-mode-hook 'nnir-mode)
-  (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 server)))
-  (with-current-buffer nntp-server-buffer
-    (setq nnir-current-query group)
-    (when server (setq nnir-current-server server))
-    (setq nnir-current-group-marked gnus-group-marked)
-    (if (zerop (length nnir-artlist))
-       (nnheader-report 'nnir "Search produced empty results.")
-      ;; Remember data for cache.
-      (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 #