summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/misc/gnus.texi259
-rw-r--r--etc/NEWS19
-rw-r--r--lisp/gnus/gnus-agent.el2
-rw-r--r--lisp/gnus/gnus-cache.el2
-rw-r--r--lisp/gnus/gnus-cloud.el10
-rw-r--r--lisp/gnus/gnus-group.el54
-rw-r--r--lisp/gnus/gnus-msg.el120
-rw-r--r--lisp/gnus/gnus-registry.el13
-rw-r--r--lisp/gnus/gnus-srvr.el5
-rw-r--r--lisp/gnus/gnus-start.el2
-rw-r--r--lisp/gnus/gnus-sum.el295
-rw-r--r--lisp/gnus/gnus.el7
-rw-r--r--lisp/gnus/nndiary.el2
-rw-r--r--lisp/gnus/nnfolder.el2
-rw-r--r--lisp/gnus/nnheader.el344
-rw-r--r--lisp/gnus/nnimap.el10
-rw-r--r--lisp/gnus/nnir.el857
-rw-r--r--lisp/gnus/nnmaildir.el2
-rw-r--r--lisp/gnus/nnml.el2
-rw-r--r--lisp/gnus/nnselect.el864
-rw-r--r--lisp/gnus/nnspool.el2
21 files changed, 1651 insertions, 1222 deletions
diff --git a/doc/misc/gnus.texi b/doc/misc/gnus.texi
index 0bdc2fa297d..593f113ac14 100644
--- a/doc/misc/gnus.texi
+++ b/doc/misc/gnus.texi
@@ -641,7 +641,7 @@ Select Methods
* Getting Mail:: Reading your personal mail with Gnus.
* Browsing the Web:: Getting messages from a plethora of Web sources.
* Other Sources:: Reading directories, files.
-* Combined Groups:: Combining groups into one group.
+* Virtual Groups:: Combining articles from multiple sources.
* Email Based Diary:: Using mails to manage diary events in Gnus.
* Gnus Unplugged:: Reading news and mail offline.
@@ -716,9 +716,10 @@ Document Groups
* Document Server Internals:: How to add your own document types.
-Combined Groups
+Virtual Groups
-* Virtual Groups:: Combining articles from many groups.
+* Selection Groups:: Articles selected from many places.
+* Combined Groups:: Combining multiple groups.
Email Based Diary
@@ -10407,12 +10408,20 @@ article (@code{gnus-summary-refer-references}).
@findex gnus-summary-refer-thread
@kindex A T @r{(Summary)}
Display the full thread where the current article appears
-(@code{gnus-summary-refer-thread}). This command has to fetch all the
-headers in the current group to work, so it usually takes a while. If
-you do it often, you may consider setting @code{gnus-fetch-old-headers}
-to @code{invisible} (@pxref{Filling In Threads}). This won't have any
-visible effects normally, but it'll make this command work a whole lot
-faster. Of course, it'll make group entry somewhat slow.
+(@code{gnus-summary-refer-thread}). By default this command looks for
+articles only in the current group. Some backends (currently only
+'nnimap) know how to find articles in the thread directly. In other
+cases each header in the current group must be fetched and examined,
+so it usually takes a while. If you do it often, you may consider
+setting @code{gnus-fetch-old-headers} to @code{invisible}
+(@pxref{Filling In Threads}). This won't have any visible effects
+normally, but it'll make this command work a whole lot faster. Of
+course, it'll make group entry somewhat slow.
+
+@vindex gnus-refer-thread-use-search
+If @code{gnus-refer-thread-use-search} is non-nil then those backends
+that know how to find threads directly will search not just in the
+current group but all groups on the same server.
@vindex gnus-refer-thread-limit
The @code{gnus-refer-thread-limit} variable says how many old (i.e.,
@@ -10421,6 +10430,15 @@ fetch when doing this command. The default is 200. If @code{t}, all
the available headers will be fetched. This variable can be overridden
by giving the @kbd{A T} command a numerical prefix.
+@vindex gnus-refer-thread-limit-to-thread
+In most cases @code{gnus-refer-thread} adds any articles it finds to
+the current summary buffer. (When @code{gnus-refer-thread-use-search}
+is true and the initial referral starts from a summary buffer for a
+non-virtual group this may not be possible. In this case a new summary
+buffer is created holding a virtual group with the result of the thread
+search). If @code{gnus-refer-thread-limit-to-thread} is non-nil then
+the summary buffer will be limited to articles in the thread.
+
@item M-^ (Summary)
@findex gnus-summary-refer-article
@kindex M-^ @r{(Summary)}
@@ -13262,7 +13280,7 @@ The different methods all have their peculiarities, of course.
* Getting Mail:: Reading your personal mail with Gnus.
* Browsing the Web:: Getting messages from a plethora of Web sources.
* Other Sources:: Reading directories, files.
-* Combined Groups:: Combining groups into one group.
+* Virtual Groups:: Combining articles and groups together.
* Email Based Diary:: Using mails to manage diary events in Gnus.
* Gnus Unplugged:: Reading news and mail offline.
@end menu
@@ -17834,19 +17852,133 @@ methods, but want to only use secondary ones:
@end lisp
-@node Combined Groups
-@section Combined Groups
+@node Virtual Groups
+@section Virtual Groups
-Gnus allows combining a mixture of all the other group types into bigger
-groups.
+Gnus allows combining articles from many sources, and combinations of
+whole groups together into virtual groups.
@menu
-* Virtual Groups:: Combining articles from many groups.
+* Selection Groups:: Combining articles from many groups.
+* Combined Groups:: Combining multiple groups.
@end menu
-@node Virtual Groups
-@subsection Virtual Groups
+@node Selection Groups
+@subsection Select Groups
+@cindex nnselect
+@cindex select groups
+@cindex selecting articles
+
+
+Gnus provides the @dfn{nnselect} method for creating virtual groups
+composed of collections of messages, even when these messages come
+from groups that span multiple servers and backends. For the most part
+these virtual groups behave like any other group: messages may be
+threaded, marked, moved, deleted, copied, etc.; groups may be
+ephemeral or persistent; groups may be created via
+@code{gnus-group-make-group} or browsed as foreign via
+@code{gnus-group-browse-foreign-server}.
+
+The key to using an nnselect group is specifying the messages to
+include. Each nnselect group has a group parameter
+@code{nnselect-specs} which is an alist with two elements: a function
+@code{nnselect-function}; and arguments @code{nnselect-args} to be
+passed to the function, if any.
+
+The function @code{nnselect-function} must return a vector. Each
+element of this vector is in turn a 3-element vector corresponding to
+one message. The 3 elements are: the fully-qualified group name; the
+message number; and a "score" that can be used for additional
+sorting. The values for the score are arbitrary, and are not used
+directly by the nnselect method---they may, for example, all be set to
+100.
+
+Here is an example:
+
+@lisp
+ (nnselect-specs
+ (nnselect-function . identity)
+ (nnselect-args .
+ [["nnimap+work:mail" 595 100]
+ ["nnimap+home:sent" 223 100]
+ ["nntp+news.gmane.org:gmane.emacs.gnus.general" 23666 100]]))
+@end lisp
+
+The function is the identity and the argument is just the list of
+messages to include in the virtual group.
+
+Or we may wish to create a group from the results of a search query:
+
+@lisp
+ (nnselect-specs
+ (nnselect-function . nnir-run-query)
+ (nnselect-args
+ (nnir-query-spec
+ (query . "FLAGGED")
+ (criteria . ""))
+ (nnir-group-spec
+ ("nnimap:home")
+ ("nnimap:work"))))
+@end lisp
+
+This creates a group including all flagged messages from all groups on
+two imap servers, "home" and "work".
+
+And one last example. Here is a function that runs a search query to
+find all message that have been received recently from certain groups:
+
+@lisp
+(defun my-recent-email (args)
+ (let ((query-spec
+ (list
+ (cons 'query
+ (format-time-string "SENTSINCE %d-%b-%Y"
+ (time-subtract (current-time)
+ (days-to-time (car args)))))
+ (cons 'criteria "")))
+ (group-spec (cadr args)))
+ (nnir-run-query (cons 'nnir-specs
+ (list (cons 'nnir-query-spec query-spec)
+ (cons 'nnir-group-spec group-spec))))))
+@end lisp
+
+Then an nnselect-specs
+
+@lisp
+ (nnselect-specs
+ (nnselect-function . my-recent-email)
+ (nnselect-args . (7 (("nnimap:home") ("nnimap:work")))))
+@end lisp
+
+will provide a group composed of all messages on the home and work
+servers received in the last 7 days.
+
+Refreshing the selection of an nnselect group by running the
+@code{nnselect-function} may take a long time to
+complete. Consequently nnselect groups are not refreshed by default
+when @code{gnus-group-get-new-news} is invoked. In those cases where
+running the function is not too time-consuming, a non-nil group
+parameter of @code{nnselect-rescan} will allow automatic refreshing. A
+refresh can always be invoked manually through
+@code{gnus-group-get-new-news-this-group}.
+
+The nnir interface (@pxref{nnir}) includes engines for searching a
+variety of backends. While the details of each search engine vary, the
+result of an nnir search is always a vector of the sort used by the
+nnselect method, and the results of nnir queries are usually viewed
+using an nnselect group. Indeed the standard search function
+@code{gnus-group-read-ephemeral-search-group} just creates an
+ephemeral nnselect group with the appropriate nnir query as the
+@code{nnselect-specs}. nnir originally included both the search
+engines and the glue to connect search results to gnus. Over time this
+glue evolved into the nnselect method. The two had
+a mostly amicable parting so that nnselect could pursue its dream of
+becoming a fully functioning backend, but occasional conflicts may
+still linger.
+
+@node Combined Groups
+@subsection Combined Groups
@cindex nnvirtual
@cindex virtual groups
@cindex merging groups
@@ -21238,14 +21370,26 @@ four days, Gnus will decay the scores four times, for instance.
@chapter Searching
@cindex searching
-FIXME: Add a brief overview of Gnus search capabilities. A brief
-comparison of nnir, nnmairix, contrib/gnus-namazu would be nice
-as well.
-
-This chapter describes tools for searching groups and servers for
-articles matching a query and then retrieving those articles. Gnus
-provides a simpler mechanism for searching through articles in a summary buffer
-to find those matching a pattern. @xref{Searching for Articles}.
+FIXME: A brief comparison of nnir, nnmairix, contrib/gnus-namazu would
+be nice.
+
+Gnus has various ways of finding articles that match certain criteria
+(from a particular author, on a certain subject, etc). The simplest
+method is to enter a group and then either "limit" the summary buffer
+to the desired articles using the limiting commands (@xref{Limiting}),
+or searching through messages in the summary buffer (@xref{Searching
+for Articles}).
+
+Limiting commands and summary buffer searching work on subsets of the
+articles already fetched from the servers, and these commands won’t
+query the server for additional articles. While simple, these methods
+are therefore inadequate if the desired articles span multiple groups,
+or if the group is so large that fetching all articles is
+impractical. Many backends (such as imap, notmuch, namazu, etc.)
+provide their own facilities to search for articles directly on the
+server and gnus can take advantage of these methods. This chapter
+describes tools for searching groups and servers for articles matching
+a query.
@menu
* nnir:: Searching with various engines.
@@ -21275,7 +21419,7 @@ through mail and news repositories. Different backends (like
interface.
The @code{nnimap} search engine should work with no configuration.
-Other engines require a local index that needs to be created and
+Other engines may require a local index that needs to be created and
maintained outside of Gnus.
@@ -21283,23 +21427,19 @@ maintained outside of Gnus.
@subsection Basic Usage
In the group buffer typing @kbd{G G} will search the group on the
-current line by calling @code{gnus-group-make-nnir-group}. This prompts
-for a query string, creates an ephemeral @code{nnir} group containing
+current line by calling @code{gnus-group-make-search-group}. This prompts
+for a query string, creates an ephemeral @code{nnselect} group containing
the articles that match this query, and takes you to a summary buffer
showing these articles. Articles may then be read, moved and deleted
using the usual commands.
-The @code{nnir} group made in this way is an @code{ephemeral} group,
-and some changes are not permanent: aside from reading, moving, and
-deleting, you can't act on the original article. But there is an
-alternative: you can @emph{warp} (i.e., jump) to the original group
-for the article on the current line with @kbd{A W}, aka
-@code{gnus-warp-to-article}. Even better, the function
-@code{gnus-summary-refer-thread}, bound by default in summary buffers
-to @kbd{A T}, will first warp to the original group before it works
-its magic and includes all the articles in the thread. From here you
-can read, move and delete articles, but also copy them, alter article
-marks, whatever. Go nuts.
+The @code{nnselect} group made in this way is an @code{ephemeral}
+group, and will disappear upon exit from the group. However changes
+made in the group are permanently reflected in the real groups from
+which the articles are drawn. It is occasionally convenient to view
+articles found through searching in their original group. You can
+@emph{warp} (i.e., jump) to the original group for the article on the
+current line with @kbd{A W}, aka @code{gnus-warp-to-article}.
You say you want to search more than just the group on the current line?
No problem: just process-mark the groups you want to search. You want
@@ -21307,14 +21447,14 @@ even more? Calling for an nnir search with the cursor on a topic heading
will search all the groups under that heading.
Still not enough? OK, in the server buffer
-@code{gnus-group-make-nnir-group} (now bound to @kbd{G}) will search all
-groups from the server on the current line. Too much? Want to ignore
-certain groups when searching, like spam groups? Just customize
-@code{nnir-ignored-newsgroups}.
+@code{gnus-group-make-search-group} (now bound to @kbd{G}) will search
+all groups from the server on the current line. Too much? Want to
+ignore certain groups when searching, like spam groups? Just
+customize @code{nnir-ignored-newsgroups}.
One more thing: individual search engines may have special search
features. You can access these special features by giving a prefix-arg
-to @code{gnus-group-make-nnir-group}. If you are searching multiple
+to @code{gnus-group-make-search-group}. If you are searching multiple
groups with different search engines you will be prompted for the
special search features for each engine separately.
@@ -21371,8 +21511,7 @@ variable is set to use the @code{imap} engine for all servers using the
your servers with an @code{nnimap} backend you could change this to
@lisp
-'((nnimap . namazu)
- (nntp . gmane))
+'((nnimap . namazu))
@end lisp
@node The imap Engine
@@ -21575,7 +21714,7 @@ This engine is obsolete.
@item nnir-method-default-engines
Alist of pairs of server backends and search engines. The default
-associations are
+association is
@example
(nnimap . imap)
@end example
@@ -21584,32 +21723,6 @@ associations are
A regexp to match newsgroups in the active file that should be skipped
when searching all groups on a server.
-@item nnir-summary-line-format
-The format specification to be used for lines in an nnir summary buffer.
-All the items from @code{gnus-summary-line-format} are available, along with
-three items unique to nnir summary buffers:
-
-@example
-%Z Search retrieval score value (integer)
-%G Article original full group name (string)
-%g Article original short group name (string)
-@end example
-
-If @code{nil} (the default) this will use @code{gnus-summary-line-format}.
-
-@item nnir-retrieve-headers-override-function
-If non-@code{nil}, a function that retrieves article headers rather than using
-the gnus built-in function. This function takes an article list and
-group as arguments and populates the @code{nntp-server-buffer} with the
-retrieved headers. It should then return either 'nov or 'headers
-indicating the retrieved header format. Failure to retrieve headers
-should return @code{nil}.
-
-If this variable is @code{nil}, or if the provided function returns
-@code{nil} for a search result, @code{gnus-retrieve-headers} will be
-called instead."
-
-
@end table
diff --git a/etc/NEWS b/etc/NEWS
index e0ea8f53cc8..da3928d6e49 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -317,7 +317,24 @@ tags to be considered as well.
** Gnus
+++
-*** New user option 'gnus-dbus-close-on-sleep'.
+*** New backend 'nnselect'
+The newly added nnselect backend allows creating groups from an
+arbitrary list of articles that may come from multiple groups and
+servers. These groups generally behave like any other group: they may
+be ephemeral or persistent, and allow article marking, moving,
+deletion, etc. Nnselect groups may be created like any other group,
+but there is also a convenience function for the common case of
+obtaining the list of articles as a result of a search:
+'gnus-group-make-search-group' (G g) that will prompt for an nnir
+search query and create a dedicated group for that search. As part of
+this addition, the variable 'nnir-summary-line-format' has been
+removed; it's functionality is now available directly in the
+'gnus-summary-line-format' 'G' and 'g' specs. The variable
+'gnus-refer-thread-use-nnir' has been renamed
+'gnus-refer-thread-use-search'.
+
++++
+*** New user option 'gnus-dbus-close-on-sleep'
On systems with D-Bus support, it is now possible to register a signal
to close all Gnus servers before the system sleeps.
diff --git a/lisp/gnus/gnus-agent.el b/lisp/gnus/gnus-agent.el
index 88873f47bd5..03e447e072a 100644
--- a/lisp/gnus/gnus-agent.el
+++ b/lisp/gnus/gnus-agent.el
@@ -3934,7 +3934,7 @@ If REREAD is not nil, downloaded articles are marked as unread."
(mm-with-unibyte-buffer
(nnheader-insert-file-contents file)
(nnheader-remove-body)
- (setq header (nnheader-parse-naked-head)))
+ (setq header (nnheader-parse-head t)))
(setf (mail-header-number header) (car downloaded))
(if nov-arts
(let ((key (concat "^" (int-to-string (car nov-arts))
diff --git a/lisp/gnus/gnus-cache.el b/lisp/gnus/gnus-cache.el
index 02a8ea723d3..7ca3bf1ce1c 100644
--- a/lisp/gnus/gnus-cache.el
+++ b/lisp/gnus/gnus-cache.el
@@ -186,7 +186,7 @@ it's not cached."
(gnus-cache-update-file-total-fetched-for group file))
(setq lines-chars (nnheader-get-lines-and-char))
(nnheader-remove-body)
- (setq headers (nnheader-parse-naked-head))
+ (setq headers (nnheader-parse-head t))
(setf (mail-header-number headers) number)
(setf (mail-header-lines headers) (car lines-chars))
(setf (mail-header-chars headers) (cadr lines-chars))
diff --git a/lisp/gnus/gnus-cloud.el b/lisp/gnus/gnus-cloud.el
index 673a4d22988..e40b2eb418d 100644
--- a/lisp/gnus/gnus-cloud.el
+++ b/lisp/gnus/gnus-cloud.el
@@ -391,6 +391,8 @@ When FULL is t, upload everything, not just a difference from the last full."
(gnus-group-refresh-group group))
(gnus-error 2 "Failed to upload Gnus Cloud data to %s" group)))))
+(defvar gnus-alter-header-function)
+
(defun gnus-cloud-add-timestamps (elems)
(dolist (elem elems)
(let* ((file-name (plist-get elem :file-name))
@@ -409,9 +411,11 @@ When FULL is t, upload everything, not just a difference from the last full."
(when (gnus-retrieve-headers (gnus-uncompress-range active) group)
(with-current-buffer nntp-server-buffer
(goto-char (point-min))
- (while (and (not (eobp))
- (setq head (nnheader-parse-head)))
- (push head headers))))
+ (while (setq head (nnheader-parse-head))
+ (when gnus-alter-header-function
+ (funcall gnus-alter-header-function head))
+ (push head headers))
+ ))
(sort (nreverse headers)
(lambda (h1 h2)
(> (gnus-cloud-chunk-sequence (mail-header-subject h1))
diff --git a/lisp/gnus/gnus-group.el b/lisp/gnus/gnus-group.el
index 2cbbe624602..ad6e0e30bca 100644
--- a/lisp/gnus/gnus-group.el
+++ b/lisp/gnus/gnus-group.el
@@ -49,8 +49,6 @@
(autoload 'gnus-agent-total-fetched-for "gnus-agent")
(autoload 'gnus-cache-total-fetched-for "gnus-cache")
-(autoload 'gnus-group-make-nnir-group "nnir")
-
(autoload 'gnus-cloud-upload-all-data "gnus-cloud")
(autoload 'gnus-cloud-download-all-data "gnus-cloud")
@@ -663,7 +661,8 @@ simple manner."
"D" gnus-group-enter-directory
"f" gnus-group-make-doc-group
"w" gnus-group-make-web-group
- "G" gnus-group-make-nnir-group
+ "G" gnus-group-read-ephemeral-search-group
+ "g" gnus-group-make-search-group
"M" gnus-group-read-ephemeral-group
"r" gnus-group-rename-group
"R" gnus-group-make-rss-group
@@ -909,7 +908,8 @@ simple manner."
["Add the help group" gnus-group-make-help-group t]
["Make a doc group..." gnus-group-make-doc-group t]
["Make a web group..." gnus-group-make-web-group t]
- ["Make a search group..." gnus-group-make-nnir-group t]
+ ["Read a search group..." gnus-group-read-ephemeral-search-group t]
+ ["Make a search group..." gnus-group-make-search-group t]
["Make a virtual group..." gnus-group-make-empty-virtual t]
["Add a group to a virtual..." gnus-group-add-to-virtual t]
["Make an ephemeral group..." gnus-group-read-ephemeral-group t]
@@ -3166,6 +3166,52 @@ mail messages or news articles in files that have numeric names."
(gnus-group-real-name group)
(list 'nndir (gnus-group-real-name group) (list 'nndir-directory dir)))))
+
+(autoload 'nnir-make-specs "nnir")
+(autoload 'gnus-group-topic-name "gnus-topic")
+
+;; Temporary to make group creation easier
+(defun gnus-group-make-search-group (nnir-extra-parms &optional specs)
+ (interactive "P")
+ (let ((name (gnus-read-group "Group name: ")))
+ (with-current-buffer gnus-group-buffer
+ (gnus-group-make-group
+ name
+ (list 'nnselect "nnselect")
+ nil
+ (list
+ (cons 'nnselect-specs
+ (list
+ (cons 'nnselect-function 'nnir-run-query)
+ (cons 'nnselect-args
+ (nnir-make-specs nnir-extra-parms specs)))))))))
+
+(defun gnus-group-read-ephemeral-search-group (nnir-extra-parms &optional specs)
+ "Create an nnselect group based on a search. 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")
+ (gnus-group-read-ephemeral-group
+ (concat "nnselect-" (message-unique-id))
+ (list 'nnselect "nnselect")
+ nil
+ (cons (current-buffer) gnus-current-window-configuration)
+ ; nil
+ nil nil
+ (list
+ (cons 'nnselect-specs
+ (list
+ (cons 'nnselect-function 'nnir-run-query)
+ (cons 'nnselect-args
+ (nnir-make-specs nnir-extra-parms specs))))
+ (cons 'nnselect-artlist nil))))
+
(defun gnus-group-add-to-virtual (n vgroup)
"Add the current group to a virtual group."
(interactive
diff --git a/lisp/gnus/gnus-msg.el b/lisp/gnus/gnus-msg.el
index e770abc2cdf..7bc7fb5be41 100644
--- a/lisp/gnus/gnus-msg.el
+++ b/lisp/gnus/gnus-msg.el
@@ -393,10 +393,9 @@ only affect the Gcc copy, but not the original message."
(gnus-inews-make-draft-meta-information
,gnus-newsgroup-name ',articles)))
-(autoload 'nnir-article-number "nnir" nil nil 'macro)
-(autoload 'nnir-article-group "nnir" nil nil 'macro)
-(autoload 'gnus-nnir-group-p "nnir")
-
+(autoload 'nnselect-article-number "nnselect" nil nil 'macro)
+(autoload 'nnselect-article-group "nnselect" nil nil 'macro)
+(autoload 'gnus-nnselect-group-p "nnselect")
(defvar gnus-article-reply nil)
(defmacro gnus-setup-message (config &rest forms)
@@ -404,22 +403,24 @@ only affect the Gcc copy, but not the original message."
(winconf-name (make-symbol "gnus-setup-message-winconf-name"))
(buffer (make-symbol "gnus-setup-message-buffer"))
(article (make-symbol "gnus-setup-message-article"))
+ (oarticle (make-symbol "gnus-setup-message-oarticle"))
(yanked (make-symbol "gnus-setup-yanked-articles"))
(group (make-symbol "gnus-setup-message-group")))
`(let ((,winconf (current-window-configuration))
(,winconf-name gnus-current-window-configuration)
(,buffer (buffer-name (current-buffer)))
- (,article (if (and (gnus-nnir-group-p gnus-newsgroup-name)
- gnus-article-reply)
- (nnir-article-number (or (car-safe gnus-article-reply)
- gnus-article-reply))
- gnus-article-reply))
+ (,article (when gnus-article-reply
+ (or (nnselect-article-number
+ (or (car-safe gnus-article-reply)
+ gnus-article-reply))
+ gnus-article-reply)))
+ (,oarticle gnus-article-reply)
(,yanked gnus-article-yanked-articles)
- (,group (if (and (gnus-nnir-group-p gnus-newsgroup-name)
- gnus-article-reply)
- (nnir-article-group (or (car-safe gnus-article-reply)
- gnus-article-reply))
- gnus-newsgroup-name))
+ (,group (when gnus-article-reply
+ (or (nnselect-article-group
+ (or (car-safe gnus-article-reply)
+ gnus-article-reply))
+ gnus-newsgroup-name)))
(message-header-setup-hook
(copy-sequence message-header-setup-hook))
(mbl mml-buffer-list)
@@ -460,24 +461,23 @@ only affect the Gcc copy, but not the original message."
(unwind-protect
(progn
,@forms)
- (gnus-inews-add-send-actions ,winconf ,buffer ,article ,config
+ (gnus-inews-add-send-actions ,winconf ,buffer ,oarticle ,config
,yanked ,winconf-name)
(setq gnus-message-buffer (current-buffer))
(set (make-local-variable 'gnus-message-group-art)
(cons ,group ,article))
- (set (make-local-variable 'gnus-newsgroup-name) ,group)
- ;; Enable highlighting of different citation levels
- (when gnus-message-highlight-citation
- (gnus-message-citation-mode 1))
- (gnus-run-hooks 'gnus-message-setup-hook)
- (if (eq major-mode 'message-mode)
- (let ((mbl1 mml-buffer-list))
- (setq mml-buffer-list mbl) ;; Global value
- (set (make-local-variable 'mml-buffer-list) mbl1);; Local value
- (add-hook 'change-major-mode-hook 'mml-destroy-buffers nil t)
- (add-hook 'kill-buffer-hook 'mml-destroy-buffers t t))
- (mml-destroy-buffers)
- (setq mml-buffer-list mbl)))
+ ;; Enable highlighting of different citation levels
+ (when gnus-message-highlight-citation
+ (gnus-message-citation-mode 1))
+ (gnus-run-hooks 'gnus-message-setup-hook)
+ (if (eq major-mode 'message-mode)
+ (let ((mbl1 mml-buffer-list))
+ (setq mml-buffer-list mbl) ;; Global value
+ (set (make-local-variable 'mml-buffer-list) mbl1);; Local value
+ (add-hook 'change-major-mode-hook 'mml-destroy-buffers nil t)
+ (add-hook 'kill-buffer-hook 'mml-destroy-buffers t t))
+ (mml-destroy-buffers)
+ (setq mml-buffer-list mbl)))
(message-hide-headers)
(gnus-add-buffer)
(gnus-configure-windows ,config t)
@@ -521,12 +521,10 @@ instead."
mail-buf)
(unwind-protect
(progn
- (setq gnus-newsgroup-name "")
+ (let ((gnus-newsgroup-name ""))
(gnus-setup-message 'message
(message-mail to subject other-headers continue
- nil yank-action send-actions return-action)))
- (with-current-buffer buf
- (setq gnus-newsgroup-name group-name)))
+ nil yank-action send-actions return-action)))))
(when switch-action
(setq mail-buf (current-buffer))
(switch-to-buffer buf)
@@ -617,18 +615,15 @@ If ARG is 1, prompt for a group name to find the posting style."
(buffer (current-buffer)))
(unwind-protect
(progn
- (setq gnus-newsgroup-name
- (if arg
- (if (= 1 (prefix-numeric-value arg))
- (gnus-group-completing-read
- "Use posting style of group"
- nil (gnus-read-active-file-p))
- (gnus-group-group-name))
- ""))
- ;; #### see comment in gnus-setup-message -- drv
- (gnus-setup-message 'message (message-mail)))
- (with-current-buffer buffer
- (setq gnus-newsgroup-name group)))))
+ (let ((gnus-newsgroup-name
+ (if arg
+ (if (= 1 (prefix-numeric-value arg))
+ (gnus-group-completing-read
+ "Use posting style of group"
+ nil (gnus-read-active-file-p))
+ (gnus-group-group-name))
+ "")))
+ (gnus-setup-message 'message (message-mail)))))))
(defun gnus-group-news (&optional arg)
"Start composing a news.
@@ -647,19 +642,16 @@ network. The corresponding back end must have a `request-post' method."
(buffer (current-buffer)))
(unwind-protect
(progn
- (setq gnus-newsgroup-name
+ (let ((gnus-newsgroup-name
(if arg
(if (= 1 (prefix-numeric-value arg))
(gnus-group-completing-read "Use group"
nil
(gnus-read-active-file-p))
(gnus-group-group-name))
- ""))
- ;; #### see comment in gnus-setup-message -- drv
+ "")))
(gnus-setup-message 'message
- (message-news (gnus-group-real-name gnus-newsgroup-name))))
- (with-current-buffer buffer
- (setq gnus-newsgroup-name group)))))
+ (message-news (gnus-group-real-name gnus-newsgroup-name))))))))
(defun gnus-group-post-news (&optional arg)
"Start composing a message (a news by default).
@@ -694,18 +686,15 @@ posting style."
(buffer (current-buffer)))
(unwind-protect
(progn
- (setq gnus-newsgroup-name
+ (let ((gnus-newsgroup-name
(if arg
(if (= 1 (prefix-numeric-value arg))
(gnus-group-completing-read "Use group"
nil
(gnus-read-active-file-p))
"")
- gnus-newsgroup-name))
- ;; #### see comment in gnus-setup-message -- drv
- (gnus-setup-message 'message (message-mail)))
- (with-current-buffer buffer
- (setq gnus-newsgroup-name group)))))
+ gnus-newsgroup-name)))
+ (gnus-setup-message 'message (message-mail)))))))
(defun gnus-summary-news-other-window (&optional arg)
"Start composing a news in another window.
@@ -724,24 +713,21 @@ network. The corresponding back end must have a `request-post' method."
(buffer (current-buffer)))
(unwind-protect
(progn
- (setq gnus-newsgroup-name
+ (let ((gnus-newsgroup-name
(if arg
(if (= 1 (prefix-numeric-value arg))
(gnus-group-completing-read "Use group"
nil
(gnus-read-active-file-p))
"")
- gnus-newsgroup-name))
- ;; #### see comment in gnus-setup-message -- drv
+ gnus-newsgroup-name)))
(gnus-setup-message 'message
(progn
(message-news (gnus-group-real-name gnus-newsgroup-name))
(set (make-local-variable 'gnus-discouraged-post-methods)
(remove
(car (gnus-find-method-for-group gnus-newsgroup-name))
- gnus-discouraged-post-methods)))))
- (with-current-buffer buffer
- (setq gnus-newsgroup-name group)))))
+ gnus-discouraged-post-methods)))))))))
(defun gnus-summary-post-news (&optional arg)
"Start composing a message. Post to the current group by default.
@@ -823,7 +809,7 @@ active, the entire article will be yanked."
(with-current-buffer gnus-article-copy
(save-restriction
(nnheader-narrow-to-headers)
- (nnheader-parse-naked-head)))))
+ (nnheader-parse-head t)))))
(message-yank-original)
(message-exchange-point-and-mark)
(setq beg (or beg (mark t))))
@@ -1993,10 +1979,10 @@ process-mark several articles, they will all be attached."
(gnus-summary-iterate n
(gnus-summary-select-article)
(with-current-buffer destination
- ;; Attach at the end of the buffer.
- (save-excursion
- (goto-char (point-max))
- (message-forward-make-body-mime gnus-original-article-buffer))))
+ ;; Attach at the end of the buffer.
+ (save-excursion
+ (goto-char (point-max))
+ (message-forward-make-body-mime gnus-original-article-buffer))))
(gnus-configure-windows 'message t)))
(provide 'gnus-msg)
diff --git a/lisp/gnus/gnus-registry.el b/lisp/gnus/gnus-registry.el
index 1ac1d05e033..65bcd0e8a36 100644
--- a/lisp/gnus/gnus-registry.el
+++ b/lisp/gnus/gnus-registry.el
@@ -427,6 +427,8 @@ This is not required after changing `gnus-registry-cache-file'."
(gnus-message 4 "Removed %d ignored entries from the Gnus registry"
(- old-size (registry-size db)))))
+(declare-function gnus-nnselect-group-p "nnselect" (group))
+(declare-function nnselect-article-group "nnselect" (article))
;; article move/copy/spool/delete actions
(defun gnus-registry-action (action data-header from &optional to method)
(let* ((id (mail-header-id data-header))
@@ -437,7 +439,10 @@ This is not required after changing `gnus-registry-cache-file'."
(or (cdr-safe (assq 'To extra)) "")))
(sender (nth 0 (gnus-registry-extract-addresses
(mail-header-from data-header))))
- (from (gnus-group-guess-full-name-from-command-method from))
+ (from (gnus-group-guess-full-name-from-command-method
+ (if (gnus-nnselect-group-p from)
+ (nnselect-article-group (mail-header-number data-header))
+ from)))
(to (if to (gnus-group-guess-full-name-from-command-method to) nil)))
(gnus-message 7 "Gnus registry: article %s %s from %s to %s"
id (if method "respooling" "going") from to)
@@ -788,7 +793,7 @@ Consults `gnus-registry-unfollowed-groups' and
Consults `gnus-registry-ignored-groups' and
`nnmail-split-fancy-with-parent-ignore-groups'."
(and group
- (or (gnus-grep-in-list
+ (or (gnus-virtual-group-p group) (gnus-grep-in-list
group
(delq nil (mapcar (lambda (g)
(cond
@@ -1218,7 +1223,7 @@ is `ask', ask the user; or if `gnus-registry-install' is non-nil, enable it."
(gnus-registry-initialize)))
gnus-registry-enabled)
-;; largely based on nnir-warp-to-article
+;; largely based on nnselect-warp-to-article
(defun gnus-try-warping-via-registry ()
"Try to warp via the registry.
This will be done via the current article's source group based on
@@ -1242,7 +1247,7 @@ data stored in the registry."
(gnus-ephemeral-group-p group) ;; any ephemeral group
(memq (car (gnus-find-method-for-group group))
;; Specific methods; this list may need to expand.
- '(nnir)))
+ '(nnselect)))
;; remember that we've seen this group already
(push group seen-groups)
diff --git a/lisp/gnus/gnus-srvr.el b/lisp/gnus/gnus-srvr.el
index 095e05408d6..8cb80b2f520 100644
--- a/lisp/gnus/gnus-srvr.el
+++ b/lisp/gnus/gnus-srvr.el
@@ -34,7 +34,8 @@
(require 'gnus-range)
(require 'gnus-cloud)
-(autoload 'gnus-group-make-nnir-group "nnir")
+(autoload 'gnus-group-read-ephemeral-search-group "nnselect")
+;;(autoload 'gnus-group-make-permanent-search-group "nnselect")
(defcustom gnus-server-exit-hook nil
"Hook run when exiting the server buffer."
@@ -176,7 +177,7 @@ If nil, a faster, but more primitive, buffer is used instead."
"g" gnus-server-regenerate-server
- "G" gnus-group-make-nnir-group
+ "G" gnus-group-read-ephemeral-search-group
"z" gnus-server-compact-server
diff --git a/lisp/gnus/gnus-start.el b/lisp/gnus/gnus-start.el
index fe600f107ce..e4f05de5f8e 100644
--- a/lisp/gnus/gnus-start.el
+++ b/lisp/gnus/gnus-start.el
@@ -1802,7 +1802,7 @@ backend check whether the group actually exists."
;; by one.
(t
(dolist (info infos)
- (gnus-activate-group (gnus-info-group info) nil nil method t))))))
+ (gnus-activate-group (gnus-info-group info) t nil method t))))))
(defun gnus-make-hashtable-from-newsrc-alist ()
"Create a hash table from `gnus-newsrc-alist'.
diff --git a/lisp/gnus/gnus-sum.el b/lisp/gnus/gnus-sum.el
index c53f81fe026..8f37fc88284 100644
--- a/lisp/gnus/gnus-sum.el
+++ b/lisp/gnus/gnus-sum.el
@@ -85,8 +85,8 @@
(autoload 'gnus-article-outlook-unwrap-lines "deuglify" nil t)
(autoload 'gnus-article-outlook-repair-attribution "deuglify" nil t)
(autoload 'gnus-article-outlook-rearrange-citation "deuglify" nil t)
-(autoload 'nnir-article-rsv "nnir" nil nil 'macro)
-(autoload 'nnir-article-group "nnir" nil nil 'macro)
+(autoload 'nnselect-article-rsv "nnselect" nil nil)
+(autoload 'nnselect-article-group "nnselect" nil nil)
(defcustom gnus-kill-summary-on-exit t
"If non-nil, kill the summary buffer when you exit from it.
@@ -144,9 +144,9 @@ If t, fetch all the available old headers."
:type '(choice number
(sexp :menu-tag "other" t)))
-(defcustom gnus-refer-thread-use-nnir nil
- "Use nnir to search an entire server when referring threads.
-A nil value will only search for thread-related articles in the
+(defcustom gnus-refer-thread-use-search nil
+ "Search an entire server when referring threads. A
+nil value will only search for thread-related articles in the
current group."
:version "24.1"
:group 'gnus-thread
@@ -884,6 +884,7 @@ controls how articles are sorted."
(function-item gnus-article-sort-by-subject)
(function-item gnus-article-sort-by-date)
(function-item gnus-article-sort-by-score)
+ (function-item gnus-article-sort-by-rsv)
(function-item gnus-article-sort-by-random)
(function :tag "other"))
(boolean :tag "Reverse order"))))
@@ -927,6 +928,7 @@ subthreads, customize `gnus-subthread-sort-functions'."
(function-item gnus-thread-sort-by-subject)
(function-item gnus-thread-sort-by-date)
(function-item gnus-thread-sort-by-score)
+ (function-item gnus-thread-sort-by-rsv)
(function-item gnus-thread-sort-by-most-recent-number)
(function-item gnus-thread-sort-by-most-recent-date)
(function-item gnus-thread-sort-by-random)
@@ -1433,16 +1435,13 @@ the normal Gnus MIME machinery."
(?c (or (mail-header-chars gnus-tmp-header) 0) ?d)
(?k (gnus-summary-line-message-size gnus-tmp-header) ?s)
(?L gnus-tmp-lines ?s)
- (?Z (or (nnir-article-rsv (mail-header-number gnus-tmp-header))
- 0)
- ?d)
- (?G (or (nnir-article-group (mail-header-number gnus-tmp-header))
- "")
- ?s)
+ (?Z (or (nnselect-article-rsv (mail-header-number gnus-tmp-header))
+ 0) ?d)
+ (?G (or (nnselect-article-group (mail-header-number gnus-tmp-header))
+ "") ?s)
(?g (or (gnus-group-short-name
- (nnir-article-group (mail-header-number gnus-tmp-header)))
- "")
- ?s)
+ (nnselect-article-group (mail-header-number gnus-tmp-header)))
+ "") ?s)
(?O gnus-tmp-downloaded ?c)
(?I gnus-tmp-indentation ?s)
(?T (if (= gnus-tmp-level 0) "" (make-string (frame-width) ? )) ?s)
@@ -1619,6 +1618,8 @@ This list will always be a subset of gnus-newsgroup-undownloaded.")
(defvar gnus-newsgroup-sparse nil)
+(defvar gnus-newsgroup-selection nil)
+
(defvar gnus-current-article nil)
(defvar gnus-article-current nil)
(defvar gnus-current-headers nil)
@@ -1653,6 +1654,8 @@ This list will always be a subset of gnus-newsgroup-undownloaded.")
gnus-newsgroup-undownloaded
gnus-newsgroup-unsendable
+ gnus-newsgroup-selection
+
gnus-newsgroup-begin gnus-newsgroup-end
gnus-newsgroup-last-rmail gnus-newsgroup-last-mail
gnus-newsgroup-last-folder gnus-newsgroup-last-file
@@ -4532,48 +4535,14 @@ Returns HEADER if it was entered in the DEPENDENCIES. Returns nil otherwise."
;; This function has to be called with point after the article number
;; on the beginning of the line.
(defsubst gnus-nov-parse-line (number dependencies &optional force-new)
- (let ((eol (point-at-eol))
- header references in-reply-to)
-
+ (let (header)
;; overview: [num subject from date id refs chars lines misc]
(unwind-protect
- (let (x)
- (narrow-to-region (point) eol)
- (unless (eobp)
- (forward-char))
-
- (setq header
- (make-full-mail-header
- number ; number
- (condition-case () ; subject
- (gnus-remove-odd-characters
- (funcall gnus-decode-encoded-word-function
- (setq x (nnheader-nov-field))))
- (error x))
- (condition-case () ; from
- (gnus-remove-odd-characters
- (funcall gnus-decode-encoded-address-function
- (setq x (nnheader-nov-field))))
- (error x))
- (nnheader-nov-field) ; date
- (nnheader-nov-read-message-id number) ; id
- (setq references (nnheader-nov-field)) ; refs
- (nnheader-nov-read-integer) ; chars
- (nnheader-nov-read-integer) ; lines
- (unless (eobp)
- (if (looking-at "Xref: ")
- (goto-char (match-end 0)))
- (nnheader-nov-field)) ; Xref
- (nnheader-nov-parse-extra)))) ; extra
-
+ (narrow-to-region (point) (point-at-eol))
+ (unless (eobp)
+ (forward-char))
+ (setq header (nnheader-parse-nov number))
(widen))
-
- (when (and (string= references "")
- (setq in-reply-to (mail-header-extra header))
- (setq in-reply-to (cdr (assq 'In-Reply-To in-reply-to))))
- (setf (mail-header-references header)
- (gnus-extract-message-id-from-in-reply-to in-reply-to)))
-
(when gnus-alter-header-function
(funcall gnus-alter-header-function header))
(gnus-dependencies-add-header header dependencies force-new)))
@@ -5104,6 +5073,17 @@ using some other form will lead to serious barfage."
(gnus-article-sort-by-date
(gnus-thread-header h1) (gnus-thread-header h2)))
+(defsubst gnus-article-sort-by-rsv (h1 h2)
+ "Sort articles by rsv."
+ (when gnus-newsgroup-selection
+ (< (nnselect-article-rsv (mail-header-number h1))
+ (nnselect-article-rsv (mail-header-number h2)))))
+
+(defun gnus-thread-sort-by-rsv (h1 h2)
+ "Sort threads by root article rsv."
+ (gnus-article-sort-by-rsv
+ (gnus-thread-header h1) (gnus-thread-header h2)))
+
(defsubst gnus-article-sort-by-score (h1 h2)
"Sort articles by root article score.
Unscored articles will be counted as having a score of zero."
@@ -5634,22 +5614,32 @@ or a straight list of headers."
"Fetch headers of ARTICLES."
(gnus-message 7 "Fetching headers for %s..." gnus-newsgroup-name)
(prog1
- (if (eq 'nov
- (setq gnus-headers-retrieved-by
- (gnus-retrieve-headers
- articles gnus-newsgroup-name
- (or limit
- ;; We might want to fetch old headers, but
- ;; not if there is only 1 article.
- (and (or (and
- (not (eq gnus-fetch-old-headers 'some))
- (not (numberp gnus-fetch-old-headers)))
- (> (length articles) 1))
- gnus-fetch-old-headers)))))
- (gnus-get-newsgroup-headers-xover
- articles force-new dependencies gnus-newsgroup-name t)
- (gnus-get-newsgroup-headers dependencies force-new))
- (gnus-message 7 "Fetching headers for %s...done" gnus-newsgroup-name)))
+ (pcase (setq gnus-headers-retrieved-by
+ (gnus-retrieve-headers
+ articles gnus-newsgroup-name
+ (or limit
+ ;; We might want to fetch old headers, but
+ ;; not if there is only 1 article.
+ (and (or (and
+ (not (eq gnus-fetch-old-headers 'some))
+ (not (numberp gnus-fetch-old-headers)))
+ (> (length articles) 1))
+ gnus-fetch-old-headers))))
+ ('nov
+ (gnus-get-newsgroup-headers-xover
+ articles force-new dependencies gnus-newsgroup-name t))
+ ('headers
+ (gnus-get-newsgroup-headers dependencies force-new))
+ ((pred listp)
+ (let ((dependencies
+ (or dependencies
+ (with-current-buffer gnus-summary-buffer
+ gnus-newsgroup-dependencies))))
+ (delq nil (mapcar #'(lambda (header)
+ (gnus-dependencies-add-header
+ header dependencies force-new))
+ gnus-headers-retrieved-by)))))
+ (gnus-message 7 "Fetching headers for %s...done" gnus-newsgroup-name)))
(defun gnus-select-newsgroup (group &optional read-all select-articles)
"Select newsgroup GROUP.
@@ -6405,12 +6395,11 @@ The resulting hash table is returned, or nil if no Xrefs were found."
(gnus-group-update-group group t))))))
(defun gnus-get-newsgroup-headers (&optional dependencies force-new)
- (let ((cur nntp-server-buffer)
- (dependencies
+ (let ((dependencies
(or dependencies
(with-current-buffer gnus-summary-buffer
gnus-newsgroup-dependencies)))
- headers id end ref number
+ headers
(mail-parse-charset gnus-newsgroup-charset)
(mail-parse-ignored-charsets
(save-current-buffer (condition-case nil
@@ -6418,146 +6407,15 @@ The resulting hash table is returned, or nil if no Xrefs were found."
(error))
gnus-newsgroup-ignored-charsets)))
(with-current-buffer nntp-server-buffer
- ;; Translate all TAB characters into SPACE characters.
- (subst-char-in-region (point-min) (point-max) ?\t ? t)
- (subst-char-in-region (point-min) (point-max) ?\r ? t)
- (ietf-drums-unfold-fws)
(gnus-run-hooks 'gnus-parse-headers-hook)
- (let ((case-fold-search t)
- in-reply-to header p lines chars)
+ (let ((nnmail-extra-headers gnus-extra-headers)
+ header)
(goto-char (point-min))
- ;; Search to the beginning of the next header. Error messages
- ;; do not begin with 2 or 3.
- (while (re-search-forward "^[23][0-9]+ " nil t)
- (setq id nil
- ref nil)
- ;; This implementation of this function, with nine
- ;; search-forwards instead of the one re-search-forward and
- ;; a case (which basically was the old function) is actually
- ;; about twice as fast, even though it looks messier. You
- ;; can't have everything, I guess. Speed and elegance
- ;; doesn't always go hand in hand.
- (setq
- header
- (make-full-mail-header
- ;; Number.
- (prog1
- (setq number (read cur))
- (end-of-line)
- (setq p (point))
- (narrow-to-region (point)
- (or (and (search-forward "\n.\n" nil t)
- (- (point) 2))
- (point))))
- ;; Subject.
- (progn
- (goto-char p)
- (if (search-forward "\nsubject:" nil t)
- (funcall gnus-decode-encoded-word-function
- (nnheader-header-value))
- "(none)"))
- ;; From.
- (progn
- (goto-char p)
- (if (search-forward "\nfrom:" nil t)
- (funcall gnus-decode-encoded-address-function
- (nnheader-header-value))
- "(nobody)"))
- ;; Date.
- (progn
- (goto-char p)
- (if (search-forward "\ndate:" nil t)
- (nnheader-header-value) ""))
- ;; Message-ID.
- (progn
- (goto-char p)
- (setq id (if (re-search-forward
- "^message-id: *\\(<[^\n\t> ]+>\\)" nil t)
- ;; We do it this way to make sure the Message-ID
- ;; is (somewhat) syntactically valid.
- (buffer-substring (match-beginning 1)
- (match-end 1))
- ;; If there was no message-id, we just fake one
- ;; to make subsequent routines simpler.
- (nnheader-generate-fake-message-id number))))
- ;; References.
- (progn
- (goto-char p)
- (if (search-forward "\nreferences:" nil t)
- (progn
- (setq end (point))
- (prog1
- (nnheader-header-value)
- (setq ref
- (buffer-substring
- (progn
- (end-of-line)
- (search-backward ">" end t)
- (1+ (point)))
- (progn
- (search-backward "<" end t)
- (point))))))
- ;; Get the references from the in-reply-to header if there
- ;; were no references and the in-reply-to header looks
- ;; promising.
- (if (and (search-forward "\nin-reply-to:" nil t)
- (setq in-reply-to (nnheader-header-value))
- (string-match "<[^>]+>" in-reply-to))
- (let (ref2)
- (setq ref (substring in-reply-to (match-beginning 0)
- (match-end 0)))
- (while (string-match "<[^>]+>" in-reply-to (match-end 0))
- (setq ref2 (substring in-reply-to (match-beginning 0)
- (match-end 0)))
- (when (> (length ref2) (length ref))
- (setq ref ref2)))
- ref)
- (setq ref nil))))
- ;; Chars.
- (progn
- (goto-char p)
- (if (search-forward "\nchars: " nil t)
- (if (numberp (setq chars (ignore-errors (read cur))))
- chars -1)
- -1))
- ;; Lines.
- (progn
- (goto-char p)
- (if (search-forward "\nlines: " nil t)
- (if (numberp (setq lines (ignore-errors (read cur))))
- lines -1)
- -1))
- ;; Xref.
- (progn
- (goto-char p)
- (and (search-forward "\nxref:" nil t)
- (nnheader-header-value)))
- ;; Extra.
- (when gnus-extra-headers
- (let ((extra gnus-extra-headers)
- out)
- (while extra
- (goto-char p)
- (when (search-forward
- (concat "\n" (symbol-name (car extra)) ":") nil t)
- (push (cons (car extra) (nnheader-header-value))
- out))
- (pop extra))
- out))))
- (when (equal id ref)
- (setq ref nil))
-
- (when gnus-alter-header-function
- (funcall gnus-alter-header-function header)
- (setq id (mail-header-id header)
- ref (gnus-parent-id (mail-header-references header))))
-
+ (while (setq header (nnheader-parse-head))
(when (setq header
(gnus-dependencies-add-header
header dependencies force-new))
- (push header headers))
- (goto-char (point-max))
- (widen))
+ (push header headers)))
(nreverse headers)))))
;; Goes through the xover lines and returns a list of vectors
@@ -8702,7 +8560,8 @@ SCORE."
When called interactively, ID is the Message-ID of the current
article. If thread-only is non-nil limit the summary buffer to
these articles."
- (interactive (list (mail-header-id (gnus-summary-article-header))))
+ (interactive (list (mail-header-id (gnus-summary-article-header))
+ current-prefix-arg))
(let ((articles (gnus-articles-in-thread
(gnus-id-to-thread (gnus-root-id id))))
;;we REALLY want the whole thread---this prevents cut-threads
@@ -9125,13 +8984,13 @@ Return the number of articles fetched."
result))
(defun gnus-summary-refer-thread (&optional limit)
- "Fetch all articles in the current thread. For backends
-that know how to search for threads (currently only 'nnimap)
-a non-numeric prefix arg will use nnir to search the entire
+ "Fetch all articles in the current thread. For backends that
+know how to search for threads (currently only 'nnimap) a
+non-numeric prefix arg will search the entire
server; without a prefix arg only the current group is
-searched. If the variable `gnus-refer-thread-use-nnir' is
-non-nil the prefix arg has the reverse meaning. If no
-backend-specific `request-thread' function is available fetch
+searched. If the variable `gnus-refer-thread-use-search' is
+non-nil the prefix arg has the reverse meaning. If no
+backend-specific 'request-thread function is available fetch
LIMIT (the numerical prefix) old headers. If LIMIT is
non-numeric or nil fetch the number specified by the
`gnus-refer-thread-limit' variable."
@@ -9141,9 +9000,9 @@ non-numeric or nil fetch the number specified by the
(gnus-inhibit-demon t)
(gnus-summary-ignore-duplicates t)
(gnus-read-all-available-headers t)
- (gnus-refer-thread-use-nnir
+ (gnus-refer-thread-use-search
(if (and (not (null limit)) (listp limit))
- (not gnus-refer-thread-use-nnir) gnus-refer-thread-use-nnir))
+ (not gnus-refer-thread-use-search) gnus-refer-thread-use-search))
(new-headers
(if (gnus-check-backend-function
'request-thread gnus-newsgroup-name)
@@ -9284,9 +9143,9 @@ non-numeric or nil fetch the number specified by the
(dolist (method gnus-refer-article-method)
(push (if (eq 'current method)
gnus-current-select-method
- (if (eq 'nnir (car method))
+ (if (eq 'nnselect (car method))
(list
- 'nnir
+ 'nnselect
(or (cadr method)
(gnus-method-to-server gnus-current-select-method)))
method))
diff --git a/lisp/gnus/gnus.el b/lisp/gnus/gnus.el
index 295395c79c2..4e3fc9868b4 100644
--- a/lisp/gnus/gnus.el
+++ b/lisp/gnus/gnus.el
@@ -1607,7 +1607,7 @@ total number of articles in the group.")
:variable-default (mapcar
(lambda (g) (list g t))
'("delayed$" "drafts$" "queue$" "INBOX$"
- "^nnmairix:" "^nnir:" "archive"))
+ "^nnmairix:" "^nnselect:" "archive"))
:variable-document
"Groups in which the registry should be turned off."
:variable-group gnus-registry
@@ -3153,7 +3153,10 @@ that that variable is buffer-local to the summary buffers."
(defun gnus-kill-ephemeral-group (group)
"Remove ephemeral GROUP from relevant structures."
- (remhash group gnus-newsrc-hashtb))
+ (remhash group gnus-newsrc-hashtb)
+ (setq gnus-newsrc-alist
+ (delq (assoc group gnus-newsrc-alist)
+ gnus-newsrc-alist)))
(defun gnus-simplify-mode-line ()
"Make mode lines a bit simpler."
diff --git a/lisp/gnus/nndiary.el b/lisp/gnus/nndiary.el
index 945ef0351e5..7894285bdf3 100644
--- a/lisp/gnus/nndiary.el
+++ b/lisp/gnus/nndiary.el
@@ -992,7 +992,7 @@ all. This may very well take some time.")
(narrow-to-region
(goto-char (point-min))
(if (search-forward "\n\n" nil t) (1- (point)) (point-max))))
- (let ((headers (nnheader-parse-naked-head)))
+ (let ((headers (nnheader-parse-head t)))
(setf (mail-header-chars headers) chars)
(setf (mail-header-number headers) number)
headers))))
diff --git a/lisp/gnus/nnfolder.el b/lisp/gnus/nnfolder.el
index c27af1742d8..6ff99056d84 100644
--- a/lisp/gnus/nnfolder.el
+++ b/lisp/gnus/nnfolder.el
@@ -1160,7 +1160,7 @@ This command does not work if you use short group names."
(if (search-forward "\n\n" e t) (setq e (1- (point)))))
(with-temp-buffer
(insert-buffer-substring buf b e)
- (let ((headers (nnheader-parse-naked-head)))
+ (let ((headers (nnheader-parse-head t)))
(setf (mail-header-chars headers) chars)
(setf (mail-header-number headers) number)
headers)))))
diff --git a/lisp/gnus/nnheader.el b/lisp/gnus/nnheader.el
index fee7a169ff9..1a50697bf5d 100644
--- a/lisp/gnus/nnheader.el
+++ b/lisp/gnus/nnheader.el
@@ -28,6 +28,10 @@
(eval-when-compile (require 'cl-lib))
+(defvar gnus-decode-encoded-word-function)
+(defvar gnus-decode-encoded-address-function)
+(defvar gnus-alter-header-function)
+
(defvar nnmail-extra-headers)
(defvar gnus-newsgroup-name)
(defvar jka-compr-compression-info-list)
@@ -39,6 +43,7 @@
(require 'mail-utils)
(require 'mm-util)
(require 'gnus-util)
+(autoload 'gnus-remove-odd-characters "gnus-sum")
(autoload 'gnus-range-add "gnus-range")
(autoload 'gnus-remove-from-range "gnus-range")
;; FIXME none of these are used explicitly in this file.
@@ -188,124 +193,167 @@ on your system, you could say something like:
(autoload 'ietf-drums-unfold-fws "ietf-drums")
-(defun nnheader-parse-naked-head (&optional number)
- ;; This function unfolds continuation lines in this buffer
- ;; destructively. When this side effect is unwanted, use
- ;; `nnheader-parse-head' instead of this function.
- (let ((case-fold-search t)
- (buffer-read-only nil)
+
+(defsubst nnheader-head-make-header (number)
+ "Using data of type 'head in the current buffer
+ return a full mail header with article NUMBER."
+ (let ((p (point-min))
(cur (current-buffer))
- (p (point-min))
- in-reply-to lines ref)
- (nnheader-remove-cr-followed-by-lf)
- (ietf-drums-unfold-fws)
- (subst-char-in-region (point-min) (point-max) ?\t ? )
- (goto-char p)
- (insert "\n")
- (prog1
- ;; This implementation of this function, with nine
- ;; search-forwards instead of the one re-search-forward and a
- ;; case (which basically was the old function) is actually
- ;; about twice as fast, even though it looks messier. You
- ;; can't have everything, I guess. Speed and elegance don't
- ;; always go hand in hand.
- (make-full-mail-header
- ;; Number.
- (or number 0)
- ;; Subject.
- (progn
- (goto-char p)
- (if (search-forward "\nsubject:" nil t)
- (nnheader-header-value) "(none)"))
- ;; From.
- (progn
- (goto-char p)
- (if (search-forward "\nfrom:" nil t)
- (nnheader-header-value) "(nobody)"))
- ;; Date.
- (progn
- (goto-char p)
- (if (search-forward "\ndate:" nil t)
- (nnheader-header-value) ""))
- ;; Message-ID.
- (progn
- (goto-char p)
- (if (search-forward "\nmessage-id:" nil t)
- (buffer-substring
- (1- (or (search-forward "<" (point-at-eol) t)
- (point)))
- (or (search-forward ">" (point-at-eol) t) (point)))
- ;; If there was no message-id, we just fake one to make
- ;; subsequent routines simpler.
- (nnheader-generate-fake-message-id number)))
- ;; References.
- (progn
+ in-reply-to chars lines end ref)
+ ;; This implementation of this function, with nine
+ ;; search-forwards instead of the one re-search-forward and a
+ ;; case (which basically was the old function) is actually
+ ;; about twice as fast, even though it looks messier. You
+ ;; can't have everything, I guess. Speed and elegance don't
+ ;; always go hand in hand.
+ (make-full-mail-header
+ ;; Number.
+ number
+ ;; Subject.
+ (progn
+ (goto-char p)
+ (if (search-forward "\nsubject:" nil t)
+ (funcall gnus-decode-encoded-word-function
+ (nnheader-header-value))
+ "(none)"))
+ ;; From.
+ (progn
+ (goto-char p)
+ (if (search-forward "\nfrom:" nil t)
+ (funcall gnus-decode-encoded-address-function
+ (nnheader-header-value))
+ "(nobody)"))
+ ;; Date.
+ (progn
+ (goto-char p)
+ (if (search-forward "\ndate:" nil t)
+ (nnheader-header-value) ""))
+ ;; Message-ID.
+ (progn
+ (goto-char p)
+ (if (re-search-forward
+ "^message-id: *\\(<[^\n\t> ]+>\\)" nil t)
+ ;; We do it this way to make sure the Message-ID
+ ;; is (somewhat) syntactically valid.
+ (buffer-substring (match-beginning 1)
+ (match-end 1))
+ ;; If there was no message-id, we just fake one to make
+ ;; subsequent routines simpler.
+ (nnheader-generate-fake-message-id number)))
+ ;; References.
+ (progn
+ (goto-char p)
+ (if (search-forward "\nreferences:" nil t)
+ (progn
+ (setq end (point))
+ (prog1
+ (nnheader-header-value)
+ (setq ref
+ (buffer-substring
+ (progn
+ (end-of-line)
+ (search-backward ">" end t)
+ (1+ (point)))
+ (progn
+ (search-backward "<" end t)
+ (point))))))
+ ;; Get the references from the in-reply-to header if there
+ ;; were no references and the in-reply-to header looks
+ ;; promising.
+ (if (and (search-forward "\nin-reply-to:" nil t)
+ (setq in-reply-to (nnheader-header-value))
+ (string-match "<[^>]+>" in-reply-to))
+ (let (ref2)
+ (setq ref (substring in-reply-to (match-beginning 0)
+ (match-end 0)))
+ (while (string-match "<[^>]+>" in-reply-to (match-end 0))
+ (setq ref2 (substring in-reply-to (match-beginning 0)
+ (match-end 0)))
+ (when (> (length ref2) (length ref))
+ (setq ref ref2)))
+ ref)
+ nil)))
+ ;; Chars.
+ (progn
+ (goto-char p)
+ (if (search-forward "\nchars: " nil t)
+ (if (numberp (setq chars (ignore-errors (read cur))))
+ chars -1)
+ -1))
+ ;; Lines.
+ (progn
+ (goto-char p)
+ (if (search-forward "\nlines: " nil t)
+ (if (numberp (setq lines (ignore-errors (read cur))))
+ lines -1)
+ -1))
+ ;; Xref.
+ (progn
+ (goto-char p)
+ (and (search-forward "\nxref:" nil t)
+ (nnheader-header-value)))
+ ;; Extra.
+ (when nnmail-extra-headers
+ (let ((extra nnmail-extra-headers)
+ out)
+ (while extra
(goto-char p)
- (if (search-forward "\nreferences:" nil t)
- (nnheader-header-value)
- ;; Get the references from the in-reply-to header if
- ;; there were no references and the in-reply-to header
- ;; looks promising.
- (if (and (search-forward "\nin-reply-to:" nil t)
- (setq in-reply-to (nnheader-header-value))
- (string-match "<[^\n>]+>" in-reply-to))
- (let (ref2)
- (setq ref (substring in-reply-to (match-beginning 0)
- (match-end 0)))
- (while (string-match "<[^\n>]+>"
- in-reply-to (match-end 0))
- (setq ref2 (substring in-reply-to (match-beginning 0)
- (match-end 0)))
- (when (> (length ref2) (length ref))
- (setq ref ref2)))
- ref)
- nil)))
- ;; Chars.
- 0
- ;; Lines.
- (progn
- (goto-char p)
- (if (search-forward "\nlines: " nil t)
- (if (numberp (setq lines (read cur)))
- lines 0)
- 0))
- ;; Xref.
- (progn
- (goto-char p)
- (and (search-forward "\nxref:" nil t)
- (nnheader-header-value)))
- ;; Extra.
- (when nnmail-extra-headers
- (let ((extra nnmail-extra-headers)
- out)
- (while extra
- (goto-char p)
- (when (search-forward
- (concat "\n" (symbol-name (car extra)) ":") nil t)
- (push (cons (car extra) (nnheader-header-value))
- out))
- (pop extra))
- out)))
- (goto-char p)
- (delete-char 1))))
-
-(defun nnheader-parse-head (&optional naked)
- (let ((cur (current-buffer)) num beg end)
- (when (if naked
- (setq num 0
- beg (point-min)
- end (point-max))
- ;; Search to the beginning of the next header. Error
- ;; messages do not begin with 2 or 3.
- (when (re-search-forward "^[23][0-9]+ " nil t)
- (setq num (read cur)
- beg (point)
- end (if (search-forward "\n.\n" nil t)
- (goto-char (- (point) 2))
- (point)))))
- (with-temp-buffer
- (insert-buffer-substring cur beg end)
- (nnheader-parse-naked-head num)))))
+ (when (search-forward
+ (concat "\n" (symbol-name (car extra)) ":") nil t)
+ (push (cons (car extra) (nnheader-header-value))
+ out))
+ (pop extra))
+ out)))))
+
+(defun nnheader-parse-head (&optional naked temp)
+ "Parse data of type 'header in the current buffer and return a
+ mail header, modifying the buffer contents in the process. The
+ buffer is assumed to begin each header with an \"Article
+ retrieved\" line with an article number; If NAKED is non-nil
+ this line is assumed absent, and the buffer should contain a
+ single header's worth of data. If TEMP is non-nil the data is
+ first copied to a temporary buffer leaving the original buffer
+ untouched."
+ (let ((cur (current-buffer))
+ (num 0)
+ (beg (point-min))
+ (end (point-max))
+ buf)
+ (when (or naked
+ ;; Search to the beginning of the next header. Error
+ ;; messages do not begin with 2 or 3.
+ (when (re-search-forward "^[23][0-9]+ " nil t)
+ (setq num (read cur)
+ beg (point)
+ end (if (search-forward "\n.\n" nil t)
+ (goto-char (- (point) 2))
+ (point)))))
+ ;; When TEMP copy the data to a temporary buffer
+ (if temp
+ (progn
+ (set-buffer (setq buf (generate-new-buffer " *nnheader-temp*")))
+ (insert-buffer-substring cur beg end))
+ ;; Otherwise just narrow to the data
+ (narrow-to-region beg end))
+ (let ((case-fold-search t)
+ (buffer-read-only nil)
+ header)
+ (nnheader-remove-cr-followed-by-lf)
+ (ietf-drums-unfold-fws)
+ (subst-char-in-region (point-min) (point-max) ?\t ? t)
+ (subst-char-in-region (point-min) (point-max) ?\r ? t)
+ (goto-char (point-min))
+ (insert "\n")
+ (setq header (nnheader-head-make-header num))
+ (goto-char (point-min))
+ (delete-char 1)
+ (if temp
+ (kill-buffer buf)
+ (goto-char (point-max))
+ (widen))
+ (when gnus-alter-header-function
+ (funcall gnus-alter-header-function header))
+ header))))
(defmacro nnheader-nov-skip-field ()
'(search-forward "\t" eol 'move))
@@ -347,24 +395,43 @@ on your system, you could say something like:
'id)
(nnheader-generate-fake-message-id ,number))))
-(defun nnheader-parse-nov ()
+(defalias 'nnheader-nov-make-header 'nnheader-parse-nov)
+(autoload 'gnus-extract-message-id-from-in-reply-to "gnus-sum")
+
+(defun nnheader-parse-nov (&optional number)
(let ((eol (point-at-eol))
- (number (nnheader-nov-read-integer)))
- (vector
- number ; number
- (nnheader-nov-field) ; subject
- (nnheader-nov-field) ; from
- (nnheader-nov-field) ; date
- (nnheader-nov-read-message-id number) ; id
- (nnheader-nov-field) ; refs
- (nnheader-nov-read-integer) ; chars
- (nnheader-nov-read-integer) ; lines
- (if (eq (char-after) ?\n)
- nil
- (if (looking-at "Xref: ")
- (goto-char (match-end 0)))
- (nnheader-nov-field)) ; Xref
- (nnheader-nov-parse-extra)))) ; extra
+ references in-reply-to x header)
+ (setq header
+ (make-full-mail-header
+ (or number (nnheader-nov-read-integer)) ; number
+ (condition-case () ; subject
+ (gnus-remove-odd-characters
+ (funcall gnus-decode-encoded-word-function
+ (setq x (nnheader-nov-field))))
+ (error x))
+ (condition-case () ; from
+ (gnus-remove-odd-characters
+ (funcall gnus-decode-encoded-address-function
+ (setq x (nnheader-nov-field))))
+ (error x))
+ (nnheader-nov-field) ; date
+ (nnheader-nov-read-message-id number) ; id
+ (setq references (nnheader-nov-field)) ; refs
+ (nnheader-nov-read-integer) ; chars
+ (nnheader-nov-read-integer) ; lines
+ (unless (eobp)
+ (if (looking-at "Xref: ")
+ (goto-char (match-end 0)))
+ (nnheader-nov-field)) ; Xref
+ (nnheader-nov-parse-extra))) ; extra
+
+ (when (and (string= references "")
+ (setq in-reply-to (mail-header-extra header))
+ (setq in-reply-to (cdr (assq 'In-Reply-To in-reply-to))))
+ (setf (mail-header-references header)
+ (gnus-extract-message-id-from-in-reply-to in-reply-to)))
+ header))
+
(defun nnheader-insert-nov (header)
(princ (mail-header-number header) (current-buffer))
@@ -399,17 +466,6 @@ on your system, you could say something like:
(delete-char 1))
(forward-line 1)))
-(defun nnheader-parse-overview-file (file)
- "Parse FILE and return a list of headers."
- (mm-with-unibyte-buffer
- (nnheader-insert-file-contents file)
- (goto-char (point-min))
- (let (headers)
- (while (not (eobp))
- (push (nnheader-parse-nov) headers)
- (forward-line 1))
- (nreverse headers))))
-
(defun nnheader-write-overview-file (file headers)
"Write HEADERS to FILE."
(with-temp-file file
diff --git a/lisp/gnus/nnimap.el b/lisp/gnus/nnimap.el
index 507e12a55e7..d797e893f51 100644
--- a/lisp/gnus/nnimap.el
+++ b/lisp/gnus/nnimap.el
@@ -1686,7 +1686,7 @@ If LIMIT, first try to limit the search to the N last articles."
(gnus-add-to-range
(gnus-add-to-range
(gnus-range-add (gnus-info-read info)
- vanished)
+ vanished)
(cdr (assq '%Flagged flags)))
(cdr (assq '%Seen flags))))
(let ((marks (gnus-info-marks info)))
@@ -1851,15 +1851,15 @@ If LIMIT, first try to limit the search to the N last articles."
(setq nnimap-status-string "Read-only server")
nil)
-(defvar gnus-refer-thread-use-nnir) ;; gnus-sum.el
+(defvar gnus-refer-thread-use-search) ;; gnus-sum.el
(declare-function gnus-fetch-headers "gnus-sum"
(articles &optional limit force-new dependencies))
-(autoload 'nnir-search-thread "nnir")
+(autoload 'nnselect-search-thread "nnselect")
(deffoo nnimap-request-thread (header &optional group server)
- (if gnus-refer-thread-use-nnir
- (nnir-search-thread header)
+ (if gnus-refer-thread-use-search
+ (nnselect-search-thread header)
(when (nnimap-change-group group server)
(let* ((cmd (nnimap-make-thread-query header))
(result (with-current-buffer (nnimap-buffer)
diff --git a/lisp/gnus/nnir.el b/lisp/gnus/nnir.el
index 722969c21ba..2ec39cf34c9 100644
--- a/lisp/gnus/nnir.el
+++ b/lisp/gnus/nnir.el
@@ -10,6 +10,7 @@
;; IMAP search improved by Daniel Pittman <daniel@rimspace.net>.
;; nnmaildir support for Swish++ and Namazu backends by:
;; Justus Piater <Justus <at> Piater.name>
+;; Mostly rewritten by Andrew Cohen <cohen@bu.edu> from 2010
;; Keywords: news mail searching ir
;; This file is part of GNU Emacs.
@@ -29,20 +30,11 @@
;;; Commentary:
-;; What does it do? Well, it allows you to search your mail using
-;; some search engine (imap, namazu, swish-e 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 `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.
+;; What does it do? Well, it searches your mail using some search
+;; engine (imap, namazu, swish-e, gmane and others -- see later).
;; The Lisp setup may involve setting a few variables and setting up the
-;; search engine. You can define the variables in the server definition
+;; search engine. You can define the variables in the server definition
;; like this :
;; (setq gnus-secondary-select-methods '(
;; (nnimap "" (nnimap-address "localhost")
@@ -53,6 +45,45 @@
;; an alist, type `C-h v nnir-engines RET' for more information; this
;; includes examples for setting `nnir-search-engine', too.)
+;; The entry to searching is the single 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 a vector, each element of which
+;; should in turn be a three-element vector with the form: [fully
+;; prefixed group-name of the article; the article number; 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).
+
+;; A vector of this form is used by the nnselect backend to create
+;; virtual groups. So nnir-run-query is a suitable function to use in
+;; nnselect groups.
+
+;; The default sorting order of articles in an nnselect summary buffer
+;; is based on the order of the articles in the above mentioned
+;; vector, so that's where you can do the sorting you'd like. Maybe
+;; it would be nice to have a way of displaying the search result
+;; sorted differently?
+
+;; So what do you need to do when you want to add another search
+;; engine? You write a function that executes the query. Temporary
+;; data from the search engine can be put in `nnir-tmp-buffer'. This
+;; 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' as a server variable.
+
;; If you use one of the local indices (namazu, find-grep, swish) you
;; must also set up a search engine backend.
@@ -75,13 +106,13 @@
;; ,----
;; | package conf; # Don't remove this line!
;; |
-;; | # Paths which will not be indexed. Don't use `^' or `$' anchors.
+;; | # Paths which will not be indexed. Don't use `^' or `$' anchors.
;; | $EXCLUDE_PATH = "spam|sent";
;; |
-;; | # Header fields which should be searchable. case-insensitive
+;; | # Header fields which should be searchable. case-insensitive
;; | $REMAIN_HEADER = "from|date|message-id|subject";
;; |
-;; | # Searchable fields. case-insensitive
+;; | # Searchable fields. case-insensitive
;; | $SEARCH_FIELD = "from|date|message-id|subject";
;; |
;; | # The max length of a word.
@@ -121,72 +152,17 @@
;; | (nnml-active-file "~/News/cache/active"))
;; `----
-;; Developer information:
-
-;; I have tried to make the code expandable. Basically, it is divided
-;; into two layers. The upper layer is somewhat like the `nnvirtual'
-;; backend: given a specification of what articles to show from
-;; another backend, it creates a group containing exactly those
-;; articles. The lower layer issues a query to a search engine and
-;; produces such a specification of what articles to show from the
-;; other backend.
-
-;; The interface between the two layers consists of the single
-;; 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
-;; vector, so that's where you can do the sorting you'd like. Maybe
-;; it would be nice to have a way of displaying the search result
-;; sorted differently?
-
-;; So what do you need to do when you want to add another search
-;; engine? You write a function that executes the query. Temporary
-;; data from the search engine can be put in `nnir-tmp-buffer'. This
-;; 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' as a server variable.
;;; Code:
;;; Setup:
-(require 'nnoo)
-(require 'gnus-group)
-(require 'message)
-(require 'gnus-util)
(eval-when-compile (require 'cl-lib))
+(require 'gnus)
;;; Internal Variables:
-(defvar nnir-memo-query nil
- "Internal: stores current query.")
-
-(defvar nnir-memo-server nil
- "Internal: stores current server.")
-
-(defvar nnir-artlist nil
- "Internal: stores search result.")
+(defvar gnus-inhibit-demon)
(defvar nnir-search-history ()
"Internal: the history for querying search options in nnir.")
@@ -203,30 +179,19 @@
("to" . "TO")
("from" . "FROM")
("body" . "BODY")
- ("imap" . ""))
+ ("imap" . "")
+ ("gmail" . "X-GM-RAW"))
"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
-email header field.")
+ "The IMAP search item for anything other than `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.")
;;; Helper macros
-;; Data type article list.
-
-(defmacro nnir-artlist-length (artlist)
- "Return number of articles in artlist."
- `(length ,artlist))
-
-(defmacro nnir-artlist-article (artlist n)
- "Return from ARTLIST the Nth artitem (counting starting at 1)."
- `(when (> ,n 0)
- (elt ,artlist (1- ,n))))
-
(defmacro nnir-artitem-group (artitem)
"Return the group from the ARTITEM."
`(elt ,artitem 0))
@@ -239,52 +204,6 @@ email header field.")
"Return the Retrieval Status Value (RSV, score) from the ARTITEM."
`(elt ,artitem 2))
-(defmacro nnir-article-group (article)
- "Return the group for ARTICLE."
- `(nnir-artitem-group (nnir-artlist-article nnir-artlist ,article)))
-
-(defmacro nnir-article-number (article)
- "Return the number for ARTICLE."
- `(nnir-artitem-number (nnir-artlist-article nnir-artlist ,article)))
-
-(defmacro nnir-article-rsv (article)
- "Return the rsv for ARTICLE."
- `(nnir-artitem-rsv (nnir-artlist-article nnir-artlist ,article)))
-
-(defsubst nnir-article-ids (article)
- "Return the pair `(nnir id . real id)' of ARTICLE."
- (cons article (nnir-article-number article)))
-
-(defmacro nnir-categorize (sequence keyfunc &optional valuefunc)
- "Sort 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)
-
-(nnoo-declare nnir)
-(nnoo-define-basics nnir)
-
-(gnus-declare-backend "nnir" 'mail 'virtual)
-
;;; User Customizable Variables:
@@ -293,43 +212,17 @@ is `(VALUEFUNC member)'."
:group 'gnus)
(defcustom nnir-ignored-newsgroups ""
- "A regexp to match newsgroups in the active file that should
-be skipped when searching."
+ "Newsgroups to skip when searching.
+Any newsgroup in the active file matching this regexp will be
+skipped when searching."
:version "24.1"
:type '(regexp)
:group 'nnir)
-(defcustom nnir-summary-line-format nil
- "The format specification of the lines in an nnir summary buffer.
-
-All the items from `gnus-summary-line-format' are available, along
-with three items unique to nnir summary buffers:
-
-%Z Search retrieval score value (integer)
-%G Article original full group name (string)
-%g Article original short group name (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-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.
-
-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-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\"."
+ "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))
@@ -357,9 +250,9 @@ Instead, use this:
:group 'nnir)
(defcustom nnir-swish++-remove-prefix (concat (getenv "HOME") "/Mail/")
- "The prefix to remove from each file name returned by swish++
-in order to get a group name (albeit with / instead of .). This is a
-regular expression.
+ "The prefix to remove from swish++ file names to get group names.
+Resulting names have '/' in place of '.'. This is a regular
+expression.
This variable is very similar to `nnir-namazu-remove-prefix', except
that it is for swish++, not Namazu."
@@ -408,9 +301,9 @@ This could be a server parameter."
:group 'nnir)
(defcustom nnir-swish-e-remove-prefix (concat (getenv "HOME") "/Mail/")
- "The prefix to remove from each file name returned by swish-e
-in order to get a group name (albeit with / instead of .). This is a
-regular expression.
+ "The prefix to remove from swish-e file names to get group names.
+Resulting names have '/' in place of '.'. This is a regular
+expression.
This variable is very similar to `nnir-namazu-remove-prefix', except
that it is for swish-e, not Namazu.
@@ -441,8 +334,8 @@ Instead, use this:
:group 'nnir)
(defcustom nnir-hyrex-remove-prefix (concat (getenv "HOME") "/Mail/")
- "The prefix to remove from each file name returned by HyREX
-in order to get a group name (albeit with / instead of .).
+ "The prefix to remove from HyREX file names to get group names.
+Restulting names have '/' in place of '.'.
For example, suppose that HyREX returns file names such as
\"/home/john/Mail/mail/misc/42\". For this example, use the following
@@ -478,8 +371,8 @@ Instead, use this:
:group 'nnir)
(defcustom nnir-namazu-remove-prefix (concat (getenv "HOME") "/Mail/")
- "The prefix to remove from each file name returned by Namazu
-in order to get a group name (albeit with / instead of .).
+ "The prefix to remove from Namazu file names to get group names.
+Resulting names have '/' in place of '.'.
For example, suppose that Namazu returns file names such as
\"/home/john/Mail/mail/misc/42\". For this example, use the following
@@ -509,9 +402,9 @@ Instead, use this:
(defcustom nnir-notmuch-remove-prefix
(regexp-quote (or (getenv "MAILDIR") (expand-file-name "~/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.
+ "The prefix to remove from notmuch file names to get group names.
+Resulting names have '/' in place of '.'. This is a regular
+expression.
This variable is very similar to `nnir-namazu-remove-prefix', except
that it is for notmuch, not Namazu."
@@ -590,347 +483,12 @@ Add an entry here when adding a new search engine.")
,@(mapcar (lambda (elem) (list 'const (car elem)))
nnir-engines)))))
-;; Gnus glue.
-
-(declare-function gnus-group-topic-name "gnus-topic" ())
-(declare-function gnus-topic-find-groups "gnus-topic"
- (topic &optional level all lowest recursive))
-
-(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))
- (mapcar (lambda (entry)
- (gnus-info-group (cadr entry)))
- (gnus-topic-find-groups (gnus-group-topic-name)
- nil t nil t))))
- 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.
- (let ((backend (car (gnus-server-to-method server))))
- (if backend
- (nnoo-change-server backend server definitions)
- (add-hook 'gnus-summary-generate-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)
-
-(defvar gnus-inhibit-demon)
-
-(deffoo nnir-retrieve-headers (articles &optional _group _server _fetch-old)
- (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)
- (pcase (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))
- (_ (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
- (setf (mail-header-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)))
-
-(defvar gnus-article-decode-hook)
-
-(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-request-article only groks message ids for nnimap servers: %s"
- server)
- (save-excursion
- (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)))
- (or (and artgroup
- artnumber
- (gnus-request-update-mark artgroup artnumber mark))
- mark)))
-
-(deffoo nnir-request-set-mark (group actions &optional server)
- (nnir-possibly-change-group group server)
- (let (mlist)
- (dolist (action actions)
- (cl-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.
- (setf (gnus-info-marks info) nil)
- (setf (gnus-info-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)))
- (setf (gnus-info-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)
- (cl-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))))
-
-
-
(defmacro nnir-add-result (dirnam artno score prefix server artlist)
- "Ask `nnir-compose-result' to construct a result vector,
-and if it is non-nil, add it to ARTLIST."
+ "Construct a result vector and add it to ARTLIST.
+DIRNAM, ARTNO, SCORE, PREFIX and SERVER are passed to
+`nnir-compose-result' to make the vector. Only add the result if
+non-nil."
`(let ((result (nnir-compose-result ,dirnam ,artno ,score ,prefix ,server)))
(when (not (null result))
(push result ,artlist))))
@@ -940,9 +498,9 @@ and if it is non-nil, add it to ARTLIST."
;; Helper function currently used by the Swish++ and Namazu backends;
;; perhaps useful for other backends as well
(defun nnir-compose-result (dirnam article score prefix server)
- "Extract the group from DIRNAM, and create a result vector
-ready to be added to the list of search results."
-
+ "Construct a result vector.
+The DIRNAM, ARTICLE, SCORE, PREFIX, and SERVER are used to
+construct the vector entries."
;; remove nnir-*-remove-prefix from beginning of dirnam filename
(when (string-match (concat "^" prefix) dirnam)
(setq dirnam (replace-match "" t t dirnam)))
@@ -977,13 +535,14 @@ ready to be added to the list of search results."
;; 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."
+ "Run the QUERY search against an IMAP back-end server SRV.
+Search GROUPS, or all active groups on SRV if GROUPS is nil.
+This uses a custom query language parser; see
+`nnir-imap-make-query' for details on the language and supported
+extensions."
(save-excursion
(let ((qstring (cdr (assq 'query query)))
(server (cadr (gnus-server-to-method srv)))
-;; (defs (nth 2 (gnus-server-to-method srv)))
(criteria (or (cdr (assq 'criteria query))
(cdr (assoc nnir-imap-default-search-key
nnir-imap-search-arguments))))
@@ -995,38 +554,37 @@ for details on the language and supported extensions."
(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)))
+ (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
-expression, returning the string query to make.
+ "Make an IMAP search expression from QSTRING and CRITERIA.
This implements a little language designed to return the expected
results to an arbitrary query string to the end user.
@@ -1063,7 +621,7 @@ In the future the following will be added to the language:
(defun nnir-imap-query-to-imap (criteria query)
- "Turn an s-expression format QUERY into IMAP."
+ "Turn an s-expression format QUERY with CRITERIA into IMAP."
(mapconcat
;; Turn the expressions into IMAP text
(lambda (item)
@@ -1099,8 +657,9 @@ In the future the following will be added to the language:
(defun nnir-imap-parse-query (string)
- "Turn STRING into an s-expression based query based on the IMAP
-query language as defined in `nnir-imap-make-query'.
+ "Turn STRING into an s-expression query.
+STRING is based on the IMAP query language as defined in
+`nnir-imap-make-query'.
This involves turning individual tokens into higher level terms
that the search language can then understand and use."
@@ -1116,7 +675,7 @@ that the search language can then understand and use."
(defun nnir-imap-next-expr (&optional count)
- "Return the next expression from the current buffer."
+ "Return the next (COUNT) expression from the current buffer."
(let ((term (nnir-imap-next-term count))
(next (nnir-imap-peek-symbol)))
;; Are we looking at an 'or' expression?
@@ -1129,7 +688,7 @@ that the search language can then understand and use."
(defun nnir-imap-next-term (&optional count)
- "Return the next term from the current buffer."
+ "Return the next (COUNT) term from the current buffer."
(let ((term (nnir-imap-next-symbol count)))
;; What sort of term is this?
(cond
@@ -1147,9 +706,10 @@ that the search language can then understand and use."
(nnir-imap-next-symbol)))
(defun nnir-imap-next-symbol (&optional count)
- "Return the next symbol from the current buffer, or nil if we are
-at the end of the buffer. If supplied COUNT skips some symbols before
-returning the one at the supplied position."
+ "Return the next (COUNT) symbol from the current buffer.
+Return nil if we are at the end of the buffer. If supplied COUNT
+skips some symbols before returning the one at the supplied
+position."
(when (and (numberp count) (> count 1))
(nnir-imap-next-symbol (1- count)))
(let ((case-fold-search t))
@@ -1180,7 +740,7 @@ returning the one at the supplied position."
(buffer-substring start end)))))))
(defun nnir-imap-delimited-string (delimiter)
- "Return a delimited string from the current buffer."
+ "Return a string delimited by DELIMITER from the current buffer."
(let ((start (point)) end)
(forward-char 1) ; skip the first delimiter.
(while (not end)
@@ -1207,7 +767,7 @@ returning the one at the supplied position."
;; - file size
;; - group
(defun nnir-run-swish++ (query server &optional _group)
- "Run QUERY against swish++.
+ "Run QUERY on SERVER against swish++.
Returns a vector of (group name, file name) pairs (also vectors,
actually).
@@ -1297,7 +857,7 @@ Windows NT 4.0."
;; Swish-E interface.
(defun nnir-run-swish-e (query server &optional _group)
- "Run given QUERY against swish-e.
+ "Run given QUERY on SERVER against swish-e.
Returns a vector of (group name, file name) pairs (also vectors,
actually).
@@ -1392,6 +952,7 @@ Tested with swish-e-2.0.1 on Windows NT 4.0."
;; HyREX interface
(defun nnir-run-hyrex (query server &optional group)
+ "Run given QUERY with GROUP on SERVER against hyrex."
(save-excursion
(let ((artlist nil)
(groupspec (cdr (assq 'hyrex-group query)))
@@ -1463,7 +1024,7 @@ Tested with swish-e-2.0.1 on Windows NT 4.0."
;; Namazu interface
(defun nnir-run-namazu (query server &optional _group)
- "Run given QUERY against Namazu.
+ "Run QUERY on SERVER against Namazu.
Returns a vector of (group name, file name) pairs (also vectors,
actually).
@@ -1533,7 +1094,7 @@ Tested with Namazu 2.0.6 on a GNU/Linux system."
(nnir-artitem-rsv y)))))))))
(defun nnir-run-notmuch (query server &optional groups)
- "Run QUERY against notmuch.
+ "Run QUERY with GROUPS from SERVER against notmuch.
Returns a vector of (group name, file name) pairs (also vectors,
actually). If GROUPS is a list of group names, use them to
construct path: search terms (see the variable
@@ -1617,7 +1178,7 @@ construct path: search terms (see the variable
artlist)))
(defun nnir-run-find-grep (query server &optional grouplist)
- "Run find and grep to obtain matching articles."
+ "Run find and grep to QUERY GROUPLIST on SERVER for matching articles."
(let* ((method (gnus-server-to-method server))
(sym (intern
(concat (symbol-name (car method)) "-directory")))
@@ -1703,14 +1264,10 @@ construct path: search terms (see the variable
;;; Util Code:
-(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)
- "Read additional search parameters according to `nnir-engines'."
+ "Read additional search parameters for NNIR-SEARCH-ENGINE.
+Parameters are according to `nnir-engines'."
(let ((parmspec (nth 2 (assoc nnir-search-engine nnir-engines))))
(mapcar #'nnir-read-parm parmspec)))
@@ -1727,7 +1284,7 @@ PARMSPEC is a cons cell, the car is a symbol, the cdr is a prompt."
(cons sym (read-string prompt)))))
(defun nnir-run-query (specs)
- "Invoke appropriate search engine function (see `nnir-engines')."
+ "Invoke search engine appropriate for SPECS (see `nnir-engines')."
(apply #'vconcat
(mapcar
(lambda (x)
@@ -1736,10 +1293,11 @@ PARMSPEC is a cons cell, the car is a symbol, the cdr is a prompt."
(search-func (cadr (assoc search-engine nnir-engines))))
(and search-func
(funcall search-func (cdr (assq 'nnir-query-spec specs))
- server (cadr x)))))
+ server (cdr x)))))
(cdr (assq 'nnir-group-spec specs)))))
(defun nnir-server-to-search-engine (server)
+ "Find search engine for SERVER."
(or (nnir-read-server-parm 'nnir-search-engine server t)
(cdr (assoc (car (gnus-server-to-method server))
nnir-method-default-engines))))
@@ -1754,48 +1312,10 @@ environment unless NOT-GLOBAL is non-nil."
((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)))
-
-(autoload 'nnimap-make-thread-query "nnimap")
-(declare-function gnus-registry-get-id-key "gnus-registry" (id key))
-
-(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
- (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
- (cl-pushnew (list registry-server) server :test #'equal))
- (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)))))
+(autoload 'gnus-request-list "gnus-int")
(defun nnir-get-active (srv)
+ "Return the active list for SRV."
(let ((method (gnus-server-to-method srv))
groups)
(gnus-request-list method)
@@ -1835,82 +1355,37 @@ is also searched."
(forward-line)))))
groups))
-;; Behind gnus-registry-enabled test.
-(declare-function gnus-registry-action "gnus-registry"
- (action data-header from &optional to method))
-
-(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)
- (when (and nnir-summary-line-format
- (not (string= nnir-summary-line-format
- gnus-summary-line-format)))
- (setq gnus-summary-line-format nnir-summary-line-format)
- (gnus-update-format-specifications nil 'summary))
- (when (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)
+(autoload 'nnselect-categorize "nnselect" nil nil)
+(autoload 'gnus-group-topic-name "gnus-topic" nil nil)
+(defvar gnus-group-marked)
+(defvar gnus-topic-alist)
+
+(defun nnir-make-specs (nnir-extra-parms &optional specs)
+ "Make the query-spec and group-spec for a search with NNIR-EXTRA-PARMS.
+Query for the specs, or use SPECS."
+ (let* ((group-spec
+ (or (cdr (assq 'nnir-group-spec specs))
+ (if (gnus-server-server-name)
+ (list (list (gnus-server-server-name)))
+ (nnselect-categorize
+ (or gnus-group-marked
+ (if (gnus-group-group-name)
+ (list (gnus-group-group-name))
+ (cdr (assoc (gnus-group-topic-name) gnus-topic-alist))))
+ 'nnselect-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))))))
+ (list (cons 'nnir-query-spec query-spec)
+ (cons 'nnir-group-spec group-spec))))
;; The end.
(provide 'nnir)
diff --git a/lisp/gnus/nnmaildir.el b/lisp/gnus/nnmaildir.el
index 9c7b1254413..81a148db669 100644
--- a/lisp/gnus/nnmaildir.el
+++ b/lisp/gnus/nnmaildir.el
@@ -492,7 +492,7 @@ This variable is set by `nnmaildir-request-article'.")
(setq nov-mid 0))
(goto-char (point-min))
(delete-char 1)
- (setq nov (nnheader-parse-naked-head)
+ (setq nov (nnheader-parse-head t)
field (or (mail-header-lines nov) 0)))
(unless (or (zerop field) (nnmaildir--param pgname 'distrust-Lines:))
(setq nov-mid field))
diff --git a/lisp/gnus/nnml.el b/lisp/gnus/nnml.el
index baf5d54b74d..ad608b6575e 100644
--- a/lisp/gnus/nnml.el
+++ b/lisp/gnus/nnml.el
@@ -766,7 +766,7 @@ article number. This function is called narrowed to an article."
(if (re-search-forward "\n\r?\n" nil t)
(1- (point))
(point-max))))
- (let ((headers (nnheader-parse-naked-head)))
+ (let ((headers (nnheader-parse-head t)))
(setf (mail-header-chars headers) chars)
(setf (mail-header-number headers) number)
headers))))
diff --git a/lisp/gnus/nnselect.el b/lisp/gnus/nnselect.el
new file mode 100644
index 00000000000..460bc63132c
--- /dev/null
+++ b/lisp/gnus/nnselect.el
@@ -0,0 +1,864 @@
+;;; nnselect.el --- a virtual group backend -*- lexical-binding:t -*-
+
+;; Copyright (C) 2020 Free Software Foundation, Inc.
+
+;; Author: Andrew Cohen <cohen@andy.bu.edu>
+;; Keywords: news mail
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This is a "virtual" backend that allows an aribtrary list of
+;; articles to be treated as a gnus group. An nnselect group uses an
+;; nnselect-spec group parameter to specify this list of
+;; articles. nnselect-spec is an alist with two keys:
+;; nnselect-function, whose value should be a function that returns
+;; the list of articles, and nnselect-args. The function will be
+;; applied to the arguments to generate the list of articles. The
+;; return value should be a vector, each element of which should in
+;; turn be a vector of three elements: a real prefixed group name, an
+;; article number in that group, and an integer score. The score is
+;; not used by nnselect but may be used by other code to help in
+;; sorting. Most functions will just chose a fixed number, such as
+;; 100, for this score.
+
+;; For example the search function `nnir-run-query' applied to
+;; arguments specifying a search query (see "nnir.el") can be used to
+;; return a list of articles from a search. Or the function can be the
+;; identity and the args a vector of articles.
+
+
+;;; Code:
+
+;;; Setup:
+
+(require 'gnus-art)
+(require 'nnir)
+
+(eval-when-compile (require 'cl-lib))
+
+;; Set up the backend
+
+(nnoo-declare nnselect)
+
+(nnoo-define-basics nnselect)
+
+(gnus-declare-backend "nnselect" 'post-mail 'virtual)
+
+;;; Internal Variables:
+
+(defvar gnus-inhibit-demon)
+(defvar gnus-message-group-art)
+
+;; For future use
+(defvoo nnselect-directory gnus-directory
+ "Directory for the nnselect backend.")
+
+(defvoo nnselect-active-file
+ (expand-file-name "nnselect-active" nnselect-directory)
+ "nnselect active file.")
+
+(defvoo nnselect-groups-file
+ (expand-file-name "nnselect-newsgroups" nnselect-directory)
+ "nnselect groups description file.")
+
+;;; Helper routines.
+(defun nnselect-compress-artlist (artlist)
+ "Compress ARTLIST."
+ (let (selection)
+ (pcase-dolist (`(,artgroup . ,arts)
+ (nnselect-categorize artlist 'nnselect-artitem-group))
+ (let (list)
+ (pcase-dolist (`(,rsv . ,articles)
+ (nnselect-categorize
+ arts 'nnselect-artitem-rsv 'nnselect-artitem-number))
+ (push (cons rsv (gnus-compress-sequence (sort articles '<)))
+ list))
+ (push (cons artgroup list) selection)))
+ selection))
+
+(defun nnselect-uncompress-artlist (artlist)
+ "Uncompress ARTLIST."
+ (if (vectorp artlist)
+ artlist
+ (let (selection)
+ (pcase-dolist (`(,artgroup (,artrsv . ,artseq)) artlist)
+ (setq selection
+ (vconcat
+ (cl-map 'vector
+ #'(lambda (art)
+ (vector artgroup art artrsv))
+ (gnus-uncompress-sequence artseq)) selection)))
+ selection)))
+
+(defun nnselect-group-server (group)
+ "Return the server for GROUP."
+ (gnus-group-server group))
+
+;; Data type article list.
+
+(define-inline nnselect-artlist-length (artlist)
+ (inline-quote (length ,artlist)))
+
+(define-inline nnselect-artlist-article (artlist n)
+ "Return from ARTLIST the Nth artitem (counting starting at 1)."
+ (inline-quote (when (> ,n 0)
+ (elt ,artlist (1- ,n)))))
+
+(define-inline nnselect-artitem-group (artitem)
+ "Return the group from the ARTITEM."
+ (inline-quote (elt ,artitem 0)))
+
+(define-inline nnselect-artitem-number (artitem)
+ "Return the number from the ARTITEM."
+ (inline-quote (elt ,artitem 1)))
+
+(define-inline nnselect-artitem-rsv (artitem)
+ "Return the Retrieval Status Value (RSV, score) from the ARTITEM."
+ (inline-quote (elt ,artitem 2)))
+
+(define-inline nnselect-article-group (article)
+ "Return the group for ARTICLE."
+ (inline-quote
+ (nnselect-artitem-group (nnselect-artlist-article
+ gnus-newsgroup-selection ,article))))
+
+(define-inline nnselect-article-number (article)
+ "Return the number for ARTICLE."
+ (inline-quote (nnselect-artitem-number
+ (nnselect-artlist-article
+ gnus-newsgroup-selection ,article))))
+
+(define-inline nnselect-article-rsv (article)
+ "Return the rsv for ARTICLE."
+ (inline-quote (nnselect-artitem-rsv
+ (nnselect-artlist-article
+ gnus-newsgroup-selection ,article))))
+
+(define-inline nnselect-article-id (article)
+ "Return the pair `(nnselect id . real id)' of ARTICLE."
+ (inline-quote (cons ,article (nnselect-article-number ,article))))
+
+(define-inline nnselect-categorize (sequence keyfunc &optional valuefunc)
+ "Sorts a sequence into categories.
+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' (or `(valuefunc member)' if `valuefunc' is non-nil)."
+ (inline-letevals (sequence keyfunc valuefunc)
+ (inline-quote (let ((valuefunc (or ,valuefunc 'identity))
+ result)
+ (unless (null ,sequence)
+ (mapc
+ (lambda (member)
+ (let* ((key (funcall ,keyfunc member))
+ (value (funcall valuefunc member))
+ (kr (assoc key result)))
+ (if kr
+ (push value (cdr kr))
+ (push (list key value) result))))
+ (reverse ,sequence))
+ result)))))
+
+
+;; Unclear whether a macro or an inline function is best.
+;; (defmacro nnselect-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' (or `(valuefunc member)' if `valuefunc' is non-nil)."
+;; (let ((key (make-symbol "key"))
+;; (value (make-symbol "value"))
+;; (result (make-symbol "result"))
+;; (valuefunc (or valuefunc 'identity)))
+;; `(unless (null ,sequence)
+;; (let (,result)
+;; (mapc
+;; (lambda (member)
+;; (let* ((,key (,keyfunc member))
+;; (,value (,valuefunc member))
+;; (kr (assoc ,key ,result)))
+;; (if kr
+;; (push ,value (cdr kr))
+;; (push (list ,key ,value) ,result))))
+;; (reverse ,sequence))
+;; ,result))))
+
+(define-inline ids-by-group (articles)
+ (inline-quote
+ (nnselect-categorize ,articles 'nnselect-article-group
+ 'nnselect-article-id)))
+
+(define-inline numbers-by-group (articles)
+ (inline-quote
+ (nnselect-categorize
+ ,articles 'nnselect-article-group 'nnselect-article-number)))
+
+
+(defmacro nnselect-add-prefix (group)
+ "Ensures that the GROUP has an nnselect prefix."
+ `(gnus-group-prefixed-name
+ (gnus-group-short-name ,group) '(nnselect "nnselect")))
+
+(defmacro nnselect-get-artlist (group)
+ "Retrieve the list of articles for GROUP."
+ `(when (gnus-nnselect-group-p ,group)
+ (nnselect-uncompress-artlist
+ (gnus-group-get-parameter ,group 'nnselect-artlist t))))
+
+(defmacro nnselect-add-novitem (novitem)
+ "Add NOVITEM to the list of headers."
+ `(let* ((novitem ,novitem)
+ (artno (and novitem
+ (mail-header-number novitem)))
+ (art (car-safe (rassq artno artids))))
+ (when art
+ (setf (mail-header-number novitem) art)
+ (push novitem headers))))
+
+;;; User Customizable Variables:
+
+(defgroup nnselect nil
+ "Virtual groups in Gnus with arbitrary selection methods."
+ :group 'gnus)
+
+(defcustom nnselect-retrieve-headers-override-function nil
+ "A function that retrieves article headers for ARTICLES from GROUP.
+The retrieved headers should populate the `nntp-server-buffer'.
+Returns either the retrieved header format 'nov or 'headers.
+
+If this variable is nil, or if the provided function returns nil,
+ `gnus-retrieve-headers' will be called instead."
+ :version "24.1" :type '(function) :group 'nnselect)
+
+
+;; Gnus backend interface functions.
+
+(deffoo nnselect-open-server (server &optional definitions)
+ ;; Just set the server variables appropriately.
+ (let ((backend (or (car (gnus-server-to-method server)) 'nnselect)))
+ (nnoo-change-server backend server definitions)))
+
+;; (deffoo nnselect-server-opened (&optional server)
+;; "Is SERVER the current virtual server?"
+;; (if (string-empty-p server)
+;; t
+;; (let ((backend (car (gnus-server-to-method server))))
+;; (nnoo-current-server-p (or backend 'nnselect) server))))
+
+(deffoo nnselect-server-opened (&optional _server)
+ t)
+
+
+(deffoo nnselect-request-group (group &optional _server _dont-check info)
+ (let* ((group (nnselect-add-prefix group))
+ (nnselect-artlist (nnselect-get-artlist group))
+ length)
+ ;; Check for cached select result or run the selection and cache
+ ;; the result.
+ (unless nnselect-artlist
+ (gnus-group-set-parameter
+ group 'nnselect-artlist
+ (nnselect-compress-artlist (setq nnselect-artlist
+ (nnselect-run
+ (gnus-group-get-parameter group 'nnselect-specs t)))))
+ (nnselect-request-update-info
+ group (or info (gnus-get-info group))))
+ (if (zerop (setq length (nnselect-artlist-length nnselect-artlist)))
+ (progn
+ (nnheader-report 'nnselect "Selection produced empty results.")
+ (nnheader-insert ""))
+ (with-current-buffer nntp-server-buffer
+ (nnheader-insert "211 %d %d %d %s\n"
+ length ; total #
+ 1 ; first #
+ length ; last #
+ group))) ; group name
+ nnselect-artlist))
+
+
+(deffoo nnselect-retrieve-headers (articles group &optional _server fetch-old)
+ (let ((group (nnselect-add-prefix group)))
+ (with-current-buffer (gnus-summary-buffer-name group)
+ (setq gnus-newsgroup-selection (or gnus-newsgroup-selection
+ (nnselect-get-artlist group)))
+ (let ((gnus-inhibit-demon t)
+ (gartids (ids-by-group articles))
+ headers)
+ (with-current-buffer nntp-server-buffer
+ (pcase-dolist (`(,artgroup . ,artids) gartids)
+ (let ((artlist (sort (mapcar 'cdr artids) '<))
+ (gnus-override-method (gnus-find-method-for-group artgroup))
+ (fetch-old
+ (or
+ (car-safe
+ (gnus-group-find-parameter artgroup
+ 'gnus-fetch-old-headers t))
+ fetch-old)))
+ (erase-buffer)
+ (pcase (setq gnus-headers-retrieved-by
+ (or
+ (and
+ nnselect-retrieve-headers-override-function
+ (funcall
+ nnselect-retrieve-headers-override-function
+ artlist artgroup))
+ (gnus-retrieve-headers
+ artlist artgroup fetch-old)))
+ ('nov
+ (goto-char (point-min))
+ (while (not (eobp))
+ (nnselect-add-novitem
+ (nnheader-parse-nov))
+ (forward-line 1)))
+ ('headers
+ (goto-char (point-min))
+ (while (not (eobp))
+ (nnselect-add-novitem
+ (nnheader-parse-head))
+ (forward-line 1)))
+ ((pred listp)
+ (dolist (novitem gnus-headers-retrieved-by)
+ (nnselect-add-novitem novitem)))
+ (_ (error "Unknown header type %s while requesting articles \
+ of group %s" gnus-headers-retrieved-by artgroup)))))
+ (setq headers
+ (sort
+ headers
+ (lambda (x y)
+ (< (mail-header-number x) (mail-header-number y))))))))))
+
+
+(deffoo nnselect-request-article (article &optional _group server to-buffer)
+ (let* ((gnus-override-method nil)
+ servers group-art artlist)
+ (if (numberp article)
+ (with-current-buffer gnus-summary-buffer
+ (unless (zerop (nnselect-artlist-length
+ gnus-newsgroup-selection))
+ (setq group-art (cons (nnselect-article-group article)
+ (nnselect-article-number article)))))
+ ;; message-id: either coming from a referral or a pseudo-article
+ ;; find the servers for a pseudo-article
+ (if (eq 'nnselect (car (gnus-server-to-method server)))
+ (with-current-buffer gnus-summary-buffer
+ (let ((thread (gnus-id-to-thread article)))
+ (when thread
+ (mapc
+ #'(lambda (x)
+ (when (and x (> x 0))
+ (cl-pushnew
+ (list
+ (gnus-method-to-server
+ (gnus-find-method-for-group
+ (nnselect-article-group x)))) servers :test 'equal)))
+ (gnus-articles-in-thread thread)))))
+ (setq servers (list (list server))))
+ (setq artlist
+ (nnir-run-query
+ (list
+ (cons 'nnir-query-spec
+ (list (cons 'query (format "HEADER Message-ID %s" article))
+ (cons 'criteria "") (cons 'shortcut t)))
+ (cons 'nnir-group-spec servers))))
+ (unless (zerop (nnselect-artlist-length artlist))
+ (setq
+ group-art
+ (cons
+ (nnselect-artitem-group (nnselect-artlist-article artlist 1))
+ (nnselect-artitem-number (nnselect-artlist-article artlist 1))))))
+ (when (numberp (cdr group-art))
+ (message "Requesting article %d from group %s"
+ (cdr group-art) (car group-art))
+ (if to-buffer
+ (with-current-buffer to-buffer
+ (let ((gnus-article-decode-hook nil))
+ (gnus-request-article-this-buffer
+ (cdr group-art) (car group-art))))
+ (gnus-request-article (cdr group-art) (car group-art)))
+ group-art)))
+
+
+(deffoo nnselect-request-move-article
+ (article _group _server accept-form &optional last _internal-move-group)
+ (let* ((artgroup (nnselect-article-group article))
+ (artnumber (nnselect-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 artgroup))
+ (move-is-internal (gnus-server-equal from-method to-method)))
+ (unless (gnus-check-backend-function
+ 'request-move-article artgroup)
+ (error "The group %s does not support article moving" artgroup))
+ (gnus-request-move-article
+ artnumber
+ artgroup
+ (nth 1 from-method)
+ accept-form
+ last
+ (and move-is-internal
+ to-newsgroup ; Not respooling
+ (gnus-group-real-name to-newsgroup)))))
+
+
+(deffoo nnselect-request-expire-articles
+ (articles _group &optional _server force)
+ (if force
+ (let (not-expired)
+ (pcase-dolist (`(,artgroup . ,artids) (ids-by-group articles))
+ (let ((artlist (sort (mapcar 'cdr artids) '<)))
+ (unless (gnus-check-backend-function 'request-expire-articles
+ artgroup)
+ (error "Group %s does not support article expiration" artgroup))
+ (unless (gnus-check-server (gnus-find-method-for-group artgroup))
+ (error "Couldn't open server for group %s" artgroup))
+ (push (mapcar #'(lambda (art)
+ (car (rassq art artids)))
+ (let ((nnimap-expunge 'immediately))
+ (gnus-request-expire-articles
+ artlist artgroup force)))
+ not-expired)))
+ (sort (delq nil not-expired) '<))
+ articles))
+
+
+(deffoo nnselect-warp-to-article ()
+ (let* ((cur (if (> (gnus-summary-article-number) 0)
+ (gnus-summary-article-number)
+ (error "Can't warp to a pseudo-article")))
+ (artgroup (nnselect-article-group cur))
+ (artnumber (nnselect-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 nnselect summary buffer.
+ ;;(gnus-summary-exit)
+ ;; and if the nnselect 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 artgroup t t nil
+ nil (list artnumber))))
+
+
+;; we pass this through to the real group in case it wants to adjust
+;; the mark. We also use this to mark an article expirable iff it is
+;; expirable in the real group.
+(deffoo nnselect-request-update-mark (_group article mark)
+ (let* ((artgroup (nnselect-article-group article))
+ (artnumber (nnselect-article-number article))
+ (gmark (gnus-request-update-mark artgroup artnumber mark)))
+ (when (and artnumber
+ (memq mark gnus-auto-expirable-marks)
+ (= mark gmark)
+ (gnus-group-auto-expirable-p artgroup))
+ (setq gmark gnus-expirable-mark))
+ gmark))
+
+
+(deffoo nnselect-request-set-mark (_group actions &optional _server)
+ (mapc
+ (lambda (request) (gnus-request-set-mark (car request) (cdr request)))
+ (nnselect-categorize
+ (cl-mapcan
+ (lambda (act)
+ (cl-destructuring-bind (range action marks) act
+ (mapcar
+ (lambda (artgroup)
+ (list (car artgroup)
+ (gnus-compress-sequence (sort (cdr artgroup) '<))
+ action marks))
+ (numbers-by-group
+ (gnus-uncompress-range range)))))
+ actions)
+ 'car 'cdr)))
+
+(deffoo nnselect-request-update-info (group info &optional _server)
+ (let* ((group (nnselect-add-prefix group))
+ (gnus-newsgroup-selection (or gnus-newsgroup-selection
+ (nnselect-get-artlist group))))
+ (gnus-info-set-marks info nil)
+ (setf (gnus-info-read info) nil)
+ (pcase-dolist (`(,artgroup . ,nartids)
+ (ids-by-group
+ (number-sequence 1 (nnselect-artlist-length
+ gnus-newsgroup-selection))))
+ (let* ((gnus-newsgroup-active nil)
+ (artids (cl-sort nartids '< :key 'car))
+ (group-info (gnus-get-info artgroup))
+ (marks (gnus-info-marks group-info))
+ (unread (gnus-uncompress-sequence
+ (gnus-range-difference (gnus-active artgroup)
+ (gnus-info-read group-info)))))
+ (gnus-atomic-progn
+ (setf (gnus-info-read info)
+ (gnus-add-to-range
+ (gnus-info-read info)
+ (delq nil
+ (mapcar
+ #'(lambda (art)
+ (unless (memq (cdr art) unread) (car art)))
+ artids))))
+ (pcase-dolist (`(,type . ,range) marks)
+ (setq range (gnus-uncompress-sequence range))
+ (gnus-add-marked-articles
+ group type
+ (delq nil
+ (mapcar
+ #'(lambda (art)
+ (when (memq (cdr art) range)
+ (car art))) artids)))))))
+ (gnus-set-active group (cons 1 (nnselect-artlist-length
+ gnus-newsgroup-selection)))))
+
+
+(deffoo nnselect-request-thread (header &optional group server)
+ (with-current-buffer gnus-summary-buffer
+ (let ((group (nnselect-add-prefix group))
+ ;; find the best group for the originating article. if its a
+ ;; pseudo-article look for real articles in the same thread
+ ;; and see where they come from.
+ (artgroup (nnselect-article-group
+ (if (> (mail-header-number header) 0)
+ (mail-header-number header)
+ (if (> (gnus-summary-article-number) 0)
+ (gnus-summary-article-number)
+ (let ((thread
+ (gnus-id-to-thread (mail-header-id header))))
+ (when thread
+ (cl-some #'(lambda (x)
+ (when (and x (> x 0)) x))
+ (gnus-articles-in-thread thread)))))))))
+ ;; Check if we are dealing with an imap backend.
+ (if (eq 'nnimap
+ (car (gnus-find-method-for-group artgroup)))
+ ;; If so we perform the query, massage the result, and return
+ ;; the new headers back to the caller to incorporate into the
+ ;; current summary buffer.
+ (let* ((group-spec
+ (list (delq nil (list
+ (or server (gnus-group-server artgroup))
+ (unless gnus-refer-thread-use-search
+ artgroup)))))
+ (query-spec
+ (list (cons 'query (nnimap-make-thread-query header))
+ (cons 'criteria "")))
+ (last (nnselect-artlist-length gnus-newsgroup-selection))
+ (first (1+ last))
+ (new-nnselect-artlist
+ (nnir-run-query
+ (list (cons 'nnir-query-spec query-spec)
+ (cons 'nnir-group-spec group-spec))))
+ old-arts seq
+ headers)
+ (mapc
+ #'(lambda (article)
+ (if
+ (setq seq
+ (cl-position article
+ gnus-newsgroup-selection :test 'equal))
+ (push (1+ seq) old-arts)
+ (setq gnus-newsgroup-selection
+ (vconcat gnus-newsgroup-selection (vector article)))
+ (cl-incf last)))
+ new-nnselect-artlist)
+ (setq headers
+ (gnus-fetch-headers
+ (append (sort old-arts '<)
+ (number-sequence first last)) nil t))
+ (gnus-group-set-parameter
+ group
+ 'nnselect-artlist
+ (nnselect-compress-artlist gnus-newsgroup-selection))
+ (when (>= last first)
+ (let (new-marks)
+ (pcase-dolist (`(,artgroup . ,artids)
+ (ids-by-group (number-sequence first last)))
+ (pcase-dolist (`(,type . ,marked)
+ (gnus-info-marks (gnus-get-info artgroup)))
+ (setq marked (gnus-uncompress-sequence marked))
+ (when (setq new-marks
+ (delq nil
+ (mapcar
+ #'(lambda (art)
+ (when (memq (cdr art) marked)
+ (car art)))
+ artids)))
+ (nconc
+ (symbol-value
+ (intern
+ (format "gnus-newsgroup-%s"
+ (car (rassq type gnus-article-mark-lists)))))
+ new-marks)))))
+ (setq gnus-newsgroup-active
+ (cons 1 (nnselect-artlist-length gnus-newsgroup-selection)))
+ (gnus-set-active
+ group
+ (cons 1 (nnselect-artlist-length gnus-newsgroup-selection))))
+ headers)
+ ;; If not an imap backend just warp to the original article
+ ;; group and punt back to gnus-summary-refer-thread.
+ (and (gnus-warp-to-article) (gnus-summary-refer-thread))))))
+
+
+(deffoo nnselect-close-group (group &optional _server)
+ (let ((group (nnselect-add-prefix group)))
+ (unless gnus-group-is-exiting-without-update-p
+ (nnselect-push-info group))
+ (setq gnus-newsgroup-selection nil)
+ (when (gnus-ephemeral-group-p group)
+ (gnus-kill-ephemeral-group group)
+ (setq gnus-ephemeral-servers
+ (assq-delete-all 'nnselect gnus-ephemeral-servers)))))
+
+
+(deffoo nnselect-request-create-group (group &optional _server args)
+ (message "Creating nnselect group %s" group)
+ (let* ((group (gnus-group-prefixed-name group '(nnselect "nnselect")))
+ (specs (assq 'nnselect-specs args))
+ (function-spec
+ (or (alist-get 'nnselect-function specs)
+ (intern (completing-read "Function: " obarray #'functionp))))
+ (args-spec
+ (or (alist-get 'nnselect-args specs)
+ (read-from-minibuffer "Args: " nil nil t nil "nil")))
+ (nnselect-specs (list (cons 'nnselect-function function-spec)
+ (cons 'nnselect-args args-spec))))
+ (gnus-group-set-parameter group 'nnselect-specs nnselect-specs)
+ (gnus-group-set-parameter
+ group 'nnselect-artlist
+ (nnselect-compress-artlist (or (alist-get 'nnselect-artlist args)
+ (nnselect-run nnselect-specs))))
+ (nnselect-request-update-info group (gnus-get-info group)))
+ t)
+
+
+(deffoo nnselect-request-type (_group &optional article)
+ (if (and (numberp article) (> article 0))
+ (gnus-request-type
+ (nnselect-article-group article) (nnselect-article-number article))
+ 'unknown))
+
+(deffoo nnselect-request-post (&optional _server)
+ (if (not gnus-message-group-art)
+ (nnheader-report 'nnselect "Can't post to an nnselect group")
+ (gnus-request-post
+ (gnus-find-method-for-group
+ (nnselect-article-group (cdr gnus-message-group-art))))))
+
+
+(deffoo nnselect-request-rename-group (_group _new-name &optional _server)
+ t)
+
+
+(deffoo nnselect-request-scan (group _method)
+ (when (and group
+ (gnus-group-get-parameter (nnselect-add-prefix group)
+ 'nnselect-rescan t))
+ (nnselect-request-group-scan group)))
+
+
+(deffoo nnselect-request-group-scan (group &optional _server _info)
+ (let* ((group (nnselect-add-prefix group))
+ (artlist (nnselect-run
+ (gnus-group-get-parameter group 'nnselect-specs t))))
+ (gnus-set-active group (cons 1 (nnselect-artlist-length
+ artlist)))
+ (gnus-group-set-parameter
+ group 'nnselect-artlist
+ (nnselect-compress-artlist artlist))))
+
+;; Add any undefined required backend functions
+
+;; (nnoo-define-skeleton nnselect)
+
+;;; Util Code:
+
+(defun gnus-nnselect-group-p (group)
+ "Say whether GROUP is nnselect or not."
+ (or (and (gnus-group-prefixed-p group)
+ (eq 'nnselect (car (gnus-find-method-for-group group))))
+ (eq 'nnselect (car gnus-command-method))))
+
+
+(defun nnselect-run (specs)
+ "Apply nnselect-function to nnselect-args from SPECS.
+Return an article list."
+ (let ((func (alist-get 'nnselect-function specs))
+ (args (alist-get 'nnselect-args specs)))
+ (funcall func args)))
+
+
+(defun nnselect-search-thread (header)
+ "Make an nnselect group containing the thread with 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
+ (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 (cl-pushnew (list registry-server) server
+ :test 'equal))
+ (gnus-group-read-ephemeral-group
+ (concat "nnselect-" (message-unique-id))
+ (list 'nnselect "nnselect")
+ nil
+ (cons (current-buffer) gnus-current-window-configuration)
+ ; nil
+ nil nil
+ (list
+ (cons 'nnselect-specs
+ (list
+ (cons 'nnselect-function 'nnir-run-query)
+ (cons 'nnselect-args
+ (list (cons 'nnir-query-spec query)
+ (cons 'nnir-group-spec server)))))
+ (cons 'nnselect-artlist nil)))
+ (gnus-summary-goto-subject (gnus-id-to-article (mail-header-id header)))))
+
+
+
+(defun nnselect-push-info (group)
+ "Copy mark-lists from GROUP to the originating groups."
+ (let ((select-unreads (numbers-by-group gnus-newsgroup-unreads))
+ (select-reads (numbers-by-group
+ (gnus-uncompress-range
+ (gnus-info-read (gnus-get-info group)))))
+ (select-unseen (numbers-by-group gnus-newsgroup-unseen))
+ (gnus-newsgroup-active nil)
+ mark-list type-list)
+ (pcase-dolist (`(,mark . ,type) gnus-article-mark-lists)
+ (when (setq type-list
+ (symbol-value (intern (format "gnus-newsgroup-%s" mark))))
+ (push (cons type
+ (numbers-by-group
+ (gnus-uncompress-range type-list))) mark-list)))
+ (pcase-dolist (`(,artgroup . ,artlist)
+ (numbers-by-group gnus-newsgroup-articles))
+ (let* ((group-info (gnus-get-info artgroup))
+ (old-unread (gnus-list-of-unread-articles artgroup))
+ newmarked)
+ (when group-info
+ (pcase-dolist (`(,_mark . ,type) gnus-article-mark-lists)
+ (let ((select-type
+ (sort
+ (cdr (assoc artgroup (alist-get type mark-list)))
+ '<)) list)
+ (setq list
+ (gnus-uncompress-range
+ (gnus-add-to-range
+ (gnus-remove-from-range
+ (alist-get type (gnus-info-marks group-info))
+ artlist)
+ select-type)))
+
+ (when list
+ ;; Get rid of the entries of the articles that have the
+ ;; default score.
+ (when (and (eq type 'score)
+ gnus-save-score
+ list)
+ (let* ((arts list)
+ (prev (cons nil list))
+ (all prev))
+ (while arts
+ (if (or (not (consp (car arts)))
+ (= (cdar arts) gnus-summary-default-score))
+ (setcdr prev (cdr arts))
+ (setq prev arts))
+ (setq arts (cdr arts)))
+ (setq list (cdr all)))))
+
+ (when (or (eq (gnus-article-mark-to-type type) 'list)
+ (eq (gnus-article-mark-to-type type) 'range))
+ (setq list
+ (gnus-compress-sequence (sort list '<) t)))
+
+ ;; When exiting the group, everything that's previously been
+ ;; unseen is now seen.
+ (when (eq type 'seen)
+ (setq list (gnus-range-add
+ list (cdr (assoc artgroup select-unseen)))))
+
+ (when (or list (eq type 'unexist))
+ (push (cons type list) newmarked))))
+
+ (gnus-atomic-progn
+ ;; Enter these new marks into the info of the group.
+ (if (nthcdr 3 group-info)
+ (setcar (nthcdr 3 group-info) newmarked)
+ ;; Add the marks lists to the end of the info.
+ (when newmarked
+ (setcdr (nthcdr 2 group-info) (list newmarked))))
+
+ ;; Cut off the end of the info if there's nothing else there.
+ (let ((i 5))
+ (while (and (> i 2)
+ (not (nth i group-info)))
+ (when (nthcdr (cl-decf i) group-info)
+ (setcdr (nthcdr i group-info) nil))))
+
+ ;; update read and unread
+ (gnus-update-read-articles
+ artgroup
+ (gnus-uncompress-range
+ (gnus-add-to-range
+ (gnus-remove-from-range
+ old-unread
+ (cdr (assoc artgroup select-reads)))
+ (sort (cdr (assoc artgroup select-unreads)) '<))))
+ (gnus-get-unread-articles-in-group
+ group-info (gnus-active artgroup) t)
+ (gnus-group-update-group artgroup t t)))))))
+
+
+(declare-function gnus-registry-get-id-key "gnus-registry" (id key))
+
+(defun gnus-summary-make-search-group (nnir-extra-parms)
+ "Search a group from the summary buffer.
+Pass NNIR-EXTRA-PARMS on to the search engine."
+ (interactive "P")
+ (gnus-warp-to-article)
+ (let ((spec
+ (list
+ (cons 'nnir-group-spec
+ (list (list
+ (gnus-group-server gnus-newsgroup-name)
+ gnus-newsgroup-name))))))
+ (gnus-group-make-search-group nnir-extra-parms spec)))
+
+
+;; The end.
+(provide 'nnselect)
+
+;;; nnselect.el ends here
diff --git a/lisp/gnus/nnspool.el b/lisp/gnus/nnspool.el
index 33b68fa989e..0b6bba5fea7 100644
--- a/lisp/gnus/nnspool.el
+++ b/lisp/gnus/nnspool.el
@@ -422,7 +422,7 @@ there.")
(nnspool-article-pathname nnspool-current-group article))
(nnheader-insert-article-line article)
(goto-char (point-min))
- (let ((headers (nnheader-parse-head)))
+ (let ((headers (nnheader-parse-head nil t)))
(set-buffer cur)
(goto-char (point-max))
(nnheader-insert-nov headers)))